https://github.com/zyn0217 updated https://github.com/llvm/llvm-project/pull/82310
>From f340fb3dd66e436566d5a65da0a35d77a6a0bde6 Mon Sep 17 00:00:00 2001 From: Younan Zhang <zyn7...@gmail.com> Date: Tue, 20 Feb 2024 14:54:14 +0800 Subject: [PATCH 1/5] 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 1b901a27fd19d1..bfe00cb6b21ee6 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -329,6 +329,8 @@ Bug Fixes to C++ Support when one of the function had more specialized templates. Fixes (`#82509 <https://github.com/llvm/llvm-project/issues/82509>`_) and (`#74494 <https://github.com/llvm/llvm-project/issues/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 9cebaff63bb0db..7aed4d5cbc002e 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 592c7871a4a55d..c6c40b1811442d 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -10131,6 +10131,8 @@ class Sema final { /// We are building deduction guides for a class. BuildingDeductionGuides, + + TypeAliasTemplateInstantiation, } Kind; /// Was the enclosing context a non-instantiation SFINAE context? @@ -10220,6 +10222,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<TemplateArgument> 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 b9ed5dedfa4223..43d6e2230fb129 100644 --- a/clang/lib/Frontend/FrontendActions.cpp +++ b/clang/lib/Frontend/FrontendActions.cpp @@ -426,6 +426,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 2878e4d31ee8fe..5cc6236c3991b6 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -614,14 +614,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 7e91815c2d52a8..5d173686c5cabb 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -4024,9 +4024,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 371378485626c2..7d401336741638 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -282,7 +282,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) { @@ -313,9 +314,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<TypeAliasTemplateDecl>(CSC.Entity), *CurrentTATD = TATD; + FunctionDecl *LambdaCallOperator = Rec->getLambdaCallOperator(); + 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; + } + while (TATD->getInstantiatedFromMemberTemplate()) + TATD = TATD->getInstantiatedFromMemberTemplate(); + // Constraint template parameters have a deeper depth. + if (cast<CXXRecordDecl>(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); } @@ -412,7 +446,7 @@ MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs( R = HandleFunction(Function, Result, Pattern, RelativeToPrimary, ForConstraintInstantiation); } else if (const auto *Rec = dyn_cast<CXXRecordDecl>(CurDecl)) { - R = HandleRecordDecl(Rec, Result, Context, ForConstraintInstantiation); + R = HandleRecordDecl(*this, Rec, Result, Context, ForConstraintInstantiation); } else if (const auto *CSD = dyn_cast<ImplicitConceptSpecializationDecl>(CurDecl)) { R = HandleImplicitConceptSpecializationDecl(CSD, Result); @@ -469,6 +503,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. @@ -614,6 +649,15 @@ Sema::InstantiatingTemplate::InstantiatingTemplate( PointOfInstantiation, InstantiationRange, Param, Template, TemplateArgs) {} +Sema::InstantiatingTemplate::InstantiatingTemplate( + Sema &SemaRef, SourceLocation PointOfInstantiation, + TypeAliasTemplateDecl *Template, ArrayRef<TemplateArgument> 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<TemplateArgument> TemplateArgs, @@ -1131,6 +1175,8 @@ void Sema::PrintInstantiationStack() { Diags.Report(Active->PointOfInstantiation, diag::note_building_deduction_guide_here); break; + case CodeSynthesisContext::TypeAliasTemplateInstantiation: + break; } } } @@ -1208,6 +1254,7 @@ std::optional<TemplateDeductionInfo *> Sema::isSFINAEContext() const { break; case CodeSynthesisContext::Memoization: + case CodeSynthesisContext::TypeAliasTemplateInstantiation: break; } @@ -1479,6 +1526,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<TemplateInstantiator> RAII(*this); diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 9c696e072ba4a7..2d8675690972ff 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<TemplateArgument>() + : TemplateArgs.getInnermost()); TypeAliasTemplateDecl *PrevAliasTemplate = nullptr; if (getPreviousDeclForInstantiation<TypedefNameDecl>(Pattern)) { diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 409aee73d960eb..e1f01ddeeb86bc 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<CXXRecordDecl::LambdaDependencyKind>(LSI->Lambda->getLambdaDependencyKind()); + } + QualType TransformReferenceType(TypeLocBuilder &TLB, ReferenceTypeLoc TL); StmtResult TransformCompoundStmt(CompoundStmt *S, bool IsStmtExpr); @@ -13928,6 +13932,11 @@ TreeTransform<Derived>::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 00000000000000..c3931287cb6404 --- /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 <class> +concept True = true; + +template <class> +concept False = false; // #False + +template <class T> struct S { + template <class... U> using type = decltype([](U...) {}(U()...)); + template <class U> using type2 = decltype([](auto) {}(1)); + template <class U> using type3 = decltype([](True auto) {}(1)); + template <class> + using type4 = decltype([](auto... pack) { return sizeof...(pack); }(1, 2)); + + template <class U> using type5 = decltype([](False auto...) {}(1)); // #Type5 + + template <class U> + using type6 = decltype([]<True> {}.template operator()<char>()); + template <class U> + using type7 = decltype([]<False> {}.template operator()<char>()); // #Type7 + + template <class U> + using type8 = decltype([]() // #Type8 + requires(sizeof(U) == 32) // #Type8-requirement + {}()); + + template <class... U> + using type9 = decltype([]<True>(U...) {}.template operator()<char>(U()...)); + // https://github.com/llvm/llvm-project/issues/76674 + template <class U> + using type10 = decltype([]<class V> { return V(); }.template operator()<U>()); + + template <class U> using type11 = decltype([] { return U{}; }); +}; + +template <class> using Meow = decltype([]<True> {}.template operator()<int>()); + +template <class... U> +using MeowMeow = decltype([]<True>(U...) {}.template operator()<char>(U()...)); + +// https://github.com/llvm/llvm-project/issues/70601 +template <class> using U = decltype([]<True> {}.template operator()<int>()); + +U<int> foo(); + +void bar() { + using T = S<int>::type<int, int, int>; + using T2 = S<int>::type2<int>; + using T3 = S<int>::type3<char>; + using T4 = S<int>::type4<void>; + using T5 = S<int>::type5<void>; // #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 = <int>]}} + // expected-note@#Type5 {{because 'int' does not satisfy 'False'}} + // expected-note@#False {{because 'false' evaluated to false}} + + using T6 = S<int>::type6<void>; + using T7 = S<int>::type7<void>; // #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<int>::type8<char>; // #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<int>::type9<long, long, char>; + using T10 = S<int>::type10<int>; + using T11 = S<int>::type11<int>; + int x = T11()(); + using T12 = Meow<int>; + using T13 = MeowMeow<char, int, long, unsigned>; +} + +} // namespace lambda_calls >From 0a2fce6e1006c0cd51009eea7b6086b9ca6447bc Mon Sep 17 00:00:00 2001 From: Younan Zhang <zyn7...@gmail.com> Date: Tue, 20 Feb 2024 16:16:09 +0800 Subject: [PATCH 2/5] 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 c6c40b1811442d..c7d4ad54a4d693 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -10132,6 +10132,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 5cc6236c3991b6..5908b941f21a7d 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -614,13 +614,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 5d173686c5cabb..b61f0068775bd3 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -4025,8 +4025,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 7d401336741638..a94b48ecd13ffb 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -282,8 +282,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) { @@ -317,35 +316,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<TypeAliasTemplateDecl>(CSC.Entity), *CurrentTATD = TATD; - FunctionDecl *LambdaCallOperator = Rec->getLambdaCallOperator(); - 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; - } - while (TATD->getInstantiatedFromMemberTemplate()) - TATD = TATD->getInstantiatedFromMemberTemplate(); - // Constraint template parameters have a deeper depth. - if (cast<CXXRecordDecl>(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<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()); } } } @@ -446,7 +478,8 @@ MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs( R = HandleFunction(Function, Result, Pattern, RelativeToPrimary, ForConstraintInstantiation); } else if (const auto *Rec = dyn_cast<CXXRecordDecl>(CurDecl)) { - R = HandleRecordDecl(*this, Rec, Result, Context, ForConstraintInstantiation); + R = HandleRecordDecl(*this, Rec, Result, Context, + ForConstraintInstantiation); } else if (const auto *CSD = dyn_cast<ImplicitConceptSpecializationDecl>(CurDecl)) { R = HandleImplicitConceptSpecializationDecl(CSD, Result); @@ -1529,7 +1562,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 e1f01ddeeb86bc..fa3ac0ec0ea781 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<CXXRecordDecl::LambdaDependencyKind>(LSI->Lambda->getLambdaDependencyKind()); + CXXRecordDecl::LambdaDependencyKind + ComputeLambdaDependency(LambdaScopeInfo *LSI) { + return static_cast<CXXRecordDecl::LambdaDependencyKind>( + LSI->Lambda->getLambdaDependencyKind()); } QualType TransformReferenceType(TypeLocBuilder &TLB, ReferenceTypeLoc TL); @@ -13932,8 +13934,28 @@ TreeTransform<Derived>::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 <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. 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 60b01b51a4db51b003f0bcea80eb9a4431d534cd Mon Sep 17 00:00:00 2001 From: Younan Zhang <zyn7...@gmail.com> Date: Tue, 20 Feb 2024 21:41:19 +0800 Subject: [PATCH 3/5] 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 2d8675690972ff..b03ff1cf511d7d 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<TemplateArgument>() - : TemplateArgs.getInnermost()); + : (TemplateArgs.begin() + TemplateArgs.getNumLevels() - 1 - + D->getTemplateDepth()) + ->Args); TypeAliasTemplateDecl *PrevAliasTemplate = nullptr; if (getPreviousDeclForInstantiation<TypedefNameDecl>(Pattern)) { >From afd41889a68d1ba37c601d44dc43f0316fdb5ceb Mon Sep 17 00:00:00 2001 From: Younan Zhang <zyn7...@gmail.com> Date: Fri, 8 Mar 2024 15:52:08 +0800 Subject: [PATCH 4/5] 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 a94b48ecd13ffb..0b7d2d90743644 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -79,6 +79,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<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, @@ -175,7 +250,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) { @@ -206,8 +281,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( @@ -311,74 +401,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()); } } } @@ -475,7 +527,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, @@ -689,7 +741,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 fa3ac0ec0ea781..e3c980c7c898f7 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -13935,8 +13935,10 @@ 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)); // }; @@ -13944,14 +13946,27 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) { // 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 diff --git a/clang/test/SemaTemplate/alias-template-with-lambdas.cpp b/clang/test/SemaTemplate/alias-template-with-lambdas.cpp index c3931287cb6404..128fe2f8f73903 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<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 >From 90b15d998027a5d6379ba3e28f56149153bf981d Mon Sep 17 00:00:00 2001 From: Younan Zhang <zyn7...@gmail.com> Date: Fri, 8 Mar 2024 18:08:33 +0800 Subject: [PATCH 5/5] 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 bfe00cb6b21ee6..37fa4f2cff4756 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -330,7 +330,7 @@ Bug Fixes to C++ Support Fixes (`#82509 <https://github.com/llvm/llvm-project/issues/82509>`_) and (`#74494 <https://github.com/llvm/llvm-project/issues/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 ^^^^^^^^^^^^^^^^^^^^^^^^^ _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits