Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Slightly refactor & Fix GH82104
  • Loading branch information
zyn0217 committed Mar 9, 2024
commit d3a76a1d09a4b7f86bdcab9fb4725d42eb883e79
186 changes: 119 additions & 67 deletions clang/lib/Sema/SemaTemplateInstantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,81 @@ struct Response {
return R;
}
};

// Retrieve the primary template for a lambda call operator. It's
// unfortunate that we only have the mappings of call operators rather
// than lambda classes.
const FunctionDecl *
getPrimaryTemplateOfGenericLambda(const FunctionDecl *LambdaCallOperator) {
while (true) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What cases do we have where this takes more than 1 step? I also wonder if we'd be better off finding the primary CXXRecordDecl, then picking it up from there? We could use getTemplateInstantiationPattern then getDescribedClassTemplate I think?

Copy link
Contributor Author

@zyn0217 zyn0217 Apr 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Sorry for replying this late, I just got around to this PR.)

What cases do we have where this takes more than 1 step?

The lambda itself might be defined within a template, and I presume transforming that template introduces multilevel mappings.

I also wonder if we'd be better off finding the primary CXXRecordDecl, then picking it up from there? We could use getTemplateInstantiationPattern then getDescribedClassTemplate I think?

If we examine TemplateInstantiator::transformedLocalDecl, we'd see there's only handling for CXXMethodDecls of adding such mappings that we can extract from the Decls themselves. I think it is possible that we can use the primary CXXRecordDecl approach, although looking for that Decl might involve handling LocalInstantiationScopes, which IMO is not super straightforward.

if (auto *FTD = dyn_cast_if_present<FunctionTemplateDecl>(
LambdaCallOperator->getDescribedTemplate());
FTD && FTD->getInstantiatedFromMemberTemplate()) {
LambdaCallOperator =
FTD->getInstantiatedFromMemberTemplate()->getTemplatedDecl();
} else if (auto *Prev = cast<CXXMethodDecl>(LambdaCallOperator)
->getInstantiatedFromMemberFunction())
LambdaCallOperator = Prev;
else
break;
}
return LambdaCallOperator;
}

struct EnclosingTypeAliasTemplateDetails {
TypeAliasTemplateDecl *Template = nullptr;
TypeAliasTemplateDecl *PrimaryTypeAliasDecl = nullptr;
ArrayRef<TemplateArgument> AssociatedTemplateArguments;

explicit operator bool() noexcept { return Template; }
};

// Find the enclosing type alias template Decl from CodeSynthesisContexts, as
// well as its primary template and instantiating template arguments.
EnclosingTypeAliasTemplateDetails
getEnclosingTypeAliasTemplateDecl(Sema &SemaRef) {
for (auto &CSC : llvm::reverse(SemaRef.CodeSynthesisContexts)) {
if (CSC.Kind != Sema::CodeSynthesisContext::SynthesisKind::
TypeAliasTemplateInstantiation)
continue;
EnclosingTypeAliasTemplateDetails Result;
auto *TATD = cast<TypeAliasTemplateDecl>(CSC.Entity),
*Next = TATD->getInstantiatedFromMemberTemplate();
Result = {
/*Template=*/TATD,
/*PrimaryTypeAliasDecl=*/TATD,
/*AssociatedTemplateArguments=*/CSC.template_arguments(),
};
while (Next) {
Result.PrimaryTypeAliasDecl = Next;
Next = Next->getInstantiatedFromMemberTemplate();
}
return Result;
}
return {};
}

// Check if we are currently inside of a lambda expression that is
// surrounded by a using alias declaration. e.g.
// template <class> using type = decltype([](auto) { ^ }());
// By checking if:
// 1. The lambda expression and the using alias declaration share the
// same declaration context.
// 2. They have the same template depth.
// We have to do so since a TypeAliasTemplateDecl (or a TypeAliasDecl) is never
// a DeclContext, nor does it have an associated specialization Decl from which
// we could collect these template arguments.
bool isLambdaEnclosedByTypeAliasDecl(
const FunctionDecl *PrimaryLambdaCallOperator,
const TypeAliasTemplateDecl *PrimaryTypeAliasDecl) {
return cast<CXXRecordDecl>(PrimaryLambdaCallOperator->getDeclContext())
->getTemplateDepth() ==
PrimaryTypeAliasDecl->getTemplateDepth() &&
getLambdaAwareParentOfDeclContext(
const_cast<FunctionDecl *>(PrimaryLambdaCallOperator)) ==
PrimaryTypeAliasDecl->getDeclContext();
}

// Add template arguments from a variable template instantiation.
Response
HandleVarTemplateSpec(const VarTemplateSpecializationDecl *VarTemplSpec,
Expand Down Expand Up @@ -176,7 +251,7 @@ HandleClassTemplateSpec(const ClassTemplateSpecializationDecl *ClassTemplSpec,
return Response::UseNextDecl(ClassTemplSpec);
}

Response HandleFunction(const FunctionDecl *Function,
Response HandleFunction(Sema &SemaRef, const FunctionDecl *Function,
MultiLevelTemplateArgumentList &Result,
const FunctionDecl *Pattern, bool RelativeToPrimary,
bool ForConstraintInstantiation) {
Expand Down Expand Up @@ -207,8 +282,23 @@ Response HandleFunction(const FunctionDecl *Function,

// If this function is a generic lambda specialization, we are done.
if (!ForConstraintInstantiation &&
isGenericLambdaCallOperatorOrStaticInvokerSpecialization(Function))
isGenericLambdaCallOperatorOrStaticInvokerSpecialization(Function)) {
// TypeAliasTemplateDecls should be taken into account, e.g.
// when we're deducing the return type of a lambda.
//
// template <class> int Value = 0;
// template <class T>
// using T = decltype([]<int U = 0>() { return Value<T>; }());
//
if (auto TypeAlias = getEnclosingTypeAliasTemplateDecl(SemaRef)) {
if (isLambdaEnclosedByTypeAliasDecl(
/*PrimaryLambdaCallOperator=*/getPrimaryTemplateOfGenericLambda(
Function),
/*PrimaryTypeAliasDecl=*/TypeAlias.PrimaryTypeAliasDecl))
return Response::UseNextDecl(Function);
}
return Response::Done();
}

} else if (Function->getDescribedFunctionTemplate()) {
assert(
Expand Down Expand Up @@ -312,74 +402,36 @@ Response HandleRecordDecl(Sema &SemaRef, const CXXRecordDecl *Rec,
return Response::ChangeDecl(Rec->getLexicalDeclContext());
}

// This is to make sure we pick up the VarTemplateSpecializationDecl that this
// lambda is defined inside of.
// This is to make sure we pick up the VarTemplateSpecializationDecl or the
// TypeAliasTemplateDecl that this lambda is defined inside of.
if (Rec->isLambda()) {
if (const Decl *LCD = Rec->getLambdaContextDecl())
return Response::ChangeDecl(LCD);
// Attempt to retrieve the template arguments for a using alias declaration.
// Retrieve the template arguments for a using alias declaration.
// This is necessary for constraint checking, since we always keep
// constraints relative to the primary template.
if (ForConstraintInstantiation && !SemaRef.CodeSynthesisContexts.empty()) {
for (auto &CSC : llvm::reverse(SemaRef.CodeSynthesisContexts)) {
if (CSC.Kind != Sema::CodeSynthesisContext::SynthesisKind::
TypeAliasTemplateInstantiation)
continue;
auto *TATD = cast<TypeAliasTemplateDecl>(CSC.Entity),
*CurrentTATD = TATD;
FunctionDecl *LambdaCallOperator = Rec->getLambdaCallOperator();
// Retrieve the 'primary' template for a lambda call operator. It's
// unfortunate that we only have the mappings of call operators rather
// than lambda classes.
while (true) {
auto *FTD = dyn_cast_if_present<FunctionTemplateDecl>(
LambdaCallOperator->getDescribedTemplate());
if (FTD && FTD->getInstantiatedFromMemberTemplate()) {
LambdaCallOperator =
FTD->getInstantiatedFromMemberTemplate()->getTemplatedDecl();
} else if (auto *Prev = cast<CXXMethodDecl>(LambdaCallOperator)
->getInstantiatedFromMemberFunction())
LambdaCallOperator = Prev;
else
break;
}
// Same applies for type alias Decl. We perform this to obtain the
// "canonical" template parameter depths.
while (TATD->getInstantiatedFromMemberTemplate())
TATD = TATD->getInstantiatedFromMemberTemplate();
// Tell if we're currently inside of a lambda expression that is
// surrounded by a using alias declaration. e.g.
// template <class> using type = decltype([](auto) { ^ }());
// By checking if:
// 1. The lambda expression and the using alias declaration share the
// same declaration context.
// 2. They have the same template depth.
// Then we assume the template arguments from the using alias
// declaration are essential for constraint instantiation. We have to do
// so since a TypeAliasTemplateDecl (or a TypeAliasDecl) is never a
// DeclContext, nor does it have an associated specialization Decl from
// which we could collect these template arguments.
if (cast<CXXRecordDecl>(LambdaCallOperator->getDeclContext())
->getTemplateDepth() == TATD->getTemplateDepth() &&
getLambdaAwareParentOfDeclContext(LambdaCallOperator) ==
TATD->getDeclContext()) {
Result.addOuterTemplateArguments(CurrentTATD,
CSC.template_arguments(),
/*Final=*/false);
// Visit the parent of the current type alias declaration rather than
// the lambda thereof. We have the following case:
// struct S {
// template <class> using T = decltype([]<Concept> {} ());
// };
// void foo() {
// S::T var;
// }
// The instantiated lambda expression (which we're visiting at 'var')
// has a function DeclContext 'foo' rather than the Record DeclContext
// S. This seems to be an oversight that we may want to set a Sema
// Context from the CXXScopeSpec before substituting into T to me.
return Response::ChangeDecl(CurrentTATD->getDeclContext());
}
if (auto TypeAlias = getEnclosingTypeAliasTemplateDecl(SemaRef)) {
const FunctionDecl *PrimaryLambdaCallOperator =
getPrimaryTemplateOfGenericLambda(Rec->getLambdaCallOperator());
if (isLambdaEnclosedByTypeAliasDecl(PrimaryLambdaCallOperator,
TypeAlias.PrimaryTypeAliasDecl)) {
Result.addOuterTemplateArguments(TypeAlias.Template,
TypeAlias.AssociatedTemplateArguments,
/*Final=*/false);
// Visit the parent of the current type alias declaration rather than
// the lambda thereof.
// E.g., in the following example:
// struct S {
// template <class> using T = decltype([]<Concept> {} ());
// };
// void foo() {
// S::T var;
// }
// The instantiated lambda expression (which we're visiting at 'var')
// has a function DeclContext 'foo' rather than the Record DeclContext
// S. This seems to be an oversight to me that we may want to set a
// Sema Context from the CXXScopeSpec before substituting into T.
return Response::ChangeDecl(TypeAlias.Template->getDeclContext());
}
}
}
Expand Down Expand Up @@ -476,7 +528,7 @@ MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs(
R = HandleClassTemplateSpec(ClassTemplSpec, Result,
SkipForSpecialization);
} else if (const auto *Function = dyn_cast<FunctionDecl>(CurDecl)) {
R = HandleFunction(Function, Result, Pattern, RelativeToPrimary,
R = HandleFunction(*this, Function, Result, Pattern, RelativeToPrimary,
ForConstraintInstantiation);
} else if (const auto *Rec = dyn_cast<CXXRecordDecl>(CurDecl)) {
R = HandleRecordDecl(*this, Rec, Result, Context,
Expand Down Expand Up @@ -690,7 +742,7 @@ Sema::InstantiatingTemplate::InstantiatingTemplate(
: InstantiatingTemplate(
SemaRef, Sema::CodeSynthesisContext::TypeAliasTemplateInstantiation,
PointOfInstantiation, InstantiationRange, /*Entity=*/Template,
nullptr, TemplateArgs) {}
/*Template=*/nullptr, TemplateArgs) {}

Sema::InstantiatingTemplate::InstantiatingTemplate(
Sema &SemaRef, SourceLocation PointOfInstantiation, TemplateDecl *Template,
Expand Down
35 changes: 25 additions & 10 deletions clang/lib/Sema/TreeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -13943,23 +13943,38 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
SavedContext.pop();

// Recompute the dependency of the lambda so that we can defer the lambda call
// construction until after we have sufficient template arguments. For
// example, template <class> struct S {
// construction until after we have all the necessary template arguments. For
// example, given
//
// template <class> struct S {
// template <class U>
// using Type = decltype([](U){}(42.0));
// };
// void foo() {
// using T = S<int>::Type<float>;
// ^~~~~~
// }
// We would end up here from instantiating the S<int> as we're ensuring the
// completeness. That would make us transform the lambda call expression
// despite the fact that we don't see the argument for U yet. We have a
// mechanism that circumvents the semantic checking if the CallExpr is
// dependent. We can harness that by recomputing the lambda dependency from
// the instantiation arguments. I'm putting it here rather than the above
// since we can see transformed lambda parameters in case that they're
// useful for calculation.
//
// We would end up here from instantiating S<int> when ensuring its
// completeness. That would transform the lambda call expression regardless of
// the absence of the corresponding argument for U.
//
// Going ahead with unsubstituted type U makes things worse: we would soon
// compare the argument type (which is float) against the parameter U
// somewhere in Sema::BuildCallExpr. Then we would quickly run into a bogus
// error suggesting unmatched types 'U' and 'float'!
//
// That said, everything will be fine if we defer that semantic checking.
// Fortunately, we have such a mechanism that bypasses it if the CallExpr is
// dependent. Since the CallExpr's dependency boils down to the lambda's
// dependency in this case, we can harness that by recomputing the dependency
// from the instantiation arguments.
//
// FIXME: Creating the type of a lambda requires us to have a dependency
// value, which happens before its substitution. We update its dependency
// *after* the substitution in case we can't decide the dependency
// so early, e.g. because we want to see if any of the *substituted*
// parameters are dependent.
DependencyKind = getDerived().ComputeLambdaDependency(&LSICopy);
Class->setLambdaDependencyKind(DependencyKind);
// Clean up the type cache created previously. Then, we re-create a type for
Expand Down
11 changes: 11 additions & 0 deletions clang/test/SemaTemplate/alias-template-with-lambdas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,15 @@ void bar() {
using T13 = MeowMeow<char, int, long, unsigned>;
}

namespace GH82104 {

template <typename, typename...> int Zero = 0;

template <typename T, typename...U>
using T14 = decltype([]<int V = 0>() { return Zero<T, U...>; }());

template <typename T> using T15 = T14<T, T>;

} // namespace GH82104

} // namespace lambda_calls