llvm-project/clang/lib/Sema/SemaConcept.cpp
Matheus Izvekov cfee056b4e
[clang] NFC: introduce UnsignedOrNone as a replacement for std::optional<unsigned> (#134142)
This introduces a new class 'UnsignedOrNone', which models a lite
version of `std::optional<unsigned>`, but has the same size as
'unsigned'.

This replaces most uses of `std::optional<unsigned>`, and similar
schemes utilizing 'int' and '-1' as sentinel.

Besides the smaller size advantage, this is simpler to serialize, as its
internal representation is a single unsigned int as well.
2025-04-03 14:27:18 -03:00

2115 lines
83 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//===-- SemaConcept.cpp - Semantic Analysis for Constraints and Concepts --===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements semantic analysis for C++ constraints and concepts.
//
//===----------------------------------------------------------------------===//
#include "clang/Sema/SemaConcept.h"
#include "TreeTransform.h"
#include "clang/AST/ASTLambda.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/ExprConcepts.h"
#include "clang/Basic/OperatorPrecedence.h"
#include "clang/Sema/EnterExpressionEvaluationContext.h"
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Overload.h"
#include "clang/Sema/ScopeInfo.h"
#include "clang/Sema/Sema.h"
#include "clang/Sema/SemaInternal.h"
#include "clang/Sema/Template.h"
#include "clang/Sema/TemplateDeduction.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/PointerUnion.h"
#include "llvm/ADT/StringExtras.h"
#include <optional>
using namespace clang;
using namespace sema;
namespace {
class LogicalBinOp {
SourceLocation Loc;
OverloadedOperatorKind Op = OO_None;
const Expr *LHS = nullptr;
const Expr *RHS = nullptr;
public:
LogicalBinOp(const Expr *E) {
if (auto *BO = dyn_cast<BinaryOperator>(E)) {
Op = BinaryOperator::getOverloadedOperator(BO->getOpcode());
LHS = BO->getLHS();
RHS = BO->getRHS();
Loc = BO->getExprLoc();
} else if (auto *OO = dyn_cast<CXXOperatorCallExpr>(E)) {
// If OO is not || or && it might not have exactly 2 arguments.
if (OO->getNumArgs() == 2) {
Op = OO->getOperator();
LHS = OO->getArg(0);
RHS = OO->getArg(1);
Loc = OO->getOperatorLoc();
}
}
}
bool isAnd() const { return Op == OO_AmpAmp; }
bool isOr() const { return Op == OO_PipePipe; }
explicit operator bool() const { return isAnd() || isOr(); }
const Expr *getLHS() const { return LHS; }
const Expr *getRHS() const { return RHS; }
OverloadedOperatorKind getOp() const { return Op; }
ExprResult recreateBinOp(Sema &SemaRef, ExprResult LHS) const {
return recreateBinOp(SemaRef, LHS, const_cast<Expr *>(getRHS()));
}
ExprResult recreateBinOp(Sema &SemaRef, ExprResult LHS,
ExprResult RHS) const {
assert((isAnd() || isOr()) && "Not the right kind of op?");
assert((!LHS.isInvalid() && !RHS.isInvalid()) && "not good expressions?");
if (!LHS.isUsable() || !RHS.isUsable())
return ExprEmpty();
// We should just be able to 'normalize' these to the builtin Binary
// Operator, since that is how they are evaluated in constriant checks.
return BinaryOperator::Create(SemaRef.Context, LHS.get(), RHS.get(),
BinaryOperator::getOverloadedOpcode(Op),
SemaRef.Context.BoolTy, VK_PRValue,
OK_Ordinary, Loc, FPOptionsOverride{});
}
};
}
bool Sema::CheckConstraintExpression(const Expr *ConstraintExpression,
Token NextToken, bool *PossibleNonPrimary,
bool IsTrailingRequiresClause) {
// C++2a [temp.constr.atomic]p1
// ..E shall be a constant expression of type bool.
ConstraintExpression = ConstraintExpression->IgnoreParenImpCasts();
if (LogicalBinOp BO = ConstraintExpression) {
return CheckConstraintExpression(BO.getLHS(), NextToken,
PossibleNonPrimary) &&
CheckConstraintExpression(BO.getRHS(), NextToken,
PossibleNonPrimary);
} else if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpression))
return CheckConstraintExpression(C->getSubExpr(), NextToken,
PossibleNonPrimary);
QualType Type = ConstraintExpression->getType();
auto CheckForNonPrimary = [&] {
if (!PossibleNonPrimary)
return;
*PossibleNonPrimary =
// We have the following case:
// template<typename> requires func(0) struct S { };
// The user probably isn't aware of the parentheses required around
// the function call, and we're only going to parse 'func' as the
// primary-expression, and complain that it is of non-bool type.
//
// However, if we're in a lambda, this might also be:
// []<typename> requires var () {};
// Which also looks like a function call due to the lambda parentheses,
// but unlike the first case, isn't an error, so this check is skipped.
(NextToken.is(tok::l_paren) &&
(IsTrailingRequiresClause ||
(Type->isDependentType() &&
isa<UnresolvedLookupExpr>(ConstraintExpression) &&
!dyn_cast_if_present<LambdaScopeInfo>(getCurFunction())) ||
Type->isFunctionType() ||
Type->isSpecificBuiltinType(BuiltinType::Overload))) ||
// We have the following case:
// template<typename T> requires size_<T> == 0 struct S { };
// The user probably isn't aware of the parentheses required around
// the binary operator, and we're only going to parse 'func' as the
// first operand, and complain that it is of non-bool type.
getBinOpPrecedence(NextToken.getKind(),
/*GreaterThanIsOperator=*/true,
getLangOpts().CPlusPlus11) > prec::LogicalAnd;
};
// An atomic constraint!
if (ConstraintExpression->isTypeDependent()) {
CheckForNonPrimary();
return true;
}
if (!Context.hasSameUnqualifiedType(Type, Context.BoolTy)) {
Diag(ConstraintExpression->getExprLoc(),
diag::err_non_bool_atomic_constraint) << Type
<< ConstraintExpression->getSourceRange();
CheckForNonPrimary();
return false;
}
if (PossibleNonPrimary)
*PossibleNonPrimary = false;
return true;
}
namespace {
struct SatisfactionStackRAII {
Sema &SemaRef;
bool Inserted = false;
SatisfactionStackRAII(Sema &SemaRef, const NamedDecl *ND,
const llvm::FoldingSetNodeID &FSNID)
: SemaRef(SemaRef) {
if (ND) {
SemaRef.PushSatisfactionStackEntry(ND, FSNID);
Inserted = true;
}
}
~SatisfactionStackRAII() {
if (Inserted)
SemaRef.PopSatisfactionStackEntry();
}
};
} // namespace
static bool
DiagRecursiveConstraintEval(Sema &S, llvm::FoldingSetNodeID &ID,
const NamedDecl *Templ, const Expr *E,
const MultiLevelTemplateArgumentList &MLTAL) {
E->Profile(ID, S.Context, /*Canonical=*/true);
for (const auto &List : MLTAL)
for (const auto &TemplateArg : List.Args)
TemplateArg.Profile(ID, S.Context);
// Note that we have to do this with our own collection, because there are
// times where a constraint-expression check can cause us to need to evaluate
// other constriants that are unrelated, such as when evaluating a recovery
// expression, or when trying to determine the constexpr-ness of special
// members. Otherwise we could just use the
// Sema::InstantiatingTemplate::isAlreadyBeingInstantiated function.
if (S.SatisfactionStackContains(Templ, ID)) {
S.Diag(E->getExprLoc(), diag::err_constraint_depends_on_self)
<< const_cast<Expr *>(E) << E->getSourceRange();
return true;
}
return false;
}
static ExprResult EvaluateAtomicConstraint(
Sema &S, const Expr *AtomicExpr, const NamedDecl *Template,
SourceLocation TemplateNameLoc, const MultiLevelTemplateArgumentList &MLTAL,
ConstraintSatisfaction &Satisfaction) {
EnterExpressionEvaluationContext ConstantEvaluated(
S, Sema::ExpressionEvaluationContext::ConstantEvaluated,
Sema::ReuseLambdaContextDecl);
// Atomic constraint - substitute arguments and check satisfaction.
ExprResult SubstitutedExpression;
{
TemplateDeductionInfo Info(TemplateNameLoc);
Sema::InstantiatingTemplate Inst(
S, AtomicExpr->getBeginLoc(),
Sema::InstantiatingTemplate::ConstraintSubstitution{},
// FIXME: improve const-correctness of InstantiatingTemplate
const_cast<NamedDecl *>(Template), Info, AtomicExpr->getSourceRange());
if (Inst.isInvalid())
return ExprError();
llvm::FoldingSetNodeID ID;
if (Template &&
DiagRecursiveConstraintEval(S, ID, Template, AtomicExpr, MLTAL)) {
Satisfaction.IsSatisfied = false;
Satisfaction.ContainsErrors = true;
return ExprEmpty();
}
SatisfactionStackRAII StackRAII(S, Template, ID);
// We do not want error diagnostics escaping here.
Sema::SFINAETrap Trap(S);
SubstitutedExpression =
S.SubstConstraintExpr(const_cast<Expr *>(AtomicExpr), MLTAL);
if (SubstitutedExpression.isInvalid() || Trap.hasErrorOccurred()) {
// C++2a [temp.constr.atomic]p1
// ...If substitution results in an invalid type or expression, the
// constraint is not satisfied.
if (!Trap.hasErrorOccurred())
// A non-SFINAE error has occurred as a result of this
// substitution.
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.emplace_back(
new (S.Context) ConstraintSatisfaction::SubstitutionDiagnostic{
SubstDiag.first, StringRef(Mem, MessageSize)});
Satisfaction.IsSatisfied = false;
return ExprEmpty();
}
}
if (!S.CheckConstraintExpression(SubstitutedExpression.get()))
return ExprError();
// [temp.constr.atomic]p3: To determine if an atomic constraint is
// satisfied, the parameter mapping and template arguments are first
// substituted into its expression. If substitution results in an
// invalid type or expression, the constraint is not satisfied.
// Otherwise, the lvalue-to-rvalue conversion is performed if necessary,
// and E shall be a constant expression of type bool.
//
// Perform the L to R Value conversion if necessary. We do so for all
// non-PRValue categories, else we fail to extend the lifetime of
// temporaries, and that fails the constant expression check.
if (!SubstitutedExpression.get()->isPRValue())
SubstitutedExpression = ImplicitCastExpr::Create(
S.Context, SubstitutedExpression.get()->getType(), CK_LValueToRValue,
SubstitutedExpression.get(),
/*BasePath=*/nullptr, VK_PRValue, FPOptionsOverride());
return SubstitutedExpression;
}
static UnsignedOrNone EvaluateFoldExpandedConstraintSize(
Sema &S, const CXXFoldExpr *FE, const NamedDecl *Template,
SourceLocation TemplateNameLoc, const MultiLevelTemplateArgumentList &MLTAL,
ConstraintSatisfaction &Satisfaction) {
// We should ignore errors in the presence of packs of different size.
Sema::SFINAETrap Trap(S);
Expr *Pattern = 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 = FE->getNumExpansions();
if (S.CheckParameterPacksForExpansion(
FE->getEllipsisLoc(), Pattern->getSourceRange(), Unexpanded, MLTAL,
Expand, RetainExpansion, NumExpansions) ||
!Expand || RetainExpansion)
return std::nullopt;
if (NumExpansions && S.getLangOpts().BracketDepth < *NumExpansions) {
S.Diag(FE->getEllipsisLoc(),
clang::diag::err_fold_expression_limit_exceeded)
<< *NumExpansions << S.getLangOpts().BracketDepth
<< FE->getSourceRange();
S.Diag(FE->getEllipsisLoc(), diag::note_bracket_depth);
return std::nullopt;
}
return NumExpansions;
}
static ExprResult calculateConstraintSatisfaction(
Sema &S, const Expr *ConstraintExpr, const NamedDecl *Template,
SourceLocation TemplateNameLoc, const MultiLevelTemplateArgumentList &MLTAL,
ConstraintSatisfaction &Satisfaction);
static ExprResult calculateConstraintSatisfaction(
Sema &S, const Expr *LHS, OverloadedOperatorKind Op, const Expr *RHS,
const NamedDecl *Template, SourceLocation TemplateNameLoc,
const MultiLevelTemplateArgumentList &MLTAL,
ConstraintSatisfaction &Satisfaction) {
size_t EffectiveDetailEndIndex = Satisfaction.Details.size();
ExprResult LHSRes = calculateConstraintSatisfaction(
S, LHS, Template, TemplateNameLoc, MLTAL, Satisfaction);
if (LHSRes.isInvalid())
return ExprError();
bool IsLHSSatisfied = Satisfaction.IsSatisfied;
if (Op == clang::OO_PipePipe && IsLHSSatisfied)
// [temp.constr.op] p3
// A disjunction is a constraint taking two operands. To determine if
// a disjunction is satisfied, the satisfaction of the first operand
// is checked. If that is satisfied, the disjunction is satisfied.
// Otherwise, the disjunction is satisfied if and only if the second
// operand is satisfied.
// LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
return LHSRes;
if (Op == clang::OO_AmpAmp && !IsLHSSatisfied)
// [temp.constr.op] p2
// A conjunction is a constraint taking two operands. To determine if
// a conjunction is satisfied, the satisfaction of the first operand
// 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.
// LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
return LHSRes;
ExprResult RHSRes = calculateConstraintSatisfaction(
S, RHS, Template, TemplateNameLoc, MLTAL, Satisfaction);
if (RHSRes.isInvalid())
return ExprError();
bool IsRHSSatisfied = Satisfaction.IsSatisfied;
// Current implementation adds diagnostic information about the falsity
// of each false atomic constraint expression when it evaluates them.
// When the evaluation results to `false || true`, the information
// generated during the evaluation of left-hand side is meaningless
// because the whole expression evaluates to true.
// The following code removes the irrelevant diagnostic information.
// FIXME: We should probably delay the addition of diagnostic information
// until we know the entire expression is false.
if (Op == clang::OO_PipePipe && IsRHSSatisfied) {
auto EffectiveDetailEnd = Satisfaction.Details.begin();
std::advance(EffectiveDetailEnd, EffectiveDetailEndIndex);
Satisfaction.Details.erase(EffectiveDetailEnd, Satisfaction.Details.end());
}
if (!LHSRes.isUsable() || !RHSRes.isUsable())
return ExprEmpty();
return BinaryOperator::Create(S.Context, LHSRes.get(), RHSRes.get(),
BinaryOperator::getOverloadedOpcode(Op),
S.Context.BoolTy, VK_PRValue, OK_Ordinary,
LHS->getBeginLoc(), FPOptionsOverride{});
}
static ExprResult calculateConstraintSatisfaction(
Sema &S, const CXXFoldExpr *FE, const NamedDecl *Template,
SourceLocation TemplateNameLoc, const MultiLevelTemplateArgumentList &MLTAL,
ConstraintSatisfaction &Satisfaction) {
bool Conjunction = FE->getOperator() == BinaryOperatorKind::BO_LAnd;
size_t EffectiveDetailEndIndex = Satisfaction.Details.size();
ExprResult Out;
if (FE->isLeftFold() && FE->getInit()) {
Out = calculateConstraintSatisfaction(S, FE->getInit(), Template,
TemplateNameLoc, MLTAL, Satisfaction);
if (Out.isInvalid())
return ExprError();
// If the first clause of a conjunction is not satisfied,
// or if the first clause of a disjection is satisfied,
// we have established satisfaction of the whole constraint
// and we should not continue further.
if (Conjunction != Satisfaction.IsSatisfied)
return Out;
}
UnsignedOrNone NumExpansions = EvaluateFoldExpandedConstraintSize(
S, FE, Template, TemplateNameLoc, MLTAL, Satisfaction);
if (!NumExpansions)
return ExprError();
for (unsigned I = 0; I < *NumExpansions; I++) {
Sema::ArgPackSubstIndexRAII SubstIndex(S, I);
ExprResult Res = calculateConstraintSatisfaction(
S, FE->getPattern(), Template, TemplateNameLoc, MLTAL, Satisfaction);
if (Res.isInvalid())
return ExprError();
bool IsRHSSatisfied = Satisfaction.IsSatisfied;
if (!Conjunction && IsRHSSatisfied) {
auto EffectiveDetailEnd = Satisfaction.Details.begin();
std::advance(EffectiveDetailEnd, EffectiveDetailEndIndex);
Satisfaction.Details.erase(EffectiveDetailEnd,
Satisfaction.Details.end());
}
if (Out.isUnset())
Out = Res;
else if (!Res.isUnset()) {
Out = BinaryOperator::Create(
S.Context, Out.get(), Res.get(), FE->getOperator(), S.Context.BoolTy,
VK_PRValue, OK_Ordinary, FE->getBeginLoc(), FPOptionsOverride{});
}
if (Conjunction != IsRHSSatisfied)
return Out;
}
if (FE->isRightFold() && FE->getInit()) {
ExprResult Res = calculateConstraintSatisfaction(
S, FE->getInit(), Template, TemplateNameLoc, MLTAL, Satisfaction);
if (Out.isInvalid())
return ExprError();
if (Out.isUnset())
Out = Res;
else if (!Res.isUnset()) {
Out = BinaryOperator::Create(
S.Context, Out.get(), Res.get(), FE->getOperator(), S.Context.BoolTy,
VK_PRValue, OK_Ordinary, FE->getBeginLoc(), FPOptionsOverride{});
}
}
if (Out.isUnset()) {
Satisfaction.IsSatisfied = Conjunction;
Out = S.BuildEmptyCXXFoldExpr(FE->getBeginLoc(), FE->getOperator());
}
return Out;
}
static ExprResult calculateConstraintSatisfaction(
Sema &S, const Expr *ConstraintExpr, const NamedDecl *Template,
SourceLocation TemplateNameLoc, const MultiLevelTemplateArgumentList &MLTAL,
ConstraintSatisfaction &Satisfaction) {
ConstraintExpr = ConstraintExpr->IgnoreParenImpCasts();
if (LogicalBinOp BO = ConstraintExpr)
return calculateConstraintSatisfaction(
S, BO.getLHS(), BO.getOp(), BO.getRHS(), Template, TemplateNameLoc,
MLTAL, Satisfaction);
if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpr)) {
// These aren't evaluated, so we don't care about cleanups, so we can just
// evaluate these as if the cleanups didn't exist.
return calculateConstraintSatisfaction(
S, C->getSubExpr(), Template, TemplateNameLoc, MLTAL, Satisfaction);
}
if (auto *FE = dyn_cast<CXXFoldExpr>(ConstraintExpr);
FE && S.getLangOpts().CPlusPlus26 &&
(FE->getOperator() == BinaryOperatorKind::BO_LAnd ||
FE->getOperator() == BinaryOperatorKind::BO_LOr)) {
return calculateConstraintSatisfaction(S, FE, Template, TemplateNameLoc,
MLTAL, Satisfaction);
}
// FIXME: We should not treat ConceptSpecializationExpr as atomic constraints.
// An atomic constraint expression
ExprResult SubstitutedAtomicExpr = EvaluateAtomicConstraint(
S, ConstraintExpr, Template, TemplateNameLoc, MLTAL, Satisfaction);
if (SubstitutedAtomicExpr.isInvalid())
return ExprError();
if (!SubstitutedAtomicExpr.isUsable())
// 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(
new (S.Context) ConstraintSatisfaction::SubstitutionDiagnostic{
SubstitutedAtomicExpr.get()->getBeginLoc(),
StringRef(Mem, MessageSize)});
return SubstitutedAtomicExpr;
}
EnterExpressionEvaluationContext ConstantEvaluated(
S, Sema::ExpressionEvaluationContext::ConstantEvaluated);
SmallVector<PartialDiagnosticAt, 2> EvaluationDiags;
Expr::EvalResult EvalResult;
EvalResult.Diag = &EvaluationDiags;
if (!SubstitutedAtomicExpr.get()->EvaluateAsConstantExpr(EvalResult,
S.Context) ||
!EvaluationDiags.empty()) {
// C++2a [temp.constr.atomic]p1
// ...E shall be a constant expression of type bool.
S.Diag(SubstitutedAtomicExpr.get()->getBeginLoc(),
diag::err_non_constant_constraint_expression)
<< SubstitutedAtomicExpr.get()->getSourceRange();
for (const PartialDiagnosticAt &PDiag : EvaluationDiags)
S.Diag(PDiag.first, PDiag.second);
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());
return SubstitutedAtomicExpr;
}
static ExprResult calculateConstraintSatisfaction(
Sema &S, const NamedDecl *Template, SourceLocation TemplateNameLoc,
const MultiLevelTemplateArgumentList &MLTAL, const Expr *ConstraintExpr,
ConstraintSatisfaction &Satisfaction) {
return calculateConstraintSatisfaction(S, ConstraintExpr, Template,
TemplateNameLoc, MLTAL, Satisfaction);
}
static bool CheckConstraintSatisfaction(
Sema &S, const NamedDecl *Template,
ArrayRef<AssociatedConstraint> AssociatedConstraints,
llvm::SmallVectorImpl<Expr *> &Converted,
const MultiLevelTemplateArgumentList &TemplateArgsLists,
SourceRange TemplateIDRange, ConstraintSatisfaction &Satisfaction) {
if (AssociatedConstraints.empty()) {
Satisfaction.IsSatisfied = true;
return false;
}
if (TemplateArgsLists.isAnyArgInstantiationDependent()) {
// No need to check satisfaction for dependent constraint expressions.
Satisfaction.IsSatisfied = true;
return false;
}
ArrayRef<TemplateArgument> TemplateArgs =
TemplateArgsLists.getNumSubstitutedLevels() > 0
? TemplateArgsLists.getOutermost()
: ArrayRef<TemplateArgument>{};
Sema::InstantiatingTemplate Inst(S, TemplateIDRange.getBegin(),
Sema::InstantiatingTemplate::ConstraintsCheck{},
const_cast<NamedDecl *>(Template), TemplateArgs, TemplateIDRange);
if (Inst.isInvalid())
return true;
for (const AssociatedConstraint &AC : AssociatedConstraints) {
Sema::ArgPackSubstIndexRAII _(S, AC.ArgPackSubstIndex);
ExprResult Res = calculateConstraintSatisfaction(
S, Template, TemplateIDRange.getBegin(), TemplateArgsLists,
AC.ConstraintExpr, Satisfaction);
if (Res.isInvalid())
return true;
Converted.push_back(Res.get());
if (!Satisfaction.IsSatisfied) {
// Backfill the 'converted' list with nulls so we can keep the Converted
// and unconverted lists in sync.
Converted.append(AssociatedConstraints.size() - Converted.size(),
nullptr);
// [temp.constr.op] p2
// [...] To determine if a conjunction is satisfied, the satisfaction
// of the first operand is checked. If that is not satisfied, the
// conjunction is not satisfied. [...]
return false;
}
}
return false;
}
bool Sema::CheckConstraintSatisfaction(
const NamedDecl *Template,
ArrayRef<AssociatedConstraint> AssociatedConstraints,
llvm::SmallVectorImpl<Expr *> &ConvertedConstraints,
const MultiLevelTemplateArgumentList &TemplateArgsLists,
SourceRange TemplateIDRange, ConstraintSatisfaction &OutSatisfaction) {
if (AssociatedConstraints.empty()) {
OutSatisfaction.IsSatisfied = true;
return false;
}
if (!Template) {
return ::CheckConstraintSatisfaction(
*this, nullptr, AssociatedConstraints, ConvertedConstraints,
TemplateArgsLists, TemplateIDRange, OutSatisfaction);
}
// Invalid templates could make their way here. Substituting them could result
// in dependent expressions.
if (Template->isInvalidDecl()) {
OutSatisfaction.IsSatisfied = false;
return true;
}
// A list of the template argument list flattened in a predictible manner for
// the purposes of caching. The ConstraintSatisfaction type is in AST so it
// has no access to the MultiLevelTemplateArgumentList, so this has to happen
// here.
llvm::SmallVector<TemplateArgument, 4> FlattenedArgs;
for (auto List : TemplateArgsLists)
FlattenedArgs.insert(FlattenedArgs.end(), List.Args.begin(),
List.Args.end());
llvm::FoldingSetNodeID ID;
ConstraintSatisfaction::Profile(ID, Context, Template, FlattenedArgs);
void *InsertPos;
if (auto *Cached = SatisfactionCache.FindNodeOrInsertPos(ID, InsertPos)) {
OutSatisfaction = *Cached;
return false;
}
auto Satisfaction =
std::make_unique<ConstraintSatisfaction>(Template, FlattenedArgs);
if (::CheckConstraintSatisfaction(*this, Template, AssociatedConstraints,
ConvertedConstraints, TemplateArgsLists,
TemplateIDRange, *Satisfaction)) {
OutSatisfaction = *Satisfaction;
return true;
}
if (auto *Cached = SatisfactionCache.FindNodeOrInsertPos(ID, InsertPos)) {
// The evaluation of this constraint resulted in us trying to re-evaluate it
// recursively. This isn't really possible, except we try to form a
// RecoveryExpr as a part of the evaluation. If this is the case, just
// return the 'cached' version (which will have the same result), and save
// ourselves the extra-insert. If it ever becomes possible to legitimately
// recursively check a constraint, we should skip checking the 'inner' one
// above, and replace the cached version with this one, as it would be more
// specific.
OutSatisfaction = *Cached;
return false;
}
// Else we can simply add this satisfaction to the list.
OutSatisfaction = *Satisfaction;
// We cannot use InsertPos here because CheckConstraintSatisfaction might have
// invalidated it.
// Note that entries of SatisfactionCache are deleted in Sema's destructor.
SatisfactionCache.InsertNode(Satisfaction.release());
return false;
}
bool Sema::CheckConstraintSatisfaction(
const ConceptSpecializationExpr *ConstraintExpr,
ConstraintSatisfaction &Satisfaction) {
MultiLevelTemplateArgumentList MLTAL(ConstraintExpr->getNamedConcept(),
ConstraintExpr->getTemplateArguments(),
true);
return calculateConstraintSatisfaction(
*this, ConstraintExpr, ConstraintExpr->getNamedConcept(),
ConstraintExpr->getConceptNameLoc(), MLTAL, Satisfaction)
.isInvalid();
}
bool Sema::SetupConstraintScope(
FunctionDecl *FD, std::optional<ArrayRef<TemplateArgument>> TemplateArgs,
const MultiLevelTemplateArgumentList &MLTAL,
LocalInstantiationScope &Scope) {
assert(!isLambdaCallOperator(FD) &&
"Use LambdaScopeForCallOperatorInstantiationRAII to handle lambda "
"instantiations");
if (FD->isTemplateInstantiation() && FD->getPrimaryTemplate()) {
FunctionTemplateDecl *PrimaryTemplate = FD->getPrimaryTemplate();
InstantiatingTemplate Inst(
*this, FD->getPointOfInstantiation(),
Sema::InstantiatingTemplate::ConstraintsCheck{}, PrimaryTemplate,
TemplateArgs ? *TemplateArgs : ArrayRef<TemplateArgument>{},
SourceRange());
if (Inst.isInvalid())
return true;
// addInstantiatedParametersToScope creates a map of 'uninstantiated' to
// 'instantiated' parameters and adds it to the context. For the case where
// this function is a template being instantiated NOW, we also need to add
// the list of current template arguments to the list so that they also can
// be picked out of the map.
if (auto *SpecArgs = FD->getTemplateSpecializationArgs()) {
MultiLevelTemplateArgumentList JustTemplArgs(FD, SpecArgs->asArray(),
/*Final=*/false);
if (addInstantiatedParametersToScope(
FD, PrimaryTemplate->getTemplatedDecl(), Scope, JustTemplArgs))
return true;
}
// If this is a member function, make sure we get the parameters that
// reference the original primary template.
if (FunctionTemplateDecl *FromMemTempl =
PrimaryTemplate->getInstantiatedFromMemberTemplate()) {
if (addInstantiatedParametersToScope(FD, FromMemTempl->getTemplatedDecl(),
Scope, MLTAL))
return true;
}
return false;
}
if (FD->getTemplatedKind() == FunctionDecl::TK_MemberSpecialization ||
FD->getTemplatedKind() == FunctionDecl::TK_DependentNonTemplate) {
FunctionDecl *InstantiatedFrom =
FD->getTemplatedKind() == FunctionDecl::TK_MemberSpecialization
? FD->getInstantiatedFromMemberFunction()
: FD->getInstantiatedFromDecl();
InstantiatingTemplate Inst(
*this, FD->getPointOfInstantiation(),
Sema::InstantiatingTemplate::ConstraintsCheck{}, InstantiatedFrom,
TemplateArgs ? *TemplateArgs : ArrayRef<TemplateArgument>{},
SourceRange());
if (Inst.isInvalid())
return true;
// Case where this was not a template, but instantiated as a
// child-function.
if (addInstantiatedParametersToScope(FD, InstantiatedFrom, Scope, MLTAL))
return true;
}
return false;
}
// This function collects all of the template arguments for the purposes of
// constraint-instantiation and checking.
std::optional<MultiLevelTemplateArgumentList>
Sema::SetupConstraintCheckingTemplateArgumentsAndScope(
FunctionDecl *FD, std::optional<ArrayRef<TemplateArgument>> TemplateArgs,
LocalInstantiationScope &Scope) {
MultiLevelTemplateArgumentList MLTAL;
// Collect the list of template arguments relative to the 'primary' template.
// We need the entire list, since the constraint is completely uninstantiated
// at this point.
MLTAL =
getTemplateInstantiationArgs(FD, FD->getLexicalDeclContext(),
/*Final=*/false, /*Innermost=*/std::nullopt,
/*RelativeToPrimary=*/true,
/*Pattern=*/nullptr,
/*ForConstraintInstantiation=*/true);
// Lambdas are handled by LambdaScopeForCallOperatorInstantiationRAII.
if (isLambdaCallOperator(FD))
return MLTAL;
if (SetupConstraintScope(FD, TemplateArgs, MLTAL, Scope))
return std::nullopt;
return MLTAL;
}
bool Sema::CheckFunctionConstraints(const FunctionDecl *FD,
ConstraintSatisfaction &Satisfaction,
SourceLocation UsageLoc,
bool ForOverloadResolution) {
// Don't check constraints if the function is dependent. Also don't check if
// this is a function template specialization, as the call to
// CheckinstantiatedFunctionTemplateConstraints after this will check it
// better.
if (FD->isDependentContext() ||
FD->getTemplatedKind() ==
FunctionDecl::TK_FunctionTemplateSpecialization) {
Satisfaction.IsSatisfied = true;
return false;
}
// A lambda conversion operator has the same constraints as the call operator
// and constraints checking relies on whether we are in a lambda call operator
// (and may refer to its parameters), so check the call operator instead.
// Note that the declarations outside of the lambda should also be
// considered. Turning on the 'ForOverloadResolution' flag results in the
// LocalInstantiationScope not looking into its parents, but we can still
// access Decls from the parents while building a lambda RAII scope later.
if (const auto *MD = dyn_cast<CXXConversionDecl>(FD);
MD && isLambdaConversionOperator(const_cast<CXXConversionDecl *>(MD)))
return CheckFunctionConstraints(MD->getParent()->getLambdaCallOperator(),
Satisfaction, UsageLoc,
/*ShouldAddDeclsFromParentScope=*/true);
DeclContext *CtxToSave = const_cast<FunctionDecl *>(FD);
while (isLambdaCallOperator(CtxToSave) || FD->isTransparentContext()) {
if (isLambdaCallOperator(CtxToSave))
CtxToSave = CtxToSave->getParent()->getParent();
else
CtxToSave = CtxToSave->getNonTransparentContext();
}
ContextRAII SavedContext{*this, CtxToSave};
LocalInstantiationScope Scope(*this, !ForOverloadResolution);
std::optional<MultiLevelTemplateArgumentList> MLTAL =
SetupConstraintCheckingTemplateArgumentsAndScope(
const_cast<FunctionDecl *>(FD), {}, Scope);
if (!MLTAL)
return true;
Qualifiers ThisQuals;
CXXRecordDecl *Record = nullptr;
if (auto *Method = dyn_cast<CXXMethodDecl>(FD)) {
ThisQuals = Method->getMethodQualifiers();
Record = const_cast<CXXRecordDecl *>(Method->getParent());
}
CXXThisScopeRAII ThisScope(*this, Record, ThisQuals, Record != nullptr);
LambdaScopeForCallOperatorInstantiationRAII LambdaScope(
*this, const_cast<FunctionDecl *>(FD), *MLTAL, Scope,
ForOverloadResolution);
return CheckConstraintSatisfaction(
FD, FD->getTrailingRequiresClause(), *MLTAL,
SourceRange(UsageLoc.isValid() ? UsageLoc : FD->getLocation()),
Satisfaction);
}
// Figure out the to-translation-unit depth for this function declaration for
// the purpose of seeing if they differ by constraints. This isn't the same as
// getTemplateDepth, because it includes already instantiated parents.
static unsigned
CalculateTemplateDepthForConstraints(Sema &S, const NamedDecl *ND,
bool SkipForSpecialization = false) {
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
ND, ND->getLexicalDeclContext(), /*Final=*/false,
/*Innermost=*/std::nullopt,
/*RelativeToPrimary=*/true,
/*Pattern=*/nullptr,
/*ForConstraintInstantiation=*/true, SkipForSpecialization);
return MLTAL.getNumLevels();
}
namespace {
class AdjustConstraintDepth : public TreeTransform<AdjustConstraintDepth> {
unsigned TemplateDepth = 0;
public:
using inherited = TreeTransform<AdjustConstraintDepth>;
AdjustConstraintDepth(Sema &SemaRef, unsigned TemplateDepth)
: inherited(SemaRef), TemplateDepth(TemplateDepth) {}
using inherited::TransformTemplateTypeParmType;
QualType TransformTemplateTypeParmType(TypeLocBuilder &TLB,
TemplateTypeParmTypeLoc TL, bool) {
const TemplateTypeParmType *T = TL.getTypePtr();
TemplateTypeParmDecl *NewTTPDecl = nullptr;
if (TemplateTypeParmDecl *OldTTPDecl = T->getDecl())
NewTTPDecl = cast_or_null<TemplateTypeParmDecl>(
TransformDecl(TL.getNameLoc(), OldTTPDecl));
QualType Result = getSema().Context.getTemplateTypeParmType(
T->getDepth() + TemplateDepth, T->getIndex(), T->isParameterPack(),
NewTTPDecl);
TemplateTypeParmTypeLoc NewTL = TLB.push<TemplateTypeParmTypeLoc>(Result);
NewTL.setNameLoc(TL.getNameLoc());
return Result;
}
};
} // namespace
static const Expr *SubstituteConstraintExpressionWithoutSatisfaction(
Sema &S, const Sema::TemplateCompareNewDeclInfo &DeclInfo,
const Expr *ConstrExpr) {
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
DeclInfo.getDecl(), DeclInfo.getLexicalDeclContext(), /*Final=*/false,
/*Innermost=*/std::nullopt,
/*RelativeToPrimary=*/true,
/*Pattern=*/nullptr, /*ForConstraintInstantiation=*/true,
/*SkipForSpecialization*/ false);
if (MLTAL.getNumSubstitutedLevels() == 0)
return ConstrExpr;
Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/false);
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
// equivalence.
std::optional<LocalInstantiationScope> ScopeForParameters;
if (const NamedDecl *ND = DeclInfo.getDecl();
ND && ND->isFunctionOrFunctionTemplate()) {
ScopeForParameters.emplace(S, /*CombineWithOuterScope=*/true);
const FunctionDecl *FD = ND->getAsFunction();
for (auto *PVD : FD->parameters()) {
if (!PVD->isParameterPack()) {
ScopeForParameters->InstantiatedLocal(PVD, PVD);
continue;
}
// This is hacky: we're mapping the parameter pack to a size-of-1 argument
// to avoid building SubstTemplateTypeParmPackTypes for
// PackExpansionTypes. The SubstTemplateTypeParmPackType node would
// otherwise reference the AssociatedDecl of the template arguments, which
// is, in this case, the template declaration.
//
// However, as we are in the process of comparing potential
// re-declarations, the canonical declaration is the declaration itself at
// this point. So if we didn't expand these packs, we would end up with an
// incorrect profile difference because we will be profiling the
// canonical types!
//
// FIXME: Improve the "no-transform" machinery in FindInstantiatedDecl so
// that we can eliminate the Scope in the cases where the declarations are
// not necessarily instantiated. It would also benefit the noexcept
// specifier comparison.
ScopeForParameters->MakeInstantiatedLocalArgPack(PVD);
ScopeForParameters->InstantiatedLocalPackArg(PVD, PVD);
}
}
std::optional<Sema::CXXThisScopeRAII> ThisScope;
// See TreeTransform::RebuildTemplateSpecializationType. A context scope is
// essential for having an injected class as the canonical type for a template
// specialization type at the rebuilding stage. This guarantees that, for
// out-of-line definitions, injected class name types and their equivalent
// template specializations can be profiled to the same value, which makes it
// possible that e.g. constraints involving C<Class<T>> and C<Class> are
// perceived identical.
std::optional<Sema::ContextRAII> ContextScope;
const DeclContext *DC = [&] {
if (!DeclInfo.getDecl())
return DeclInfo.getDeclContext();
return DeclInfo.getDecl()->getFriendObjectKind()
? DeclInfo.getLexicalDeclContext()
: DeclInfo.getDeclContext();
}();
if (auto *RD = dyn_cast<CXXRecordDecl>(DC)) {
ThisScope.emplace(S, const_cast<CXXRecordDecl *>(RD), Qualifiers());
ContextScope.emplace(S, const_cast<DeclContext *>(cast<DeclContext>(RD)),
/*NewThisContext=*/false);
}
EnterExpressionEvaluationContext UnevaluatedContext(
S, Sema::ExpressionEvaluationContext::Unevaluated,
Sema::ReuseLambdaContextDecl);
ExprResult SubstConstr = S.SubstConstraintExprWithoutSatisfaction(
const_cast<clang::Expr *>(ConstrExpr), MLTAL);
if (SFINAE.hasErrorOccurred() || !SubstConstr.isUsable())
return nullptr;
return SubstConstr.get();
}
bool Sema::AreConstraintExpressionsEqual(const NamedDecl *Old,
const Expr *OldConstr,
const TemplateCompareNewDeclInfo &New,
const Expr *NewConstr) {
if (OldConstr == NewConstr)
return true;
// C++ [temp.constr.decl]p4
if (Old && !New.isInvalid() && !New.ContainsDecl(Old) &&
Old->getLexicalDeclContext() != New.getLexicalDeclContext()) {
if (const Expr *SubstConstr =
SubstituteConstraintExpressionWithoutSatisfaction(*this, Old,
OldConstr))
OldConstr = SubstConstr;
else
return false;
if (const Expr *SubstConstr =
SubstituteConstraintExpressionWithoutSatisfaction(*this, New,
NewConstr))
NewConstr = SubstConstr;
else
return false;
}
llvm::FoldingSetNodeID ID1, ID2;
OldConstr->Profile(ID1, Context, /*Canonical=*/true);
NewConstr->Profile(ID2, Context, /*Canonical=*/true);
return ID1 == ID2;
}
bool Sema::FriendConstraintsDependOnEnclosingTemplate(const FunctionDecl *FD) {
assert(FD->getFriendObjectKind() && "Must be a friend!");
// The logic for non-templates is handled in ASTContext::isSameEntity, so we
// don't have to bother checking 'DependsOnEnclosingTemplate' for a
// non-function-template.
assert(FD->getDescribedFunctionTemplate() &&
"Non-function templates don't need to be checked");
SmallVector<AssociatedConstraint, 3> ACs;
FD->getDescribedFunctionTemplate()->getAssociatedConstraints(ACs);
unsigned OldTemplateDepth = CalculateTemplateDepthForConstraints(*this, FD);
for (const AssociatedConstraint &AC : ACs)
if (ConstraintExpressionDependsOnEnclosingTemplate(FD, OldTemplateDepth,
AC.ConstraintExpr))
return true;
return false;
}
bool Sema::EnsureTemplateArgumentListConstraints(
TemplateDecl *TD, const MultiLevelTemplateArgumentList &TemplateArgsLists,
SourceRange TemplateIDRange) {
ConstraintSatisfaction Satisfaction;
llvm::SmallVector<AssociatedConstraint, 3> AssociatedConstraints;
TD->getAssociatedConstraints(AssociatedConstraints);
if (CheckConstraintSatisfaction(TD, AssociatedConstraints, TemplateArgsLists,
TemplateIDRange, Satisfaction))
return true;
if (!Satisfaction.IsSatisfied) {
SmallString<128> TemplateArgString;
TemplateArgString = " ";
TemplateArgString += getTemplateArgumentBindingsText(
TD->getTemplateParameters(), TemplateArgsLists.getInnermost().data(),
TemplateArgsLists.getInnermost().size());
Diag(TemplateIDRange.getBegin(),
diag::err_template_arg_list_constraints_not_satisfied)
<< (int)getTemplateNameKindForDiagnostics(TemplateName(TD)) << TD
<< TemplateArgString << TemplateIDRange;
DiagnoseUnsatisfiedConstraint(Satisfaction);
return true;
}
return false;
}
bool Sema::CheckInstantiatedFunctionTemplateConstraints(
SourceLocation PointOfInstantiation, FunctionDecl *Decl,
ArrayRef<TemplateArgument> TemplateArgs,
ConstraintSatisfaction &Satisfaction) {
// In most cases we're not going to have constraints, so check for that first.
FunctionTemplateDecl *Template = Decl->getPrimaryTemplate();
// Note - code synthesis context for the constraints check is created
// inside CheckConstraintsSatisfaction.
SmallVector<AssociatedConstraint, 3> TemplateAC;
Template->getAssociatedConstraints(TemplateAC);
if (TemplateAC.empty()) {
Satisfaction.IsSatisfied = true;
return false;
}
// Enter the scope of this instantiation. We don't use
// PushDeclContext because we don't have a scope.
Sema::ContextRAII savedContext(*this, Decl);
LocalInstantiationScope Scope(*this);
std::optional<MultiLevelTemplateArgumentList> MLTAL =
SetupConstraintCheckingTemplateArgumentsAndScope(Decl, TemplateArgs,
Scope);
if (!MLTAL)
return true;
Qualifiers ThisQuals;
CXXRecordDecl *Record = nullptr;
if (auto *Method = dyn_cast<CXXMethodDecl>(Decl)) {
ThisQuals = Method->getMethodQualifiers();
Record = Method->getParent();
}
CXXThisScopeRAII ThisScope(*this, Record, ThisQuals, Record != nullptr);
LambdaScopeForCallOperatorInstantiationRAII LambdaScope(
*this, const_cast<FunctionDecl *>(Decl), *MLTAL, Scope);
return CheckConstraintSatisfaction(Template, TemplateAC, *MLTAL,
PointOfInstantiation, Satisfaction);
}
static void diagnoseUnsatisfiedRequirement(Sema &S,
concepts::ExprRequirement *Req,
bool First) {
assert(!Req->isSatisfied()
&& "Diagnose() can only be used on an unsatisfied requirement");
switch (Req->getSatisfactionStatus()) {
case concepts::ExprRequirement::SS_Dependent:
llvm_unreachable("Diagnosing a dependent requirement");
break;
case concepts::ExprRequirement::SS_ExprSubstitutionFailure: {
auto *SubstDiag = Req->getExprSubstitutionDiagnostic();
if (!SubstDiag->DiagMessage.empty())
S.Diag(SubstDiag->DiagLoc,
diag::note_expr_requirement_expr_substitution_error)
<< (int)First << SubstDiag->SubstitutedEntity
<< SubstDiag->DiagMessage;
else
S.Diag(SubstDiag->DiagLoc,
diag::note_expr_requirement_expr_unknown_substitution_error)
<< (int)First << SubstDiag->SubstitutedEntity;
break;
}
case concepts::ExprRequirement::SS_NoexceptNotMet:
S.Diag(Req->getNoexceptLoc(),
diag::note_expr_requirement_noexcept_not_met)
<< (int)First << Req->getExpr();
break;
case concepts::ExprRequirement::SS_TypeRequirementSubstitutionFailure: {
auto *SubstDiag =
Req->getReturnTypeRequirement().getSubstitutionDiagnostic();
if (!SubstDiag->DiagMessage.empty())
S.Diag(SubstDiag->DiagLoc,
diag::note_expr_requirement_type_requirement_substitution_error)
<< (int)First << SubstDiag->SubstitutedEntity
<< SubstDiag->DiagMessage;
else
S.Diag(SubstDiag->DiagLoc,
diag::note_expr_requirement_type_requirement_unknown_substitution_error)
<< (int)First << SubstDiag->SubstitutedEntity;
break;
}
case concepts::ExprRequirement::SS_ConstraintsNotSatisfied: {
ConceptSpecializationExpr *ConstraintExpr =
Req->getReturnTypeRequirementSubstitutedConstraintExpr();
if (ConstraintExpr->getTemplateArgsAsWritten()->NumTemplateArgs == 1) {
// A simple case - expr type is the type being constrained and the concept
// was not provided arguments.
Expr *e = Req->getExpr();
S.Diag(e->getBeginLoc(),
diag::note_expr_requirement_constraints_not_satisfied_simple)
<< (int)First << S.Context.getReferenceQualifiedType(e)
<< ConstraintExpr->getNamedConcept();
} else {
S.Diag(ConstraintExpr->getBeginLoc(),
diag::note_expr_requirement_constraints_not_satisfied)
<< (int)First << ConstraintExpr;
}
S.DiagnoseUnsatisfiedConstraint(ConstraintExpr->getSatisfaction());
break;
}
case concepts::ExprRequirement::SS_Satisfied:
llvm_unreachable("We checked this above");
}
}
static void diagnoseUnsatisfiedRequirement(Sema &S,
concepts::TypeRequirement *Req,
bool First) {
assert(!Req->isSatisfied()
&& "Diagnose() can only be used on an unsatisfied requirement");
switch (Req->getSatisfactionStatus()) {
case concepts::TypeRequirement::SS_Dependent:
llvm_unreachable("Diagnosing a dependent requirement");
return;
case concepts::TypeRequirement::SS_SubstitutionFailure: {
auto *SubstDiag = Req->getSubstitutionDiagnostic();
if (!SubstDiag->DiagMessage.empty())
S.Diag(SubstDiag->DiagLoc,
diag::note_type_requirement_substitution_error) << (int)First
<< SubstDiag->SubstitutedEntity << SubstDiag->DiagMessage;
else
S.Diag(SubstDiag->DiagLoc,
diag::note_type_requirement_unknown_substitution_error)
<< (int)First << SubstDiag->SubstitutedEntity;
return;
}
default:
llvm_unreachable("Unknown satisfaction status");
return;
}
}
static void diagnoseWellFormedUnsatisfiedConstraintExpr(Sema &S,
Expr *SubstExpr,
bool First = true);
static void diagnoseUnsatisfiedRequirement(Sema &S,
concepts::NestedRequirement *Req,
bool First) {
using SubstitutionDiagnostic = std::pair<SourceLocation, StringRef>;
for (auto &Record : Req->getConstraintSatisfaction()) {
if (auto *SubstDiag = Record.dyn_cast<SubstitutionDiagnostic *>())
S.Diag(SubstDiag->first, diag::note_nested_requirement_substitution_error)
<< (int)First << Req->getInvalidConstraintEntity()
<< SubstDiag->second;
else
diagnoseWellFormedUnsatisfiedConstraintExpr(S, Record.dyn_cast<Expr *>(),
First);
First = false;
}
}
static void diagnoseWellFormedUnsatisfiedConstraintExpr(Sema &S,
Expr *SubstExpr,
bool First) {
SubstExpr = SubstExpr->IgnoreParenImpCasts();
if (BinaryOperator *BO = dyn_cast<BinaryOperator>(SubstExpr)) {
switch (BO->getOpcode()) {
// These two cases will in practice only be reached when using fold
// expressions with || and &&, since otherwise the || and && will have been
// broken down into atomic constraints during satisfaction checking.
case BO_LOr:
// Or evaluated to false - meaning both RHS and LHS evaluated to false.
diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getLHS(), First);
diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getRHS(),
/*First=*/false);
return;
case BO_LAnd: {
bool LHSSatisfied =
BO->getLHS()->EvaluateKnownConstInt(S.Context).getBoolValue();
if (LHSSatisfied) {
// LHS is true, so RHS must be false.
diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getRHS(), First);
return;
}
// LHS is false
diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getLHS(), First);
// RHS might also be false
bool RHSSatisfied =
BO->getRHS()->EvaluateKnownConstInt(S.Context).getBoolValue();
if (!RHSSatisfied)
diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getRHS(),
/*First=*/false);
return;
}
case BO_GE:
case BO_LE:
case BO_GT:
case BO_LT:
case BO_EQ:
case BO_NE:
if (BO->getLHS()->getType()->isIntegerType() &&
BO->getRHS()->getType()->isIntegerType()) {
Expr::EvalResult SimplifiedLHS;
Expr::EvalResult SimplifiedRHS;
BO->getLHS()->EvaluateAsInt(SimplifiedLHS, S.Context,
Expr::SE_NoSideEffects,
/*InConstantContext=*/true);
BO->getRHS()->EvaluateAsInt(SimplifiedRHS, S.Context,
Expr::SE_NoSideEffects,
/*InConstantContext=*/true);
if (!SimplifiedLHS.Diag && ! SimplifiedRHS.Diag) {
S.Diag(SubstExpr->getBeginLoc(),
diag::note_atomic_constraint_evaluated_to_false_elaborated)
<< (int)First << SubstExpr
<< toString(SimplifiedLHS.Val.getInt(), 10)
<< BinaryOperator::getOpcodeStr(BO->getOpcode())
<< toString(SimplifiedRHS.Val.getInt(), 10);
return;
}
}
break;
default:
break;
}
} else if (auto *CSE = dyn_cast<ConceptSpecializationExpr>(SubstExpr)) {
if (CSE->getTemplateArgsAsWritten()->NumTemplateArgs == 1) {
S.Diag(
CSE->getSourceRange().getBegin(),
diag::
note_single_arg_concept_specialization_constraint_evaluated_to_false)
<< (int)First
<< CSE->getTemplateArgsAsWritten()->arguments()[0].getArgument()
<< CSE->getNamedConcept();
} else {
S.Diag(SubstExpr->getSourceRange().getBegin(),
diag::note_concept_specialization_constraint_evaluated_to_false)
<< (int)First << CSE;
}
S.DiagnoseUnsatisfiedConstraint(CSE->getSatisfaction());
return;
} else if (auto *RE = dyn_cast<RequiresExpr>(SubstExpr)) {
// FIXME: RequiresExpr should store dependent diagnostics.
for (concepts::Requirement *Req : RE->getRequirements())
if (!Req->isDependent() && !Req->isSatisfied()) {
if (auto *E = dyn_cast<concepts::ExprRequirement>(Req))
diagnoseUnsatisfiedRequirement(S, E, First);
else if (auto *T = dyn_cast<concepts::TypeRequirement>(Req))
diagnoseUnsatisfiedRequirement(S, T, First);
else
diagnoseUnsatisfiedRequirement(
S, cast<concepts::NestedRequirement>(Req), First);
break;
}
return;
} else if (auto *TTE = dyn_cast<TypeTraitExpr>(SubstExpr);
TTE && TTE->getTrait() == clang::TypeTrait::BTT_IsDeducible) {
assert(TTE->getNumArgs() == 2);
S.Diag(SubstExpr->getSourceRange().getBegin(),
diag::note_is_deducible_constraint_evaluated_to_false)
<< TTE->getArg(0)->getType() << TTE->getArg(1)->getType();
return;
}
S.Diag(SubstExpr->getSourceRange().getBegin(),
diag::note_atomic_constraint_evaluated_to_false)
<< (int)First << 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;
return;
}
diagnoseWellFormedUnsatisfiedConstraintExpr(S, cast<Expr *>(Record), First);
}
void
Sema::DiagnoseUnsatisfiedConstraint(const ConstraintSatisfaction& Satisfaction,
bool First) {
assert(!Satisfaction.IsSatisfied &&
"Attempted to diagnose a satisfied constraint");
for (auto &Record : Satisfaction.Details) {
diagnoseUnsatisfiedConstraintExpr(*this, Record, First);
First = false;
}
}
void Sema::DiagnoseUnsatisfiedConstraint(
const ASTConstraintSatisfaction &Satisfaction,
bool First) {
assert(!Satisfaction.IsSatisfied &&
"Attempted to diagnose a satisfied constraint");
for (auto &Record : Satisfaction) {
diagnoseUnsatisfiedConstraintExpr(*this, Record, First);
First = false;
}
}
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());
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;
}
const NormalizedConstraint *clang::getNormalizedAssociatedConstraints(
Sema &S, const NamedDecl *ConstrainedDecl,
ArrayRef<AssociatedConstraint> AssociatedConstraints) {
return S.getNormalizedAssociatedConstraints(ConstrainedDecl,
AssociatedConstraints);
}
static bool
substituteParameterMappings(Sema &S, NormalizedConstraint &N,
ConceptDecl *Concept,
const MultiLevelTemplateArgumentList &MLTAL,
const ASTTemplateArgumentListInfo *ArgsAsWritten) {
if (N.isCompound()) {
if (substituteParameterMappings(S, N.getLHS(), Concept, MLTAL,
ArgsAsWritten))
return true;
return substituteParameterMappings(S, N.getRHS(), Concept, MLTAL,
ArgsAsWritten);
}
if (N.isFoldExpanded()) {
Sema::ArgPackSubstIndexRAII _(S, std::nullopt);
return substituteParameterMappings(
S, N.getFoldExpandedConstraint()->Constraint, Concept, MLTAL,
ArgsAsWritten);
}
TemplateParameterList *TemplateParams = Concept->getTemplateParameters();
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();
Sema::InstantiatingTemplate Inst(
S, InstLocBegin,
Sema::InstantiatingTemplate::ParameterMappingSubstitution{},
const_cast<NamedDecl *>(Atomic.ConstraintDecl),
{InstLocBegin, InstLocEnd});
if (Inst.isInvalid())
return true;
if (S.SubstTemplateArguments(*Atomic.ParameterMapping, MLTAL, SubstArgs))
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());
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);
return substituteParameterMappings(S, N, CSE->getNamedConcept(), MLTAL,
CSE->getTemplateArgsAsWritten());
}
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);
} else {
Constraint = CompoundConstraint(
new (C)
NormalizedConstraintPair{NormalizedConstraint(C, Other.getLHS()),
NormalizedConstraint(C, Other.getRHS())},
Other.getCompoundKind());
}
}
NormalizedConstraint &NormalizedConstraint::getLHS() const {
assert(isCompound() && "getLHS called on a non-compound constraint.");
return cast<CompoundConstraint>(Constraint).getPointer()->LHS;
}
NormalizedConstraint &NormalizedConstraint::getRHS() const {
assert(isCompound() && "getRHS called on a non-compound constraint.");
return cast<CompoundConstraint>(Constraint).getPointer()->RHS;
}
std::optional<NormalizedConstraint>
NormalizedConstraint::fromAssociatedConstraints(
Sema &S, const NamedDecl *D, ArrayRef<AssociatedConstraint> ACs) {
assert(ACs.size() != 0);
auto Conjunction = fromConstraintExpr(S, D, ACs[0].ConstraintExpr);
if (!Conjunction)
return std::nullopt;
for (unsigned I = 1; I < ACs.size(); ++I) {
auto Next = fromConstraintExpr(S, D, ACs[I].ConstraintExpr);
if (!Next)
return std::nullopt;
*Conjunction = NormalizedConstraint(S.Context, std::move(*Conjunction),
std::move(*Next), CCK_Conjunction);
}
return Conjunction;
}
std::optional<NormalizedConstraint>
NormalizedConstraint::fromConstraintExpr(Sema &S, const NamedDecl *D,
const Expr *E) {
assert(E != nullptr);
// C++ [temp.constr.normal]p1.1
// [...]
// - The normal form of an expression (E) is the normal form of E.
// [...]
E = E->IgnoreParenImpCasts();
// C++2a [temp.param]p4:
// [...] If T is not a pack, then E is E', otherwise E is (E' && ...).
// Fold expression is considered atomic constraints per current wording.
// See http://cplusplus.github.io/concepts-ts/ts-active.html#28
if (LogicalBinOp BO = E) {
auto LHS = fromConstraintExpr(S, D, BO.getLHS());
if (!LHS)
return std::nullopt;
auto RHS = fromConstraintExpr(S, D, BO.getRHS());
if (!RHS)
return std::nullopt;
return NormalizedConstraint(S.Context, std::move(*LHS), std::move(*RHS),
BO.isAnd() ? CCK_Conjunction : CCK_Disjunction);
} else if (auto *CSE = dyn_cast<const ConceptSpecializationExpr>(E)) {
const NormalizedConstraint *SubNF;
{
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 std::nullopt;
// 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 Cs
// 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.
// [...]
ConceptDecl *CD = CSE->getNamedConcept();
SubNF = S.getNormalizedAssociatedConstraints(
CD, AssociatedConstraint(CD->getConstraintExpr()));
if (!SubNF)
return std::nullopt;
}
std::optional<NormalizedConstraint> New;
New.emplace(S.Context, *SubNF);
if (substituteParameterMappings(S, *New, CSE))
return std::nullopt;
return New;
} else 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.
FoldExpandedConstraint::FoldOperatorKind Kind =
FE->getOperator() == BinaryOperatorKind::BO_LAnd
? FoldExpandedConstraint::FoldOperatorKind::And
: FoldExpandedConstraint::FoldOperatorKind::Or;
if (FE->getInit()) {
auto LHS = fromConstraintExpr(S, D, FE->getLHS());
auto RHS = fromConstraintExpr(S, D, FE->getRHS());
if (!LHS || !RHS)
return std::nullopt;
if (FE->isRightFold())
RHS = NormalizedConstraint{new (S.Context) FoldExpandedConstraint{
Kind, std::move(*RHS), FE->getPattern()}};
else
LHS = NormalizedConstraint{new (S.Context) FoldExpandedConstraint{
Kind, std::move(*LHS), FE->getPattern()}};
return NormalizedConstraint(
S.Context, std::move(*LHS), std::move(*RHS),
FE->getOperator() == BinaryOperatorKind::BO_LAnd ? CCK_Conjunction
: CCK_Disjunction);
}
auto Sub = fromConstraintExpr(S, D, FE->getPattern());
if (!Sub)
return std::nullopt;
return NormalizedConstraint{new (S.Context) FoldExpandedConstraint{
Kind, std::move(*Sub), FE->getPattern()}};
}
return NormalizedConstraint{new (S.Context) AtomicConstraint(E, D)};
}
bool FoldExpandedConstraint::AreCompatibleForSubsumption(
const FoldExpandedConstraint &A, const FoldExpandedConstraint &B) {
// [C++26] [temp.constr.fold]
// Two fold expanded constraints are compatible for subsumption
// if their respective constraints both contain an equivalent unexpanded pack.
llvm::SmallVector<UnexpandedParameterPack> APacks, BPacks;
Sema::collectUnexpandedParameterPacks(const_cast<Expr *>(A.Pattern), APacks);
Sema::collectUnexpandedParameterPacks(const_cast<Expr *>(B.Pattern), BPacks);
for (const UnexpandedParameterPack &APack : APacks) {
std::pair<unsigned, unsigned> DepthAndIndex = getDepthAndIndex(APack);
auto it = llvm::find_if(BPacks, [&](const UnexpandedParameterPack &BPack) {
return getDepthAndIndex(BPack) == DepthAndIndex;
});
if (it != BPacks.end())
return true;
}
return false;
}
bool Sema::IsAtLeastAsConstrained(const NamedDecl *D1,
MutableArrayRef<AssociatedConstraint> AC1,
const NamedDecl *D2,
MutableArrayRef<AssociatedConstraint> AC2,
bool &Result) {
#ifndef NDEBUG
if (const auto *FD1 = dyn_cast<FunctionDecl>(D1)) {
auto IsExpectedEntity = [](const FunctionDecl *FD) {
FunctionDecl::TemplatedKind Kind = FD->getTemplatedKind();
return Kind == FunctionDecl::TK_NonTemplate ||
Kind == FunctionDecl::TK_FunctionTemplate;
};
const auto *FD2 = dyn_cast<FunctionDecl>(D2);
assert(IsExpectedEntity(FD1) && FD2 && IsExpectedEntity(FD2) &&
"use non-instantiated function declaration for constraints partial "
"ordering");
}
#endif
if (AC1.empty()) {
Result = AC2.empty();
return false;
}
if (AC2.empty()) {
// TD1 has associated constraints and TD2 does not.
Result = true;
return false;
}
std::pair<const NamedDecl *, const NamedDecl *> Key{D1, D2};
auto CacheEntry = SubsumptionCache.find(Key);
if (CacheEntry != SubsumptionCache.end()) {
Result = CacheEntry->second;
return false;
}
unsigned Depth1 = CalculateTemplateDepthForConstraints(*this, D1, true);
unsigned Depth2 = CalculateTemplateDepthForConstraints(*this, D2, true);
for (size_t I = 0; I != AC1.size() && I != AC2.size(); ++I) {
if (Depth2 > Depth1) {
AC1[I].ConstraintExpr =
AdjustConstraintDepth(*this, Depth2 - Depth1)
.TransformExpr(const_cast<Expr *>(AC1[I].ConstraintExpr))
.get();
} else if (Depth1 > Depth2) {
AC2[I].ConstraintExpr =
AdjustConstraintDepth(*this, Depth1 - Depth2)
.TransformExpr(const_cast<Expr *>(AC2[I].ConstraintExpr))
.get();
}
}
SubsumptionChecker SC(*this);
std::optional<bool> Subsumes = SC.Subsumes(D1, AC1, D2, AC2);
if (!Subsumes) {
// Normalization failed
return true;
}
Result = *Subsumes;
SubsumptionCache.try_emplace(Key, *Subsumes);
return false;
}
bool Sema::MaybeEmitAmbiguousAtomicConstraintsDiagnostic(
const NamedDecl *D1, ArrayRef<AssociatedConstraint> AC1,
const NamedDecl *D2, ArrayRef<AssociatedConstraint> AC2) {
if (isSFINAEContext())
// No need to work here because our notes would be discarded.
return false;
if (AC1.empty() || AC2.empty())
return false;
const Expr *AmbiguousAtomic1 = nullptr, *AmbiguousAtomic2 = nullptr;
auto IdenticalExprEvaluator = [&](const AtomicConstraint &A,
const AtomicConstraint &B) {
if (!A.hasMatchingParameterMapping(Context, B))
return false;
const Expr *EA = A.ConstraintExpr, *EB = B.ConstraintExpr;
if (EA == EB)
return true;
// Not the same source level expression - are the expressions
// identical?
llvm::FoldingSetNodeID IDA, IDB;
EA->Profile(IDA, Context, /*Canonical=*/true);
EB->Profile(IDB, Context, /*Canonical=*/true);
if (IDA != IDB)
return false;
AmbiguousAtomic1 = EA;
AmbiguousAtomic2 = EB;
return true;
};
{
// The subsumption checks might cause diagnostics
SFINAETrap Trap(*this);
auto *Normalized1 = getNormalizedAssociatedConstraints(D1, AC1);
if (!Normalized1)
return false;
auto *Normalized2 = getNormalizedAssociatedConstraints(D2, AC2);
if (!Normalized2)
return false;
SubsumptionChecker SC(*this);
bool Is1AtLeastAs2Normally = SC.Subsumes(Normalized1, Normalized2);
bool Is2AtLeastAs1Normally = SC.Subsumes(Normalized2, Normalized1);
SubsumptionChecker SC2(*this, IdenticalExprEvaluator);
bool Is1AtLeastAs2 = SC2.Subsumes(Normalized1, Normalized2);
bool Is2AtLeastAs1 = SC2.Subsumes(Normalized2, Normalized1);
if (Is1AtLeastAs2 == Is1AtLeastAs2Normally &&
Is2AtLeastAs1 == Is2AtLeastAs1Normally)
// Same result - no ambiguity was caused by identical atomic expressions.
return false;
}
// A different result! Some ambiguous atomic constraint(s) caused a difference
assert(AmbiguousAtomic1 && AmbiguousAtomic2);
Diag(AmbiguousAtomic1->getBeginLoc(), diag::note_ambiguous_atomic_constraints)
<< AmbiguousAtomic1->getSourceRange();
Diag(AmbiguousAtomic2->getBeginLoc(),
diag::note_ambiguous_atomic_constraints_similar_expression)
<< AmbiguousAtomic2->getSourceRange();
return true;
}
concepts::ExprRequirement::ExprRequirement(
Expr *E, bool IsSimple, SourceLocation NoexceptLoc,
ReturnTypeRequirement Req, SatisfactionStatus Status,
ConceptSpecializationExpr *SubstitutedConstraintExpr) :
Requirement(IsSimple ? RK_Simple : RK_Compound, Status == SS_Dependent,
Status == SS_Dependent &&
(E->containsUnexpandedParameterPack() ||
Req.containsUnexpandedParameterPack()),
Status == SS_Satisfied), Value(E), NoexceptLoc(NoexceptLoc),
TypeReq(Req), SubstitutedConstraintExpr(SubstitutedConstraintExpr),
Status(Status) {
assert((!IsSimple || (Req.isEmpty() && NoexceptLoc.isInvalid())) &&
"Simple requirement must not have a return type requirement or a "
"noexcept specification");
assert((Status > SS_TypeRequirementSubstitutionFailure && Req.isTypeConstraint()) ==
(SubstitutedConstraintExpr != nullptr));
}
concepts::ExprRequirement::ExprRequirement(
SubstitutionDiagnostic *ExprSubstDiag, bool IsSimple,
SourceLocation NoexceptLoc, ReturnTypeRequirement Req) :
Requirement(IsSimple ? RK_Simple : RK_Compound, Req.isDependent(),
Req.containsUnexpandedParameterPack(), /*IsSatisfied=*/false),
Value(ExprSubstDiag), NoexceptLoc(NoexceptLoc), TypeReq(Req),
Status(SS_ExprSubstitutionFailure) {
assert((!IsSimple || (Req.isEmpty() && NoexceptLoc.isInvalid())) &&
"Simple requirement must not have a return type requirement or a "
"noexcept specification");
}
concepts::ExprRequirement::ReturnTypeRequirement::
ReturnTypeRequirement(TemplateParameterList *TPL) :
TypeConstraintInfo(TPL, false) {
assert(TPL->size() == 1);
const TypeConstraint *TC =
cast<TemplateTypeParmDecl>(TPL->getParam(0))->getTypeConstraint();
assert(TC &&
"TPL must have a template type parameter with a type constraint");
auto *Constraint =
cast<ConceptSpecializationExpr>(TC->getImmediatelyDeclaredConstraint());
bool Dependent =
Constraint->getTemplateArgsAsWritten() &&
TemplateSpecializationType::anyInstantiationDependentTemplateArguments(
Constraint->getTemplateArgsAsWritten()->arguments().drop_front(1));
TypeConstraintInfo.setInt(Dependent ? true : false);
}
concepts::TypeRequirement::TypeRequirement(TypeSourceInfo *T) :
Requirement(RK_Type, T->getType()->isInstantiationDependentType(),
T->getType()->containsUnexpandedParameterPack(),
// We reach this ctor with either dependent types (in which
// IsSatisfied doesn't matter) or with non-dependent type in
// which the existence of the type indicates satisfaction.
/*IsSatisfied=*/true),
Value(T),
Status(T->getType()->isInstantiationDependentType() ? SS_Dependent
: SS_Satisfied) {}
NormalizedConstraint::CompoundConstraintKind
NormalizedConstraint::getCompoundKind() const {
assert(isCompound() && "getCompoundKind on a non-compound constraint..");
return cast<CompoundConstraint>(Constraint).getInt();
}
AtomicConstraint *NormalizedConstraint::getAtomicConstraint() const {
assert(isAtomic() && "getAtomicConstraint called on non-atomic constraint.");
return cast<AtomicConstraint *>(Constraint);
}
FoldExpandedConstraint *
NormalizedConstraint::getFoldExpandedConstraint() const {
assert(isFoldExpanded() &&
"getFoldExpandedConstraint called on non-fold-expanded constraint.");
return cast<FoldExpandedConstraint *>(Constraint);
}
//
//
// ------------------------ Subsumption -----------------------------------
//
//
template <> struct llvm::DenseMapInfo<llvm::FoldingSetNodeID> {
static FoldingSetNodeID getEmptyKey() {
FoldingSetNodeID ID;
ID.AddInteger(std::numeric_limits<unsigned>::max());
return ID;
}
static FoldingSetNodeID getTombstoneKey() {
FoldingSetNodeID ID;
for (unsigned I = 0; I < sizeof(ID) / sizeof(unsigned); ++I) {
ID.AddInteger(std::numeric_limits<unsigned>::max());
}
return ID;
}
static unsigned getHashValue(const FoldingSetNodeID &Val) {
return Val.ComputeHash();
}
static bool isEqual(const FoldingSetNodeID &LHS,
const FoldingSetNodeID &RHS) {
return LHS == RHS;
}
};
SubsumptionChecker::SubsumptionChecker(Sema &SemaRef,
SubsumptionCallable Callable)
: SemaRef(SemaRef), Callable(Callable), NextID(1) {}
uint16_t SubsumptionChecker::getNewLiteralId() {
assert((unsigned(NextID) + 1 < std::numeric_limits<uint16_t>::max()) &&
"too many constraints!");
return NextID++;
}
auto SubsumptionChecker::find(AtomicConstraint *Ori) -> Literal {
auto &Elems = AtomicMap[Ori->ConstraintExpr];
// C++ [temp.constr.order] p2
// - an atomic constraint A subsumes another atomic constraint B
// if and only if the A and B are identical [...]
//
// C++ [temp.constr.atomic] p2
// Two atomic constraints are identical if they are formed from the
// same expression and the targets of the parameter mappings are
// equivalent according to the rules for expressions [...]
// Because subsumption of atomic constraints is an identity
// relationship that does not require further analysis
// We cache the results such that if an atomic constraint literal
// subsumes another, their literal will be the same
llvm::FoldingSetNodeID ID;
const auto &Mapping = Ori->ParameterMapping;
ID.AddBoolean(Mapping.has_value());
if (Mapping) {
for (const TemplateArgumentLoc &TAL : *Mapping) {
SemaRef.getASTContext()
.getCanonicalTemplateArgument(TAL.getArgument())
.Profile(ID, SemaRef.getASTContext());
}
}
auto It = Elems.find(ID);
if (It == Elems.end()) {
It =
Elems
.insert({ID, MappedAtomicConstraint{Ori, Literal{getNewLiteralId(),
Literal::Atomic}}})
.first;
ReverseMap[It->second.ID.Value] = Ori;
}
return It->getSecond().ID;
}
auto SubsumptionChecker::find(FoldExpandedConstraint *Ori) -> Literal {
auto &Elems = FoldMap[Ori->Pattern];
FoldExpendedConstraintKey K;
K.Kind = Ori->Kind;
auto It = llvm::find_if(Elems, [&K](const FoldExpendedConstraintKey &Other) {
return K.Kind == Other.Kind;
});
if (It == Elems.end()) {
K.ID = {getNewLiteralId(), Literal::FoldExpanded};
It = Elems.insert(Elems.end(), std::move(K));
ReverseMap[It->ID.Value] = Ori;
}
return It->ID;
}
auto SubsumptionChecker::CNF(const NormalizedConstraint &C) -> CNFFormula {
return SubsumptionChecker::Normalize<CNFFormula>(C);
}
auto SubsumptionChecker::DNF(const NormalizedConstraint &C) -> DNFFormula {
return SubsumptionChecker::Normalize<DNFFormula>(C);
}
///
/// \brief SubsumptionChecker::Normalize
///
/// Normalize a formula to Conjunctive Normal Form or
/// Disjunctive normal form.
///
/// Each Atomic (and Fold Expanded) constraint gets represented by
/// a single id to reduce space.
///
/// To minimize risks of exponential blow up, if two atomic
/// constraints subsumes each other (same constraint and mapping),
/// they are represented by the same literal.
///
template <typename FormulaType>
FormulaType SubsumptionChecker::Normalize(const NormalizedConstraint &NC) {
FormulaType Res;
auto Add = [&, this](Clause C) {
// Sort each clause and remove duplicates for faster comparisons.
llvm::sort(C);
C.erase(llvm::unique(C), C.end());
AddUniqueClauseToFormula(Res, std::move(C));
};
if (NC.isAtomic())
return {{find(NC.getAtomicConstraint())}};
if (NC.isFoldExpanded())
return {{find(NC.getFoldExpandedConstraint())}};
FormulaType Left, Right;
SemaRef.runWithSufficientStackSpace(SourceLocation(), [&] {
Left = Normalize<FormulaType>(NC.getLHS());
Right = Normalize<FormulaType>(NC.getRHS());
});
if (NC.getCompoundKind() == FormulaType::Kind) {
Res = std::move(Left);
Res.reserve(Left.size() + Right.size());
std::for_each(std::make_move_iterator(Right.begin()),
std::make_move_iterator(Right.end()), Add);
return Res;
}
Res.reserve(Left.size() * Right.size());
for (const auto &LTransform : Left) {
for (const auto &RTransform : Right) {
Clause Combined;
Combined.reserve(LTransform.size() + RTransform.size());
llvm::copy(LTransform, std::back_inserter(Combined));
llvm::copy(RTransform, std::back_inserter(Combined));
Add(std::move(Combined));
}
}
return Res;
}
void SubsumptionChecker::AddUniqueClauseToFormula(Formula &F, Clause C) {
for (auto &Other : F) {
if (llvm::equal(C, Other))
return;
}
F.push_back(C);
}
std::optional<bool> SubsumptionChecker::Subsumes(
const NamedDecl *DP, ArrayRef<AssociatedConstraint> P, const NamedDecl *DQ,
ArrayRef<AssociatedConstraint> Q) {
const NormalizedConstraint *PNormalized =
getNormalizedAssociatedConstraints(SemaRef, DP, P);
if (!PNormalized)
return std::nullopt;
const NormalizedConstraint *QNormalized =
getNormalizedAssociatedConstraints(SemaRef, DQ, Q);
if (!QNormalized)
return std::nullopt;
return Subsumes(PNormalized, QNormalized);
}
bool SubsumptionChecker::Subsumes(const NormalizedConstraint *P,
const NormalizedConstraint *Q) {
DNFFormula DNFP = DNF(*P);
CNFFormula CNFQ = CNF(*Q);
return Subsumes(DNFP, CNFQ);
}
bool SubsumptionChecker::Subsumes(const DNFFormula &PDNF,
const CNFFormula &QCNF) {
for (const auto &Pi : PDNF) {
for (const auto &Qj : QCNF) {
// C++ [temp.constr.order] p2
// - [...] a disjunctive clause Pi subsumes a conjunctive clause Qj if
// and only if there exists an atomic constraint Pia in Pi for which
// there exists an atomic constraint, Qjb, in Qj such that Pia
// subsumes Qjb.
if (!DNFSubsumes(Pi, Qj))
return false;
}
}
return true;
}
bool SubsumptionChecker::DNFSubsumes(const Clause &P, const Clause &Q) {
return llvm::any_of(P, [&](Literal LP) {
return llvm::any_of(Q, [this, LP](Literal LQ) { return Subsumes(LP, LQ); });
});
}
bool SubsumptionChecker::Subsumes(const FoldExpandedConstraint *A,
const FoldExpandedConstraint *B) {
std::pair<const FoldExpandedConstraint *, const FoldExpandedConstraint *> Key{
A, B};
auto It = FoldSubsumptionCache.find(Key);
if (It == FoldSubsumptionCache.end()) {
// C++ [temp.constr.order]
// a fold expanded constraint A subsumes another fold expanded
// constraint B if they are compatible for subsumption, have the same
// fold-operator, and the constraint of A subsumes that of B.
bool DoesSubsume =
A->Kind == B->Kind &&
FoldExpandedConstraint::AreCompatibleForSubsumption(*A, *B) &&
Subsumes(&A->Constraint, &B->Constraint);
It = FoldSubsumptionCache.try_emplace(std::move(Key), DoesSubsume).first;
}
return It->second;
}
bool SubsumptionChecker::Subsumes(Literal A, Literal B) {
if (A.Kind != B.Kind)
return false;
switch (A.Kind) {
case Literal::Atomic:
if (!Callable)
return A.Value == B.Value;
return Callable(
*static_cast<const AtomicConstraint *>(ReverseMap[A.Value]),
*static_cast<const AtomicConstraint *>(ReverseMap[B.Value]));
case Literal::FoldExpanded:
return Subsumes(
static_cast<const FoldExpandedConstraint *>(ReverseMap[A.Value]),
static_cast<const FoldExpandedConstraint *>(ReverseMap[B.Value]));
}
llvm_unreachable("unknown literal kind");
}