[clang] make concept normalization a SFINAE context (#197356)

It is incorrect to allow the substitution failures in concept
normalization to make the program ill-formed.

These can happen when comparing the equivalence of constraints for
redeclaration checking, and a failure here only means these declarations
are not equivalent.

For now, these diagnostics are simply discarded. It would be nice if
some day, as part of diagnostics for non-matching out-of-line
definitions, we would explain why matching failed for each candidate,
and then add these as notes explaining why the constraints were not
equivalent.
This commit is contained in:
Matheus Izvekov 2026-05-13 16:36:28 -03:00 committed by GitHub
parent c501f3747a
commit bb38a428e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 47 additions and 96 deletions

View file

@ -180,7 +180,6 @@ const char *getMainFileRange(const Diag &D, const SourceManager &SM,
case diag::note_checking_constraints_for_class_spec_id_here:
case diag::note_checking_constraints_for_function_here:
case diag::note_constraint_substitution_here:
case diag::note_constraint_normalization_here:
case diag::note_parameter_mapping_substitution_here:
R = N.Range;
return "in template";

View file

@ -593,6 +593,8 @@ Bug Fixes to C++ Support
- Fixed a crash when evaluating uninitialized GCC vector/ext_vector_type vectors in ``constexpr``. (#GH180044)
- Fixed a crash when `explicit(bool)` is used with an incomplete enumeration. (#GH183887)
- Fixed a crash on ``typeid`` of incomplete local types during template instantiation. (#GH63242), (#GH176397)
- Fixed spurious diagnostics produced when checking if constraints are equivalent for redeclarations,
which could make the program mistakenly ill-formed.
- Fixed a crash when an immediate-invoked ``consteval`` lambda is used as an invalid initializer. (#GH185270)
- Fixed an assertion failure when using a global destructor with a target with a non-default program address space. (#GH186484)

View file

@ -6027,8 +6027,6 @@ def note_checking_constraints_for_function_here : Note<
"while checking constraint satisfaction for function '%0' required here">;
def note_constraint_substitution_here : Note<
"while substituting template arguments into constraint expression here">;
def note_constraint_normalization_here : Note<
"while calculating associated constraint of template %0 here">;
def note_parameter_mapping_substitution_here : Note<
"while substituting into concept arguments here; substitution failures not "
"allowed in concept arguments">;

View file

@ -13273,9 +13273,6 @@ public:
// We are substituting template arguments into a constraint expression.
ConstraintSubstitution,
// We are normalizing a constraint expression.
ConstraintNormalization,
// Instantiating a Requires Expression parameter clause.
RequirementParameterInstantiation,
@ -13494,12 +13491,6 @@ public:
ConstraintSubstitution, NamedDecl *Template,
SourceRange InstantiationRange);
struct ConstraintNormalization {};
/// \brief Note that we are normalizing a constraint expression.
InstantiatingTemplate(Sema &SemaRef, SourceLocation PointOfInstantiation,
ConstraintNormalization, NamedDecl *Template,
SourceRange InstantiationRange);
struct ParameterMappingSubstitution {};
/// \brief Note that we are subtituting into the parameter mapping of an
/// atomic constraint during constraint normalization.

View file

@ -450,8 +450,6 @@ private:
return "ConstraintsCheck";
case CodeSynthesisContext::ConstraintSubstitution:
return "ConstraintSubstitution";
case CodeSynthesisContext::ConstraintNormalization:
return "ConstraintNormalization";
case CodeSynthesisContext::RequirementParameterInstantiation:
return "RequirementParameterInstantiation";
case CodeSynthesisContext::ParameterMappingSubstitution:

View file

@ -1316,7 +1316,7 @@ static ExprResult
SubstituteConceptsInConstraintExpression(Sema &S, const NamedDecl *D,
const ConceptSpecializationExpr *CSE,
UnsignedOrNone SubstIndex) {
Sema::SFINAETrap Trap(S);
// [C++2c] [temp.constr.normal]
// Otherwise, to form CE, any non-dependent concept template argument Ai
// is substituted into the constraint-expression of C.
@ -1515,14 +1515,6 @@ static const Expr *SubstituteConstraintExpressionWithoutSatisfaction(
if (MLTAL.getNumSubstitutedLevels() == 0)
return ConstrExpr;
Sema::NonSFINAEContext _(S);
Sema::InstantiatingTemplate Inst(
S, DeclInfo.getLocation(),
Sema::InstantiatingTemplate::ConstraintNormalization{},
const_cast<NamedDecl *>(DeclInfo.getDecl()), SourceRange{});
if (Inst.isInvalid())
return nullptr;
// Set up a dummy 'instantiation' scope in the case of reference to function
// parameters that the surrounding function hasn't been instantiated yet. Note
// this may happen while we're comparing two templates' constraint
@ -1604,6 +1596,7 @@ bool Sema::AreConstraintExpressionsEqual(const NamedDecl *Old,
// C++ [temp.constr.decl]p4
if (Old && !New.isInvalid() && !New.ContainsDecl(Old) &&
Old->getLexicalDeclContext() != New.getLexicalDeclContext()) {
Sema::SFINAETrap _(*this);
if (const Expr *SubstConstr =
SubstituteConstraintExpressionWithoutSatisfaction(*this, Old,
OldConstr))
@ -2406,51 +2399,36 @@ NormalizedConstraint *NormalizedConstraint::fromConstraintExpr(
return CompoundConstraint::Create(
S.Context, LHS, BO.isAnd() ? CCK_Conjunction : CCK_Disjunction, RHS);
} else if (auto *CSE = dyn_cast<const ConceptSpecializationExpr>(E)) {
}
if (auto *CSE = dyn_cast<const ConceptSpecializationExpr>(E)) {
// C++ [temp.constr.normal]p1.1
// [...]
// The normal form of an id-expression of the form C<A1, A2, ..., AN>,
// where C names a concept, is the normal form of the
// constraint-expression of C, after substituting A1, A2, ..., AN for C’s
// respective template parameters in the parameter mappings in each atomic
// constraint. If any such substitution results in an invalid type or
// expression, the program is ill-formed; no diagnostic is required.
// [...]
NormalizedConstraint *SubNF;
{
Sema::NonSFINAEContext _(S);
Sema::InstantiatingTemplate Inst(
S, CSE->getExprLoc(),
Sema::InstantiatingTemplate::ConstraintNormalization{},
// FIXME: improve const-correctness of InstantiatingTemplate
const_cast<NamedDecl *>(D), CSE->getSourceRange());
if (Inst.isInvalid())
return nullptr;
// C++ [temp.constr.normal]p1.1
// [...]
// The normal form of an id-expression of the form C<A1, A2, ..., AN>,
// where C names a concept, is the normal form of the
// constraint-expression of C, after substituting A1, A2, ..., AN for C’s
// respective template parameters in the parameter mappings in each atomic
// constraint. If any such substitution results in an invalid type or
// expression, the program is ill-formed; no diagnostic is required.
// [...]
// Use canonical declarations to merge ConceptDecls across
// different modules.
ConceptDecl *CD = CSE->getNamedConcept()->getCanonicalDecl();
ExprResult Res =
SubstituteConceptsInConstraintExpression(S, D, CSE, SubstIndex);
if (!Res.isUsable())
return nullptr;
if (ExprResult Res =
SubstituteConceptsInConstraintExpression(S, D, CSE, SubstIndex);
Res.isUsable())
// Use canonical declarations to merge ConceptDecls across different
// modules.
SubNF = NormalizedConstraint::fromAssociatedConstraints(
S, CD, AssociatedConstraint(Res.get(), SubstIndex));
if (!SubNF)
return nullptr;
}
S, CSE->getNamedConcept()->getCanonicalDecl(),
AssociatedConstraint(Res.get(), SubstIndex));
else
return nullptr;
return ConceptIdConstraint::Create(S.getASTContext(),
CSE->getConceptReference(), SubNF, D,
CSE, SubstIndex);
} else if (auto *FE = dyn_cast<const CXXFoldExpr>(E);
FE && S.getLangOpts().CPlusPlus26 &&
(FE->getOperator() == BinaryOperatorKind::BO_LAnd ||
FE->getOperator() == BinaryOperatorKind::BO_LOr)) {
}
if (auto *FE = dyn_cast<const CXXFoldExpr>(E);
FE && S.getLangOpts().CPlusPlus26 &&
(FE->getOperator() == BinaryOperatorKind::BO_LAnd ||
FE->getOperator() == BinaryOperatorKind::BO_LOr)) {
// Normalize fold expressions in C++26.

View file

@ -583,7 +583,6 @@ bool Sema::CodeSynthesisContext::isInstantiationRecord() const {
case ExceptionSpecEvaluation:
case ConstraintSubstitution:
case ParameterMappingSubstitution:
case ConstraintNormalization:
case RewritingOperatorAsSpaceship:
case InitializingStructuredBinding:
case MarkingClassDllexported:
@ -793,14 +792,6 @@ Sema::InstantiatingTemplate::InstantiatingTemplate(
SemaRef, CodeSynthesisContext::ConstraintSubstitution,
PointOfInstantiation, InstantiationRange, Template, nullptr, {}) {}
Sema::InstantiatingTemplate::InstantiatingTemplate(
Sema &SemaRef, SourceLocation PointOfInstantiation,
ConstraintNormalization, NamedDecl *Template,
SourceRange InstantiationRange)
: InstantiatingTemplate(
SemaRef, CodeSynthesisContext::ConstraintNormalization,
PointOfInstantiation, InstantiationRange, Template) {}
Sema::InstantiatingTemplate::InstantiatingTemplate(
Sema &SemaRef, SourceLocation PointOfInstantiation,
ParameterMappingSubstitution, NamedDecl *Template,
@ -1245,12 +1236,6 @@ void Sema::PrintInstantiationStack(InstantiationContextDiagFuncRef DiagFunc) {
PDiag(diag::note_constraint_substitution_here)
<< Active->InstantiationRange);
break;
case CodeSynthesisContext::ConstraintNormalization:
DiagFunc(Active->PointOfInstantiation,
PDiag(diag::note_constraint_normalization_here)
<< cast<NamedDecl>(Active->Entity)
<< Active->InstantiationRange);
break;
case CodeSynthesisContext::ParameterMappingSubstitution:
DiagFunc(Active->PointOfInstantiation,
PDiag(diag::note_parameter_mapping_substitution_here)
@ -2862,7 +2847,7 @@ TypeSourceInfo *Sema::SubstType(TypeSourceInfo *T,
SourceLocation Loc,
DeclarationName Entity,
bool AllowDeducedTST) {
assert(!CodeSynthesisContexts.empty() &&
assert((!CodeSynthesisContexts.empty() || isSFINAEContext()) &&
"Cannot perform an instantiation without some context on the "
"instantiation stack");
@ -2879,7 +2864,7 @@ TypeSourceInfo *Sema::SubstType(TypeLoc TL,
const MultiLevelTemplateArgumentList &Args,
SourceLocation Loc,
DeclarationName Entity) {
assert(!CodeSynthesisContexts.empty() &&
assert((!CodeSynthesisContexts.empty() || isSFINAEContext()) &&
"Cannot perform an instantiation without some context on the "
"instantiation stack");
@ -2910,7 +2895,7 @@ QualType Sema::SubstType(QualType T,
const MultiLevelTemplateArgumentList &TemplateArgs,
SourceLocation Loc, DeclarationName Entity,
bool *IsIncompleteSubstitution) {
assert(!CodeSynthesisContexts.empty() &&
assert((!CodeSynthesisContexts.empty() || isSFINAEContext()) &&
"Cannot perform an instantiation without some context on the "
"instantiation stack");
@ -2957,7 +2942,7 @@ TypeSourceInfo *Sema::SubstFunctionDeclType(TypeSourceInfo *T,
CXXRecordDecl *ThisContext,
Qualifiers ThisTypeQuals,
bool EvaluateConstraints) {
assert(!CodeSynthesisContexts.empty() &&
assert((!CodeSynthesisContexts.empty() || isSFINAEContext()) &&
"Cannot perform an instantiation without some context on the "
"instantiation stack");
@ -3274,7 +3259,7 @@ bool Sema::SubstParmTypes(
SmallVectorImpl<QualType> &ParamTypes,
SmallVectorImpl<ParmVarDecl *> *OutParams,
ExtParameterInfoBuilder &ParamInfos) {
assert(!CodeSynthesisContexts.empty() &&
assert((!CodeSynthesisContexts.empty() || isSFINAEContext()) &&
"Cannot perform an instantiation without some context on the "
"instantiation stack");
@ -4502,6 +4487,8 @@ ExprResult Sema::SubstConstraintExprWithoutSatisfaction(
ExprResult Sema::SubstConceptTemplateArguments(
const ConceptSpecializationExpr *CSE, const Expr *ConstraintExpr,
const MultiLevelTemplateArgumentList &MLTAL) {
assert(isSFINAEContext());
TemplateInstantiator Instantiator(*this, MLTAL, SourceLocation(),
DeclarationName());
const ASTTemplateArgumentListInfo *ArgsAsWritten =
@ -4509,16 +4496,6 @@ ExprResult Sema::SubstConceptTemplateArguments(
TemplateArgumentListInfo SubstArgs(ArgsAsWritten->getLAngleLoc(),
ArgsAsWritten->getRAngleLoc());
NonSFINAEContext _(*this);
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() +

View file

@ -228,9 +228,7 @@ namespace constrained_member_sfinae {
template<int N> struct S {
template<class T>
static constexpr int constrained_method() requires (sizeof(int[N * 1073741824 + 4]) == 16) { // expected-warning {{variable length arrays in C++ are a Clang extension}} \
expected-note {{value 4294967296 is outside the range of representable values of type 'int'}} \
expected-note {{while calculating associated constraint of template 'constrained_method' here}}
static constexpr int constrained_method() requires (sizeof(int[N * 1073741824 + 4]) == 16) {
return CONSTRAINED_METHOD_1;
}
@ -897,3 +895,13 @@ template<typename Q> requires is_valid<Q>
class Inner2 {};
}
namespace NoDiagnosticsInNormalization {
template<class T> struct S {
template<class> void f() requires (T::v > 0) {}
template<class> void f() requires (sizeof(T) > 1);
};
template<>
template<class>
void S<int>::f() requires (sizeof(int) > 1) {}
} // namespace NoDiagnosticsInNormalization