[clang] bc8c168 - [clang] fix matching constrained out-of-line definitions of class specialization member function templates (#192806)

via cfe-commits cfe-commits at lists.llvm.org
Mon Apr 20 07:23:55 PDT 2026


Author: Matheus Izvekov
Date: 2026-04-20T11:23:50-03:00
New Revision: bc8c168238ce1e2e03e1dd38086b3d67e430c5e4

URL: https://github.com/llvm/llvm-project/commit/bc8c168238ce1e2e03e1dd38086b3d67e430c5e4
DIFF: https://github.com/llvm/llvm-project/commit/bc8c168238ce1e2e03e1dd38086b3d67e430c5e4.diff

LOG: [clang] fix matching constrained out-of-line definitions of class specialization member function templates (#192806)

The method which gathered the template arguments for transforming
constraints was incorrectly skipping adding the arguments for function
templates which are members of class template specializations.

This fixes that, and removes an undocumented workaround for template
alias CTAD.

Also adds a test case showing #139276 caused a profiling issue with
PackIndexExprs,
which for the tests added in that PR gave the false impression they were
fixing the
problem, but were actually causing the implementation to be too
accepting, which
masked the bug solved in this patch.

Added: 
    

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/lib/Sema/SemaTemplateDeductionGuide.cpp
    clang/lib/Sema/SemaTemplateInstantiate.cpp
    clang/test/SemaTemplate/concepts-out-of-line-def.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index b9381c69807e4..e89c73fbade58 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -475,6 +475,7 @@ Bug Fixes to C++ Support
 - Correctly diagnose uses of ``co_await`` / ``co_yield`` in the default argument of nested function declarations. (#GH98923)
 - Fixed a crash when diagnosing an invalid static member function with an explicit object parameter (#GH177741)
 - Clang incorrectly instantiated variable specializations outside of the immediate context. (#GH54439)
+- Fixed a bug matching constrained out-of-line definitions of class members.
 - Fixed a crash when instantiating an invalid out-of-line static data member definition in a local class. (#GH176152)
 - Fixed a crash when pack expansions are used as arguments for non-pack parameters of built-in templates. (#GH180307)
 - Fixed a bug where captured variables in non-mutable lambdas were incorrectly treated as mutable 

diff  --git a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
index 11a671cb9ff8d..69fe21c3eaf3e 100644
--- a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
+++ b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
@@ -924,52 +924,13 @@ buildAssociatedConstraints(Sema &SemaRef, FunctionTemplateDecl *F,
     }
   }
 
-  // A list of template arguments for transforming the require-clause of F.
-  // It must contain the entire set of template argument lists.
-  MultiLevelTemplateArgumentList ArgsForBuildingRC;
+  auto ArgsForBuildingRC = SemaRef.getTemplateInstantiationArgs(
+      F, F->getLexicalDeclContext(),
+      /*Final=*/false, /*Innermost=*/TemplateArgsForBuildingRC,
+      /*RelativeToPrimary=*/true,
+      /*Pattern=*/nullptr,
+      /*ForConstraintInstantiation=*/true);
   ArgsForBuildingRC.setKind(clang::TemplateSubstitutionKind::Rewrite);
-  ArgsForBuildingRC.addOuterTemplateArguments(TemplateArgsForBuildingRC);
-  // For 2), if the underlying deduction guide F is nested in a class 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.
-  //
-  // NOTE: The underlying deduction guide F is instantiated -- either from an
-  // explicitly-written deduction guide member, or from a constructor.
-  // getInstantiatedFromMemberTemplate() can only handle the former case, so we
-  // check the DeclContext kind.
-  if (F->getLexicalDeclContext()->getDeclKind() ==
-      clang::Decl::ClassTemplateSpecialization) {
-    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())

diff  --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 8dfe33f8684bd..59d4fde6ada64 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -346,58 +346,56 @@ Response HandleFunction(Sema &SemaRef, const FunctionDecl *Function,
 Response HandleFunctionTemplateDecl(Sema &SemaRef,
                                     const FunctionTemplateDecl *FTD,
                                     MultiLevelTemplateArgumentList &Result) {
-  if (!isa<ClassTemplateSpecializationDecl>(FTD->getDeclContext())) {
-    Result.addOuterTemplateArguments(
-        const_cast<FunctionTemplateDecl *>(FTD),
-        const_cast<FunctionTemplateDecl *>(FTD)->getInjectedTemplateArgs(
-            SemaRef.Context),
-        /*Final=*/false);
+  Result.addOuterTemplateArguments(
+      const_cast<FunctionTemplateDecl *>(FTD),
+      const_cast<FunctionTemplateDecl *>(FTD)->getInjectedTemplateArgs(
+          SemaRef.Context),
+      /*Final=*/false);
 
-    NestedNameSpecifier NNS = FTD->getTemplatedDecl()->getQualifier();
-
-    for (const Type *Ty = NNS.getKind() == NestedNameSpecifier::Kind::Type
-                              ? NNS.getAsType()
-                              : nullptr,
-                    *NextTy = nullptr;
-         Ty && Ty->isInstantiationDependentType();
-         Ty = std::exchange(NextTy, nullptr)) {
-      if (NestedNameSpecifier P = Ty->getPrefix();
-          P.getKind() == NestedNameSpecifier::Kind::Type)
-        NextTy = P.getAsType();
-      const auto *TSTy = dyn_cast<TemplateSpecializationType>(Ty);
-      if (!TSTy)
-        continue;
+  NestedNameSpecifier NNS = FTD->getTemplatedDecl()->getQualifier();
+
+  for (const Type *Ty = NNS.getKind() == NestedNameSpecifier::Kind::Type
+                            ? NNS.getAsType()
+                            : nullptr,
+                  *NextTy = nullptr;
+       Ty && Ty->isInstantiationDependentType();
+       Ty = std::exchange(NextTy, nullptr)) {
+    if (NestedNameSpecifier P = Ty->getPrefix();
+        P.getKind() == NestedNameSpecifier::Kind::Type)
+      NextTy = P.getAsType();
+    const auto *TSTy = dyn_cast<TemplateSpecializationType>(Ty);
+    if (!TSTy)
+      continue;
 
-      ArrayRef<TemplateArgument> Arguments = TSTy->template_arguments();
-      // Prefer template arguments from the injected-class-type if possible.
-      // For example,
-      // ```cpp
-      // template <class... Pack> struct S {
-      //   template <class T> void foo();
-      // };
-      // template <class... Pack> template <class T>
-      //           ^^^^^^^^^^^^^ InjectedTemplateArgs
-      //           They're of kind TemplateArgument::Pack, not of
-      //           TemplateArgument::Type.
-      // void S<Pack...>::foo() {}
-      //        ^^^^^^^
-      //        TSTy->template_arguments() (which are of PackExpansionType)
-      // ```
-      // This meets the contract in
-      // TreeTransform::TryExpandParameterPacks that the template arguments
-      // for unexpanded parameters should be of a Pack kind.
-      if (TSTy->isCurrentInstantiation()) {
-        auto *RD = TSTy->getCanonicalTypeInternal()->getAsCXXRecordDecl();
-        if (ClassTemplateDecl *CTD = RD->getDescribedClassTemplate())
-          Arguments = CTD->getInjectedTemplateArgs(SemaRef.Context);
-        else if (auto *Specialization =
-                     dyn_cast<ClassTemplateSpecializationDecl>(RD))
-          Arguments = Specialization->getTemplateInstantiationArgs().asArray();
-      }
-      Result.addOuterTemplateArguments(
-          TSTy->getTemplateName().getAsTemplateDecl(), Arguments,
-          /*Final=*/false);
+    ArrayRef<TemplateArgument> Arguments = TSTy->template_arguments();
+    // Prefer template arguments from the injected-class-type if possible.
+    // For example,
+    // ```cpp
+    // template <class... Pack> struct S {
+    //   template <class T> void foo();
+    // };
+    // template <class... Pack> template <class T>
+    //           ^^^^^^^^^^^^^ InjectedTemplateArgs
+    //           They're of kind TemplateArgument::Pack, not of
+    //           TemplateArgument::Type.
+    // void S<Pack...>::foo() {}
+    //        ^^^^^^^
+    //        TSTy->template_arguments() (which are of PackExpansionType)
+    // ```
+    // This meets the contract in
+    // TreeTransform::TryExpandParameterPacks that the template arguments
+    // for unexpanded parameters should be of a Pack kind.
+    if (TSTy->isCurrentInstantiation()) {
+      auto *RD = TSTy->getCanonicalTypeInternal()->getAsCXXRecordDecl();
+      if (ClassTemplateDecl *CTD = RD->getDescribedClassTemplate())
+        Arguments = CTD->getInjectedTemplateArgs(SemaRef.Context);
+      else if (auto *Specialization =
+                   dyn_cast<ClassTemplateSpecializationDecl>(RD))
+        Arguments = Specialization->getTemplateInstantiationArgs().asArray();
     }
+    Result.addOuterTemplateArguments(
+        TSTy->getTemplateName().getAsTemplateDecl(), Arguments,
+        /*Final=*/false);
   }
 
   return Response::ChangeDecl(FTD->getLexicalDeclContext());

diff  --git a/clang/test/SemaTemplate/concepts-out-of-line-def.cpp b/clang/test/SemaTemplate/concepts-out-of-line-def.cpp
index 9811b18f4301b..b6b709a7d1864 100644
--- a/clang/test/SemaTemplate/concepts-out-of-line-def.cpp
+++ b/clang/test/SemaTemplate/concepts-out-of-line-def.cpp
@@ -838,7 +838,7 @@ auto TplClass<int>::buggy() -> void {}
 
 }
 
-namespace PackIndexExpr {
+namespace PackIndexExpr1 {
 template <int... T>
 concept C = true;
 
@@ -852,7 +852,81 @@ template <>
 template <int... Ts>
 requires C<Ts...[0]>
 auto TplClass<int>::buggy() -> void {}
-}
+} // namespace PackIndexExpr1
+
+namespace PackIndexExpr2 {
+  template <int... T> concept C = true;
+
+  namespace t1 {
+    template <int...> struct TplClass { // expected-note {{defined here}}
+      template <int... Ts, int... Us>
+      requires C<Ts...[0]>
+      static auto buggy() -> void;
+    };
+
+    template <>
+    template <int... Ts, int... Us>
+    requires C<Us...[0]>
+    auto TplClass<0>::buggy() -> void {}
+    // expected-error at -1 {{out-of-line definition of 'buggy' does not match any declaration}}
+  } // namespace t1
+  namespace t2 {
+    template <int...> struct TplClass { // expected-note {{defined here}}
+      template <int... Ts>
+      requires C<Ts...[0]>
+      static auto buggy() -> void;
+    };
+
+    template <>
+    template <int... Ts>
+    requires C<Ts...[1]>
+    auto TplClass<0>::buggy() -> void {}
+    // expected-error at -1 {{out-of-line definition of 'buggy' does not match any declaration}}
+  } // namespace t2
+  namespace t3 {
+    template <int... Us> struct TplClass { // expected-note {{defined here}}
+      template <int... Ts>
+      requires C<Us...[0]>
+      static auto buggy() -> void;
+    };
+
+    template <>
+    template <int... Ts>
+    requires C<Ts...[0]>
+    auto TplClass<0>::buggy() -> void {}
+    // expected-error at -1 {{out-of-line definition of 'buggy' does not match any declaration}}
+  } // namespace t3
+} // namespace PackIndexExpr2
+
+namespace FuncTemplateInClass {
+  template <int T> concept C = true;
+
+  namespace t1 {
+    template <int> struct TplClass {
+      template <int Ts>
+      requires C<Ts>
+      static auto buggy() -> void;
+    };
+
+    template <>
+    template <int Ts>
+    requires C<Ts>
+    auto TplClass<0>::buggy() -> void {}
+  } //namespace t1
+  namespace t2 {
+    template <int> struct TplClass { // expected-note {{defined here}}
+      template <int Ts, int Us>
+      requires C<Ts>
+      static auto buggy() -> void;
+    };
+
+    template <>
+    template <int Ts, int Us>
+    requires C<Us>
+    auto TplClass<0>::buggy() -> void {}
+    // expected-error at -1 {{out-of-line definition of 'buggy' does not match any declaration}}
+  } //namespace t2
+} // namespace FuncTemplateInClass
 
 namespace GH139476 {
 


        


More information about the cfe-commits mailing list