Skip to content

Commit 23341c3

Browse files
authored
[Clang] Substitute non dependent concepts in constraints (#163827)
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. ``` https://eel.is/c++draft/temp.constr.normal#1.4 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.
1 parent 9aacc1a commit 23341c3

File tree

6 files changed

+309
-10
lines changed

6 files changed

+309
-10
lines changed

clang/include/clang/Sema/Sema.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13385,6 +13385,13 @@ class Sema final : public SemaBase {
1338513385
const MultiLevelTemplateArgumentList &TemplateArgs,
1338613386
TemplateArgumentListInfo &Outputs);
1338713387

13388+
/// Substitute concept template arguments in the constraint expression
13389+
/// of a concept-id. This is used to implement [temp.constr.normal].
13390+
ExprResult
13391+
SubstConceptTemplateArguments(const ConceptSpecializationExpr *CSE,
13392+
const Expr *ConstraintExpr,
13393+
const MultiLevelTemplateArgumentList &MLTAL);
13394+
1338813395
bool SubstTemplateArgumentsInParameterMapping(
1338913396
ArrayRef<TemplateArgumentLoc> Args, SourceLocation BaseLoc,
1339013397
const MultiLevelTemplateArgumentList &TemplateArgs,

clang/lib/AST/TemplateBase.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -340,13 +340,14 @@ bool TemplateArgument::isPackExpansion() const {
340340
}
341341

342342
bool TemplateArgument::isConceptOrConceptTemplateParameter() const {
343-
if (getKind() == TemplateArgument::Template) {
344-
if (isa<ConceptDecl>(getAsTemplate().getAsTemplateDecl()))
345-
return true;
346-
else if (auto *TTP = dyn_cast_if_present<TemplateTemplateParmDecl>(
347-
getAsTemplate().getAsTemplateDecl()))
348-
return TTP->templateParameterKind() == TNK_Concept_template;
349-
}
343+
if (getKind() != TemplateArgument::Template)
344+
return false;
345+
346+
if (isa_and_nonnull<ConceptDecl>(getAsTemplate().getAsTemplateDecl()))
347+
return true;
348+
if (auto *TTP = llvm::dyn_cast_or_null<TemplateTemplateParmDecl>(
349+
getAsTemplate().getAsTemplateDecl()))
350+
return TTP->templateParameterKind() == TNK_Concept_template;
350351
return false;
351352
}
352353

clang/lib/Sema/SemaConcept.cpp

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,13 +1217,51 @@ bool Sema::CheckConstraintSatisfaction(
12171217
return false;
12181218
}
12191219

1220+
static const ExprResult
1221+
SubstituteConceptsInConstrainExpression(Sema &S, const NamedDecl *D,
1222+
const ConceptSpecializationExpr *CSE,
1223+
UnsignedOrNone SubstIndex) {
1224+
1225+
// [C++2c] [temp.constr.normal]
1226+
// Otherwise, to form CE, any non-dependent concept template argument Ai
1227+
// is substituted into the constraint-expression of C.
1228+
// If any such substitution results in an invalid concept-id,
1229+
// the program is ill-formed; no diagnostic is required.
1230+
1231+
ConceptDecl *Concept = CSE->getNamedConcept()->getCanonicalDecl();
1232+
Sema::ArgPackSubstIndexRAII _(S, SubstIndex);
1233+
1234+
const ASTTemplateArgumentListInfo *ArgsAsWritten =
1235+
CSE->getTemplateArgsAsWritten();
1236+
if (llvm::none_of(
1237+
ArgsAsWritten->arguments(), [&](const TemplateArgumentLoc &ArgLoc) {
1238+
return !ArgLoc.getArgument().isDependent() &&
1239+
ArgLoc.getArgument().isConceptOrConceptTemplateParameter();
1240+
})) {
1241+
return Concept->getConstraintExpr();
1242+
}
1243+
1244+
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
1245+
Concept, Concept->getLexicalDeclContext(),
1246+
/*Final=*/false, CSE->getTemplateArguments(),
1247+
/*RelativeToPrimary=*/true,
1248+
/*Pattern=*/nullptr,
1249+
/*ForConstraintInstantiation=*/true);
1250+
return S.SubstConceptTemplateArguments(CSE, Concept->getConstraintExpr(),
1251+
MLTAL);
1252+
}
1253+
12201254
bool Sema::CheckConstraintSatisfaction(
12211255
const ConceptSpecializationExpr *ConstraintExpr,
12221256
ConstraintSatisfaction &Satisfaction) {
12231257

1258+
ExprResult Res = SubstituteConceptsInConstrainExpression(
1259+
*this, nullptr, ConstraintExpr, ArgPackSubstIndex);
1260+
if (!Res.isUsable())
1261+
return true;
1262+
12241263
llvm::SmallVector<AssociatedConstraint, 1> Constraints;
1225-
Constraints.emplace_back(
1226-
ConstraintExpr->getNamedConcept()->getConstraintExpr());
1264+
Constraints.emplace_back(Res.get());
12271265

12281266
MultiLevelTemplateArgumentList MLTAL(ConstraintExpr->getNamedConcept(),
12291267
ConstraintExpr->getTemplateArguments(),
@@ -2249,8 +2287,14 @@ NormalizedConstraint *NormalizedConstraint::fromConstraintExpr(
22492287
// Use canonical declarations to merge ConceptDecls across
22502288
// different modules.
22512289
ConceptDecl *CD = CSE->getNamedConcept()->getCanonicalDecl();
2290+
2291+
ExprResult Res =
2292+
SubstituteConceptsInConstrainExpression(S, D, CSE, SubstIndex);
2293+
if (!Res.isUsable())
2294+
return nullptr;
2295+
22522296
SubNF = NormalizedConstraint::fromAssociatedConstraints(
2253-
S, CD, AssociatedConstraint(CD->getConstraintExpr(), SubstIndex));
2297+
S, CD, AssociatedConstraint(Res.get(), SubstIndex));
22542298

22552299
if (!SubNF)
22562300
return nullptr;

clang/lib/Sema/SemaTemplateInstantiate.cpp

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "clang/Sema/Template.h"
3636
#include "clang/Sema/TemplateDeduction.h"
3737
#include "clang/Sema/TemplateInstCallback.h"
38+
#include "llvm/ADT/SmallVectorExtras.h"
3839
#include "llvm/ADT/StringExtras.h"
3940
#include "llvm/Support/ErrorHandling.h"
4041
#include "llvm/Support/SaveAndRestore.h"
@@ -4487,6 +4488,119 @@ ExprResult Sema::SubstConstraintExprWithoutSatisfaction(
44874488
return Instantiator.TransformExpr(E);
44884489
}
44894490

4491+
ExprResult Sema::SubstConceptTemplateArguments(
4492+
const ConceptSpecializationExpr *CSE, const Expr *ConstraintExpr,
4493+
const MultiLevelTemplateArgumentList &MLTAL) {
4494+
TemplateInstantiator Instantiator(*this, MLTAL, SourceLocation(),
4495+
DeclarationName());
4496+
const ASTTemplateArgumentListInfo *ArgsAsWritten =
4497+
CSE->getTemplateArgsAsWritten();
4498+
TemplateArgumentListInfo SubstArgs(ArgsAsWritten->getLAngleLoc(),
4499+
ArgsAsWritten->getRAngleLoc());
4500+
4501+
Sema::InstantiatingTemplate Inst(
4502+
*this, ArgsAsWritten->arguments().front().getSourceRange().getBegin(),
4503+
Sema::InstantiatingTemplate::ConstraintNormalization{},
4504+
CSE->getNamedConcept(),
4505+
ArgsAsWritten->arguments().front().getSourceRange());
4506+
4507+
if (Inst.isInvalid())
4508+
return ExprError();
4509+
4510+
if (Instantiator.TransformConceptTemplateArguments(
4511+
ArgsAsWritten->getTemplateArgs(),
4512+
ArgsAsWritten->getTemplateArgs() +
4513+
ArgsAsWritten->getNumTemplateArgs(),
4514+
SubstArgs))
4515+
return true;
4516+
4517+
llvm::SmallVector<TemplateArgument, 4> NewArgList = llvm::map_to_vector(
4518+
SubstArgs.arguments(),
4519+
[](const TemplateArgumentLoc &Loc) { return Loc.getArgument(); });
4520+
4521+
MultiLevelTemplateArgumentList MLTALForConstraint =
4522+
getTemplateInstantiationArgs(
4523+
CSE->getNamedConcept(),
4524+
CSE->getNamedConcept()->getLexicalDeclContext(),
4525+
/*Final=*/false,
4526+
/*Innermost=*/NewArgList,
4527+
/*RelativeToPrimary=*/true,
4528+
/*Pattern=*/nullptr,
4529+
/*ForConstraintInstantiation=*/true);
4530+
4531+
// Rebuild a constraint, only substituting non-dependent concept names
4532+
// and nothing else.
4533+
// Given C<SomeType, SomeValue, SomeConceptName, SomeDependentConceptName>.
4534+
// only SomeConceptName is substituted, in the constraint expression of C.
4535+
struct ConstraintExprTransformer : TreeTransform<ConstraintExprTransformer> {
4536+
using Base = TreeTransform<ConstraintExprTransformer>;
4537+
MultiLevelTemplateArgumentList &MLTAL;
4538+
4539+
ConstraintExprTransformer(Sema &SemaRef,
4540+
MultiLevelTemplateArgumentList &MLTAL)
4541+
: TreeTransform(SemaRef), MLTAL(MLTAL) {}
4542+
4543+
ExprResult TransformExpr(Expr *E) {
4544+
if (!E)
4545+
return E;
4546+
switch (E->getStmtClass()) {
4547+
case Stmt::BinaryOperatorClass:
4548+
case Stmt::ConceptSpecializationExprClass:
4549+
case Stmt::ParenExprClass:
4550+
case Stmt::UnresolvedLookupExprClass:
4551+
return Base::TransformExpr(E);
4552+
default:
4553+
break;
4554+
}
4555+
return E;
4556+
}
4557+
4558+
// Rebuild both branches of a conjunction / disjunction
4559+
// even if there is a substitution failure in one of
4560+
// the branch.
4561+
ExprResult TransformBinaryOperator(BinaryOperator *E) {
4562+
if (!(E->getOpcode() == BinaryOperatorKind::BO_LAnd ||
4563+
E->getOpcode() == BinaryOperatorKind::BO_LOr))
4564+
return E;
4565+
4566+
ExprResult LHS = TransformExpr(E->getLHS());
4567+
ExprResult RHS = TransformExpr(E->getRHS());
4568+
4569+
if (LHS.get() == E->getLHS() && RHS.get() == E->getRHS())
4570+
return E;
4571+
4572+
return BinaryOperator::Create(SemaRef.Context, LHS.get(), RHS.get(),
4573+
E->getOpcode(), SemaRef.Context.BoolTy,
4574+
VK_PRValue, OK_Ordinary,
4575+
E->getOperatorLoc(), FPOptionsOverride{});
4576+
}
4577+
4578+
bool TransformTemplateArgument(const TemplateArgumentLoc &Input,
4579+
TemplateArgumentLoc &Output,
4580+
bool Uneval = false) {
4581+
if (Input.getArgument().isConceptOrConceptTemplateParameter())
4582+
return Base::TransformTemplateArgument(Input, Output, Uneval);
4583+
4584+
Output = Input;
4585+
return false;
4586+
}
4587+
4588+
ExprResult TransformUnresolvedLookupExpr(UnresolvedLookupExpr *E,
4589+
bool IsAddressOfOperand = false) {
4590+
if (E->isConceptReference()) {
4591+
ExprResult Res = SemaRef.SubstExpr(E, MLTAL);
4592+
return Res;
4593+
}
4594+
return E;
4595+
}
4596+
};
4597+
4598+
ConstraintExprTransformer Transformer(*this, MLTALForConstraint);
4599+
ExprResult Res =
4600+
Transformer.TransformExpr(const_cast<Expr *>(ConstraintExpr));
4601+
return Res;
4602+
}
4603+
44904604
ExprResult Sema::SubstInitializer(Expr *Init,
44914605
const MultiLevelTemplateArgumentList &TemplateArgs,
44924606
bool CXXDirectInit) {

clang/lib/Sema/TreeTransform.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,12 @@ class TreeTransform {
694694
TemplateArgumentListInfo &Outputs,
695695
bool Uneval = false);
696696

697+
template <typename InputIterator>
698+
bool TransformConceptTemplateArguments(InputIterator First,
699+
InputIterator Last,
700+
TemplateArgumentListInfo &Outputs,
701+
bool Uneval = false);
702+
697703
/// Checks if the argument pack from \p In will need to be expanded and does
698704
/// the necessary prework.
699705
/// Whether the expansion is needed is captured in Info.Expand.
@@ -5192,6 +5198,49 @@ bool TreeTransform<Derived>::TransformTemplateArguments(
51925198
return false;
51935199
}
51945200

5201+
template <typename Derived>
5202+
template <typename InputIterator>
5203+
bool TreeTransform<Derived>::TransformConceptTemplateArguments(
5204+
InputIterator First, InputIterator Last, TemplateArgumentListInfo &Outputs,
5205+
bool Uneval) {
5206+
5207+
// [C++26][temp.constr.normal]
5208+
// any non-dependent concept template argument
5209+
// is substituted into the constraint-expression of C.
5210+
auto isNonDependentConceptArgument = [](const TemplateArgument &Arg) {
5211+
return !Arg.isDependent() && Arg.isConceptOrConceptTemplateParameter();
5212+
};
5213+
5214+
for (; First != Last; ++First) {
5215+
TemplateArgumentLoc Out;
5216+
TemplateArgumentLoc In = *First;
5217+
5218+
if (In.getArgument().getKind() == TemplateArgument::Pack) {
5219+
typedef TemplateArgumentLocInventIterator<Derived,
5220+
TemplateArgument::pack_iterator>
5221+
PackLocIterator;
5222+
if (TransformConceptTemplateArguments(
5223+
PackLocIterator(*this, In.getArgument().pack_begin()),
5224+
PackLocIterator(*this, In.getArgument().pack_end()), Outputs,
5225+
Uneval))
5226+
return true;
5227+
continue;
5228+
}
5229+
5230+
if (!isNonDependentConceptArgument(In.getArgument())) {
5231+
Outputs.addArgument(In);
5232+
continue;
5233+
}
5234+
5235+
if (getDerived().TransformTemplateArgument(In, Out, Uneval))
5236+
return true;
5237+
5238+
Outputs.addArgument(Out);
5239+
}
5240+
5241+
return false;
5242+
}
5243+
51955244
// FIXME: Find ways to reduce code duplication for pack expansions.
51965245
template <typename Derived>
51975246
bool TreeTransform<Derived>::PreparePackForExpansion(TemplateArgumentLoc In,

clang/test/SemaCXX/cxx2c-template-template-param.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,87 @@ template <A<concept missing<int>> T> // expected-error {{expected expression}} \
350350
// expected-error {{expected unqualified-id}}
351351
auto f();
352352
}
353+
354+
namespace concept_arg_normalization {
355+
356+
template <typename T,
357+
template <typename...> concept C1>
358+
concept one = (C1<T>); // #concept-arg-one
359+
360+
template <typename T>
361+
concept A = true; // #concept-arg-A
362+
363+
template <typename T>
364+
concept BetterA = A<T> && true;
365+
366+
template <typename T>
367+
concept B = true; // #concept-arg-B
368+
369+
template <typename T>
370+
concept False = false; // #concept-arg-False
371+
372+
template <typename T>
373+
requires one<T, A>
374+
void f1(T){} // #concept-arg-f1-1
375+
376+
template <typename T>
377+
requires one<T, B>
378+
void f1(T){} // #concept-arg-f1-2
379+
380+
template <typename T>
381+
requires one<T, A>
382+
void f2(T){}
383+
384+
template <typename T>
385+
requires one<T, BetterA>
386+
void f2(T){}
387+
388+
389+
template <template <typename> concept CT>
390+
requires one<int, A>
391+
void f3(){} // #concept-arg-f3-1
392+
393+
template <template <typename> concept CT>
394+
requires one<int, CT>
395+
void f3(){} // #concept-arg-f3-2
396+
397+
template <typename T>
398+
requires one<T, False> void f4(T){} // #concept-arg-f4
399+
400+
401+
void test() {
402+
f1(0);
403+
// expected-error@-1 {{call to 'f1' is ambiguous}}
404+
// expected-note@#concept-arg-f1-1{{candidate function [with T = int]}}
405+
// expected-note@#concept-arg-f1-2{{candidate function [with T = int]}}
406+
// expected-note@#concept-arg-A {{similar constraint expressions not considered equivalent}}
407+
// expected-note@#concept-arg-B {{similar constraint expression here}}
408+
f2(0);
409+
410+
f3<BetterA>();
411+
// expected-error@-1 {{call to 'f3' is ambiguous}}
412+
// expected-note@#concept-arg-f3-1 {{candidate function [with CT = concept_arg_normalization::BetterA]}}
413+
// expected-note@#concept-arg-f3-2 {{candidate function [with CT = concept_arg_normalization::BetterA]}}
414+
415+
static_assert(one<int, A>);
416+
static_assert(one<int, False>);
417+
// expected-error@-1 {{static assertion failed}} \
418+
// expected-note@-1 {{because 'one<int, False>' evaluated to false}}
419+
// expected-note@#concept-arg-one {{because 'int' does not satisfy 'False'}}
420+
// expected-note@#concept-arg-False {{because 'false' evaluated to false}}
421+
422+
f4(0);
423+
// expected-error@-1 {{no matching function for call to 'f4'}}
424+
// expected-note@#concept-arg-f4 {{candidate template ignored: constraints not satisfied [with T = int]}}
425+
// expected-note@#concept-arg-f4 {{because 'one<int, False>'}}
426+
// expected-note@#concept-arg-one {{because 'int' does not satisfy 'False'}}
427+
// expected-note@#concept-arg-False {{because 'false' evaluated to false}}
428+
429+
}
430+
431+
template <typename T, template <typename...> concept C1>
432+
concept TestBinary = T::a || C1<T>;
433+
static_assert(TestBinary<int, A>);
434+
435+
436+
}

0 commit comments

Comments
 (0)