https://github.com/cor3ntin created 
https://github.com/llvm/llvm-project/pull/199671

Register implicit concept specializations in the instantiation scope, so that 
Subst* nodes are properly handled during transform.

- ImplicitConceptSpecializationDecl was modified (added a reference to the 
concept, made it a NamedDecl) - so that it was usable with the 
findinstantiation machinery, etc.

- ConceptSpecializationExprs are always substituted, not just when we need them 
for diagnostics (might have a small perf impact)

#Fixes 196375

>From 003df0a75ccd73bb8970dc6ad103d40d3d1d35f9 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <[email protected]>
Date: Tue, 26 May 2026 14:43:39 +0200
Subject: [PATCH] [Clang] Handle references to concept in Subst* nodes

---
 clang/include/clang/AST/DeclTemplate.h        |  7 +-
 clang/include/clang/Basic/DeclNodes.td        |  2 +-
 clang/lib/AST/ASTImporter.cpp                 |  7 +-
 clang/lib/AST/DeclTemplate.cpp                | 19 ++--
 clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp |  2 +-
 clang/lib/Sema/SemaConcept.cpp                | 99 ++++++++++++-------
 clang/lib/Sema/SemaTemplate.cpp               |  2 +-
 clang/lib/Sema/SemaTemplateInstantiate.cpp    |  2 +-
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |  3 +-
 clang/lib/Serialization/ASTReaderDecl.cpp     |  3 +-
 clang/lib/Serialization/ASTWriterDecl.cpp     |  5 +-
 clang/test/SemaTemplate/concepts.cpp          | 19 ++++
 12 files changed, 117 insertions(+), 53 deletions(-)

diff --git a/clang/include/clang/AST/DeclTemplate.h 
b/clang/include/clang/AST/DeclTemplate.h
index 6ca9d67af0710..0cb7b36d070d0 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -3234,23 +3234,28 @@ class ConceptDecl : public TemplateDecl, public 
Mergeable<ConceptDecl> {
 // arguments, so we can later use this to reconstitute the template arguments
 // during constraint checking.
 class ImplicitConceptSpecializationDecl final
-    : public Decl,
+    : public NamedDecl,
       private llvm::TrailingObjects<ImplicitConceptSpecializationDecl,
                                     TemplateArgument> {
+  const TemplateDecl *Template;
   unsigned NumTemplateArgs;
 
   ImplicitConceptSpecializationDecl(DeclContext *DC, SourceLocation SL,
+                                    const TemplateDecl *Template,
                                     ArrayRef<TemplateArgument> ConvertedArgs);
   ImplicitConceptSpecializationDecl(EmptyShell Empty, unsigned 
NumTemplateArgs);
 
 public:
   static ImplicitConceptSpecializationDecl *
   Create(const ASTContext &C, DeclContext *DC, SourceLocation SL,
+         const TemplateDecl *Template,
          ArrayRef<TemplateArgument> ConvertedArgs);
   static ImplicitConceptSpecializationDecl *
   CreateDeserialized(const ASTContext &C, GlobalDeclID ID,
                      unsigned NumTemplateArgs);
 
+  const TemplateDecl *getSpecializedTemplate() const { return Template; }
+
   ArrayRef<TemplateArgument> getTemplateArguments() const {
     return getTrailingObjects(NumTemplateArgs);
   }
diff --git a/clang/include/clang/Basic/DeclNodes.td 
b/clang/include/clang/Basic/DeclNodes.td
index ffb58b43812dc..c983f667040de 100644
--- a/clang/include/clang/Basic/DeclNodes.td
+++ b/clang/include/clang/Basic/DeclNodes.td
@@ -91,7 +91,7 @@ def Named : DeclNode<Decl, "named declarations", 1>;
       def ObjCImplementation : DeclNode<ObjCImpl>;
   def ObjCProperty : DeclNode<Named, "Objective-C properties">;
   def ObjCCompatibleAlias : DeclNode<Named>;
-def ImplicitConceptSpecialization : DeclNode<Decl>;
+def ImplicitConceptSpecialization : DeclNode<Named>;
 def LinkageSpec : DeclNode<Decl>, DeclContext;
 def Export : DeclNode<Decl>, DeclContext;
 def ObjCPropertyImpl : DeclNode<Decl>;
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 0d8243a6bd74b..53f9b42d1f37a 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -7052,12 +7052,17 @@ ExpectedDecl 
ASTNodeImporter::VisitImplicitConceptSpecializationDecl(
   if (Err)
     return std::move(Err);
 
+  const TemplateDecl *Concept;
+  if ((Err = importInto(Concept, D->getSpecializedTemplate())))
+    return std::move(Err);
+
   SmallVector<TemplateArgument, 2> ToArgs(D->getTemplateArguments().size());
   if (Error Err = ImportTemplateArguments(D->getTemplateArguments(), ToArgs))
     return std::move(Err);
 
   ImplicitConceptSpecializationDecl *To;
-  if (GetImportedOrCreateDecl(To, D, Importer.getToContext(), DC, ToSL, 
ToArgs))
+  if (GetImportedOrCreateDecl(To, D, Importer.getToContext(), DC, ToSL, 
Concept,
+                              ToArgs))
     return To;
   To->setLexicalDeclContext(LexicalDC);
   LexicalDC->addDeclInternal(To);
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index 275fe364e306d..2de8f0faa395e 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1127,24 +1127,25 @@ ConceptDecl *ConceptDecl::CreateDeserialized(ASTContext 
&C, GlobalDeclID ID) {
 // ImplicitConceptSpecializationDecl Implementation
 
//===----------------------------------------------------------------------===//
 ImplicitConceptSpecializationDecl::ImplicitConceptSpecializationDecl(
-    DeclContext *DC, SourceLocation SL,
+    DeclContext *DC, SourceLocation SL, const TemplateDecl *Template,
     ArrayRef<TemplateArgument> ConvertedArgs)
-    : Decl(ImplicitConceptSpecialization, DC, SL),
-      NumTemplateArgs(ConvertedArgs.size()) {
+    : NamedDecl(ImplicitConceptSpecialization, DC, SL, 
Template->getDeclName()),
+      Template(Template), NumTemplateArgs(ConvertedArgs.size()) {
   setTemplateArguments(ConvertedArgs);
 }
 
 ImplicitConceptSpecializationDecl::ImplicitConceptSpecializationDecl(
     EmptyShell Empty, unsigned NumTemplateArgs)
-    : Decl(ImplicitConceptSpecialization, Empty),
+    : NamedDecl(ImplicitConceptSpecialization, /*DC=*/nullptr, 
SourceLocation(),
+                DeclarationName()),
       NumTemplateArgs(NumTemplateArgs) {}
 
 ImplicitConceptSpecializationDecl *ImplicitConceptSpecializationDecl::Create(
     const ASTContext &C, DeclContext *DC, SourceLocation SL,
-    ArrayRef<TemplateArgument> ConvertedArgs) {
+    const TemplateDecl *Concept, ArrayRef<TemplateArgument> ConvertedArgs) {
   return new (C, DC,
               additionalSizeToAlloc<TemplateArgument>(ConvertedArgs.size()))
-      ImplicitConceptSpecializationDecl(DC, SL, ConvertedArgs);
+      ImplicitConceptSpecializationDecl(DC, SL, Concept, ConvertedArgs);
 }
 
 ImplicitConceptSpecializationDecl *
@@ -1721,6 +1722,12 @@ clang::getReplacedTemplateParameter(Decl *D, unsigned 
Index) {
     return {Info->getTemplate()->getTemplateParameters()->getParam(Index),
             Info->TemplateArguments->asArray()[Index]};
   }
+  case Decl::Kind::ImplicitConceptSpecialization: {
+    const auto *Spec = cast<ImplicitConceptSpecializationDecl>(D);
+    return {Spec->getSpecializedTemplate()->getTemplateParameters()->getParam(
+                Index),
+            Spec->getTemplateArguments()[Index]};
+  }
   default:
     llvm_unreachable("Unhandled templated declaration kind");
   }
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp 
b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
index de170c86400d2..1123c44aba74c 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
@@ -365,7 +365,7 @@ 
TemplateParameterListBuilder::constructConceptSpecializationExpr(
 
   ImplicitConceptSpecializationDecl *ImplicitCSEDecl =
       ImplicitConceptSpecializationDecl::Create(
-          Context, Builder.Record->getDeclContext(), Loc, {CSETA});
+          Context, Builder.Record->getDeclContext(), Loc, CD, {CSETA});
 
   // Constraint satisfaction is used to construct the
   // ConceptSpecailizationExpr, and represents the 2nd Template Argument,
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index ea03c3f408986..f1d6adca0c55c 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -1022,13 +1022,13 @@ ExprResult ConstraintSatisfactionChecker::EvaluateSlow(
   if (SubstitutedConceptId.isInvalid() || Trap.hasErrorOccurred())
     return ExprError();
 
-  if (Size != Satisfaction.Details.size()) {
+  //if (Size != Satisfaction.Details.size()) {
     Satisfaction.Details.insert(
         Satisfaction.Details.begin() + Size,
         UnsatisfiedConstraintRecord(
             SubstitutedConceptId.getAs<ConceptSpecializationExpr>()
                 ->getConceptReference()));
-  }
+  //}
   return SubstitutedConceptId;
 }
 
@@ -1037,6 +1037,58 @@ ExprResult ConstraintSatisfactionChecker::Evaluate(
     const MultiLevelTemplateArgumentList &MLTAL) {
 
   const ConceptReference *ConceptId = Constraint.getConceptId();
+
+  llvm::SaveAndRestore PushConceptDecl(
+      ParentConcept, cast<ConceptDecl>(ConceptId->getNamedConcept()));
+
+  unsigned Size = Satisfaction.Details.size();
+
+  auto EvaluateCSE = [&] {
+    UnsignedOrNone OuterPackSubstIndex = getOuterPackIndex(Constraint);
+    llvm::FoldingSetNodeID ID;
+    ID.AddPointer(Constraint.getConceptId());
+    ID.AddInteger(OuterPackSubstIndex.toInternalRepresentation());
+    HashParameterMapping(S, MLTAL, ID, OuterPackSubstIndex)
+        .VisitConstraint(Constraint);
+
+    if (auto Iter = S.UnsubstitutedConstraintSatisfactionCache.find(ID);
+        Iter != S.UnsubstitutedConstraintSatisfactionCache.end()) {
+
+      auto &Cached = Iter->second.Satisfaction;
+      Satisfaction.ContainsErrors = Cached.ContainsErrors;
+      Satisfaction.IsSatisfied = Cached.IsSatisfied;
+      Satisfaction.Details.insert(Satisfaction.Details.begin() + Size,
+                                  Cached.Details.begin(), 
Cached.Details.end());
+      return Iter->second.SubstExpr;
+    }
+
+    ExprResult CE = EvaluateSlow(Constraint, MLTAL, Size);
+
+    if (CE.isInvalid())
+      return ExprError();
+    UnsubstitutedConstraintSatisfactionCacheResult Cache;
+    Cache.Satisfaction.ContainsErrors = Satisfaction.ContainsErrors;
+    Cache.Satisfaction.IsSatisfied = Satisfaction.IsSatisfied;
+    Cache.Satisfaction.Details.insert(Cache.Satisfaction.Details.end(),
+                                      Satisfaction.Details.begin() + Size,
+                                      Satisfaction.Details.end());
+    Cache.SubstExpr = CE;
+    S.UnsubstitutedConstraintSatisfactionCache.insert({ID, std::move(Cache)});
+    if (CE.isInvalid())
+      return ExprError();
+    return CE;
+  };
+
+  auto Res = EvaluateCSE();
+  LocalInstantiationScope Scope(S);
+  if (Res.isUsable()) {
+    if (const auto *CSE = Res.getAs<ConceptSpecializationExpr>()) {
+      Scope.InstantiatedLocal(ConceptId->getNamedConcept(),
+                              const_cast<ImplicitConceptSpecializationDecl *>(
+                                  CSE->getSpecializationDecl()));
+    }
+  }
+
   Sema::InstantiatingTemplate InstTemplate(
       S, ConceptId->getBeginLoc(),
       Sema::InstantiatingTemplate::ConstraintsCheck{},
@@ -1052,11 +1104,6 @@ ExprResult ConstraintSatisfactionChecker::Evaluate(
   if (InstTemplate.isInvalid())
     return ExprError();
 
-  unsigned Size = Satisfaction.Details.size();
-
-  llvm::SaveAndRestore PushConceptDecl(
-      ParentConcept, cast<ConceptDecl>(ConceptId->getNamedConcept()));
-
   ExprResult E = Evaluate(Constraint.getNormalizedConstraint(), MLTAL);
 
   if (E.isInvalid()) {
@@ -1067,39 +1114,10 @@ ExprResult ConstraintSatisfactionChecker::Evaluate(
   // ConceptIdConstraint is only relevant for diagnostics,
   // so if the normalized constraint is satisfied, we should not
   // substitute into the constraint.
-  if (Satisfaction.IsSatisfied)
+  if (Res.isInvalid())
     return E;
 
-  UnsignedOrNone OuterPackSubstIndex = getOuterPackIndex(Constraint);
-  llvm::FoldingSetNodeID ID;
-  ID.AddPointer(Constraint.getConceptId());
-  ID.AddInteger(OuterPackSubstIndex.toInternalRepresentation());
-  HashParameterMapping(S, MLTAL, ID, OuterPackSubstIndex)
-      .VisitConstraint(Constraint);
-
-  if (auto Iter = S.UnsubstitutedConstraintSatisfactionCache.find(ID);
-      Iter != S.UnsubstitutedConstraintSatisfactionCache.end()) {
-
-    auto &Cached = Iter->second.Satisfaction;
-    Satisfaction.ContainsErrors = Cached.ContainsErrors;
-    Satisfaction.IsSatisfied = Cached.IsSatisfied;
-    Satisfaction.Details.insert(Satisfaction.Details.begin() + Size,
-                                Cached.Details.begin(), Cached.Details.end());
-    return Iter->second.SubstExpr;
-  }
-
-  ExprResult CE = EvaluateSlow(Constraint, MLTAL, Size);
-  if (CE.isInvalid())
-    return E;
-  UnsubstitutedConstraintSatisfactionCacheResult Cache;
-  Cache.Satisfaction.ContainsErrors = Satisfaction.ContainsErrors;
-  Cache.Satisfaction.IsSatisfied = Satisfaction.IsSatisfied;
-  Cache.Satisfaction.Details.insert(Cache.Satisfaction.Details.end(),
-                                    Satisfaction.Details.begin() + Size,
-                                    Satisfaction.Details.end());
-  Cache.SubstExpr = CE;
-  S.UnsubstitutedConstraintSatisfactionCache.insert({ID, std::move(Cache)});
-  return CE;
+  return Res;
 }
 
 ExprResult ConstraintSatisfactionChecker::Evaluate(
@@ -2327,6 +2345,11 @@ bool 
SubstituteParameterMappings::substitute(NormalizedConstraint &N) {
       InnerArgs = std::move(CTAI.SugaredConverted);
     }
 
+    LocalInstantiationScope Scope(SemaRef);
+    Scope.InstantiatedLocal(CSE->getNamedConcept(),
+                            const_cast<ImplicitConceptSpecializationDecl *>(
+                                CSE->getSpecializationDecl()));
+
     MultiLevelTemplateArgumentList MLTAL = 
SemaRef.getTemplateInstantiationArgs(
         Concept, Concept->getLexicalDeclContext(),
         /*Final=*/true, InnerArgs,
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 5667cf53fee0b..cecead933ec38 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -4911,7 +4911,7 @@ ExprResult Sema::CheckConceptTemplateId(
   // FIXME: Reland https://github.com/llvm/llvm-project/pull/101782 properly!
   auto *CSD = ImplicitConceptSpecializationDecl::Create(
       Context, NamedConcept->getDeclContext(), NamedConcept->getLocation(),
-      CTAI.SugaredConverted);
+      NamedConcept, CTAI.SugaredConverted);
   ConstraintSatisfaction Satisfaction;
   bool AreArgsDependent =
       TemplateSpecializationType::anyDependentTemplateArguments(
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp 
b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index f168c99d1ac1a..32ff54fe729a3 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -4685,7 +4685,7 @@ LocalInstantiationScope::findInstantiationOf(const Decl 
*D) {
   // If we're performing a partial substitution during template argument
   // deduction, we may not have values for template parameters yet.
   if (isa<NonTypeTemplateParmDecl>(D) || isa<TemplateTypeParmDecl>(D) ||
-      isa<TemplateTemplateParmDecl>(D))
+      isa<TemplateTemplateParmDecl>(D) || isa<ConceptDecl>(D))
     return nullptr;
 
   // Local types referenced prior to definition may require instantiation.
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp 
b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index c9bc613a7c4ea..8fc1d4ca52278 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -6933,6 +6933,7 @@ NamedDecl *Sema::FindInstantiatedDecl(SourceLocation Loc, 
NamedDecl *D,
     return D;
   if (isa<ParmVarDecl>(D) || isa<NonTypeTemplateParmDecl>(D) ||
       isa<TemplateTypeParmDecl>(D) || isa<TemplateTemplateParmDecl>(D) ||
+      isa<ConceptDecl>(D) ||
       (ParentDependsOnArgs && (ParentDC->isFunctionOrMethod() ||
                                isa<OMPDeclareReductionDecl>(ParentDC) ||
                                isa<OMPDeclareMapperDecl>(ParentDC))) ||
@@ -6963,7 +6964,7 @@ NamedDecl *Sema::FindInstantiatedDecl(SourceLocation Loc, 
NamedDecl *D,
     // deduction, we may not have values for template parameters yet. They
     // just map to themselves.
     if (isa<NonTypeTemplateParmDecl>(D) || isa<TemplateTypeParmDecl>(D) ||
-        isa<TemplateTemplateParmDecl>(D))
+        isa<TemplateTemplateParmDecl>(D) || isa<ConceptDecl>(D))
       return D;
 
     if (D->isInvalidDecl())
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp 
b/clang/lib/Serialization/ASTReaderDecl.cpp
index 6815a27537034..fadcc72fdde9d 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2438,7 +2438,8 @@ void 
ASTDeclReader::VisitImplicitConceptSpecializationDecl(
     ImplicitConceptSpecializationDecl *D) {
   // The size of the template list was read during creation of the Decl, so we
   // don't have to re-read it here.
-  VisitDecl(D);
+  VisitNamedDecl(D);
+  D->Template = cast<TemplateDecl>(readDecl());
   llvm::SmallVector<TemplateArgument, 4> Args;
   for (unsigned I = 0; I < D->NumTemplateArgs; ++I)
     Args.push_back(Record.readTemplateArgument(/*Canonicalize=*/false));
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp 
b/clang/lib/Serialization/ASTWriterDecl.cpp
index 7f5005aa666c7..794447a51e4ba 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -1870,8 +1870,11 @@ void ASTDeclWriter::VisitConceptDecl(ConceptDecl *D) {
 
 void ASTDeclWriter::VisitImplicitConceptSpecializationDecl(
     ImplicitConceptSpecializationDecl *D) {
+
   Record.push_back(D->getTemplateArguments().size());
-  VisitDecl(D);
+  VisitNamedDecl(D);
+  Record.AddDeclRef(D->getSpecializedTemplate());
+
   for (const TemplateArgument &Arg : D->getTemplateArguments())
     Record.AddTemplateArgument(Arg);
   Code = serialization::DECL_IMPLICIT_CONCEPT_SPECIALIZATION;
diff --git a/clang/test/SemaTemplate/concepts.cpp 
b/clang/test/SemaTemplate/concepts.cpp
index 1a0834452cbdb..41d72c0b0ae6c 100644
--- a/clang/test/SemaTemplate/concepts.cpp
+++ b/clang/test/SemaTemplate/concepts.cpp
@@ -1994,3 +1994,22 @@ template <> struct StorageTraits<int> {
 };
 View zbi(GetVmo(string("")));
 }
+
+
+namespace GH196375 {
+
+  template <typename En, En value>
+concept Small = (value <= 2);
+
+template <int value>
+consteval bool f()
+    requires(Small<int, value>
+             // && value == 1
+    )
+{
+    return true;
+}
+
+
+static_assert(f<4>());
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to