diff --git a/clang/include/clang/AST/ExprConcepts.h b/clang/include/clang/AST/ExprConcepts.h --- a/clang/include/clang/AST/ExprConcepts.h +++ b/clang/include/clang/AST/ExprConcepts.h @@ -423,12 +423,13 @@ } NestedRequirement(ASTContext &C, Expr *Constraint, - const ConstraintSatisfaction &Satisfaction) : - Requirement(RK_Nested, Constraint->isInstantiationDependent(), - Constraint->containsUnexpandedParameterPack(), - Satisfaction.IsSatisfied), - Value(Constraint), - Satisfaction(ASTConstraintSatisfaction::Create(C, Satisfaction)) {} + const ConstraintSatisfaction &Satisfaction) + : Requirement(RK_Nested, + Constraint && Constraint->isInstantiationDependent(), + Constraint && Constraint->containsUnexpandedParameterPack(), + Satisfaction.IsSatisfied), + Value(Constraint), + Satisfaction(ASTConstraintSatisfaction::Create(C, Satisfaction)) {} bool isSubstitutionFailure() const { return Value.is(); diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -169,7 +169,8 @@ // is checked. If that is satisfied, the disjunction is satisfied. // Otherwise, the disjunction is satisfied if and only if the second // operand is satisfied. - return BO.recreateBinOp(S, LHSRes); + // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp. + return LHSRes; if (BO.isAnd() && !IsLHSSatisfied) // [temp.constr.op] p2 @@ -178,7 +179,8 @@ // is checked. If that is not satisfied, the conjunction is not // satisfied. Otherwise, the conjunction is satisfied if and only if // the second operand is satisfied. - return BO.recreateBinOp(S, LHSRes); + // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp. + return LHSRes; ExprResult RHSRes = calculateConstraintSatisfaction( S, BO.getRHS(), Satisfaction, std::forward(Evaluator)); @@ -286,7 +288,8 @@ // bool if this is the operand of an '&&' or '||'. For example, we // might lose an lvalue-to-rvalue conversion here. If so, put it back // before we try to evaluate. - if (!SubstitutedExpression.isInvalid()) + if (SubstitutedExpression.isUsable() && + !SubstitutedExpression.isInvalid()) SubstitutedExpression = S.PerformContextuallyConvertToBool(SubstitutedExpression.get()); if (SubstitutedExpression.isInvalid() || Trap.hasErrorOccurred()) { diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -19,6 +19,7 @@ #include "clang/AST/Expr.h" #include "clang/AST/ExprConcepts.h" #include "clang/AST/PrettyDeclStackTrace.h" +#include "clang/AST/Type.h" #include "clang/AST/TypeVisitor.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/Stack.h" @@ -2328,8 +2329,12 @@ Req->getConstraintExpr()->getSourceRange()); if (ConstrInst.isInvalid()) return nullptr; - TransConstraint = TransformExpr(Req->getConstraintExpr()); - if (!TransConstraint.isInvalid()) { + llvm::SmallVector Result; + if (!SemaRef.CheckConstraintSatisfaction( + nullptr, {Req->getConstraintExpr()}, Result, TemplateArgs, + Req->getConstraintExpr()->getSourceRange(), Satisfaction)) + TransConstraint = Result[0]; + if (TransConstraint.isUsable() && !TransConstraint.isInvalid()) { bool CheckSucceeded = SemaRef.CheckConstraintExpression(TransConstraint.get()); (void)CheckSucceeded; @@ -2337,17 +2342,6 @@ "CheckConstraintExpression failed, but " "did not produce a SFINAE error"); } - // Use version of CheckConstraintSatisfaction that does no substitutions. - if (!TransConstraint.isInvalid() && - !TransConstraint.get()->isInstantiationDependent() && - !Trap.hasErrorOccurred()) { - bool CheckFailed = SemaRef.CheckConstraintSatisfaction( - TransConstraint.get(), Satisfaction); - (void)CheckFailed; - assert((!CheckFailed || Trap.hasErrorOccurred()) && - "CheckConstraintSatisfaction failed, " - "but did not produce a SFINAE error"); - } if (TransConstraint.isInvalid() || Trap.hasErrorOccurred()) return RebuildNestedRequirement(createSubstDiag(SemaRef, Info, [&] (llvm::raw_ostream& OS) { @@ -2355,7 +2349,8 @@ SemaRef.getPrintingPolicy()); })); } - if (TransConstraint.get()->isInstantiationDependent()) + if (TransConstraint.isUsable() && + TransConstraint.get()->isInstantiationDependent()) return new (SemaRef.Context) concepts::NestedRequirement(TransConstraint.get()); return new (SemaRef.Context) concepts::NestedRequirement( diff --git a/clang/test/CXX/expr/expr.prim/expr.prim.req/nested-requirement.cpp b/clang/test/CXX/expr/expr.prim/expr.prim.req/nested-requirement.cpp --- a/clang/test/CXX/expr/expr.prim/expr.prim.req/nested-requirement.cpp +++ b/clang/test/CXX/expr/expr.prim/expr.prim.req/nested-requirement.cpp @@ -19,7 +19,8 @@ template struct X { - template requires requires (U u) { requires sizeof(u) == sizeof(T); } // expected-note{{because 'sizeof (u) == sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'void'}} + template requires requires (U u) { requires sizeof(u) == sizeof(T); } + // expected-note@-1 {{because substituted constraint expression is ill-formed: invalid application of 'sizeof' to an incomplete type 'void'}} struct r4 {}; }; @@ -41,7 +42,7 @@ template concept C2 = requires (T a) { requires sizeof(a) == 4; // OK - requires a == 0; // expected-note{{because 'a == 0' would be invalid: constraint variable 'a' cannot be used in an evaluated context}} + requires a == 0; // expected-note{{because substituted constraint expression is ill-formed: constraint variable 'a' cannot be used in an evaluated context}} }; static_assert(C2); // expected-note{{because 'int' does not satisfy 'C2'}} expected-error{{static assertion failed}} } @@ -51,3 +52,110 @@ X.next(); }; +namespace SubstitutionFailureNestedRequires { +template concept True = true; +template concept False = false; + +struct S { double value; }; + +template +concept Pipes = requires (T x) { + requires True || True || False; + requires False || True || True; +}; + +template +concept Amps1 = requires (T x) { + requires True && True && !False; + // expected-note@-1{{because substituted constraint expression is ill-formed: member reference base type 'int' is not a structure or union}} +}; +template +concept Amps2 = requires (T x) { + requires True && True; +}; + +static_assert(Pipes); +static_assert(Pipes); + +static_assert(Amps1); +static_assert(!Amps1); + +static_assert(Amps2); +static_assert(!Amps2); + +template +void foo1() requires requires (T x) { // expected-note {{candidate template ignored: constraints not satisfied [with T = int]}} + requires + True // expected-note {{because substituted constraint expression is ill-formed: member reference base type 'int' is not a structure or union}} + && True; +} {} +template void fooPipes() requires Pipes {} +template void fooAmps1() requires Amps1 {} +// expected-note@-1 {{candidate template ignored: constraints not satisfied [with T = int]}} \ +// expected-note@-1 {{because 'int' does not satisfy 'Amps1'}} + +void foo() { + foo1(); + foo1(); // expected-error {{no matching function for call to 'foo1'}} + fooPipes(); + fooPipes(); + fooAmps1(); + fooAmps1(); // expected-error {{no matching function for call to 'fooAmps1'}} +} + +template +concept HasNoValue = requires (T x) { + requires !True && True; +}; +// FIXME: 'int' does not satisfy 'HasNoValue' currently since `!True` is an invalid expression. +// But, in principle, it should be constant-evaluated to true. +static_assert(!HasNoValue); +static_assert(!HasNoValue); + +template constexpr bool NotAConceptTrue = true; +template +concept SFinNestedRequires = requires (T x) { + // SF in a non-concept specialisation should also be evaluated to false. + requires NotAConceptTrue || NotAConceptTrue; +}; +static_assert(SFinNestedRequires); +static_assert(SFinNestedRequires); +template +void foo() requires SFinNestedRequires {} +void bar() { + foo(); + foo(); +} +namespace ErrorExpressions_NotSF { +template struct X { static constexpr bool value = T::value; }; \ +// expected-error {{type 'int' cannot be used prior to '::' because it has no members}} +struct True { static constexpr bool value = true; }; +struct False { static constexpr bool value = false; }; +template concept C = true; +template concept F = false; + +template requires requires(T) { requires C || X::value; } void foo(); \ + +template requires requires(T) { requires C && X::value; } void bar(); \ +// expected-note {{while substituting template arguments into constraint expression here}} \ +// expected-note {{while checking the satisfaction of nested requirement requested here}} \ +// expected-note {{candidate template ignored: constraints not satisfied [with T = False]}} \ +// expected-note {{because 'X::value' evaluated to false}} \ +// expected-note {{in instantiation of static data member}} \ +// expected-note {{in instantiation of requirement here}} \ +// expected-note {{while checking the satisfaction of nested requirement requested here}} \ +// expected-note {{while substituting template arguments into constraint expression here}} +template requires requires(T) { requires F || (X::value && C); } void baz(); + +void func() { + foo(); + foo(); + foo(); + + bar(); + bar(); // expected-error {{no matching function for call to 'bar'}} + bar(); // expected-note {{while checking constraint satisfaction for template 'bar' required here}} \ + // expected-note {{in instantiation of function template specialization}} +} +} +} diff --git a/clang/test/SemaTemplate/instantiate-requires-expr.cpp b/clang/test/SemaTemplate/instantiate-requires-expr.cpp --- a/clang/test/SemaTemplate/instantiate-requires-expr.cpp +++ b/clang/test/SemaTemplate/instantiate-requires-expr.cpp @@ -190,7 +190,7 @@ template struct a { template requires - (requires { requires sizeof(T::a) == 0; }, false) // expected-note{{because 'requires { requires <>; } , false' evaluated to false}} + (requires { requires sizeof(T::a) == 0; }, false) // expected-note{{because 'requires { requires ; } , false' evaluated to false}} struct r {}; };