diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -212,6 +212,11 @@ of FAM-like arrays. - Clang now correctly diagnoses a warning when defercencing a void pointer in C mode. This fixes `Issue 53631 `_ +- Clang will now diagnose an overload set where a candidate has a constraint that + refers to an expression with a previous error as nothing viable, so that it + doesn't generate strange cascading errors, particularly in cases where a + subsuming constraint fails, which would result in a less-specific overload to + be selected. Non-comprehensive list of changes in this release ------------------------------------------------- diff --git a/clang/include/clang/AST/ASTConcept.h b/clang/include/clang/AST/ASTConcept.h --- a/clang/include/clang/AST/ASTConcept.h +++ b/clang/include/clang/AST/ASTConcept.h @@ -44,6 +44,7 @@ using Detail = llvm::PointerUnion; bool IsSatisfied = false; + bool ContainsErrors = false; /// \brief Pairs of unsatisfied atomic constraint expressions along with the /// substituted constraint expr, if the template arguments could be @@ -78,6 +79,7 @@ UnsatisfiedConstraintRecord> { std::size_t NumRecords; bool IsSatisfied : 1; + bool ContainsErrors : 1; const UnsatisfiedConstraintRecord *begin() const { return getTrailingObjects(); diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2845,6 +2845,8 @@ "template template parameter|template}0 %1%2">; def note_substituted_constraint_expr_is_ill_formed : Note< "because substituted constraint expression is ill-formed%0">; +def note_constraint_references_error + : Note<"constraint depends on a previously diagnosed expression">; def note_atomic_constraint_evaluated_to_false : Note< "%select{and|because}0 '%1' evaluated to false">; def note_concept_specialization_constraint_evaluated_to_false : Note< diff --git a/clang/include/clang/Sema/Overload.h b/clang/include/clang/Sema/Overload.h --- a/clang/include/clang/Sema/Overload.h +++ b/clang/include/clang/Sema/Overload.h @@ -928,6 +928,8 @@ return ExplicitCallArguments; } + bool NotValidBecauseConstraintExprHasError() const; + private: friend class OverloadCandidateSet; OverloadCandidate() diff --git a/clang/lib/AST/ASTConcept.cpp b/clang/lib/AST/ASTConcept.cpp --- a/clang/lib/AST/ASTConcept.cpp +++ b/clang/lib/AST/ASTConcept.cpp @@ -19,10 +19,11 @@ #include "llvm/ADT/FoldingSet.h" using namespace clang; -ASTConstraintSatisfaction::ASTConstraintSatisfaction(const ASTContext &C, - const ConstraintSatisfaction &Satisfaction): - NumRecords{Satisfaction.Details.size()}, - IsSatisfied{Satisfaction.IsSatisfied} { +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()) @@ -46,7 +47,6 @@ } } - ASTConstraintSatisfaction * ASTConstraintSatisfaction::Create(const ASTContext &C, const ConstraintSatisfaction &Satisfaction) { diff --git a/clang/lib/AST/ComputeDependence.cpp b/clang/lib/AST/ComputeDependence.cpp --- a/clang/lib/AST/ComputeDependence.cpp +++ b/clang/lib/AST/ComputeDependence.cpp @@ -853,7 +853,10 @@ ExprDependence D = ValueDependent ? ExprDependence::Value : ExprDependence::None; - return D | toExprDependence(TA); + auto Res = D | toExprDependence(TA); + if(!ValueDependent && E->getSatisfaction().ContainsErrors) + Res |= ExprDependence::Error; + return Res; } ExprDependence clang::computeDependence(ObjCArrayLiteral *E) { 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 @@ -206,6 +206,30 @@ // Evaluator has decided satisfaction without yielding an expression. return ExprEmpty(); + // We don't have the ability to evaluate this, since it contains a + // RecoveryExpr, so we want to fail overload resolution. Otherwise, + // we'd potentially pick up a different overload, and cause confusing + // diagnostics. SO, add a failure detail that will cause us to make this + // overload set not viable. + if (SubstitutedAtomicExpr.get()->containsErrors()) { + Satisfaction.IsSatisfied = false; + Satisfaction.ContainsErrors = true; + + PartialDiagnostic Msg = S.PDiag(diag::note_constraint_references_error); + SmallString<128> DiagString; + DiagString = ": "; + Msg.EmitToString(S.getDiagnostics(), DiagString); + unsigned MessageSize = DiagString.size(); + char *Mem = new (S.Context) char[MessageSize]; + memcpy(Mem, DiagString.c_str(), MessageSize); + Satisfaction.Details.emplace_back( + ConstraintExpr, + new (S.Context) ConstraintSatisfaction::SubstitutionDiagnostic{ + SubstitutedAtomicExpr.get()->getBeginLoc(), + StringRef(Mem, MessageSize)}); + return SubstitutedAtomicExpr; + } + EnterExpressionEvaluationContext ConstantEvaluated( S, Sema::ExpressionEvaluationContext::ConstantEvaluated); SmallVector EvaluationDiags; diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -10119,6 +10119,13 @@ } } +bool OverloadCandidate::NotValidBecauseConstraintExprHasError() const { + return FailureKind == ovl_fail_bad_deduction && + DeductionFailure.Result == Sema::TDK_ConstraintsNotSatisfied && + static_cast(DeductionFailure.Data) + ->Satisfaction.ContainsErrors; +} + /// Computes the best viable function (C++ 13.3.3) /// within an overload candidate set. /// @@ -10171,10 +10178,18 @@ Best = end(); for (auto *Cand : Candidates) { Cand->Best = false; - if (Cand->Viable) + if (Cand->Viable) { if (Best == end() || isBetterOverloadCandidate(S, *Cand, *Best, Loc, Kind)) Best = Cand; + } else if (Cand->NotValidBecauseConstraintExprHasError()) { + // This candidate has constraint that we were unable to evaluate because + // it referenced an expression that contained an error. Rather than fall + // back onto a potentially unintended candidate (made worse by by + // subsuming constraints), treat this as 'no viable candidate'. + Best = end(); + return OR_No_Viable_Function; + } } // If we didn't find any viable functions, abort. diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -772,6 +772,7 @@ readConstraintSatisfaction(ASTRecordReader &Record) { ConstraintSatisfaction Satisfaction; Satisfaction.IsSatisfied = Record.readInt(); + Satisfaction.ContainsErrors = Record.readInt(); if (!Satisfaction.IsSatisfied) { unsigned NumDetailRecords = Record.readInt(); for (unsigned i = 0; i != NumDetailRecords; ++i) { diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -402,6 +402,7 @@ addConstraintSatisfaction(ASTRecordWriter &Record, const ASTConstraintSatisfaction &Satisfaction) { Record.push_back(Satisfaction.IsSatisfied); + Record.push_back(Satisfaction.ContainsErrors); if (!Satisfaction.IsSatisfied) { Record.push_back(Satisfaction.NumRecords); for (const auto &DetailRecord : Satisfaction) { diff --git a/clang/test/SemaTemplate/concepts-recovery-expr.cpp b/clang/test/SemaTemplate/concepts-recovery-expr.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaTemplate/concepts-recovery-expr.cpp @@ -0,0 +1,182 @@ +// RUN: %clang_cc1 -std=c++20 -verify %s + +// expected-error@+1{{use of undeclared identifier 'b'}} +constexpr bool CausesRecoveryExpr = b; + +template +concept ReferencesCRE = CausesRecoveryExpr; + +template requires CausesRecoveryExpr // #NVC1REQ +void NoViableCands1(){} // #NVC1 + +template requires ReferencesCRE // #NVC2REQ +void NoViableCands2(){} // #NVC2 + +template // #NVC3REQ +void NoViableCands3(){} // #NVC3 + +void NVCUse() { + NoViableCands1(); + // expected-error@-1 {{no matching function for call to 'NoViableCands1'}} + // expected-note@#NVC1{{candidate template ignored: constraints not satisfied}} + // expected-note@#NVC1REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} + + NoViableCands2(); + // expected-error@-1 {{no matching function for call to 'NoViableCands2'}} + // expected-note@#NVC2{{candidate template ignored: constraints not satisfied}} + // expected-note@#NVC2REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} + NoViableCands3(); + // expected-error@-1 {{no matching function for call to 'NoViableCands3'}} + // expected-note@#NVC3{{candidate template ignored: constraints not satisfied}} + // expected-note@#NVC3REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} +} + +template requires CausesRecoveryExpr // #OVC1REQ +void OtherViableCands1(){} // #OVC1 + +template +void OtherViableCands1(){} // #OVC1_ALT + +template requires ReferencesCRE // #OVC2REQ +void OtherViableCands2(){} // #OVC2 + +template +void OtherViableCands2(){} // #OVC2_ALT + +template // #OVC3REQ +void OtherViableCands3(){} // #OVC3 +template +void OtherViableCands3(){} // #OVC3_ALT + +void OVCUse() { + OtherViableCands1(); + // expected-error@-1 {{no matching function for call to 'OtherViableCands1'}} + // expected-note@#OVC1_ALT {{candidate function}} + // expected-note@#OVC1 {{candidate template ignored: constraints not satisfied}} + // expected-note@#OVC1REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} + OtherViableCands2(); + // expected-error@-1 {{no matching function for call to 'OtherViableCands2'}} + // expected-note@#OVC2_ALT {{candidate function}} + // expected-note@#OVC2 {{candidate template ignored: constraints not satisfied}} + // expected-note@#OVC2REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} + OtherViableCands3(); + // expected-error@-1 {{no matching function for call to 'OtherViableCands3'}} + // expected-note@#OVC3_ALT {{candidate function}} + // expected-note@#OVC3 {{candidate template ignored: constraints not satisfied}} + // expected-note@#OVC3REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} +} + +template requires CausesRecoveryExpr // #OBNVC1REQ +void OtherBadNoViableCands1(){} // #OBNVC1 + +template requires false // #OBNVC1REQ_ALT +void OtherBadNoViableCands1(){} // #OBNVC1_ALT + +template requires ReferencesCRE // #OBNVC2REQ +void OtherBadNoViableCands2(){} // #OBNVC2 + +template requires false// #OBNVC2REQ_ALT +void OtherBadNoViableCands2(){} // #OBNVC2_ALT + +template // #OBNVC3REQ +void OtherBadNoViableCands3(){} // #OBNVC3 +template requires false // #OBNVC3REQ_ALT +void OtherBadNoViableCands3(){} // #OBNVC3_ALT + +void OBNVCUse() { + OtherBadNoViableCands1(); + // expected-error@-1 {{no matching function for call to 'OtherBadNoViableCands1'}} + // expected-note@#OBNVC1_ALT {{candidate template ignored: constraints not satisfied}} + // expected-note@#OBNVC1REQ_ALT {{because 'false' evaluated to false}} + // expected-note@#OBNVC1 {{candidate template ignored: constraints not satisfied}} + // expected-note@#OBNVC1REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} + OtherBadNoViableCands2(); + // expected-error@-1 {{no matching function for call to 'OtherBadNoViableCands2'}} + // expected-note@#OBNVC2_ALT {{candidate template ignored: constraints not satisfied}} + // expected-note@#OBNVC2REQ_ALT {{because 'false' evaluated to false}} + // expected-note@#OBNVC2 {{candidate template ignored: constraints not satisfied}} + // expected-note@#OBNVC2REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} + OtherBadNoViableCands3(); + // expected-error@-1 {{no matching function for call to 'OtherBadNoViableCands3'}} + // expected-note@#OBNVC3_ALT {{candidate template ignored: constraints not satisfied}} + // expected-note@#OBNVC3REQ_ALT {{because 'false' evaluated to false}} + // expected-note@#OBNVC3 {{candidate template ignored: constraints not satisfied}} + // expected-note@#OBNVC3REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} +} + + +// Same tests with member functions. +struct OVC { +template requires CausesRecoveryExpr // #MEMOVC1REQ +void OtherViableCands1(){} // #MEMOVC1 + +template +void OtherViableCands1(){} // #MEMOVC1_ALT + +template requires ReferencesCRE // #MEMOVC2REQ +void OtherViableCands2(){} // #MEMOVC2 + +template +void OtherViableCands2(){} // #MEMOVC2_ALT + +template // #MEMOVC3REQ +void OtherViableCands3(){} // #MEMOVC3 +template +void OtherViableCands3(){} // #MEMOVC3_ALT +}; + +void MemOVCUse() { + OVC S; + S.OtherViableCands1(); + // expected-error@-1 {{no matching member function for call to 'OtherViableCands1'}} + // expected-note@#MEMOVC1_ALT {{candidate function}} + // expected-note@#MEMOVC1 {{candidate template ignored: constraints not satisfied}} + // expected-note@#MEMOVC1REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} + S.OtherViableCands2(); + // expected-error@-1 {{no matching member function for call to 'OtherViableCands2'}} + // expected-note@#MEMOVC2_ALT {{candidate function}} + // expected-note@#MEMOVC2 {{candidate template ignored: constraints not satisfied}} + // expected-note@#MEMOVC2REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} + S.OtherViableCands3(); + // expected-error@-1 {{no matching member function for call to 'OtherViableCands3'}} + // expected-note@#MEMOVC3_ALT {{candidate function}} + // expected-note@#MEMOVC3 {{candidate template ignored: constraints not satisfied}} + // expected-note@#MEMOVC3REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} +} + +struct StaticOVC { +template requires CausesRecoveryExpr // #SMEMOVC1REQ +static void OtherViableCands1(){} // #SMEMOVC1 + +template +static void OtherViableCands1(){} // #SMEMOVC1_ALT + +template requires ReferencesCRE // #SMEMOVC2REQ +static void OtherViableCands2(){} // #SMEMOVC2 + +template +static void OtherViableCands2(){} // #SMEMOVC2_ALT + +template // #SMEMOVC3REQ +static void OtherViableCands3(){} // #SMEMOVC3 +template +static void OtherViableCands3(){} // #SMEMOVC3_ALT +}; + +void StaticMemOVCUse() { + StaticOVC::OtherViableCands1(); + // expected-error@-1 {{no matching function for call to 'OtherViableCands1'}} + // expected-note@#SMEMOVC1_ALT {{candidate function}} + // expected-note@#SMEMOVC1 {{candidate template ignored: constraints not satisfied}} + // expected-note@#SMEMOVC1REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} + StaticOVC::OtherViableCands2(); + // expected-error@-1 {{no matching function for call to 'OtherViableCands2'}} + // expected-note@#SMEMOVC2_ALT {{candidate function}} + // expected-note@#SMEMOVC2 {{candidate template ignored: constraints not satisfied}} + // expected-note@#SMEMOVC2REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} + StaticOVC::OtherViableCands3(); + // expected-error@-1 {{no matching function for call to 'OtherViableCands3'}} + // expected-note@#SMEMOVC3_ALT {{candidate function}} + // expected-note@#SMEMOVC3 {{candidate template ignored: constraints not satisfied}} + // expected-note@#SMEMOVC3REQ{{because substituted constraint expression is ill-formed: constraint depends on a previously diagnosed expression}} +}