From 25f493da55e5cd7d46dda6fca6062aa56b6a3fd0 Mon Sep 17 00:00:00 2001 From: Younan Zhang Date: Tue, 20 Feb 2024 14:54:14 +0800 Subject: [PATCH 1/7] The lambda call inside of a type alias --- clang/docs/ReleaseNotes.rst | 2 + clang/include/clang/AST/DeclCXX.h | 4 + clang/include/clang/Sema/Sema.h | 8 ++ clang/lib/Frontend/FrontendActions.cpp | 2 + clang/lib/Sema/SemaConcept.cpp | 15 ++-- clang/lib/Sema/SemaTemplate.cpp | 9 +- clang/lib/Sema/SemaTemplateInstantiate.cpp | 64 ++++++++++++++- .../lib/Sema/SemaTemplateInstantiateDecl.cpp | 5 ++ clang/lib/Sema/TreeTransform.h | 9 ++ .../alias-template-with-lambdas.cpp | 82 +++++++++++++++++++ 10 files changed, 186 insertions(+), 14 deletions(-) create mode 100644 clang/test/SemaTemplate/alias-template-with-lambdas.cpp diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 690fc7ed271a3..f5dc956cf5fc4 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -344,6 +344,8 @@ Bug Fixes to C++ Support when one of the function had more specialized templates. Fixes (`#82509 `_) and (`#74494 `_) +- Clang now supports direct lambda calls inside of a type alias template declarations. + This addresses (#GH70601), (#GH76674), (#GH79555), (#GH81145) and so on. Bug Fixes to AST Handling ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index 9cebaff63bb0d..7aed4d5cbc002 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -1869,6 +1869,10 @@ class CXXRecordDecl : public RecordDecl { DL.MethodTyInfo = TS; } + void setLambdaDependencyKind(unsigned Kind) { + getLambdaData().DependencyKind = Kind; + } + void setLambdaIsGeneric(bool IsGeneric) { assert(DefinitionData && DefinitionData->IsLambda && "setting lambda property of non-lambda class"); diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index cfc1c3b349478..a5520e19a1432 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -10145,6 +10145,8 @@ class Sema final { /// We are building deduction guides for a class. BuildingDeductionGuides, + + TypeAliasTemplateInstantiation, } Kind; /// Was the enclosing context a non-instantiation SFINAE context? @@ -10234,6 +10236,12 @@ class Sema final { FunctionDecl *Entity, ExceptionSpecification, SourceRange InstantiationRange = SourceRange()); + /// Note that we are instantiating a type alias template declaration. + InstantiatingTemplate(Sema &SemaRef, SourceLocation PointOfInstantiation, + TypeAliasTemplateDecl *Template, + ArrayRef TemplateArgs, + SourceRange InstantiationRange = SourceRange()); + /// Note that we are instantiating a default argument in a /// template-id. InstantiatingTemplate(Sema &SemaRef, SourceLocation PointOfInstantiation, diff --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp index 50338bfa670f8..5f0299dfa32e2 100644 --- a/clang/lib/Frontend/FrontendActions.cpp +++ b/clang/lib/Frontend/FrontendActions.cpp @@ -452,6 +452,8 @@ class DefaultTemplateInstCallback : public TemplateInstantiationCallback { return "BuildingBuiltinDumpStructCall"; case CodeSynthesisContext::BuildingDeductionGuides: return "BuildingDeductionGuides"; + case Sema::CodeSynthesisContext::TypeAliasTemplateInstantiation: + return "TypeAliasTemplateInstantiation"; } return ""; } diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp index a8e387e35fb4c..0b84d83b4fb4b 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -615,14 +615,13 @@ bool Sema::SetupConstraintScope( // reference the original primary template. // We walk up the instantiated template chain so that nested lambdas get // handled properly. - for (FunctionTemplateDecl *FromMemTempl = - PrimaryTemplate->getInstantiatedFromMemberTemplate(); - FromMemTempl; - FromMemTempl = FromMemTempl->getInstantiatedFromMemberTemplate()) { - if (addInstantiatedParametersToScope(FD, FromMemTempl->getTemplatedDecl(), - Scope, MLTAL)) - return true; - } + FunctionTemplateDecl *FromMemTempl = + PrimaryTemplate->getInstantiatedFromMemberTemplate(); + while (FromMemTempl && FromMemTempl->getInstantiatedFromMemberTemplate()) + FromMemTempl = FromMemTempl->getInstantiatedFromMemberTemplate(); + if (FromMemTempl && addInstantiatedParametersToScope( + FD, FromMemTempl->getTemplatedDecl(), Scope, MLTAL)) + return true; return false; } diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index d62095558d0ff..30499df1797fc 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -4343,9 +4343,12 @@ QualType Sema::CheckTemplateIdType(TemplateName Name, if (Inst.isInvalid()) return QualType(); - CanonType = SubstType(Pattern->getUnderlyingType(), - TemplateArgLists, AliasTemplate->getLocation(), - AliasTemplate->getDeclName()); + InstantiatingTemplate InstTemplate( + *this, Pattern->getTypeSourceInfo()->getTypeLoc().getBeginLoc(), + AliasTemplate, TemplateArgLists.getInnermost()); + CanonType = + SubstType(Pattern->getUnderlyingType(), TemplateArgLists, + AliasTemplate->getLocation(), AliasTemplate->getDeclName()); if (CanonType.isNull()) { // If this was enable_if and we failed to find the nested type // within enable_if in a SFINAE context, dig out the specific diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index d9994d7fd37ad..50f92cc8836b1 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -283,7 +283,8 @@ Response HandleFunctionTemplateDecl(const FunctionTemplateDecl *FTD, return Response::ChangeDecl(FTD->getLexicalDeclContext()); } -Response HandleRecordDecl(const CXXRecordDecl *Rec, +Response HandleRecordDecl(Sema &SemaRef, + const CXXRecordDecl *Rec, MultiLevelTemplateArgumentList &Result, ASTContext &Context, bool ForConstraintInstantiation) { @@ -314,9 +315,42 @@ Response HandleRecordDecl(const CXXRecordDecl *Rec, // This is to make sure we pick up the VarTemplateSpecializationDecl that this // lambda is defined inside of. - if (Rec->isLambda()) + if (Rec->isLambda()) { if (const Decl *LCD = Rec->getLambdaContextDecl()) return Response::ChangeDecl(LCD); + if (ForConstraintInstantiation && !SemaRef.CodeSynthesisContexts.empty()) { + for (auto &CSC : llvm::reverse(SemaRef.CodeSynthesisContexts)) { + if (CSC.Kind == Sema::CodeSynthesisContext::SynthesisKind::TypeAliasTemplateInstantiation) { + auto *TATD = cast(CSC.Entity), *CurrentTATD = TATD; + FunctionDecl *LambdaCallOperator = Rec->getLambdaCallOperator(); + while (true) { + auto *FTD = dyn_cast_if_present( + LambdaCallOperator->getDescribedTemplate()); + if (FTD && FTD->getInstantiatedFromMemberTemplate()) { + LambdaCallOperator = + FTD->getInstantiatedFromMemberTemplate()->getTemplatedDecl(); + } else if (auto *Prev = cast(LambdaCallOperator) + ->getInstantiatedFromMemberFunction()) + LambdaCallOperator = Prev; + else + break; + } + while (TATD->getInstantiatedFromMemberTemplate()) + TATD = TATD->getInstantiatedFromMemberTemplate(); + // Constraint template parameters have a deeper depth. + if (cast(LambdaCallOperator->getDeclContext()) + ->getTemplateDepth() == TATD->getTemplateDepth() && + getLambdaAwareParentOfDeclContext(LambdaCallOperator) == + TATD->getDeclContext()) { + Result.addOuterTemplateArguments(CurrentTATD, + CSC.template_arguments(), + /*Final=*/false); + return Response::ChangeDecl(CurrentTATD->getDeclContext()); + } + } + } + } + } return Response::UseNextDecl(Rec); } @@ -413,7 +447,7 @@ MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs( R = HandleFunction(Function, Result, Pattern, RelativeToPrimary, ForConstraintInstantiation); } else if (const auto *Rec = dyn_cast(CurDecl)) { - R = HandleRecordDecl(Rec, Result, Context, ForConstraintInstantiation); + R = HandleRecordDecl(*this, Rec, Result, Context, ForConstraintInstantiation); } else if (const auto *CSD = dyn_cast(CurDecl)) { R = HandleImplicitConceptSpecializationDecl(CSD, Result); @@ -470,6 +504,7 @@ bool Sema::CodeSynthesisContext::isInstantiationRecord() const { case BuildingBuiltinDumpStructCall: case LambdaExpressionSubstitution: case BuildingDeductionGuides: + case TypeAliasTemplateInstantiation: return false; // This function should never be called when Kind's value is Memoization. @@ -615,6 +650,15 @@ Sema::InstantiatingTemplate::InstantiatingTemplate( PointOfInstantiation, InstantiationRange, Param, Template, TemplateArgs) {} +Sema::InstantiatingTemplate::InstantiatingTemplate( + Sema &SemaRef, SourceLocation PointOfInstantiation, + TypeAliasTemplateDecl *Template, ArrayRef TemplateArgs, + SourceRange InstantiationRange) + : InstantiatingTemplate( + SemaRef, Sema::CodeSynthesisContext::TypeAliasTemplateInstantiation, + PointOfInstantiation, InstantiationRange, /*Entity=*/Template, + nullptr, TemplateArgs) {} + Sema::InstantiatingTemplate::InstantiatingTemplate( Sema &SemaRef, SourceLocation PointOfInstantiation, TemplateDecl *Template, NamedDecl *Param, ArrayRef TemplateArgs, @@ -1132,6 +1176,8 @@ void Sema::PrintInstantiationStack() { Diags.Report(Active->PointOfInstantiation, diag::note_building_deduction_guide_here); break; + case CodeSynthesisContext::TypeAliasTemplateInstantiation: + break; } } } @@ -1209,6 +1255,7 @@ std::optional Sema::isSFINAEContext() const { break; case CodeSynthesisContext::Memoization: + case CodeSynthesisContext::TypeAliasTemplateInstantiation: break; } @@ -1533,6 +1580,17 @@ namespace { SubstTemplateTypeParmPackTypeLoc TL, bool SuppressObjCLifetime); + CXXRecordDecl::LambdaDependencyKind + ComputeLambdaDependency(LambdaScopeInfo *LSI) { + auto &CCS = SemaRef.CodeSynthesisContexts.back(); + if (CCS.Kind == Sema::CodeSynthesisContext::TypeAliasTemplateInstantiation) { + unsigned TypeAliasDeclDepth = CCS.Entity->getTemplateDepth(); + if (TypeAliasDeclDepth >= TemplateArgs.getNumSubstitutedLevels()) + return CXXRecordDecl::LambdaDependencyKind::LDK_AlwaysDependent; + } + return inherited::ComputeLambdaDependency(LSI); + } + ExprResult TransformLambdaExpr(LambdaExpr *E) { LocalInstantiationScope Scope(SemaRef, /*CombineWithOuterScope=*/true); Sema::ConstraintEvalRAII RAII(*this); diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 20c2c93ac9c7b..a4277d6d9efed 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -1083,6 +1083,11 @@ TemplateDeclInstantiator::VisitTypeAliasTemplateDecl(TypeAliasTemplateDecl *D) { return nullptr; TypeAliasDecl *Pattern = D->getTemplatedDecl(); + Sema::InstantiatingTemplate InstTemplate( + SemaRef, Pattern->getTypeSourceInfo()->getTypeLoc().getBeginLoc(), D, + D->getTemplateDepth() >= TemplateArgs.getNumSubstitutedLevels() + ? ArrayRef() + : TemplateArgs.getInnermost()); TypeAliasTemplateDecl *PrevAliasTemplate = nullptr; if (getPreviousDeclForInstantiation(Pattern)) { diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 2d22692f3ab75..8c9a80729f44c 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -767,6 +767,10 @@ class TreeTransform { /// the body. StmtResult SkipLambdaBody(LambdaExpr *E, Stmt *Body); + CXXRecordDecl::LambdaDependencyKind ComputeLambdaDependency(LambdaScopeInfo *LSI) { + return static_cast(LSI->Lambda->getLambdaDependencyKind()); + } + QualType TransformReferenceType(TypeLocBuilder &TLB, ReferenceTypeLoc TL); StmtResult TransformCompoundStmt(CompoundStmt *S, bool IsStmtExpr); @@ -13936,6 +13940,11 @@ TreeTransform::TransformLambdaExpr(LambdaExpr *E) { /*IsInstantiation*/ true); SavedContext.pop(); + DependencyKind = getDerived().ComputeLambdaDependency(&LSICopy); + Class->setLambdaDependencyKind(DependencyKind); + Class->setTypeForDecl(nullptr); + getSema().Context.getTypeDeclType(Class); + return getSema().BuildLambdaExpr(E->getBeginLoc(), Body.get()->getEndLoc(), &LSICopy); } diff --git a/clang/test/SemaTemplate/alias-template-with-lambdas.cpp b/clang/test/SemaTemplate/alias-template-with-lambdas.cpp new file mode 100644 index 0000000000000..c3931287cb640 --- /dev/null +++ b/clang/test/SemaTemplate/alias-template-with-lambdas.cpp @@ -0,0 +1,82 @@ +// RUN: %clang_cc1 -std=c++2c -fsyntax-only -verify %s +namespace lambda_calls { + +template +concept True = true; + +template +concept False = false; // #False + +template struct S { + template using type = decltype([](U...) {}(U()...)); + template using type2 = decltype([](auto) {}(1)); + template using type3 = decltype([](True auto) {}(1)); + template + using type4 = decltype([](auto... pack) { return sizeof...(pack); }(1, 2)); + + template using type5 = decltype([](False auto...) {}(1)); // #Type5 + + template + using type6 = decltype([] {}.template operator()()); + template + using type7 = decltype([] {}.template operator()()); // #Type7 + + template + using type8 = decltype([]() // #Type8 + requires(sizeof(U) == 32) // #Type8-requirement + {}()); + + template + using type9 = decltype([](U...) {}.template operator()(U()...)); + // https://github.com/llvm/llvm-project/issues/76674 + template + using type10 = decltype([] { return V(); }.template operator()()); + + template using type11 = decltype([] { return U{}; }); +}; + +template using Meow = decltype([] {}.template operator()()); + +template +using MeowMeow = decltype([](U...) {}.template operator()(U()...)); + +// https://github.com/llvm/llvm-project/issues/70601 +template using U = decltype([] {}.template operator()()); + +U foo(); + +void bar() { + using T = S::type; + using T2 = S::type2; + using T3 = S::type3; + using T4 = S::type4; + using T5 = S::type5; // #T5 + // expected-error@#Type5 {{no matching function for call}} + // expected-note@#T5 {{type alias 'type5' requested here}} + // expected-note@#Type5 {{constraints not satisfied [with auto:1 = ]}} + // expected-note@#Type5 {{because 'int' does not satisfy 'False'}} + // expected-note@#False {{because 'false' evaluated to false}} + + using T6 = S::type6; + using T7 = S::type7; // #T7 + // expected-error@#Type7 {{no matching member function for call}} + // expected-note@#T7 {{type alias 'type7' requested here}} + // expected-note@#Type7 {{constraints not satisfied [with $0 = char]}} + // expected-note@#Type7 {{because 'char' does not satisfy 'False'}} + // expected-note@#False {{because 'false' evaluated to false}} + + using T8 = S::type8; // #T8 + // expected-error@#Type8 {{no matching function for call}} + // expected-note@#T8 {{type alias 'type8' requested here}} + // expected-note@#Type8 {{constraints not satisfied}} + // expected-note@#Type8-requirement {{because 'sizeof(char) == 32' (1 == 32) evaluated to false}} + + using T9 = S::type9; + using T10 = S::type10; + using T11 = S::type11; + int x = T11()(); + using T12 = Meow; + using T13 = MeowMeow; +} + +} // namespace lambda_calls From 303fd1bc662f2dbb4a63cc28744a1e3c92b7a34a Mon Sep 17 00:00:00 2001 From: Younan Zhang Date: Tue, 20 Feb 2024 16:16:09 +0800 Subject: [PATCH 2/7] Format & Comments --- clang/include/clang/Sema/Sema.h | 1 + clang/lib/Sema/SemaConcept.cpp | 19 +++-- clang/lib/Sema/SemaTemplate.cpp | 5 +- clang/lib/Sema/SemaTemplateInstantiate.cpp | 96 +++++++++++++++------- clang/lib/Sema/TreeTransform.h | 26 +++++- 5 files changed, 105 insertions(+), 42 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index a5520e19a1432..eefb7db3b7ffe 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -10146,6 +10146,7 @@ class Sema final { /// We are building deduction guides for a class. BuildingDeductionGuides, + /// We are instantiating a type alias template declaration. TypeAliasTemplateInstantiation, } Kind; diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp index 0b84d83b4fb4b..0149766584bf2 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -615,13 +615,18 @@ bool Sema::SetupConstraintScope( // reference the original primary template. // We walk up the instantiated template chain so that nested lambdas get // handled properly. - FunctionTemplateDecl *FromMemTempl = - PrimaryTemplate->getInstantiatedFromMemberTemplate(); - while (FromMemTempl && FromMemTempl->getInstantiatedFromMemberTemplate()) - FromMemTempl = FromMemTempl->getInstantiatedFromMemberTemplate(); - if (FromMemTempl && addInstantiatedParametersToScope( - FD, FromMemTempl->getTemplatedDecl(), Scope, MLTAL)) - return true; + // Note that we shall not collect instantiated parameters from + // 'intermediate' transformed function templates but the primary template + // for which we have built up the template arguments relative to. Otherwise, + // we may have mismatched template parameter depth! + if (FunctionTemplateDecl *FromMemTempl = + PrimaryTemplate->getInstantiatedFromMemberTemplate()) { + while (FromMemTempl->getInstantiatedFromMemberTemplate()) + FromMemTempl = FromMemTempl->getInstantiatedFromMemberTemplate(); + if (addInstantiatedParametersToScope(FD, FromMemTempl->getTemplatedDecl(), + Scope, MLTAL)) + return true; + } return false; } diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 30499df1797fc..8856356e52142 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -4344,8 +4344,9 @@ QualType Sema::CheckTemplateIdType(TemplateName Name, return QualType(); InstantiatingTemplate InstTemplate( - *this, Pattern->getTypeSourceInfo()->getTypeLoc().getBeginLoc(), - AliasTemplate, TemplateArgLists.getInnermost()); + *this, /*PointOfInstantiation=*/AliasTemplate->getBeginLoc(), + /*Template=*/AliasTemplate, + /*TemplateArgs=*/TemplateArgLists.getInnermost()); CanonType = SubstType(Pattern->getUnderlyingType(), TemplateArgLists, AliasTemplate->getLocation(), AliasTemplate->getDeclName()); diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index 50f92cc8836b1..ad09cc8252d4f 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -283,8 +283,7 @@ Response HandleFunctionTemplateDecl(const FunctionTemplateDecl *FTD, return Response::ChangeDecl(FTD->getLexicalDeclContext()); } -Response HandleRecordDecl(Sema &SemaRef, - const CXXRecordDecl *Rec, +Response HandleRecordDecl(Sema &SemaRef, const CXXRecordDecl *Rec, MultiLevelTemplateArgumentList &Result, ASTContext &Context, bool ForConstraintInstantiation) { @@ -318,35 +317,68 @@ Response HandleRecordDecl(Sema &SemaRef, if (Rec->isLambda()) { if (const Decl *LCD = Rec->getLambdaContextDecl()) return Response::ChangeDecl(LCD); + // Attempt to 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) { - auto *TATD = cast(CSC.Entity), *CurrentTATD = TATD; - FunctionDecl *LambdaCallOperator = Rec->getLambdaCallOperator(); - while (true) { - auto *FTD = dyn_cast_if_present( - LambdaCallOperator->getDescribedTemplate()); - if (FTD && FTD->getInstantiatedFromMemberTemplate()) { - LambdaCallOperator = - FTD->getInstantiatedFromMemberTemplate()->getTemplatedDecl(); - } else if (auto *Prev = cast(LambdaCallOperator) - ->getInstantiatedFromMemberFunction()) - LambdaCallOperator = Prev; - else - break; - } - while (TATD->getInstantiatedFromMemberTemplate()) - TATD = TATD->getInstantiatedFromMemberTemplate(); - // Constraint template parameters have a deeper depth. - if (cast(LambdaCallOperator->getDeclContext()) - ->getTemplateDepth() == TATD->getTemplateDepth() && - getLambdaAwareParentOfDeclContext(LambdaCallOperator) == - TATD->getDeclContext()) { - Result.addOuterTemplateArguments(CurrentTATD, - CSC.template_arguments(), - /*Final=*/false); - return Response::ChangeDecl(CurrentTATD->getDeclContext()); - } + if (CSC.Kind != Sema::CodeSynthesisContext::SynthesisKind:: + TypeAliasTemplateInstantiation) + continue; + auto *TATD = cast(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( + LambdaCallOperator->getDescribedTemplate()); + if (FTD && FTD->getInstantiatedFromMemberTemplate()) { + LambdaCallOperator = + FTD->getInstantiatedFromMemberTemplate()->getTemplatedDecl(); + } else if (auto *Prev = cast(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 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(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 using T = decltype([] {} ()); + // }; + // 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()); } } } @@ -447,7 +479,8 @@ MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs( R = HandleFunction(Function, Result, Pattern, RelativeToPrimary, ForConstraintInstantiation); } else if (const auto *Rec = dyn_cast(CurDecl)) { - R = HandleRecordDecl(*this, Rec, Result, Context, ForConstraintInstantiation); + R = HandleRecordDecl(*this, Rec, Result, Context, + ForConstraintInstantiation); } else if (const auto *CSD = dyn_cast(CurDecl)) { R = HandleImplicitConceptSpecializationDecl(CSD, Result); @@ -1583,7 +1616,8 @@ namespace { CXXRecordDecl::LambdaDependencyKind ComputeLambdaDependency(LambdaScopeInfo *LSI) { auto &CCS = SemaRef.CodeSynthesisContexts.back(); - if (CCS.Kind == Sema::CodeSynthesisContext::TypeAliasTemplateInstantiation) { + if (CCS.Kind == + Sema::CodeSynthesisContext::TypeAliasTemplateInstantiation) { unsigned TypeAliasDeclDepth = CCS.Entity->getTemplateDepth(); if (TypeAliasDeclDepth >= TemplateArgs.getNumSubstitutedLevels()) return CXXRecordDecl::LambdaDependencyKind::LDK_AlwaysDependent; diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 8c9a80729f44c..5cacd5b368b2f 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -767,8 +767,10 @@ class TreeTransform { /// the body. StmtResult SkipLambdaBody(LambdaExpr *E, Stmt *Body); - CXXRecordDecl::LambdaDependencyKind ComputeLambdaDependency(LambdaScopeInfo *LSI) { - return static_cast(LSI->Lambda->getLambdaDependencyKind()); + CXXRecordDecl::LambdaDependencyKind + ComputeLambdaDependency(LambdaScopeInfo *LSI) { + return static_cast( + LSI->Lambda->getLambdaDependencyKind()); } QualType TransformReferenceType(TypeLocBuilder &TLB, ReferenceTypeLoc TL); @@ -13940,8 +13942,28 @@ TreeTransform::TransformLambdaExpr(LambdaExpr *E) { /*IsInstantiation*/ true); 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 struct S { + // template + // using Type = decltype([](U){}(42.0)); + // }; + // void foo() { + // using T = S::Type; + // ^~~~~~ + // } + // We would end up here from instantiating the S 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. DependencyKind = getDerived().ComputeLambdaDependency(&LSICopy); Class->setLambdaDependencyKind(DependencyKind); + // Clean up the type cache created previously. Then, we re-create a type for + // such Decl with the new DependencyKind. Class->setTypeForDecl(nullptr); getSema().Context.getTypeDeclType(Class); From 62a00af61f3c56778fd1a6679ffba9a842aad834 Mon Sep 17 00:00:00 2001 From: Younan Zhang Date: Tue, 20 Feb 2024 21:41:19 +0800 Subject: [PATCH 3/7] fixup --- clang/lib/Sema/SemaTemplateInstantiateDecl.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index a4277d6d9efed..1de64ef11381f 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -1084,10 +1084,12 @@ TemplateDeclInstantiator::VisitTypeAliasTemplateDecl(TypeAliasTemplateDecl *D) { TypeAliasDecl *Pattern = D->getTemplatedDecl(); Sema::InstantiatingTemplate InstTemplate( - SemaRef, Pattern->getTypeSourceInfo()->getTypeLoc().getBeginLoc(), D, - D->getTemplateDepth() >= TemplateArgs.getNumSubstitutedLevels() + SemaRef, D->getBeginLoc(), D, + D->getTemplateDepth() >= TemplateArgs.getNumLevels() ? ArrayRef() - : TemplateArgs.getInnermost()); + : (TemplateArgs.begin() + TemplateArgs.getNumLevels() - 1 - + D->getTemplateDepth()) + ->Args); TypeAliasTemplateDecl *PrevAliasTemplate = nullptr; if (getPreviousDeclForInstantiation(Pattern)) { From d3a76a1d09a4b7f86bdcab9fb4725d42eb883e79 Mon Sep 17 00:00:00 2001 From: Younan Zhang Date: Fri, 8 Mar 2024 15:52:08 +0800 Subject: [PATCH 4/7] Slightly refactor & Fix GH82104 --- clang/lib/Sema/SemaTemplateInstantiate.cpp | 186 +++++++++++------- clang/lib/Sema/TreeTransform.h | 35 +++- .../alias-template-with-lambdas.cpp | 11 ++ 3 files changed, 155 insertions(+), 77 deletions(-) diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index ad09cc8252d4f..ab295de350ceb 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -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) { + if (auto *FTD = dyn_cast_if_present( + LambdaCallOperator->getDescribedTemplate()); + FTD && FTD->getInstantiatedFromMemberTemplate()) { + LambdaCallOperator = + FTD->getInstantiatedFromMemberTemplate()->getTemplatedDecl(); + } else if (auto *Prev = cast(LambdaCallOperator) + ->getInstantiatedFromMemberFunction()) + LambdaCallOperator = Prev; + else + break; + } + return LambdaCallOperator; +} + +struct EnclosingTypeAliasTemplateDetails { + TypeAliasTemplateDecl *Template = nullptr; + TypeAliasTemplateDecl *PrimaryTypeAliasDecl = nullptr; + ArrayRef 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(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 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(PrimaryLambdaCallOperator->getDeclContext()) + ->getTemplateDepth() == + PrimaryTypeAliasDecl->getTemplateDepth() && + getLambdaAwareParentOfDeclContext( + const_cast(PrimaryLambdaCallOperator)) == + PrimaryTypeAliasDecl->getDeclContext(); +} + // Add template arguments from a variable template instantiation. Response HandleVarTemplateSpec(const VarTemplateSpecializationDecl *VarTemplSpec, @@ -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) { @@ -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 int Value = 0; + // template + // using T = decltype([]() { return Value; }()); + // + 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( @@ -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(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( - LambdaCallOperator->getDescribedTemplate()); - if (FTD && FTD->getInstantiatedFromMemberTemplate()) { - LambdaCallOperator = - FTD->getInstantiatedFromMemberTemplate()->getTemplatedDecl(); - } else if (auto *Prev = cast(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 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(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 using T = decltype([] {} ()); - // }; - // 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 using T = decltype([] {} ()); + // }; + // 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()); } } } @@ -476,7 +528,7 @@ MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs( R = HandleClassTemplateSpec(ClassTemplSpec, Result, SkipForSpecialization); } else if (const auto *Function = dyn_cast(CurDecl)) { - R = HandleFunction(Function, Result, Pattern, RelativeToPrimary, + R = HandleFunction(*this, Function, Result, Pattern, RelativeToPrimary, ForConstraintInstantiation); } else if (const auto *Rec = dyn_cast(CurDecl)) { R = HandleRecordDecl(*this, Rec, Result, Context, @@ -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, diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 5cacd5b368b2f..c9c70c48fd76c 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -13943,8 +13943,10 @@ TreeTransform::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 struct S { + // construction until after we have all the necessary template arguments. For + // example, given + // + // template struct S { // template // using Type = decltype([](U){}(42.0)); // }; @@ -13952,14 +13954,27 @@ TreeTransform::TransformLambdaExpr(LambdaExpr *E) { // using T = S::Type; // ^~~~~~ // } - // We would end up here from instantiating the S 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 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 diff --git a/clang/test/SemaTemplate/alias-template-with-lambdas.cpp b/clang/test/SemaTemplate/alias-template-with-lambdas.cpp index c3931287cb640..128fe2f8f7390 100644 --- a/clang/test/SemaTemplate/alias-template-with-lambdas.cpp +++ b/clang/test/SemaTemplate/alias-template-with-lambdas.cpp @@ -79,4 +79,15 @@ void bar() { using T13 = MeowMeow; } +namespace GH82104 { + +template int Zero = 0; + +template +using T14 = decltype([]() { return Zero; }()); + +template using T15 = T14; + +} // namespace GH82104 + } // namespace lambda_calls From be4164175966f92242bea8cfb0e56b8822a532be Mon Sep 17 00:00:00 2001 From: Younan Zhang Date: Fri, 8 Mar 2024 18:08:33 +0800 Subject: [PATCH 5/7] Mention GH82104 in the release note --- clang/docs/ReleaseNotes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index f5dc956cf5fc4..98ec1c2f258e2 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -345,7 +345,7 @@ Bug Fixes to C++ Support Fixes (`#82509 `_) and (`#74494 `_) - Clang now supports direct lambda calls inside of a type alias template declarations. - This addresses (#GH70601), (#GH76674), (#GH79555), (#GH81145) and so on. + This addresses (#GH70601), (#GH76674), (#GH79555), (#GH81145), (#GH82104) and so on. Bug Fixes to AST Handling ^^^^^^^^^^^^^^^^^^^^^^^^^ From 2e95a41b60b310c67dd7eafa987b5b63ae461ee6 Mon Sep 17 00:00:00 2001 From: Younan Zhang Date: Fri, 8 Mar 2024 18:11:03 +0800 Subject: [PATCH 6/7] Rephrase & Format --- clang/lib/Sema/SemaConcept.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp index 0149766584bf2..c5594a5f047f6 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -615,10 +615,8 @@ bool Sema::SetupConstraintScope( // reference the original primary template. // We walk up the instantiated template chain so that nested lambdas get // handled properly. - // Note that we shall not collect instantiated parameters from - // 'intermediate' transformed function templates but the primary template - // for which we have built up the template arguments relative to. Otherwise, - // we may have mismatched template parameter depth! + // We should only collect instantiated parameters from the primary template. + // Otherwise, we may have mismatched template parameter depth! if (FunctionTemplateDecl *FromMemTempl = PrimaryTemplate->getInstantiatedFromMemberTemplate()) { while (FromMemTempl->getInstantiatedFromMemberTemplate()) From 4fcafbea82933c010ddcfbe474fd26087476206b Mon Sep 17 00:00:00 2001 From: Younan Zhang Date: Wed, 13 Mar 2024 12:27:08 +0800 Subject: [PATCH 7/7] Validate deduced types --- .../SemaTemplate/alias-template-with-lambdas.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/clang/test/SemaTemplate/alias-template-with-lambdas.cpp b/clang/test/SemaTemplate/alias-template-with-lambdas.cpp index 128fe2f8f7390..ff94031e4d86f 100644 --- a/clang/test/SemaTemplate/alias-template-with-lambdas.cpp +++ b/clang/test/SemaTemplate/alias-template-with-lambdas.cpp @@ -77,6 +77,16 @@ void bar() { int x = T11()(); using T12 = Meow; using T13 = MeowMeow; + + static_assert(__is_same(T, void)); + static_assert(__is_same(T2, void)); + static_assert(__is_same(T3, void)); + static_assert(__is_same(T4, decltype(sizeof(0)))); + static_assert(__is_same(T6, void)); + static_assert(__is_same(T9, void)); + static_assert(__is_same(T10, int)); + static_assert(__is_same(T12, void)); + static_assert(__is_same(T13, void)); } namespace GH82104 { @@ -88,6 +98,8 @@ using T14 = decltype([]() { return Zero; }()); template using T15 = T14; +static_assert(__is_same(T15, int)); + } // namespace GH82104 } // namespace lambda_calls