https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/163827
>From 683aef0b6d71cdbc1ba269ddac55b29e27a595aa Mon Sep 17 00:00:00 2001 From: Corentin Jabot <[email protected]> Date: Thu, 16 Oct 2025 18:52:55 +0200 Subject: [PATCH 1/5] [Clang] Substitute non dependent concepts in constraints This is ``` to form CE, any non-dependent concept template argument Ai is substituted into the constraint-expression of C. If any such substitution results in an invalid concept-id, the program is ill-formed; no diagnostic is required. ``` And continues the implementation of P2841R7 (C++26). No changelog, we will add an entry for P2841R7 closer to the next release, depending on the state of avancement. --- clang/include/clang/Sema/Sema.h | 7 ++ clang/lib/AST/TemplateBase.cpp | 15 +-- clang/lib/Sema/SemaConcept.cpp | 46 +++++++- clang/lib/Sema/SemaTemplateInstantiate.cpp | 103 ++++++++++++++++++ clang/lib/Sema/TreeTransform.h | 46 ++++++++ .../SemaCXX/cxx2c-template-template-param.cpp | 84 ++++++++++++++ 6 files changed, 293 insertions(+), 8 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 37598f8530c09..e2444bc03327f 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -13385,6 +13385,13 @@ class Sema final : public SemaBase { const MultiLevelTemplateArgumentList &TemplateArgs, TemplateArgumentListInfo &Outputs); + /// Substitute concept template arguments in the constraint expression + /// of a concept-id. This is used to implement [temp.constr.normal]. + ExprResult + SubstConceptTemplateArguments(const ConceptSpecializationExpr *CSE, + const Expr *ConstraintExpr, + const MultiLevelTemplateArgumentList &MLTAL); + bool SubstTemplateArgumentsInParameterMapping( ArrayRef<TemplateArgumentLoc> Args, SourceLocation BaseLoc, const MultiLevelTemplateArgumentList &TemplateArgs, diff --git a/clang/lib/AST/TemplateBase.cpp b/clang/lib/AST/TemplateBase.cpp index 76f96fb8c5dcc..131ae6e8a478f 100644 --- a/clang/lib/AST/TemplateBase.cpp +++ b/clang/lib/AST/TemplateBase.cpp @@ -340,13 +340,14 @@ bool TemplateArgument::isPackExpansion() const { } bool TemplateArgument::isConceptOrConceptTemplateParameter() const { - if (getKind() == TemplateArgument::Template) { - if (isa<ConceptDecl>(getAsTemplate().getAsTemplateDecl())) - return true; - else if (auto *TTP = dyn_cast_if_present<TemplateTemplateParmDecl>( - getAsTemplate().getAsTemplateDecl())) - return TTP->templateParameterKind() == TNK_Concept_template; - } + if (getKind() != TemplateArgument::Template) + return false; + + if (isa_and_nonnull<ConceptDecl>(getAsTemplate().getAsTemplateDecl())) + return true; + if (auto *TTP = llvm::dyn_cast_or_null<TemplateTemplateParmDecl>( + getAsTemplate().getAsTemplateDecl())) + return TTP->templateParameterKind() == TNK_Concept_template; return false; } diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp index 87dd68269d44a..d352e5974f772 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -1217,10 +1217,48 @@ bool Sema::CheckConstraintSatisfaction( return false; } +static const ExprResult +SubstituteConceptsInConstrainExpression(Sema &S, const NamedDecl *D, + const ConceptSpecializationExpr *CSE, + UnsignedOrNone SubstIndex) { + + // [C++2c] [temp.constr.normal] + // Otherwise, to form CE, any non-dependent concept template argument Ai + // is substituted into the constraint-expression of C. + // If any such substitution results in an invalid concept-id, + // the program is ill-formed; no diagnostic is required. + + ConceptDecl *Concept = CSE->getNamedConcept()->getCanonicalDecl(); + Sema::ArgPackSubstIndexRAII _(S, SubstIndex); + + const auto *ArgsAsWritten = CSE->getTemplateArgsAsWritten(); + if (llvm::none_of( + ArgsAsWritten->arguments(), [&](const TemplateArgumentLoc &ArgLoc) { + return !ArgLoc.getArgument().isDependent() && + ArgLoc.getArgument().isConceptOrConceptTemplateParameter(); + })) { + return Concept->getConstraintExpr(); + } + + MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs( + Concept, Concept->getLexicalDeclContext(), + /*Final=*/false, CSE->getTemplateArguments(), + /*RelativeToPrimary=*/true, + /*Pattern=*/nullptr, + /*ForConstraintInstantiation=*/true); + return S.SubstConceptTemplateArguments(CSE, Concept->getConstraintExpr(), + MLTAL); +} + bool Sema::CheckConstraintSatisfaction( const ConceptSpecializationExpr *ConstraintExpr, ConstraintSatisfaction &Satisfaction) { + ExprResult Res = SubstituteConceptsInConstrainExpression( + *this, nullptr, ConstraintExpr, ArgPackSubstIndex); + if (!Res.isUsable()) + return true; + llvm::SmallVector<AssociatedConstraint, 1> Constraints; Constraints.emplace_back( ConstraintExpr->getNamedConcept()->getConstraintExpr()); @@ -2249,8 +2287,14 @@ NormalizedConstraint *NormalizedConstraint::fromConstraintExpr( // Use canonical declarations to merge ConceptDecls across // different modules. ConceptDecl *CD = CSE->getNamedConcept()->getCanonicalDecl(); + + ExprResult Res = + SubstituteConceptsInConstrainExpression(S, D, CSE, SubstIndex); + if (!Res.isUsable()) + return nullptr; + SubNF = NormalizedConstraint::fromAssociatedConstraints( - S, CD, AssociatedConstraint(CD->getConstraintExpr(), SubstIndex)); + S, CD, AssociatedConstraint(Res.get(), SubstIndex)); if (!SubNF) return nullptr; diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index bec282011b3fa..29e6b276d43c8 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -4487,6 +4487,109 @@ ExprResult Sema::SubstConstraintExprWithoutSatisfaction( return Instantiator.TransformExpr(E); } +ExprResult Sema::SubstConceptTemplateArguments( + const ConceptSpecializationExpr *CSE, const Expr *ConstraintExpr, + const MultiLevelTemplateArgumentList &MLTAL) { + TemplateInstantiator Instantiator(*this, MLTAL, SourceLocation(), + DeclarationName()); + auto *ArgsAsWritten = CSE->getTemplateArgsAsWritten(); + TemplateArgumentListInfo SubstArgs(ArgsAsWritten->getLAngleLoc(), + ArgsAsWritten->getRAngleLoc()); + + Sema::InstantiatingTemplate Inst( + *this, ArgsAsWritten->arguments().front().getSourceRange().getBegin(), + Sema::InstantiatingTemplate::ConstraintNormalization{}, + CSE->getNamedConcept(), + ArgsAsWritten->arguments().front().getSourceRange()); + + if (Instantiator.TransformConceptTemplateArguments( + ArgsAsWritten->getTemplateArgs(), + ArgsAsWritten->getTemplateArgs() + + ArgsAsWritten->getNumTemplateArgs(), + SubstArgs)) + return true; + + llvm::SmallVector<TemplateArgument, 4> NewArgList; + NewArgList.reserve(SubstArgs.arguments().size()); + for (const auto &ArgLoc : SubstArgs.arguments()) + NewArgList.push_back(ArgLoc.getArgument()); + + MultiLevelTemplateArgumentList MLTALForConstraint = + getTemplateInstantiationArgs( + CSE->getNamedConcept(), + CSE->getNamedConcept()->getLexicalDeclContext(), + /*Final=*/false, + /*Innermost=*/NewArgList, + /*RelativeToPrimary=*/true, + /*Pattern=*/nullptr, + /*ForConstraintInstantiation=*/true); + + struct ConstraintExprTransformer : TreeTransform<ConstraintExprTransformer> { + using Base = TreeTransform<ConstraintExprTransformer>; + MultiLevelTemplateArgumentList &MLTAL; + + ConstraintExprTransformer(Sema &SemaRef, + MultiLevelTemplateArgumentList &MLTAL) + : TreeTransform(SemaRef), MLTAL(MLTAL) {} + + ExprResult TransformExpr(Expr *E) { + if (!E) + return E; + switch (E->getStmtClass()) { + case Stmt::BinaryOperatorClass: + case Stmt::ConceptSpecializationExprClass: + case Stmt::ParenExprClass: + case Stmt::UnresolvedLookupExprClass: + return Base::TransformExpr(E); + default: + break; + } + return E; + } + + ExprResult TransformBinaryOperator(BinaryOperator *E) { + if (!(E->getOpcode() == BinaryOperatorKind::BO_LAnd || + E->getOpcode() == BinaryOperatorKind::BO_LOr)) + return E; + + ExprResult LHS = TransformExpr(E->getLHS()); + ExprResult RHS = TransformExpr(E->getRHS()); + + if (LHS.get() == E->getLHS() && RHS.get() == E->getRHS()) + return E; + + return BinaryOperator::Create(SemaRef.Context, LHS.get(), RHS.get(), + E->getOpcode(), SemaRef.Context.BoolTy, + VK_PRValue, OK_Ordinary, + E->getOperatorLoc(), FPOptionsOverride{}); + } + + bool TransformTemplateArgument(const TemplateArgumentLoc &Input, + TemplateArgumentLoc &Output, + bool Uneval = false) { + if (Input.getArgument().isConceptOrConceptTemplateParameter()) + return Base::TransformTemplateArgument(Input, Output, Uneval); + + Output = Input; + return false; + } + + ExprResult TransformUnresolvedLookupExpr(UnresolvedLookupExpr *E, + bool IsAddressOfOperand = false) { + if (E->isConceptReference()) { + ExprResult Res = SemaRef.SubstExpr(E, MLTAL); + return Res; + } + return E; + } + }; + + ConstraintExprTransformer Transformer(*this, MLTALForConstraint); + ExprResult Res = + Transformer.TransformExpr(const_cast<Expr *>(ConstraintExpr)); + return Res; +} + ExprResult Sema::SubstInitializer(Expr *Init, const MultiLevelTemplateArgumentList &TemplateArgs, bool CXXDirectInit) { diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 86896abc1f775..97c6f7066cff9 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -694,6 +694,12 @@ class TreeTransform { TemplateArgumentListInfo &Outputs, bool Uneval = false); + template <typename InputIterator> + bool TransformConceptTemplateArguments(InputIterator First, + InputIterator Last, + TemplateArgumentListInfo &Outputs, + bool Uneval = false); + /// Checks if the argument pack from \p In will need to be expanded and does /// the necessary prework. /// Whether the expansion is needed is captured in Info.Expand. @@ -5192,6 +5198,46 @@ bool TreeTransform<Derived>::TransformTemplateArguments( return false; } +template <typename Derived> +template <typename InputIterator> +bool TreeTransform<Derived>::TransformConceptTemplateArguments( + InputIterator First, InputIterator Last, TemplateArgumentListInfo &Outputs, + bool Uneval) { + + auto isNonDependentConcept = [](const TemplateArgument &Arg) { + return !Arg.isDependent() && Arg.isConceptOrConceptTemplateParameter(); + }; + + for (; First != Last; ++First) { + TemplateArgumentLoc Out; + TemplateArgumentLoc In = *First; + + if (In.getArgument().getKind() == TemplateArgument::Pack) { + typedef TemplateArgumentLocInventIterator<Derived, + TemplateArgument::pack_iterator> + PackLocIterator; + if (TransformConceptTemplateArguments( + PackLocIterator(*this, In.getArgument().pack_begin()), + PackLocIterator(*this, In.getArgument().pack_end()), Outputs, + Uneval)) + return true; + continue; + } + + if (!isNonDependentConcept(In.getArgument())) { + Outputs.addArgument(In); + continue; + } + + if (getDerived().TransformTemplateArgument(In, Out, Uneval)) + return true; + + Outputs.addArgument(Out); + } + + return false; +} + // FIXME: Find ways to reduce code duplication for pack expansions. template <typename Derived> bool TreeTransform<Derived>::PreparePackForExpansion(TemplateArgumentLoc In, diff --git a/clang/test/SemaCXX/cxx2c-template-template-param.cpp b/clang/test/SemaCXX/cxx2c-template-template-param.cpp index 4ad3fd95039cd..77f872d31d923 100644 --- a/clang/test/SemaCXX/cxx2c-template-template-param.cpp +++ b/clang/test/SemaCXX/cxx2c-template-template-param.cpp @@ -350,3 +350,87 @@ template <A<concept missing<int>> T> // expected-error {{expected expression}} \ // expected-error {{expected unqualified-id}} auto f(); } + +namespace concept_arg_normalization { + +template <typename T, + template <typename...> concept C1> +concept one = (C1<T>); // #concept-arg-one + +template <typename T> +concept A = true; // #concept-arg-A + +template <typename T> +concept BetterA = A<T> && true; + +template <typename T> +concept B = true; // #concept-arg-B + +template <typename T> +concept False = false; // #concept-arg-False + +template <typename T> +requires one<T, A> +void f1(T){} // #concept-arg-f1-1 + +template <typename T> +requires one<T, B> +void f1(T){} // #concept-arg-f1-2 + +template <typename T> +requires one<T, A> +void f2(T){} + +template <typename T> +requires one<T, BetterA> +void f2(T){} + + +template <template <typename> concept CT> +requires one<int, A> +void f3(){} // #concept-arg-f3-1 + +template <template <typename> concept CT> +requires one<int, CT> +void f3(){} // #concept-arg-f3-2 + +template <typename T> +requires one<T, False> void f4(T){} // #concept-arg-f4 + + +void test() { + f1(0); + // expected-error@-1 {{call to 'f1' is ambiguous}} + // expected-note@#concept-arg-f1-1{{candidate function [with T = int]}} + // expected-note@#concept-arg-f1-2{{candidate function [with T = int]}} + // expected-note@#concept-arg-A {{similar constraint expressions not considered equivalent}} + // expected-note@#concept-arg-B {{similar constraint expression here}} + f2(0); + + f3<BetterA>(); + // expected-error@-1 {{call to 'f3' is ambiguous}} + // expected-note@#concept-arg-f3-1 {{candidate function [with CT = concept_arg_normalization::BetterA]}} + // expected-note@#concept-arg-f3-2 {{candidate function [with CT = concept_arg_normalization::BetterA]}} + +static_assert(one<int, A>); +static_assert(one<int, False>); +// expected-error@-1 {{static assertion failed}} \ +// expected-note@-1 {{because 'one<int, False>' evaluated to false}} +// expected-note@#concept-arg-one {{because 'int' does not satisfy 'False'}} +// expected-note@#concept-arg-False {{because 'false' evaluated to false}} + +f4(0); +// expected-error@-1 {{no matching function for call to 'f4'}} +// expected-note@#concept-arg-f4 {{candidate template ignored: constraints not satisfied [with T = int]}} +// expected-note@#concept-arg-f4 {{because 'one<int, False>'}} +// expected-note@#concept-arg-one {{because 'int' does not satisfy 'False'}} +// expected-note@#concept-arg-False {{because 'false' evaluated to false}} + + +template <typename T, template <typename...> concept C1> +concept TestBinary = T::a || C1<T>; +static_assert(TestBinary<int, A>); + +} + +} >From 4c943e60728798d7285e82b074aafc5c6618224b Mon Sep 17 00:00:00 2001 From: Corentin Jabot <[email protected]> Date: Thu, 16 Oct 2025 19:27:08 +0200 Subject: [PATCH 2/5] fix test --- clang/test/SemaCXX/cxx2c-template-template-param.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/SemaCXX/cxx2c-template-template-param.cpp b/clang/test/SemaCXX/cxx2c-template-template-param.cpp index 77f872d31d923..704df3112277f 100644 --- a/clang/test/SemaCXX/cxx2c-template-template-param.cpp +++ b/clang/test/SemaCXX/cxx2c-template-template-param.cpp @@ -426,11 +426,11 @@ f4(0); // expected-note@#concept-arg-one {{because 'int' does not satisfy 'False'}} // expected-note@#concept-arg-False {{because 'false' evaluated to false}} +} template <typename T, template <typename...> concept C1> concept TestBinary = T::a || C1<T>; static_assert(TestBinary<int, A>); -} } >From 05fc8d8678a2c16d39aefc893a927ce0fdd7aec7 Mon Sep 17 00:00:00 2001 From: Corentin Jabot <[email protected]> Date: Fri, 17 Oct 2025 09:15:20 +0200 Subject: [PATCH 3/5] address feedback --- clang/lib/Sema/SemaConcept.cpp | 6 +++--- clang/lib/Sema/SemaTemplateInstantiate.cpp | 13 ++++++++----- clang/lib/Sema/TreeTransform.h | 7 +++++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp index d352e5974f772..04a73181831d8 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -1231,7 +1231,8 @@ SubstituteConceptsInConstrainExpression(Sema &S, const NamedDecl *D, ConceptDecl *Concept = CSE->getNamedConcept()->getCanonicalDecl(); Sema::ArgPackSubstIndexRAII _(S, SubstIndex); - const auto *ArgsAsWritten = CSE->getTemplateArgsAsWritten(); + const ASTTemplateArgumentListInfo *ArgsAsWritten = + CSE->getTemplateArgsAsWritten(); if (llvm::none_of( ArgsAsWritten->arguments(), [&](const TemplateArgumentLoc &ArgLoc) { return !ArgLoc.getArgument().isDependent() && @@ -1260,8 +1261,7 @@ bool Sema::CheckConstraintSatisfaction( return true; llvm::SmallVector<AssociatedConstraint, 1> Constraints; - Constraints.emplace_back( - ConstraintExpr->getNamedConcept()->getConstraintExpr()); + Constraints.emplace_back(Res.get()); MultiLevelTemplateArgumentList MLTAL(ConstraintExpr->getNamedConcept(), ConstraintExpr->getTemplateArguments(), diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index 29e6b276d43c8..6ed401dc4a8c1 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -4492,7 +4492,8 @@ ExprResult Sema::SubstConceptTemplateArguments( const MultiLevelTemplateArgumentList &MLTAL) { TemplateInstantiator Instantiator(*this, MLTAL, SourceLocation(), DeclarationName()); - auto *ArgsAsWritten = CSE->getTemplateArgsAsWritten(); + const ASTTemplateArgumentListInfo *ArgsAsWritten = + CSE->getTemplateArgsAsWritten(); TemplateArgumentListInfo SubstArgs(ArgsAsWritten->getLAngleLoc(), ArgsAsWritten->getRAngleLoc()); @@ -4502,6 +4503,9 @@ ExprResult Sema::SubstConceptTemplateArguments( CSE->getNamedConcept(), ArgsAsWritten->arguments().front().getSourceRange()); + if (Inst.isInvalid()) + return ExprError(); + if (Instantiator.TransformConceptTemplateArguments( ArgsAsWritten->getTemplateArgs(), ArgsAsWritten->getTemplateArgs() + @@ -4509,10 +4513,9 @@ ExprResult Sema::SubstConceptTemplateArguments( SubstArgs)) return true; - llvm::SmallVector<TemplateArgument, 4> NewArgList; - NewArgList.reserve(SubstArgs.arguments().size()); - for (const auto &ArgLoc : SubstArgs.arguments()) - NewArgList.push_back(ArgLoc.getArgument()); + llvm::SmallVector<TemplateArgument, 4> NewArgList = llvm::map_to_vector( + SubstArgs.arguments(), + [](const TemplateArgumentLoc &Loc) { return Loc.getArgument(); }); MultiLevelTemplateArgumentList MLTALForConstraint = getTemplateInstantiationArgs( diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 97c6f7066cff9..29f0c30c6534e 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -5204,7 +5204,10 @@ bool TreeTransform<Derived>::TransformConceptTemplateArguments( InputIterator First, InputIterator Last, TemplateArgumentListInfo &Outputs, bool Uneval) { - auto isNonDependentConcept = [](const TemplateArgument &Arg) { + // [C++26][temp.constr.normal] + // any non-dependent concept template argument + // is substituted into the constraint-expression of C. + auto isNonDependentConceptArgument = [](const TemplateArgument &Arg) { return !Arg.isDependent() && Arg.isConceptOrConceptTemplateParameter(); }; @@ -5224,7 +5227,7 @@ bool TreeTransform<Derived>::TransformConceptTemplateArguments( continue; } - if (!isNonDependentConcept(In.getArgument())) { + if (!isNonDependentConceptArgument(In.getArgument())) { Outputs.addArgument(In); continue; } >From aa915a72f09477e0ea6da2d07e337eb46c2a3b0c Mon Sep 17 00:00:00 2001 From: Corentin Jabot <[email protected]> Date: Fri, 17 Oct 2025 09:20:56 +0200 Subject: [PATCH 4/5] add comments --- clang/lib/Sema/SemaTemplateInstantiate.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index 6ed401dc4a8c1..2648c70ed90d2 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -4527,6 +4527,10 @@ ExprResult Sema::SubstConceptTemplateArguments( /*Pattern=*/nullptr, /*ForConstraintInstantiation=*/true); + // Rebuild a constraint, only substituting non-dependent concept names + // and nothing else. + // Given C<SomeType, SomeValue, SomeConceptName, SomeDependentConceptName>. + // only SomeConceptName is substituted, in the constraint expression of C. struct ConstraintExprTransformer : TreeTransform<ConstraintExprTransformer> { using Base = TreeTransform<ConstraintExprTransformer>; MultiLevelTemplateArgumentList &MLTAL; @@ -4550,6 +4554,9 @@ ExprResult Sema::SubstConceptTemplateArguments( return E; } + // Rebuild both branches of a conjunction / disjunction + // even if there is a substitution failure in one of + // the branch. ExprResult TransformBinaryOperator(BinaryOperator *E) { if (!(E->getOpcode() == BinaryOperatorKind::BO_LAnd || E->getOpcode() == BinaryOperatorKind::BO_LOr)) >From b247262ccb2005c0c8411c52454a2cd6b2419bee Mon Sep 17 00:00:00 2001 From: Corentin Jabot <[email protected]> Date: Fri, 17 Oct 2025 09:32:21 +0200 Subject: [PATCH 5/5] miss8ing header --- clang/lib/Sema/SemaTemplateInstantiate.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index 2648c70ed90d2..ca7e3b264cec4 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -35,6 +35,7 @@ #include "clang/Sema/Template.h" #include "clang/Sema/TemplateDeduction.h" #include "clang/Sema/TemplateInstCallback.h" +#include "llvm/ADT/SmallVectorExtras.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/SaveAndRestore.h" _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
