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

Matheus Izvekov via cfe-commits cfe-commits at lists.llvm.org
Sat Apr 18 12:54:03 PDT 2026


https://github.com/mizvekov updated https://github.com/llvm/llvm-project/pull/192806

>From 34c864cda1f69d224da1858b557695d20ff0a53f Mon Sep 17 00:00:00 2001
From: Matheus Izvekov <mizvekov at gmail.com>
Date: Sat, 18 Apr 2026 15:54:27 -0300
Subject: [PATCH] [clang] fix a bug matching constrained out-of-line
 definitions of class member functions

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

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

Also adds a test case showing #139276 causes 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.
---
 clang/docs/ReleaseNotes.rst                   |  1 +
 clang/lib/Sema/SemaTemplateDeductionGuide.cpp | 51 ++--------
 clang/lib/Sema/SemaTemplateInstantiate.cpp    | 96 +++++++++----------
 .../SemaTemplate/concepts-out-of-line-def.cpp | 65 ++++++++++++-
 4 files changed, 117 insertions(+), 96 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 3e2d287d1eb1f..87512f8f6097a 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -438,6 +438,7 @@ Bug Fixes to C++ Support
 - Fixed an alias template CTAD crash.
 - 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 8d55fb087193f..9d72241bfbc18 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..9a641c4d2c8a1 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,68 @@ 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 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