[libcxx-commits] [clang] [libcxx] [Clang][WIP] Normalize constraints before checking for satisfaction (PR #141776)
Corentin Jabot via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Sep 10 05:38:59 PDT 2025
================
@@ -543,30 +672,330 @@ static ExprResult calculateConstraintSatisfaction(
return ExprError();
}
- assert(EvalResult.Val.isInt() &&
- "evaluating bool expression didn't produce int");
- Satisfaction.IsSatisfied = EvalResult.Val.getInt().getBoolValue();
- if (!Satisfaction.IsSatisfied)
- Satisfaction.Details.emplace_back(SubstitutedAtomicExpr.get());
+ assert(EvalResult.Val.isInt() &&
+ "evaluating bool expression didn't produce int");
+ Satisfaction.IsSatisfied = EvalResult.Val.getInt().getBoolValue();
+ if (!Satisfaction.IsSatisfied)
+ Satisfaction.Details.emplace_back(SubstitutedAtomicExpr.get());
+
+ return SubstitutedAtomicExpr;
+}
+
+ExprResult CalculateConstraintSatisfaction::Calculate(
+ const AtomicConstraint &Constraint,
+ const MultiLevelTemplateArgumentList &MLTAL) {
+
+ unsigned Size = Satisfaction.Details.size();
+ llvm::FoldingSetNodeID ID;
+ UnsignedOrNone OuterPackSubstIndex =
+ Constraint.getPackSubstitutionIndex()
+ ? Constraint.getPackSubstitutionIndex()
+ : PackSubstitutionIndex;
+ // Constraint.getConstraintExpr()->Profile(ID, S.Context, /*Canonical=*/true,
+ // /*ProfileLambdaExpr=*/true);
+ auto *Previous = Constraint.getConstraintExpr();
+ ID.AddPointer(Constraint.getConstraintExpr());
+ ID.AddInteger(OuterPackSubstIndex.toInternalRepresentation());
+ ID.AddBoolean(Constraint.hasParameterMapping());
+ HashParameterMapping(S, MLTAL, ID, OuterPackSubstIndex)
+ .VisitConstraint(Constraint);
+
+ if (auto Iter = S.ConceptIdSatisfactionCache.find(ID);
+ Iter != S.ConceptIdSatisfactionCache.end()) {
+
+ auto &Cached = Iter->second.Satisfaction;
+ Satisfaction.ContainsErrors = Cached.ContainsErrors;
+ Satisfaction.IsSatisfied = Cached.IsSatisfied;
+ Satisfaction.Details.insert(Satisfaction.Details.begin() + Size,
+ Cached.Details.begin(), Cached.Details.end());
+ return Iter->second.SubstExpr;
+ }
+
+ ExprResult E = CalculateSlow(Constraint, MLTAL);
+
+ assert(Constraint.getConstraintExpr() == Previous);
+
+ CachedConceptIdConstraint Cache;
+ Cache.Satisfaction.ContainsErrors = Satisfaction.ContainsErrors;
+ Cache.Satisfaction.IsSatisfied = Satisfaction.IsSatisfied;
+ std::copy(Satisfaction.Details.begin() + Size, Satisfaction.Details.end(),
+ std::back_inserter(Cache.Satisfaction.Details));
+ Cache.SubstExpr = E;
+ Cache.E = Constraint.getConstraintExpr();
+ S.ConceptIdSatisfactionCache.insert({ID, std::move(Cache)});
+
+ return E;
+}
+
+UnsignedOrNone
+CalculateConstraintSatisfaction::EvaluateFoldExpandedConstraintSize(
+ const FoldExpandedConstraint &FE,
+ const MultiLevelTemplateArgumentList &MLTAL) {
+
+ // We should ignore errors in the presence of packs of different size.
+ Sema::SFINAETrap Trap(S);
+
+ Expr *Pattern = const_cast<Expr *>(FE.getPattern());
+
+ SmallVector<UnexpandedParameterPack, 2> Unexpanded;
+ S.collectUnexpandedParameterPacks(Pattern, Unexpanded);
+ assert(!Unexpanded.empty() && "Pack expansion without parameter packs?");
+ bool Expand = true;
+ bool RetainExpansion = false;
+ UnsignedOrNone NumExpansions(std::nullopt);
+ if (S.CheckParameterPacksForExpansion(
+ Pattern->getExprLoc(), Pattern->getSourceRange(), Unexpanded, MLTAL,
+ /*FailOnPackProducingTemplates=*/false, Expand, RetainExpansion,
+ NumExpansions) ||
+ !Expand || RetainExpansion)
+ return std::nullopt;
+
+ if (NumExpansions && S.getLangOpts().BracketDepth < *NumExpansions) {
+ S.Diag(Pattern->getExprLoc(),
+ clang::diag::err_fold_expression_limit_exceeded)
+ << *NumExpansions << S.getLangOpts().BracketDepth
+ << Pattern->getSourceRange();
+ S.Diag(Pattern->getExprLoc(), diag::note_bracket_depth);
+ return std::nullopt;
+ }
+ return NumExpansions;
+}
+
+ExprResult CalculateConstraintSatisfaction::Calculate(
+ const FoldExpandedConstraint &Constraint,
+ const MultiLevelTemplateArgumentList &MLTAL) {
+ bool Conjunction = Constraint.getFoldOperator() ==
+ FoldExpandedConstraint::FoldOperatorKind::And;
+ unsigned EffectiveDetailEndIndex = Satisfaction.Details.size();
+
+ llvm::SmallVector<TemplateArgument> SubstitutedOuterMost;
+ // FIXME: Is PackSubstitutionIndex correct?
+ llvm::SaveAndRestore _(PackSubstitutionIndex, S.ArgPackSubstIndex);
+ std::optional<MultiLevelTemplateArgumentList> SubstitutedArgs =
+ SubstitutionInTemplateArguments(
+ static_cast<const NormalizedConstraintWithParamMapping &>(Constraint),
+ MLTAL, SubstitutedOuterMost);
+ if (!SubstitutedArgs) {
+ Satisfaction.IsSatisfied = false;
+ return ExprError();
+ }
+
+ ExprResult Out;
+ UnsignedOrNone NumExpansions =
+ EvaluateFoldExpandedConstraintSize(Constraint, *SubstitutedArgs);
+ if (!NumExpansions)
+ return ExprEmpty();
+
+ if (*NumExpansions == 0) {
+ Satisfaction.IsSatisfied = Conjunction;
+ return ExprEmpty();
+ }
+
+ for (unsigned I = 0; I < *NumExpansions; I++) {
+ Sema::ArgPackSubstIndexRAII SubstIndex(S, I);
+ Satisfaction.IsSatisfied = false;
+ Satisfaction.ContainsErrors = false;
+ ExprResult Expr =
+ CalculateConstraintSatisfaction(S, Template, TemplateNameLoc,
+ UnsignedOrNone(I), Satisfaction)
+ .Calculate(Constraint.getNormalizedPattern(), *SubstitutedArgs);
+ if (Expr.isUsable()) {
+ if (Out.isUnset())
+ Out = Expr;
+ else
+ Out = BinaryOperator::Create(S.Context, Out.get(), Expr.get(),
+ Conjunction ? BinaryOperatorKind::BO_LAnd
+ : BinaryOperatorKind::BO_LOr,
+ S.Context.BoolTy, VK_PRValue, OK_Ordinary,
+ Constraint.getBeginLoc(),
+ FPOptionsOverride{});
+ } else {
+ assert(!Satisfaction.IsSatisfied);
+ }
+ if (!Conjunction && Satisfaction.IsSatisfied) {
+ Satisfaction.Details.erase(Satisfaction.Details.begin() +
+ EffectiveDetailEndIndex,
+ Satisfaction.Details.end());
+ break;
+ }
+ if (Satisfaction.IsSatisfied != Conjunction)
+ return Out;
+ }
+
+ return Out;
+}
+
+ExprResult CalculateConstraintSatisfaction::Calculate(
+ const ConceptIdConstraint &Constraint,
+ const MultiLevelTemplateArgumentList &MLTAL) {
+
+ std::optional<Sema::InstantiatingTemplate> InstTemplate;
+ InstTemplate.emplace(S, Constraint.getConceptId()->getBeginLoc(),
+ Sema::InstantiatingTemplate::ConstraintsCheck{},
+ Constraint.getConceptId()->getNamedConcept(),
+ MLTAL.getInnermost(), Constraint.getSourceRange());
+
+ unsigned Size = Satisfaction.Details.size();
+
+ ExprResult E = Calculate(Constraint.getNormalizedConstraint(), MLTAL);
+
+ if (!E.isUsable()) {
+ Satisfaction.Details.insert(Satisfaction.Details.begin() + Size,
+ Constraint.getConceptId());
+ return E;
+ }
+
+ if (Satisfaction.IsSatisfied)
+ return E;
+
+ llvm::SmallVector<TemplateArgument> SubstitutedOuterMost;
+ std::optional<MultiLevelTemplateArgumentList> SubstitutedArgs =
+ SubstitutionInTemplateArguments(Constraint, MLTAL, SubstitutedOuterMost);
+
+ if (!SubstitutedArgs) {
+ Satisfaction.IsSatisfied = false;
+ // FIXME: diagnostics?
+ return ExprError();
+ }
+
+ Sema::SFINAETrap Trap(S);
+ Sema::ArgPackSubstIndexRAII SubstIndex(
+ S, Constraint.getPackSubstitutionIndex()
+ ? Constraint.getPackSubstitutionIndex()
+ : PackSubstitutionIndex);
+
+ const ASTTemplateArgumentListInfo *Ori =
+ Constraint.getConceptId()->getTemplateArgsAsWritten();
+ TemplateDeductionInfo Info(TemplateNameLoc);
+ InstTemplate.emplace(
+ S, TemplateNameLoc, Sema::InstantiatingTemplate::ConstraintSubstitution{},
+ const_cast<NamedDecl *>(Template), Info, Constraint.getSourceRange());
+
+ TemplateArgumentListInfo OutArgs(Ori->LAngleLoc, Ori->RAngleLoc);
+ if (S.SubstTemplateArguments(Ori->arguments(), *SubstitutedArgs, OutArgs) ||
+ Trap.hasErrorOccurred()) {
+ Satisfaction.IsSatisfied = false;
+ if (!Trap.hasErrorOccurred())
+ return ExprError();
+
+ PartialDiagnosticAt SubstDiag{SourceLocation(),
+ PartialDiagnostic::NullDiagnostic()};
+ Info.takeSFINAEDiagnostic(SubstDiag);
+ // FIXME: Concepts: This is an unfortunate consequence of there
+ // being no serialization code for PartialDiagnostics and the fact
+ // that serializing them would likely take a lot more storage than
+ // just storing them as strings. We would still like, in the
+ // future, to serialize the proper PartialDiagnostic as serializing
+ // it as a string defeats the purpose of the diagnostic mechanism.
+ SmallString<128> DiagString;
+ DiagString = ": ";
+ SubstDiag.second.EmitToString(S.getDiagnostics(), DiagString);
+ unsigned MessageSize = DiagString.size();
+ char *Mem = new (S.Context) char[MessageSize];
+ memcpy(Mem, DiagString.c_str(), MessageSize);
+ Satisfaction.Details.insert(
+ Satisfaction.Details.begin() + Size,
+ new (S.Context) ConstraintSatisfaction::SubstitutionDiagnostic{
+ SubstDiag.first, StringRef(Mem, MessageSize)});
+ return ExprError();
+ }
+
+ CXXScopeSpec SS;
+ SS.Adopt(Constraint.getConceptId()->getNestedNameSpecifierLoc());
- return SubstitutedAtomicExpr;
+ ExprResult SubstitutedConceptId = S.CheckConceptTemplateId(
+ SS, Constraint.getConceptId()->getTemplateKWLoc(),
----------------
cor3ntin wrote:
We do, because we need to have the right expression to display in diagnostics
https://github.com/llvm/llvm-project/pull/141776
More information about the libcxx-commits
mailing list