Author: Haojian Wu Date: 2024-05-13T09:33:35+02:00 New Revision: d182877ba3f5ad93e061f68a9ecce38cb8cec418
URL: https://github.com/llvm/llvm-project/commit/d182877ba3f5ad93e061f68a9ecce38cb8cec418 DIFF: https://github.com/llvm/llvm-project/commit/d182877ba3f5ad93e061f68a9ecce38cb8cec418.diff LOG: [clang] CTAD alias: fix the transformation for the require-clause expr (#90961) In the clang AST, constraint nodes are deliberately not instantiated unless they are actively being evaluated. Consequently, occurrences of template parameters in the require-clause expression have a subtle "depth" difference compared to normal occurrences in places, such as function parameters. When transforming the require-clause, we must take this distinction into account. The existing implementation overlooks this consideration. This patch is to rewrite the implementation of the require-clause transformation to address this issue. Fixes #90177 Added: clang/test/AST/ast-dump-ctad-alias.cpp Modified: clang/lib/Sema/SemaTemplate.cpp clang/test/SemaCXX/cxx20-ctad-type-alias.cpp Removed: ################################################################################ diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 0e7bd8dd89573..bae00c6292703 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -2758,31 +2758,149 @@ bool hasDeclaredDeductionGuides(DeclarationName Name, DeclContext *DC) { return false; } +unsigned getTemplateParameterDepth(NamedDecl *TemplateParam) { + if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(TemplateParam)) + return TTP->getDepth(); + if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TemplateParam)) + return TTP->getDepth(); + if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam)) + return NTTP->getDepth(); + llvm_unreachable("Unhandled template parameter types"); +} + NamedDecl *transformTemplateParameter(Sema &SemaRef, DeclContext *DC, NamedDecl *TemplateParam, MultiLevelTemplateArgumentList &Args, - unsigned NewIndex) { + unsigned NewIndex, unsigned NewDepth) { if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(TemplateParam)) - return transformTemplateTypeParam(SemaRef, DC, TTP, Args, TTP->getDepth(), + return transformTemplateTypeParam(SemaRef, DC, TTP, Args, NewDepth, NewIndex); if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TemplateParam)) - return transformTemplateParam(SemaRef, DC, TTP, Args, NewIndex, - TTP->getDepth()); + return transformTemplateParam(SemaRef, DC, TTP, Args, NewIndex, NewDepth); if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam)) - return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex, - NTTP->getDepth()); + return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex, NewDepth); llvm_unreachable("Unhandled template parameter types"); } -Expr *transformRequireClause(Sema &SemaRef, FunctionTemplateDecl *FTD, - llvm::ArrayRef<TemplateArgument> TransformedArgs) { - Expr *RC = FTD->getTemplateParameters()->getRequiresClause(); +// Transform the require-clause of F if any. +// The return result is expected to be the require-clause for the synthesized +// alias deduction guide. +Expr *transformRequireClause(Sema &SemaRef, FunctionTemplateDecl *F, + TypeAliasTemplateDecl *AliasTemplate, + ArrayRef<DeducedTemplateArgument> DeduceResults) { + Expr *RC = F->getTemplateParameters()->getRequiresClause(); if (!RC) return nullptr; + + auto &Context = SemaRef.Context; + LocalInstantiationScope Scope(SemaRef); + + // In the clang AST, constraint nodes are deliberately not instantiated unless + // they are actively being evaluated. Consequently, occurrences of template + // parameters in the require-clause expression have a subtle "depth" + // diff erence compared to normal occurrences in places, such as function + // parameters. When transforming the require-clause, we must take this + // distinction into account: + // + // 1) In the transformed require-clause, occurrences of template parameters + // must use the "uninstantiated" depth; + // 2) When substituting on the require-clause expr of the underlying + // deduction guide, we must use the entire set of template argument lists; + // + // It's important to note that we're performing this transformation on an + // *instantiated* AliasTemplate. + + // For 1), if the alias template is nested within a class template, we + // calcualte the 'uninstantiated' depth by adding the substitution level back. + unsigned AdjustDepth = 0; + if (auto *PrimaryTemplate = + AliasTemplate->getInstantiatedFromMemberTemplate()) + AdjustDepth = PrimaryTemplate->getTemplateDepth(); + + // We rebuild all template parameters with the uninstantiated depth, and + // build template arguments refer to them. + SmallVector<TemplateArgument> AdjustedAliasTemplateArgs; + + for (auto *TP : *AliasTemplate->getTemplateParameters()) { + // Rebuild any internal references to earlier parameters and reindex + // as we go. + MultiLevelTemplateArgumentList Args; + Args.setKind(TemplateSubstitutionKind::Rewrite); + Args.addOuterTemplateArguments(AdjustedAliasTemplateArgs); + NamedDecl *NewParam = transformTemplateParameter( + SemaRef, AliasTemplate->getDeclContext(), TP, Args, + /*NewIndex=*/AdjustedAliasTemplateArgs.size(), + getTemplateParameterDepth(TP) + AdjustDepth); + + auto NewTemplateArgument = Context.getCanonicalTemplateArgument( + Context.getInjectedTemplateArg(NewParam)); + AdjustedAliasTemplateArgs.push_back(NewTemplateArgument); + } + // Template arguments used to transform the template arguments in + // DeducedResults. + SmallVector<TemplateArgument> TemplateArgsForBuildingRC( + F->getTemplateParameters()->size()); + // Transform the transformed template args MultiLevelTemplateArgumentList Args; Args.setKind(TemplateSubstitutionKind::Rewrite); - Args.addOuterTemplateArguments(TransformedArgs); - ExprResult E = SemaRef.SubstExpr(RC, Args); + Args.addOuterTemplateArguments(AdjustedAliasTemplateArgs); + + for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) { + const auto &D = DeduceResults[Index]; + if (D.isNull()) + continue; + TemplateArgumentLoc Input = + SemaRef.getTrivialTemplateArgumentLoc(D, QualType(), SourceLocation{}); + TemplateArgumentLoc Output; + if (!SemaRef.SubstTemplateArgument(Input, Args, Output)) { + assert(TemplateArgsForBuildingRC[Index].isNull() && + "InstantiatedArgs must be null before setting"); + TemplateArgsForBuildingRC[Index] = Output.getArgument(); + } + } + + // A list of template arguments for transforming the require-clause of F. + // It must contain the entire set of template argument lists. + MultiLevelTemplateArgumentList ArgsForBuildingRC; + ArgsForBuildingRC.setKind(clang::TemplateSubstitutionKind::Rewrite); + ArgsForBuildingRC.addOuterTemplateArguments(TemplateArgsForBuildingRC); + // For 2), if the underlying F is instantiated from a member template, we need + // the entire template argument list, as the constraint AST in the + // require-clause of F remains completely uninstantiated. + // + // For example: + // template <typename T> // depth 0 + // struct Outer { + // template <typename U> + // struct Foo { Foo(U); }; + // + // template <typename U> // depth 1 + // requires C<U> + // Foo(U) -> Foo<int>; + // }; + // template <typename U> + // using AFoo = Outer<int>::Foo<U>; + // + // In this scenario, the deduction guide for `Foo` inside `Outer<int>`: + // - The occurrence of U in the require-expression is [depth:1, index:0] + // - The occurrence of U in the function parameter is [depth:0, index:0] + // - The template parameter of U is [depth:0, index:0] + // + // We add the outer template arguments which is [int] to the multi-level arg + // list to ensure that the occurrence U in `C<U>` will be replaced with int + // during the substitution. + if (F->getInstantiatedFromMemberTemplate()) { + auto OuterLevelArgs = SemaRef.getTemplateInstantiationArgs( + F, F->getLexicalDeclContext(), + /*Final=*/false, /*Innermost=*/std::nullopt, + /*RelativeToPrimary=*/true, + /*Pattern=*/nullptr, + /*ForConstraintInstantiation=*/true); + for (auto It : OuterLevelArgs) + ArgsForBuildingRC.addOuterTemplateArguments(It.Args); + } + + ExprResult E = SemaRef.SubstExpr(RC, ArgsForBuildingRC); if (E.isInvalid()) return nullptr; return E.getAs<Expr>(); @@ -2920,7 +3038,8 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef, Args.addOuterTemplateArguments(TransformedDeducedAliasArgs); NamedDecl *NewParam = transformTemplateParameter( SemaRef, AliasTemplate->getDeclContext(), TP, Args, - /*NewIndex=*/FPrimeTemplateParams.size()); + /*NewIndex=*/FPrimeTemplateParams.size(), + getTemplateParameterDepth(TP)); FPrimeTemplateParams.push_back(NewParam); auto NewTemplateArgument = Context.getCanonicalTemplateArgument( @@ -2937,7 +3056,8 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef, // TemplateArgsForBuildingFPrime. Args.addOuterTemplateArguments(TemplateArgsForBuildingFPrime); NamedDecl *NewParam = transformTemplateParameter( - SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size()); + SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size(), + getTemplateParameterDepth(TP)); FPrimeTemplateParams.push_back(NewParam); assert(TemplateArgsForBuildingFPrime[FTemplateParamIdx].isNull() && @@ -2993,7 +3113,7 @@ BuildDeductionGuideForTypeAlias(Sema &SemaRef, auto *GG = cast<CXXDeductionGuideDecl>(FPrime); Expr *RequiresClause = - transformRequireClause(SemaRef, F, TemplateArgsForBuildingFPrime); + transformRequireClause(SemaRef, F, AliasTemplate, DeduceResults); // FIXME: implement the is_deducible constraint per C++ // [over.match.class.deduct]p3.3: diff --git a/clang/test/AST/ast-dump-ctad-alias.cpp b/clang/test/AST/ast-dump-ctad-alias.cpp new file mode 100644 index 0000000000000..423c3454ccb73 --- /dev/null +++ b/clang/test/AST/ast-dump-ctad-alias.cpp @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++2a -ast-dump %s | FileCheck -strict-whitespace %s + +template <typename, typename> +constexpr bool Concept = true; +template<typename T> // depth 0 +struct Out { + template<typename U> // depth 1 + struct Inner { + U t; + }; + + template<typename V> // depth1 + requires Concept<T, V> + Inner(V) -> Inner<V>; +}; + +template <typename X> +struct Out2 { + template<typename Y> // depth1 + using AInner = Out<int>::Inner<Y>; +}; +Out2<double>::AInner t(1.0); + +// Verify that the require-clause of alias deduction guide is transformed correctly: +// - Occurrence T should be replaced with `int`; +// - Occurrence V should be replaced with the Y with depth 1 +// +// CHECK: | `-FunctionTemplateDecl {{.*}} <deduction guide for AInner> +// CHECK-NEXT: | |-TemplateTypeParmDecl {{.*}} typename depth 0 index 0 Y +// CHECK-NEXT: | |-UnresolvedLookupExpr {{.*}} '<dependent type>' lvalue (no ADL) = 'Concept' +// CHECK-NEXT: | | |-TemplateArgument type 'int' +// CHECK-NEXT: | | | `-BuiltinType {{.*}} 'int' +// CHECK-NEXT: | | `-TemplateArgument type 'type-parameter-1-0' +// CHECK-NEXT: | | `-TemplateTypeParmType {{.*}} 'type-parameter-1-0' dependent depth 1 index 0 +// CHECK-NEXT: | |-CXXDeductionGuideDecl {{.*}} <deduction guide for AInner> 'auto (type-parameter-0-0) -> Inner<type-parameter-0-0>' +// CHECK-NEXT: | | `-ParmVarDecl {{.*}} 'type-parameter-0-0' +// CHECK-NEXT: | `-CXXDeductionGuideDecl {{.*}} used <deduction guide for AInner> 'auto (double) -> Inner<double>' implicit_instantiation +// CHECK-NEXT: | |-TemplateArgument type 'double' +// CHECK-NEXT: | | `-BuiltinType {{.*}} 'double' +// CHECK-NEXT: | `-ParmVarDecl {{.*}} 'double' diff --git a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp index e8b4383f53c5e..4c5595e409f28 100644 --- a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp +++ b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp @@ -321,3 +321,71 @@ AG ag(1.0); // choosen. static_assert(__is_same(decltype(ag.t1), int)); } // namespace test23 + +// GH90177 +// verify that the transformed require-clause of the alias deduction gudie has +// the right depth info. +namespace test24 { +class Forward; +class Key {}; + +template <typename D> +constexpr bool C = sizeof(D); + +// Case1: the alias template and the underlying deduction guide are in the same +// scope. +template <typename T> +struct Case1 { + template <typename U> + struct Foo { + Foo(U); + }; + + template <typename V> + requires (C<V>) + Foo(V) -> Foo<V>; + + template <typename Y> + using Alias = Foo<Y>; +}; +// The require-clause should be evaluated on the type Key. +Case1<Forward>::Alias t2 = Key(); + + +// Case2: the alias template and underlying deduction guide are in diff erent +// scope. +template <typename T> +struct Foo { + Foo(T); +}; +template <typename U> +requires (C<U>) +Foo(U) -> Foo<U>; + +template <typename T> +struct Case2 { + template <typename Y> + using Alias = Foo<Y>; +}; +// The require-caluse should be evaluated on the type Key. +Case2<Forward>::Alias t1 = Key(); + +// Case3: crashes on the constexpr evaluator due to the mixed-up depth in +// require-expr. +template <class T1> +struct A1 { + template<class T2> + struct A2 { + template <class T3> + struct Foo { + Foo(T3); + }; + template <class T3> + requires C<T3> + Foo(T3) -> Foo<T3>; + }; +}; +template <typename U> +using AFoo = A1<int>::A2<int>::Foo<U>; +AFoo case3(1); +} // namespace test24 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits