[libcxx-commits] [clang] [libcxx] [Clang] Normalize constraints before checking for satisfaction (PR #141776)
Matheus Izvekov via libcxx-commits
libcxx-commits at lists.llvm.org
Sun Sep 14 17:38:28 PDT 2025
================
@@ -1379,216 +1890,327 @@ static void diagnoseWellFormedUnsatisfiedConstraintExpr(Sema &S,
S.DiagnoseTypeTraitDetails(SubstExpr);
}
-template <typename SubstitutionDiagnostic>
static void diagnoseUnsatisfiedConstraintExpr(
- Sema &S, const llvm::PointerUnion<Expr *, SubstitutionDiagnostic *> &Record,
- bool First = true) {
- if (auto *Diag = Record.template dyn_cast<SubstitutionDiagnostic *>()) {
- S.Diag(Diag->first, diag::note_substituted_constraint_expr_is_ill_formed)
- << Diag->second;
+ Sema &S, const UnsatisfiedConstraintRecord &Record, SourceLocation Loc,
+ bool First, concepts::NestedRequirement *Req) {
+ if (auto *Diag = Record.template dyn_cast<
+ ConstraintSatisfaction::SubstitutionDiagnostic *>()) {
+ if (Req)
+ S.Diag(Diag->first, diag::note_nested_requirement_substitution_error)
+ << (int)First << Req->getInvalidConstraintEntity() << Diag->second;
+ else
+ S.Diag(Diag->first, diag::note_substituted_constraint_expr_is_ill_formed)
+ << Diag->second;
return;
}
-
- diagnoseWellFormedUnsatisfiedConstraintExpr(S, cast<Expr *>(Record), First);
+ if (const auto *Concept = dyn_cast<const ConceptReference *>(Record)) {
+ if (Loc.isInvalid())
+ Loc = Concept->getBeginLoc();
+ diagnoseUnsatisfiedConceptIdExpr(S, Concept, Loc, First);
+ return;
+ }
+ diagnoseWellFormedUnsatisfiedConstraintExpr(
+ S, cast<const class Expr *>(Record), First);
}
-void
-Sema::DiagnoseUnsatisfiedConstraint(const ConstraintSatisfaction& Satisfaction,
- bool First) {
+void Sema::DiagnoseUnsatisfiedConstraint(
+ const ConstraintSatisfaction &Satisfaction, SourceLocation Loc,
+ bool First) {
+
assert(!Satisfaction.IsSatisfied &&
"Attempted to diagnose a satisfied constraint");
- for (auto &Record : Satisfaction.Details) {
- diagnoseUnsatisfiedConstraintExpr(*this, Record, First);
- First = false;
- }
+ ::DiagnoseUnsatisfiedConstraint(*this, Satisfaction.Details, Loc, First);
}
void Sema::DiagnoseUnsatisfiedConstraint(
- const ASTConstraintSatisfaction &Satisfaction,
- bool First) {
+ const ConceptSpecializationExpr *ConstraintExpr, bool First) {
+
+ const ASTConstraintSatisfaction &Satisfaction =
+ ConstraintExpr->getSatisfaction();
+
assert(!Satisfaction.IsSatisfied &&
"Attempted to diagnose a satisfied constraint");
- for (auto &Record : Satisfaction) {
- diagnoseUnsatisfiedConstraintExpr(*this, Record, First);
- First = false;
- }
+
+ ::DiagnoseUnsatisfiedConstraint(*this, Satisfaction.records(),
+ ConstraintExpr->getBeginLoc(), First);
}
-const NormalizedConstraint *Sema::getNormalizedAssociatedConstraints(
- const NamedDecl *ConstrainedDecl,
- ArrayRef<AssociatedConstraint> AssociatedConstraints) {
- // In case the ConstrainedDecl comes from modules, it is necessary to use
- // the canonical decl to avoid different atomic constraints with the 'same'
- // declarations.
- ConstrainedDecl = cast<NamedDecl>(ConstrainedDecl->getCanonicalDecl());
+namespace {
- auto CacheEntry = NormalizationCache.find(ConstrainedDecl);
- if (CacheEntry == NormalizationCache.end()) {
- auto Normalized = NormalizedConstraint::fromAssociatedConstraints(
- *this, ConstrainedDecl, AssociatedConstraints);
- CacheEntry =
- NormalizationCache
- .try_emplace(ConstrainedDecl,
- Normalized
- ? new (Context) NormalizedConstraint(
- std::move(*Normalized))
- : nullptr)
- .first;
- }
- return CacheEntry->second;
-}
+class SubstituteParameterMappings {
+ Sema &SemaRef;
-const NormalizedConstraint *clang::getNormalizedAssociatedConstraints(
- Sema &S, const NamedDecl *ConstrainedDecl,
- ArrayRef<AssociatedConstraint> AssociatedConstraints) {
- return S.getNormalizedAssociatedConstraints(ConstrainedDecl,
- AssociatedConstraints);
-}
+ const MultiLevelTemplateArgumentList *MLTAL;
+ const ASTTemplateArgumentListInfo *ArgsAsWritten;
-static bool
-substituteParameterMappings(Sema &S, NormalizedConstraint &N,
- ConceptDecl *Concept,
- const MultiLevelTemplateArgumentList &MLTAL,
- const ASTTemplateArgumentListInfo *ArgsAsWritten) {
+ bool InFoldExpr;
- if (N.isCompound()) {
- if (substituteParameterMappings(S, N.getLHS(), Concept, MLTAL,
- ArgsAsWritten))
- return true;
- return substituteParameterMappings(S, N.getRHS(), Concept, MLTAL,
- ArgsAsWritten);
- }
+ SubstituteParameterMappings(Sema &SemaRef,
+ const MultiLevelTemplateArgumentList *MLTAL,
+ const ASTTemplateArgumentListInfo *ArgsAsWritten,
+ bool InFoldExpr)
+ : SemaRef(SemaRef), MLTAL(MLTAL), ArgsAsWritten(ArgsAsWritten),
+ InFoldExpr(InFoldExpr) {}
+
+ void buildParameterMapping(NormalizedConstraintWithParamMapping &N);
+
+ bool substitute(NormalizedConstraintWithParamMapping &N);
+
+ bool substitute(ConceptIdConstraint &CC);
+
+public:
+ SubstituteParameterMappings(Sema &SemaRef, bool InFoldExpr = false)
+ : SemaRef(SemaRef), MLTAL(nullptr), ArgsAsWritten(nullptr),
+ InFoldExpr(InFoldExpr) {}
+
+ bool substitute(NormalizedConstraint &N);
+};
- if (N.isFoldExpanded()) {
- Sema::ArgPackSubstIndexRAII _(S, std::nullopt);
- return substituteParameterMappings(
- S, N.getFoldExpandedConstraint()->Constraint, Concept, MLTAL,
- ArgsAsWritten);
+void SubstituteParameterMappings::buildParameterMapping(
+ NormalizedConstraintWithParamMapping &N) {
+ TemplateParameterList *TemplateParams =
+ cast<TemplateDecl>(N.getConstraintDecl())->getTemplateParameters();
+
+ llvm::SmallBitVector OccurringIndices(TemplateParams->size());
+
+ if (N.getKind() == NormalizedConstraint::ConstraintKind::Atomic) {
+ SemaRef.MarkUsedTemplateParameters(
+ static_cast<AtomicConstraint &>(N).getConstraintExpr(),
+ /*OnlyDeduced=*/false,
+ /*Depth=*/0, OccurringIndices);
+ } else if (N.getKind() ==
+ NormalizedConstraint::ConstraintKind::FoldExpanded) {
+ SemaRef.MarkUsedTemplateParameters(
+ static_cast<FoldExpandedConstraint &>(N).getPattern(),
+ /*OnlyDeduced=*/false,
+ /*Depth=*/0, OccurringIndices);
+ } else if (N.getKind() == NormalizedConstraint::ConstraintKind::ConceptId) {
+ auto *Args = static_cast<ConceptIdConstraint &>(N)
+ .getConceptId()
+ ->getTemplateArgsAsWritten();
+ if (Args)
+ SemaRef.MarkUsedTemplateParameters(Args->arguments(),
+ /*Depth=*/0, OccurringIndices);
+ }
+ TemplateArgumentLoc *TempArgs =
+ new (SemaRef.Context) TemplateArgumentLoc[OccurringIndices.count()];
+ llvm::SmallVector<NamedDecl *> UsedParams;
+ for (unsigned I = 0, J = 0, C = TemplateParams->size(); I != C; ++I) {
+ SourceLocation Loc = ArgsAsWritten->NumTemplateArgs > I
+ ? ArgsAsWritten->arguments()[I].getLocation()
+ : SourceLocation();
+ // FIXME: Investigate why we couldn't always preserve the SourceLoc. We
+ // can't assert Loc.isValid() now.
+ if (OccurringIndices[I]) {
+ NamedDecl *Param = TemplateParams->begin()[I];
+ new (&(TempArgs)[J]) TemplateArgumentLoc(
+ SemaRef.getIdentityTemplateArgumentLoc(Param, Loc));
+ UsedParams.push_back(Param);
+ J++;
+ }
}
+ auto *UsedList = TemplateParameterList::Create(
+ SemaRef.Context, TemplateParams->getTemplateLoc(),
+ TemplateParams->getLAngleLoc(), UsedParams,
+ /*RAngleLoc=*/SourceLocation(),
+ /*RequiresClause=*/nullptr);
+ N.updateParameterMapping(
+ OccurringIndices,
+ MutableArrayRef<TemplateArgumentLoc>{TempArgs, OccurringIndices.count()},
+ UsedList);
+}
- TemplateParameterList *TemplateParams = Concept->getTemplateParameters();
+bool SubstituteParameterMappings::substitute(
+ NormalizedConstraintWithParamMapping &N) {
+ if (!N.hasParameterMapping())
+ buildParameterMapping(N);
- AtomicConstraint &Atomic = *N.getAtomicConstraint();
- TemplateArgumentListInfo SubstArgs;
- if (!Atomic.ParameterMapping) {
- llvm::SmallBitVector OccurringIndices(TemplateParams->size());
- S.MarkUsedTemplateParameters(Atomic.ConstraintExpr, /*OnlyDeduced=*/false,
- /*Depth=*/0, OccurringIndices);
- TemplateArgumentLoc *TempArgs =
- new (S.Context) TemplateArgumentLoc[OccurringIndices.count()];
- for (unsigned I = 0, J = 0, C = TemplateParams->size(); I != C; ++I)
- if (OccurringIndices[I])
- new (&(TempArgs)[J++])
- TemplateArgumentLoc(S.getIdentityTemplateArgumentLoc(
- TemplateParams->begin()[I],
- // Here we assume we do not support things like
- // template<typename A, typename B>
- // concept C = ...;
- //
- // template<typename... Ts> requires C<Ts...>
- // struct S { };
- // The above currently yields a diagnostic.
- // We still might have default arguments for concept parameters.
- ArgsAsWritten->NumTemplateArgs > I
- ? ArgsAsWritten->arguments()[I].getLocation()
- : SourceLocation()));
- Atomic.ParameterMapping.emplace(TempArgs, OccurringIndices.count());
- }
- SourceLocation InstLocBegin =
- ArgsAsWritten->arguments().empty()
- ? ArgsAsWritten->getLAngleLoc()
- : ArgsAsWritten->arguments().front().getSourceRange().getBegin();
- SourceLocation InstLocEnd =
- ArgsAsWritten->arguments().empty()
- ? ArgsAsWritten->getRAngleLoc()
- : ArgsAsWritten->arguments().front().getSourceRange().getEnd();
+ SourceLocation InstLocBegin, InstLocEnd;
+ llvm::ArrayRef Arguments = ArgsAsWritten->arguments();
+ if (Arguments.empty()) {
+ InstLocBegin = ArgsAsWritten->getLAngleLoc();
+ InstLocEnd = ArgsAsWritten->getRAngleLoc();
+ } else {
+ auto SR = Arguments[0].getSourceRange();
+ InstLocBegin = SR.getBegin();
+ InstLocEnd = SR.getEnd();
+ }
Sema::InstantiatingTemplate Inst(
- S, InstLocBegin,
+ SemaRef, InstLocBegin,
Sema::InstantiatingTemplate::ParameterMappingSubstitution{},
- const_cast<NamedDecl *>(Atomic.ConstraintDecl),
+ const_cast<NamedDecl *>(N.getConstraintDecl()),
{InstLocBegin, InstLocEnd});
if (Inst.isInvalid())
return true;
- if (S.SubstTemplateArguments(*Atomic.ParameterMapping, MLTAL, SubstArgs))
+
+ // TransformTemplateArguments is unable to preserve the source location of a
+ // pack. The SourceLocation is necessary for the instantiation location.
+ // FIXME: The BaseLoc will be used as the location of the pack expansion,
+ // which is wrong.
+ TemplateArgumentListInfo SubstArgs;
+ if (SemaRef.SubstTemplateArgumentsInParameterMapping(
+ N.getParameterMapping(), N.getBeginLoc(), *MLTAL, SubstArgs,
+ /*BuildPackExpansionTypes=*/!InFoldExpr))
+ return true;
+ Sema::CheckTemplateArgumentInfo CTAI;
+ auto *TD =
+ const_cast<TemplateDecl *>(cast<TemplateDecl>(N.getConstraintDecl()));
+ if (SemaRef.CheckTemplateArgumentList(TD, N.getUsedTemplateParamList(),
+ TD->getLocation(), SubstArgs,
+ /*DefaultArguments=*/{},
+ /*PartialTemplateArgs=*/false, CTAI))
return true;
TemplateArgumentLoc *TempArgs =
- new (S.Context) TemplateArgumentLoc[SubstArgs.size()];
- std::copy(SubstArgs.arguments().begin(), SubstArgs.arguments().end(),
- TempArgs);
- Atomic.ParameterMapping.emplace(TempArgs, SubstArgs.size());
+ new (SemaRef.Context) TemplateArgumentLoc[CTAI.SugaredConverted.size()];
+
+ for (unsigned I = 0; I < CTAI.SugaredConverted.size(); ++I) {
+ SourceLocation Loc;
+ // If this is an empty pack, we have no corresponding SubstArgs.
+ if (I < SubstArgs.size())
+ Loc = SubstArgs.arguments()[I].getLocation();
+
+ TempArgs[I] = SemaRef.getTrivialTemplateArgumentLoc(
+ CTAI.SugaredConverted[I], QualType(), Loc);
+ }
+
+ MutableArrayRef<TemplateArgumentLoc> Mapping(TempArgs,
+ CTAI.SugaredConverted.size());
+ N.updateParameterMapping(N.mappingOccurenceList(), Mapping,
+ N.getUsedTemplateParamList());
return false;
}
-static bool substituteParameterMappings(Sema &S, NormalizedConstraint &N,
- const ConceptSpecializationExpr *CSE) {
- MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
- CSE->getNamedConcept(), CSE->getNamedConcept()->getLexicalDeclContext(),
- /*Final=*/false, CSE->getTemplateArguments(),
- /*RelativeToPrimary=*/true,
- /*Pattern=*/nullptr,
- /*ForConstraintInstantiation=*/true);
+bool SubstituteParameterMappings::substitute(ConceptIdConstraint &CC) {
+ assert(CC.getConstraintDecl() && MLTAL && ArgsAsWritten);
- return substituteParameterMappings(S, N, CSE->getNamedConcept(), MLTAL,
- CSE->getTemplateArgsAsWritten());
-}
+ if (substitute(static_cast<NormalizedConstraintWithParamMapping &>(CC)))
+ return true;
-NormalizedConstraint::NormalizedConstraint(ASTContext &C,
- NormalizedConstraint LHS,
- NormalizedConstraint RHS,
- CompoundConstraintKind Kind)
- : Constraint{CompoundConstraint{
- new(C) NormalizedConstraintPair{std::move(LHS), std::move(RHS)},
- Kind}} {}
-
-NormalizedConstraint::NormalizedConstraint(ASTContext &C,
- const NormalizedConstraint &Other) {
- if (Other.isAtomic()) {
- Constraint = new (C) AtomicConstraint(*Other.getAtomicConstraint());
- } else if (Other.isFoldExpanded()) {
- Constraint = new (C) FoldExpandedConstraint(
- Other.getFoldExpandedConstraint()->Kind,
- NormalizedConstraint(C, Other.getFoldExpandedConstraint()->Constraint),
- Other.getFoldExpandedConstraint()->Pattern);
+ auto *CSE = CC.getConceptSpecializationExpr();
+ assert(CSE);
+ assert(!CC.getBeginLoc().isInvalid());
+
+ SourceLocation InstLocBegin, InstLocEnd;
+ if (llvm::ArrayRef Arguments = ArgsAsWritten->arguments();
+ Arguments.empty()) {
+ InstLocBegin = ArgsAsWritten->getLAngleLoc();
+ InstLocEnd = ArgsAsWritten->getRAngleLoc();
} else {
- Constraint = CompoundConstraint(
- new (C)
- NormalizedConstraintPair{NormalizedConstraint(C, Other.getLHS()),
- NormalizedConstraint(C, Other.getRHS())},
- Other.getCompoundKind());
+ auto SR = Arguments[0].getSourceRange();
+ InstLocBegin = SR.getBegin();
+ InstLocEnd = SR.getEnd();
}
-}
+ // This is useful for name lookup across modules; see Sema::getLookupModules.
+ Sema::InstantiatingTemplate Inst(
+ SemaRef, InstLocBegin,
+ Sema::InstantiatingTemplate::ParameterMappingSubstitution{},
+ const_cast<NamedDecl *>(CC.getConstraintDecl()),
+ {InstLocBegin, InstLocEnd});
+ if (Inst.isInvalid())
+ return true;
-NormalizedConstraint &NormalizedConstraint::getLHS() const {
- assert(isCompound() && "getLHS called on a non-compound constraint.");
- return cast<CompoundConstraint>(Constraint).getPointer()->LHS;
+ TemplateArgumentListInfo Out;
+ // TransformTemplateArguments is unable to preserve the source location of a
+ // pack. The SourceLocation is necessary for the instantiation location.
+ // FIXME: The BaseLoc will be used as the location of the pack expansion,
+ // which is wrong.
+ const ASTTemplateArgumentListInfo *ArgsAsWritten =
+ CSE->getTemplateArgsAsWritten();
+ if (SemaRef.SubstTemplateArgumentsInParameterMapping(
+ ArgsAsWritten->arguments(), CC.getBeginLoc(), *MLTAL, Out,
+ /*BuildPackExpansionTypes=*/!InFoldExpr))
+ return true;
+ Sema::CheckTemplateArgumentInfo CTAI;
+ if (SemaRef.CheckTemplateArgumentList(CSE->getNamedConcept(),
+ CSE->getConceptNameInfo().getLoc(), Out,
+ /*DefaultArgs=*/{},
+ /*PartialTemplateArgs=*/false, CTAI,
+ /*UpdateArgsWithConversions=*/false))
+ return true;
+ auto TemplateArgs = *MLTAL;
+ TemplateArgs.replaceOutermostTemplateArguments(
+ TemplateArgs.getAssociatedDecl(0).first, CTAI.SugaredConverted);
+ return SubstituteParameterMappings(SemaRef, &TemplateArgs, ArgsAsWritten,
+ InFoldExpr)
+ .substitute(CC.getNormalizedConstraint());
}
-NormalizedConstraint &NormalizedConstraint::getRHS() const {
- assert(isCompound() && "getRHS called on a non-compound constraint.");
- return cast<CompoundConstraint>(Constraint).getPointer()->RHS;
+bool SubstituteParameterMappings::substitute(NormalizedConstraint &N) {
+ switch (N.getKind()) {
+ case NormalizedConstraint::ConstraintKind::Atomic: {
+ if (!MLTAL) {
+ assert(!ArgsAsWritten);
+ return false;
+ }
+ return substitute(static_cast<NormalizedConstraintWithParamMapping &>(N));
+ }
+ case NormalizedConstraint::ConstraintKind::FoldExpanded: {
+ auto &FE = static_cast<FoldExpandedConstraint &>(N);
+ if (!MLTAL) {
+ llvm::SaveAndRestore _1(InFoldExpr, true);
+ assert(!ArgsAsWritten);
+ return substitute(FE.getNormalizedPattern());
+ }
+ Sema::ArgPackSubstIndexRAII _(SemaRef, std::nullopt);
+ substitute(static_cast<NormalizedConstraintWithParamMapping &>(FE));
+ return SubstituteParameterMappings(SemaRef, /*InFoldExpr=*/true)
+ .substitute(FE.getNormalizedPattern());
+ }
+ case NormalizedConstraint::ConstraintKind::ConceptId: {
+ auto &CC = static_cast<ConceptIdConstraint &>(N);
+ if (MLTAL) {
+ assert(ArgsAsWritten);
+ return substitute(CC);
+ }
+ assert(!ArgsAsWritten);
+ const ConceptSpecializationExpr *CSE = CC.getConceptSpecializationExpr();
+ ConceptDecl *Concept = CSE->getNamedConcept();
+ MultiLevelTemplateArgumentList MLTAL = SemaRef.getTemplateInstantiationArgs(
+ Concept, Concept->getLexicalDeclContext(),
+ /*Final=*/true, CSE->getTemplateArguments(),
+ /*RelativeToPrimary=*/true,
+ /*Pattern=*/nullptr,
+ /*ForConstraintInstantiation=*/true);
+
+ // Don't build Subst* nodes to model lambda expressions.
+ // The transform of Subst* is oblivious to the lambda type.
+ MLTAL.setKind(TemplateSubstitutionKind::Rewrite);
----------------
mizvekov wrote:
@zyn0217 moving the discussion here.
I think this is incorrect, this substitution is not a rewrite, as this can basically transform a reference to template parameter into an arbitrary template argument of the corresponding kind.
A rewrite should transform a template parameter into another template parameter of the same kind, otherwise the source location kinds will be incompatible.
For example, in a non-rewrite substitution, the source location for a template parameter is compatible with the source location for its corresponding Subst* AST node kind. This makes sense, as the Subst node is a stand-in for the template parameter, and whatever it was substituted for is not part of the source location data, as it was not written.
For a rewrite, if we stick to the rule of substituting template parameters for template parameters, the source locations obviously stay compatible as they are of the same kind.
But if you allow arbitrary substitutions outside of those rules, then that doesn't work anymore.
We should add some asserts to verify this isn't happening anywhere else and doesn't happen again.
If you add back the assert in the TemplateArgumentLoc constructor, and remove this line, that should make it work.
In fact, the otherwise failing test case can be simplified to just:
```C++
template <template <class> class TT> concept C1 = sizeof(TT<int>) >= 0;
template <class T> concept C2 = C1<T::template X>;
static_assert(C2<int>);
```
I am not sure why this line is needed in the first place, that looks like a workaround.
https://github.com/llvm/llvm-project/pull/141776
More information about the libcxx-commits
mailing list