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 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(getAsTemplate().getAsTemplateDecl())) - return true; - else if (auto *TTP = dyn_cast_if_present( - getAsTemplate().getAsTemplateDecl())) - return TTP->templateParameterKind() == TNK_Concept_template; - } + if (getKind() != TemplateArgument::Template) + return false; + + if (isa_and_nonnull(getAsTemplate().getAsTemplateDecl())) + return true; + if (auto *TTP = llvm::dyn_cast_or_null( + 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..04a73181831d8 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -1217,13 +1217,51 @@ 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 ASTTemplateArgumentListInfo *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 Constraints; - Constraints.emplace_back( - ConstraintExpr->getNamedConcept()->getConstraintExpr()); + Constraints.emplace_back(Res.get()); MultiLevelTemplateArgumentList MLTAL(ConstraintExpr->getNamedConcept(), ConstraintExpr->getTemplateArguments(), @@ -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..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" @@ -4487,6 +4488,119 @@ 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()); + const ASTTemplateArgumentListInfo *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 (Inst.isInvalid()) + return ExprError(); + + if (Instantiator.TransformConceptTemplateArguments( + ArgsAsWritten->getTemplateArgs(), + ArgsAsWritten->getTemplateArgs() + + ArgsAsWritten->getNumTemplateArgs(), + SubstArgs)) + return true; + + llvm::SmallVector NewArgList = llvm::map_to_vector( + SubstArgs.arguments(), + [](const TemplateArgumentLoc &Loc) { return Loc.getArgument(); }); + + MultiLevelTemplateArgumentList MLTALForConstraint = + getTemplateInstantiationArgs( + CSE->getNamedConcept(), + CSE->getNamedConcept()->getLexicalDeclContext(), + /*Final=*/false, + /*Innermost=*/NewArgList, + /*RelativeToPrimary=*/true, + /*Pattern=*/nullptr, + /*ForConstraintInstantiation=*/true); + + // Rebuild a constraint, only substituting non-dependent concept names + // and nothing else. + // Given C. + // only SomeConceptName is substituted, in the constraint expression of C. + struct ConstraintExprTransformer : TreeTransform { + using Base = TreeTransform; + 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; + } + + // 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)) + 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(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..29f0c30c6534e 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 + 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,49 @@ bool TreeTransform::TransformTemplateArguments( return false; } +template +template +bool TreeTransform::TransformConceptTemplateArguments( + InputIterator First, InputIterator Last, TemplateArgumentListInfo &Outputs, + bool Uneval) { + + // [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(); + }; + + for (; First != Last; ++First) { + TemplateArgumentLoc Out; + TemplateArgumentLoc In = *First; + + if (In.getArgument().getKind() == TemplateArgument::Pack) { + typedef TemplateArgumentLocInventIterator + PackLocIterator; + if (TransformConceptTemplateArguments( + PackLocIterator(*this, In.getArgument().pack_begin()), + PackLocIterator(*this, In.getArgument().pack_end()), Outputs, + Uneval)) + return true; + continue; + } + + if (!isNonDependentConceptArgument(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 bool TreeTransform::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..704df3112277f 100644 --- a/clang/test/SemaCXX/cxx2c-template-template-param.cpp +++ b/clang/test/SemaCXX/cxx2c-template-template-param.cpp @@ -350,3 +350,87 @@ template > T> // expected-error {{expected expression}} \ // expected-error {{expected unqualified-id}} auto f(); } + +namespace concept_arg_normalization { + +template concept C1> +concept one = (C1); // #concept-arg-one + +template +concept A = true; // #concept-arg-A + +template +concept BetterA = A && true; + +template +concept B = true; // #concept-arg-B + +template +concept False = false; // #concept-arg-False + +template +requires one +void f1(T){} // #concept-arg-f1-1 + +template +requires one +void f1(T){} // #concept-arg-f1-2 + +template +requires one +void f2(T){} + +template +requires one +void f2(T){} + + +template