[clang] b3ce872 - Make evaluation of nested requirement consistent with requires expr.
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Mon Dec 19 11:22:13 PST 2022
Author: Utkarsh Saxena
Date: 2022-12-19T20:22:03+01:00
New Revision: b3ce87285186ba190103a90b0b49da2e45fb7d1f
URL: https://github.com/llvm/llvm-project/commit/b3ce87285186ba190103a90b0b49da2e45fb7d1f
DIFF: https://github.com/llvm/llvm-project/commit/b3ce87285186ba190103a90b0b49da2e45fb7d1f.diff
LOG: Make evaluation of nested requirement consistent with requires expr.
Fixes: https://github.com/llvm/llvm-project/issues/45563
```
template<class T> concept True = true;
template <class T>
concept C1 = requires (T) {
requires True<typename T::value> || True<T>;
};
template <class T>
constexpr bool foo()
requires True<typename T::value> || True<T> {
return true;
}
static_assert(C1<double>); // Previously failed due to SFINAE error
static_assert(foo<int>()); // but this works fine.
```
The issue here is the discrepancy between how a [nested requirement is evaluated](https://github.com/llvm/llvm-project/blob/main/clang/lib/Sema/SemaTemplateInstantiate.cpp#L2331) Vs how a [non-nested requirement is evaluated](https://github.com/llvm/llvm-project/blob/main/clang/lib/Sema/SemaConcept.cpp#L167-L200).
This patch makes constraint checking consistent for nested requirement
and trailing requires expressions by reusing the same evaluator.
Differential Revision: https://reviews.llvm.org/D138914
Added:
Modified:
clang/include/clang/AST/ASTConcept.h
clang/include/clang/AST/ASTNodeTraverser.h
clang/include/clang/AST/ExprConcepts.h
clang/include/clang/AST/RecursiveASTVisitor.h
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/include/clang/Sema/Sema.h
clang/lib/AST/ASTConcept.cpp
clang/lib/AST/StmtPrinter.cpp
clang/lib/AST/StmtProfile.cpp
clang/lib/Sema/SemaConcept.cpp
clang/lib/Sema/SemaExprCXX.cpp
clang/lib/Sema/SemaTemplateInstantiate.cpp
clang/lib/Sema/TreeTransform.h
clang/lib/Serialization/ASTReaderStmt.cpp
clang/lib/Serialization/ASTWriterStmt.cpp
clang/test/CXX/expr/expr.prim/expr.prim.req/nested-requirement.cpp
clang/test/PCH/cxx2a-requires-expr.cpp
clang/tools/libclang/CIndex.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/AST/ASTConcept.h b/clang/include/clang/AST/ASTConcept.h
index ae2f37dc4b321..e6e2d57597ea0 100644
--- a/clang/include/clang/AST/ASTConcept.h
+++ b/clang/include/clang/AST/ASTConcept.h
@@ -59,6 +59,13 @@ class ConstraintSatisfaction : public llvm::FoldingSetNode {
static void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &C,
const NamedDecl *ConstraintOwner,
ArrayRef<TemplateArgument> TemplateArgs);
+
+ bool HasSubstitutionFailure() {
+ for (const auto &Detail : Details)
+ if (Detail.second.dyn_cast<SubstitutionDiagnostic *>())
+ return true;
+ return false;
+ }
};
/// Pairs of unsatisfied atomic constraint expressions along with the
@@ -91,9 +98,13 @@ struct ASTConstraintSatisfaction final :
ASTConstraintSatisfaction(const ASTContext &C,
const ConstraintSatisfaction &Satisfaction);
+ ASTConstraintSatisfaction(const ASTContext &C,
+ const ASTConstraintSatisfaction &Satisfaction);
static ASTConstraintSatisfaction *
Create(const ASTContext &C, const ConstraintSatisfaction &Satisfaction);
+ static ASTConstraintSatisfaction *
+ Rebuild(const ASTContext &C, const ASTConstraintSatisfaction &Satisfaction);
};
/// \brief Common data class for constructs that reference concepts with
diff --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h
index 7ad5e460432c0..cc60877805f98 100644
--- a/clang/include/clang/AST/ASTNodeTraverser.h
+++ b/clang/include/clang/AST/ASTNodeTraverser.h
@@ -246,7 +246,7 @@ class ASTNodeTraverser
.getTypeConstraint()
->getImmediatelyDeclaredConstraint());
} else if (auto *NR = dyn_cast<concepts::NestedRequirement>(R)) {
- if (!NR->isSubstitutionFailure())
+ if (!NR->hasInvalidConstraint())
Visit(NR->getConstraintExpr());
}
});
diff --git a/clang/include/clang/AST/ExprConcepts.h b/clang/include/clang/AST/ExprConcepts.h
index 6dedeb5c8ddfa..a358a2a41619e 100644
--- a/clang/include/clang/AST/ExprConcepts.h
+++ b/clang/include/clang/AST/ExprConcepts.h
@@ -24,6 +24,7 @@
#include "clang/AST/TemplateBase.h"
#include "clang/AST/Type.h"
#include "clang/Basic/SourceLocation.h"
+#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/TrailingObjects.h"
#include <utility>
#include <string>
@@ -401,57 +402,61 @@ class ExprRequirement : public Requirement {
/// \brief A requires-expression requirement which is satisfied when a general
/// constraint expression is satisfied ('nested' requirements).
class NestedRequirement : public Requirement {
- llvm::PointerUnion<Expr *, SubstitutionDiagnostic *> Value;
+ Expr *Constraint = nullptr;
const ASTConstraintSatisfaction *Satisfaction = nullptr;
+ bool HasInvalidConstraint = false;
+ StringRef InvalidConstraintEntity;
public:
friend ASTStmtReader;
friend ASTStmtWriter;
- NestedRequirement(SubstitutionDiagnostic *SubstDiag) :
- Requirement(RK_Nested, /*IsDependent=*/false,
- /*ContainsUnexpandedParameterPack*/false,
- /*IsSatisfied=*/false), Value(SubstDiag) {}
-
- NestedRequirement(Expr *Constraint) :
- Requirement(RK_Nested, /*IsDependent=*/true,
- Constraint->containsUnexpandedParameterPack()),
- Value(Constraint) {
+ NestedRequirement(Expr *Constraint)
+ : Requirement(RK_Nested, /*IsDependent=*/true,
+ Constraint->containsUnexpandedParameterPack()),
+ Constraint(Constraint) {
assert(Constraint->isInstantiationDependent() &&
"Nested requirement with non-dependent constraint must be "
"constructed with a ConstraintSatisfaction object");
}
NestedRequirement(ASTContext &C, Expr *Constraint,
- const ConstraintSatisfaction &Satisfaction) :
- Requirement(RK_Nested, Constraint->isInstantiationDependent(),
- Constraint->containsUnexpandedParameterPack(),
- Satisfaction.IsSatisfied),
- Value(Constraint),
- Satisfaction(ASTConstraintSatisfaction::Create(C, Satisfaction)) {}
-
- bool isSubstitutionFailure() const {
- return Value.is<SubstitutionDiagnostic *>();
- }
-
- SubstitutionDiagnostic *getSubstitutionDiagnostic() const {
- assert(isSubstitutionFailure() &&
- "getSubstitutionDiagnostic() may not be called when there was no "
- "substitution failure.");
- return Value.get<SubstitutionDiagnostic *>();
+ const ConstraintSatisfaction &Satisfaction)
+ : Requirement(RK_Nested, Constraint->isInstantiationDependent(),
+ Constraint->containsUnexpandedParameterPack(),
+ Satisfaction.IsSatisfied),
+ Constraint(Constraint),
+ Satisfaction(ASTConstraintSatisfaction::Create(C, Satisfaction)) {}
+
+ NestedRequirement(StringRef InvalidConstraintEntity,
+ const ASTConstraintSatisfaction *Satisfaction)
+ : Requirement(RK_Nested,
+ /*IsDependent=*/false,
+ /*ContainsUnexpandedParameterPack*/ false,
+ Satisfaction->IsSatisfied),
+ Satisfaction(Satisfaction), HasInvalidConstraint(true),
+ InvalidConstraintEntity(InvalidConstraintEntity) {}
+
+ NestedRequirement(ASTContext &C, StringRef InvalidConstraintEntity,
+ const ConstraintSatisfaction &Satisfaction)
+ : NestedRequirement(InvalidConstraintEntity,
+ ASTConstraintSatisfaction::Create(C, Satisfaction)) {}
+
+ bool hasInvalidConstraint() const { return HasInvalidConstraint; }
+
+ StringRef getInvalidConstraintEntity() {
+ assert(hasInvalidConstraint());
+ return InvalidConstraintEntity;
}
Expr *getConstraintExpr() const {
- assert(!isSubstitutionFailure() && "getConstraintExpr() may not be called "
- "on nested requirements with "
- "substitution failures.");
- return Value.get<Expr *>();
+ assert(!hasInvalidConstraint() &&
+ "getConstraintExpr() may not be called "
+ "on nested requirements with invalid constraint.");
+ return Constraint;
}
const ASTConstraintSatisfaction &getConstraintSatisfaction() const {
- assert(!isSubstitutionFailure() && "getConstraintSatisfaction() may not be "
- "called on nested requirements with "
- "substitution failures.");
return *Satisfaction;
}
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index d33626df3e9c0..7036200c54866 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -600,7 +600,7 @@ bool RecursiveASTVisitor<Derived>::TraverseConceptExprRequirement(
template <typename Derived>
bool RecursiveASTVisitor<Derived>::TraverseConceptNestedRequirement(
concepts::NestedRequirement *R) {
- if (!R->isSubstitutionFailure())
+ if (!R->hasInvalidConstraint())
return getDerived().TraverseStmt(R->getConstraintExpr());
return true;
}
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 80810a299d383..53ec661285395 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2893,7 +2893,7 @@ def note_type_requirement_substitution_error : Note<
def note_type_requirement_unknown_substitution_error : Note<
"%select{and|because}0 '%1' would be invalid">;
def note_nested_requirement_substitution_error : Note<
- "%select{and|because}0 '%1' would be invalid: %2">;
+ "%select{and|because}0 '%1' would be invalid%2">;
def note_nested_requirement_unknown_substitution_error : Note<
"%select{and|because}0 '%1' would be invalid">;
def note_ambiguous_atomic_constraints : Note<
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 3c0dee4b6ed93..67ba41ffa8464 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -8509,8 +8509,8 @@ class Sema final {
concepts::Requirement::SubstitutionDiagnostic *SubstDiag);
concepts::NestedRequirement *BuildNestedRequirement(Expr *E);
concepts::NestedRequirement *
- BuildNestedRequirement(
- concepts::Requirement::SubstitutionDiagnostic *SubstDiag);
+ BuildNestedRequirement(StringRef InvalidConstraintEntity,
+ const ASTConstraintSatisfaction &Satisfaction);
ExprResult ActOnRequiresExpr(SourceLocation RequiresKWLoc,
RequiresExprBodyDecl *Body,
ArrayRef<ParmVarDecl *> LocalParameters,
diff --git a/clang/lib/AST/ASTConcept.cpp b/clang/lib/AST/ASTConcept.cpp
index 67911f7e82157..8f2d945721843 100644
--- a/clang/lib/AST/ASTConcept.cpp
+++ b/clang/lib/AST/ASTConcept.cpp
@@ -19,32 +19,48 @@
#include "llvm/ADT/FoldingSet.h"
using namespace clang;
+namespace {
+void CreatUnsatisfiedConstraintRecord(
+ const ASTContext &C, const UnsatisfiedConstraintRecord &Detail,
+ UnsatisfiedConstraintRecord *TrailingObject) {
+ if (Detail.second.is<Expr *>())
+ new (TrailingObject) UnsatisfiedConstraintRecord{
+ Detail.first,
+ UnsatisfiedConstraintRecord::second_type(Detail.second.get<Expr *>())};
+ else {
+ auto &SubstitutionDiagnostic =
+ *Detail.second.get<std::pair<SourceLocation, StringRef> *>();
+ unsigned MessageSize = SubstitutionDiagnostic.second.size();
+ char *Mem = new (C) char[MessageSize];
+ memcpy(Mem, SubstitutionDiagnostic.second.data(), MessageSize);
+ auto *NewSubstDiag = new (C) std::pair<SourceLocation, StringRef>(
+ SubstitutionDiagnostic.first, StringRef(Mem, MessageSize));
+ new (TrailingObject) UnsatisfiedConstraintRecord{
+ Detail.first, UnsatisfiedConstraintRecord::second_type(NewSubstDiag)};
+ }
+}
+} // namespace
+
ASTConstraintSatisfaction::ASTConstraintSatisfaction(
const ASTContext &C, const ConstraintSatisfaction &Satisfaction)
: NumRecords{Satisfaction.Details.size()},
IsSatisfied{Satisfaction.IsSatisfied}, ContainsErrors{
Satisfaction.ContainsErrors} {
- for (unsigned I = 0; I < NumRecords; ++I) {
- auto &Detail = Satisfaction.Details[I];
- if (Detail.second.is<Expr *>())
- new (getTrailingObjects<UnsatisfiedConstraintRecord>() + I)
- UnsatisfiedConstraintRecord{Detail.first,
- UnsatisfiedConstraintRecord::second_type(
- Detail.second.get<Expr *>())};
- else {
- auto &SubstitutionDiagnostic =
- *Detail.second.get<std::pair<SourceLocation, StringRef> *>();
- unsigned MessageSize = SubstitutionDiagnostic.second.size();
- char *Mem = new (C) char[MessageSize];
- memcpy(Mem, SubstitutionDiagnostic.second.data(), MessageSize);
- auto *NewSubstDiag = new (C) std::pair<SourceLocation, StringRef>(
- SubstitutionDiagnostic.first, StringRef(Mem, MessageSize));
- new (getTrailingObjects<UnsatisfiedConstraintRecord>() + I)
- UnsatisfiedConstraintRecord{Detail.first,
- UnsatisfiedConstraintRecord::second_type(
- NewSubstDiag)};
- }
- }
+ for (unsigned I = 0; I < NumRecords; ++I)
+ CreatUnsatisfiedConstraintRecord(
+ C, Satisfaction.Details[I],
+ getTrailingObjects<UnsatisfiedConstraintRecord>() + I);
+}
+
+ASTConstraintSatisfaction::ASTConstraintSatisfaction(
+ const ASTContext &C, const ASTConstraintSatisfaction &Satisfaction)
+ : NumRecords{Satisfaction.NumRecords},
+ IsSatisfied{Satisfaction.IsSatisfied},
+ ContainsErrors{Satisfaction.ContainsErrors} {
+ for (unsigned I = 0; I < NumRecords; ++I)
+ CreatUnsatisfiedConstraintRecord(
+ C, *(Satisfaction.begin() + I),
+ getTrailingObjects<UnsatisfiedConstraintRecord>() + I);
}
ASTConstraintSatisfaction *
@@ -57,6 +73,14 @@ ASTConstraintSatisfaction::Create(const ASTContext &C,
return new (Mem) ASTConstraintSatisfaction(C, Satisfaction);
}
+ASTConstraintSatisfaction *ASTConstraintSatisfaction::Rebuild(
+ const ASTContext &C, const ASTConstraintSatisfaction &Satisfaction) {
+ std::size_t size =
+ totalSizeToAlloc<UnsatisfiedConstraintRecord>(Satisfaction.NumRecords);
+ void *Mem = C.Allocate(size, alignof(ASTConstraintSatisfaction));
+ return new (Mem) ASTConstraintSatisfaction(C, Satisfaction);
+}
+
void ConstraintSatisfaction::Profile(
llvm::FoldingSetNodeID &ID, const ASTContext &C,
const NamedDecl *ConstraintOwner, ArrayRef<TemplateArgument> TemplateArgs) {
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index 2670233fb7255..1e0d8a5046755 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -2528,7 +2528,7 @@ void StmtPrinter::VisitRequiresExpr(RequiresExpr *E) {
} else {
auto *NestedReq = cast<concepts::NestedRequirement>(Req);
OS << "requires ";
- if (NestedReq->isSubstitutionFailure())
+ if (NestedReq->hasInvalidConstraint())
OS << "<<error-expression>>";
else
PrintExpr(NestedReq->getConstraintExpr());
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index 7f61b45413c97..3820a78d74f0b 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -1622,8 +1622,8 @@ void StmtProfiler::VisitRequiresExpr(const RequiresExpr *S) {
} else {
ID.AddInteger(concepts::Requirement::RK_Nested);
auto *NestedReq = cast<concepts::NestedRequirement>(Req);
- ID.AddBoolean(NestedReq->isSubstitutionFailure());
- if (!NestedReq->isSubstitutionFailure())
+ ID.AddBoolean(NestedReq->hasInvalidConstraint());
+ if (!NestedReq->hasInvalidConstraint())
Visit(NestedReq->getConstraintExpr());
}
}
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index 415ec70df0859..cab013de95c12 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -180,7 +180,8 @@ calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
// 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
@@ -189,7 +190,8 @@ calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
// 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<AtomicEvaluator>(Evaluator));
@@ -330,7 +332,8 @@ static ExprResult calculateConstraintSatisfaction(
// 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()) {
@@ -898,31 +901,28 @@ static void diagnoseUnsatisfiedRequirement(Sema &S,
return;
}
}
+static void diagnoseWellFormedUnsatisfiedConstraintExpr(Sema &S,
+ Expr *SubstExpr,
+ bool First = true);
static void diagnoseUnsatisfiedRequirement(Sema &S,
concepts::NestedRequirement *Req,
bool First) {
- if (Req->isSubstitutionFailure()) {
- concepts::Requirement::SubstitutionDiagnostic *SubstDiag =
- Req->getSubstitutionDiagnostic();
- if (!SubstDiag->DiagMessage.empty())
- S.Diag(SubstDiag->DiagLoc,
- diag::note_nested_requirement_substitution_error)
- << (int)First << SubstDiag->SubstitutedEntity
- << SubstDiag->DiagMessage;
+ using SubstitutionDiagnostic = std::pair<SourceLocation, StringRef>;
+ for (auto &Pair : Req->getConstraintSatisfaction()) {
+ if (auto *SubstDiag = Pair.second.dyn_cast<SubstitutionDiagnostic *>())
+ S.Diag(SubstDiag->first, diag::note_nested_requirement_substitution_error)
+ << (int)First << Req->getInvalidConstraintEntity() << SubstDiag->second;
else
- S.Diag(SubstDiag->DiagLoc,
- diag::note_nested_requirement_unknown_substitution_error)
- << (int)First << SubstDiag->SubstitutedEntity;
- return;
+ diagnoseWellFormedUnsatisfiedConstraintExpr(
+ S, Pair.second.dyn_cast<Expr *>(), First);
+ First = false;
}
- S.DiagnoseUnsatisfiedConstraint(Req->getConstraintSatisfaction(), First);
}
-
static void diagnoseWellFormedUnsatisfiedConstraintExpr(Sema &S,
Expr *SubstExpr,
- bool First = true) {
+ bool First) {
SubstExpr = SubstExpr->IgnoreParenImpCasts();
if (BinaryOperator *BO = dyn_cast<BinaryOperator>(SubstExpr)) {
switch (BO->getOpcode()) {
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 27ce8b4d93638..f73abdbdd1972 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -9086,9 +9086,11 @@ Sema::BuildNestedRequirement(Expr *Constraint) {
}
concepts::NestedRequirement *
-Sema::BuildNestedRequirement(
- concepts::Requirement::SubstitutionDiagnostic *SubstDiag) {
- return new (Context) concepts::NestedRequirement(SubstDiag);
+Sema::BuildNestedRequirement(StringRef InvalidConstraintEntity,
+ const ASTConstraintSatisfaction &Satisfaction) {
+ return new (Context) concepts::NestedRequirement(
+ InvalidConstraintEntity,
+ ASTConstraintSatisfaction::Rebuild(Context, Satisfaction));
}
RequiresExprBodyDecl *
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 0c8ae8a927c28..b23ae5c83d9e6 100644
--- 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"
@@ -26,11 +27,13 @@
#include "clang/Sema/DeclSpec.h"
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Lookup.h"
+#include "clang/Sema/Sema.h"
#include "clang/Sema/SemaConcept.h"
#include "clang/Sema/SemaInternal.h"
#include "clang/Sema/Template.h"
#include "clang/Sema/TemplateDeduction.h"
#include "clang/Sema/TemplateInstCallback.h"
+#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/TimeProfiler.h"
using namespace clang;
@@ -2306,10 +2309,10 @@ TemplateInstantiator::TransformNestedRequirement(
concepts::NestedRequirement *Req) {
if (!Req->isDependent() && !AlwaysRebuild())
return Req;
- if (Req->isSubstitutionFailure()) {
+ if (Req->hasInvalidConstraint()) {
if (AlwaysRebuild())
- return RebuildNestedRequirement(
- Req->getSubstitutionDiagnostic());
+ return RebuildNestedRequirement(Req->getInvalidConstraintEntity(),
+ Req->getConstraintSatisfaction());
return Req;
}
Sema::InstantiatingTemplate ReqInst(SemaRef,
@@ -2329,36 +2332,29 @@ TemplateInstantiator::TransformNestedRequirement(
Req->getConstraintExpr()->getSourceRange());
if (ConstrInst.isInvalid())
return nullptr;
- TransConstraint = TransformExpr(Req->getConstraintExpr());
- if (!TransConstraint.isInvalid()) {
- bool CheckSucceeded =
- SemaRef.CheckConstraintExpression(TransConstraint.get());
- (void)CheckSucceeded;
- assert((CheckSucceeded || Trap.hasErrorOccurred()) &&
- "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) {
- Req->getConstraintExpr()->printPretty(OS, nullptr,
- SemaRef.getPrintingPolicy());
- }));
+ llvm::SmallVector<Expr *> Result;
+ if (!SemaRef.CheckConstraintSatisfaction(
+ nullptr, {Req->getConstraintExpr()}, Result, TemplateArgs,
+ Req->getConstraintExpr()->getSourceRange(), Satisfaction))
+ TransConstraint = Result[0];
+ assert(!Trap.hasErrorOccurred() && "Substitution failures must be handled "
+ "by CheckConstraintSatisfaction.");
}
- if (TransConstraint.get()->isInstantiationDependent())
+ if (TransConstraint.isUsable() &&
+ TransConstraint.get()->isInstantiationDependent())
return new (SemaRef.Context)
concepts::NestedRequirement(TransConstraint.get());
+ if (TransConstraint.isInvalid() || !TransConstraint.get() ||
+ Satisfaction.HasSubstitutionFailure()) {
+ SmallString<128> Entity;
+ llvm::raw_svector_ostream OS(Entity);
+ Req->getConstraintExpr()->printPretty(OS, nullptr,
+ SemaRef.getPrintingPolicy());
+ char *EntityBuf = new (SemaRef.Context) char[Entity.size()];
+ std::copy(Entity.begin(), Entity.end(), EntityBuf);
+ return new (SemaRef.Context) concepts::NestedRequirement(
+ SemaRef.Context, StringRef(EntityBuf, Entity.size()), Satisfaction);
+ }
return new (SemaRef.Context) concepts::NestedRequirement(
SemaRef.Context, TransConstraint.get(), Satisfaction);
}
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 03dec8bd48871..36450892f9485 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -3556,9 +3556,10 @@ class TreeTransform {
}
concepts::NestedRequirement *
- RebuildNestedRequirement(
- concepts::Requirement::SubstitutionDiagnostic *SubstDiag) {
- return SemaRef.BuildNestedRequirement(SubstDiag);
+ RebuildNestedRequirement(StringRef InvalidConstraintEntity,
+ const ASTConstraintSatisfaction &Satisfaction) {
+ return SemaRef.BuildNestedRequirement(InvalidConstraintEntity,
+ Satisfaction);
}
concepts::NestedRequirement *RebuildNestedRequirement(Expr *Constraint) {
@@ -12850,10 +12851,10 @@ template<typename Derived>
concepts::NestedRequirement *
TreeTransform<Derived>::TransformNestedRequirement(
concepts::NestedRequirement *Req) {
- if (Req->isSubstitutionFailure()) {
+ if (Req->hasInvalidConstraint()) {
if (getDerived().AlwaysRebuild())
return getDerived().RebuildNestedRequirement(
- Req->getSubstitutionDiagnostic());
+ Req->getInvalidConstraintEntity(), Req->getConstraintSatisfaction());
return Req;
}
ExprResult TransConstraint =
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 0a3d1aee8e188..4ca6998b13370 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -895,9 +895,17 @@ void ASTStmtReader::VisitRequiresExpr(RequiresExpr *E) {
std::move(*Req));
} break;
case concepts::Requirement::RK_Nested: {
- if (/* IsSubstitutionDiagnostic */Record.readInt()) {
+ bool HasInvalidConstraint = Record.readInt();
+ if (HasInvalidConstraint) {
+ std::string InvalidConstraint = Record.readString();
+ char *InvalidConstraintBuf =
+ new (Record.getContext()) char[InvalidConstraint.size()];
+ std::copy(InvalidConstraint.begin(), InvalidConstraint.end(),
+ InvalidConstraintBuf);
R = new (Record.getContext()) concepts::NestedRequirement(
- readSubstitutionDiagnostic(Record));
+ Record.getContext(),
+ StringRef(InvalidConstraintBuf, InvalidConstraint.size()),
+ readConstraintSatisfaction(Record));
break;
}
Expr *E = Record.readExpr();
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index e0dcf913319bf..8ed7412070167 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -493,12 +493,12 @@ void ASTStmtWriter::VisitRequiresExpr(RequiresExpr *E) {
} else {
auto *NestedReq = cast<concepts::NestedRequirement>(R);
Record.push_back(concepts::Requirement::RK_Nested);
- Record.push_back(NestedReq->isSubstitutionFailure());
- if (NestedReq->isSubstitutionFailure()){
- addSubstitutionDiagnostic(Record,
- NestedReq->getSubstitutionDiagnostic());
+ Record.push_back(NestedReq->hasInvalidConstraint());
+ if (NestedReq->hasInvalidConstraint()) {
+ Record.AddString(NestedReq->getInvalidConstraintEntity());
+ addConstraintSatisfaction(Record, *NestedReq->Satisfaction);
} else {
- Record.AddStmt(NestedReq->Value.get<Expr *>());
+ Record.AddStmt(NestedReq->getConstraintExpr());
if (!NestedReq->isDependent())
addConstraintSatisfaction(Record, *NestedReq->Satisfaction);
}
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
index 7be1429fc787e..c494117056105 100644
--- 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
@@ -51,3 +51,115 @@ concept K = requires (T::Type X) {
X.next();
};
+namespace SubstitutionFailureNestedRequires {
+template<class T> concept True = true;
+template<class T> concept False = false;
+
+struct S { double value; };
+
+template <class T>
+concept Pipes = requires (T x) {
+ requires True<decltype(x.value)> || True<T> || False<T>;
+ requires False<T> || True<T> || True<decltype(x.value)>;
+};
+
+template <class T>
+concept Amps1 = requires (T x) {
+ requires True<decltype(x.value)> && True<T> && !False<T>; // #Amps1
+};
+template <class T>
+concept Amps2 = requires (T x) {
+ requires True<T> && True<decltype(x.value)>;
+};
+
+static_assert(Pipes<S>);
+static_assert(Pipes<double>);
+
+static_assert(Amps1<S>);
+static_assert(!Amps1<double>);
+
+static_assert(Amps2<S>);
+static_assert(!Amps2<double>);
+
+template<class T>
+void foo1() requires requires (T x) { // #foo1
+ requires
+ True<decltype(x.value)> // #foo1Value
+ && True<T>;
+} {}
+template<class T> void fooPipes() requires Pipes<T> {}
+template<class T> void fooAmps1() requires Amps1<T> {} // #fooAmps1
+void foo() {
+ foo1<S>();
+ foo1<int>(); // expected-error {{no matching function for call to 'foo1'}}
+ // expected-note@#foo1Value {{because 'True<decltype(x.value)> && True<T>' would be invalid: member reference base type 'int' is not a structure or union}}
+ // expected-note@#foo1 {{candidate template ignored: constraints not satisfied [with T = int]}}
+ fooPipes<S>();
+ fooPipes<int>();
+ fooAmps1<S>();
+ fooAmps1<int>(); // expected-error {{no matching function for call to 'fooAmps1'}}
+ // expected-note@#fooAmps1 {{candidate template ignored: constraints not satisfied [with T = int]}}
+ // expected-note@#fooAmps1 {{because 'int' does not satisfy 'Amps1'}}
+ // expected-note@#Amps1 {{because 'True<decltype(x.value)> && True<T> && !False<T>' would be invalid: member reference base type 'int' is not a structure or union}}
+}
+
+template<class T>
+concept HasNoValue = requires (T x) {
+ requires !True<decltype(x.value)> && True<T>;
+};
+// FIXME: 'int' does not satisfy 'HasNoValue' currently since `!True<decltype(x.value)>` is an invalid expression.
+// But, in principle, it should be constant-evaluated to true.
+// This happens also for requires expression and is not restricted to nested requirement.
+static_assert(!HasNoValue<int>);
+static_assert(!HasNoValue<S>);
+
+template<class T> constexpr bool NotAConceptTrue = true;
+template <class T>
+concept SFinNestedRequires = requires (T x) {
+ // SF in a non-concept specialisation should also be evaluated to false.
+ requires NotAConceptTrue<decltype(x.value)> || NotAConceptTrue<T>;
+};
+static_assert(SFinNestedRequires<int>);
+static_assert(SFinNestedRequires<S>);
+template <class T>
+void foo() requires SFinNestedRequires<T> {}
+void bar() {
+ foo<int>();
+ foo<S>();
+}
+namespace ErrorExpressions_NotSF {
+template<typename T> struct X { static constexpr bool value = T::value; }; // #X_Value
+struct True { static constexpr bool value = true; };
+struct False { static constexpr bool value = false; };
+template<typename T> concept C = true;
+template<typename T> concept F = false;
+
+template<typename T> requires requires(T) { requires C<T> || X<T>::value; } void foo();
+
+template<typename T> requires requires(T) { requires C<T> && X<T>::value; } void bar(); // #bar
+template<typename T> requires requires(T) { requires F<T> || (X<T>::value && C<T>); } void baz();
+
+void func() {
+ foo<True>();
+ foo<False>();
+ foo<int>();
+
+ bar<True>();
+ bar<False>();
+ // expected-error at -1 {{no matching function for call to 'bar'}}
+ // expected-note@#bar {{while substituting template arguments into constraint expression here}}
+ // expected-note@#bar {{while checking the satisfaction of nested requirement requested here}}
+ // expected-note@#bar {{candidate template ignored: constraints not satisfied [with T = False]}}
+ // expected-note@#bar {{because 'X<False>::value' evaluated to false}}
+
+ bar<int>();
+ // expected-note at -1 {{while checking constraint satisfaction for template 'bar<int>' required here}} \
+ // expected-note at -1 {{in instantiation of function template specialization}}
+ // expected-note@#bar {{in instantiation of static data member}}
+ // expected-note@#bar {{in instantiation of requirement here}}
+ // expected-note@#bar {{while checking the satisfaction of nested requirement requested here}}
+ // expected-note@#bar {{while substituting template arguments into constraint expression here}}
+ // expected-error@#X_Value {{type 'int' cannot be used prior to '::' because it has no members}}
+}
+}
+}
diff --git a/clang/test/PCH/cxx2a-requires-expr.cpp b/clang/test/PCH/cxx2a-requires-expr.cpp
index d958f3a147042..7f8f258a0f8f3 100644
--- a/clang/test/PCH/cxx2a-requires-expr.cpp
+++ b/clang/test/PCH/cxx2a-requires-expr.cpp
@@ -12,12 +12,13 @@ concept C2 = true;
template<typename T>
bool f() {
- // CHECK: requires (T t) { t++; { t++ } noexcept -> C; { t++ } -> C2<int>; typename T::a; requires T::val; };
+ // CHECK: requires (T t) { t++; { t++ } noexcept -> C; { t++ } -> C2<int>; typename T::a; requires T::val; requires C<typename T::val> || (C<typename T::val> || C<T>); };
return requires (T t) {
t++;
{ t++ } noexcept -> C;
{ t++ } -> C2<int>;
typename T::a;
requires T::val;
+ requires C<typename T::val> || (C<typename T::val> || C<T>);
};
}
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 9958671e03a18..5a36679970585 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -1374,7 +1374,7 @@ bool CursorVisitor::VisitConceptRequirement(const concepts::Requirement &R) {
}
case Requirement::RK_Nested: {
const NestedRequirement &NR = cast<NestedRequirement>(R);
- if (!NR.isSubstitutionFailure()) {
+ if (!NR.hasInvalidConstraint()) {
if (Visit(NR.getConstraintExpr()))
return true;
}
More information about the cfe-commits
mailing list