The NormalizationCache may be inserted recursively when normalizing template arguments with non-dependent default arguments. Since the ADT doesn't preserve iterator validity, this caused undefined behavior. This is a regression on trunk so there is no release note. Fixes https://github.com/llvm/llvm-project/issues/165238
2824 lines
107 KiB
C++
2824 lines
107 KiB
C++
//===-- 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/ASTConcept.h"
|
||
#include "clang/AST/ASTLambda.h"
|
||
#include "clang/AST/DeclCXX.h"
|
||
#include "clang/AST/ExprConcepts.h"
|
||
#include "clang/AST/RecursiveASTVisitor.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 "llvm/Support/SaveAndRestore.h"
|
||
|
||
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{});
|
||
}
|
||
};
|
||
} // namespace
|
||
|
||
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 = nullptr) {
|
||
E->Profile(ID, S.Context, /*Canonical=*/true);
|
||
if (MLTAL) {
|
||
for (const auto &List : *MLTAL)
|
||
for (const auto &TemplateArg : List.Args)
|
||
S.Context.getCanonicalTemplateArgument(TemplateArg)
|
||
.Profile(ID, S.Context);
|
||
}
|
||
if (S.SatisfactionStackContains(Templ, ID)) {
|
||
S.Diag(E->getExprLoc(), diag::err_constraint_depends_on_self)
|
||
<< E << E->getSourceRange();
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 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;
|
||
}
|
||
|
||
bool AlreadyTransformed(QualType T) {
|
||
if (T.isNull())
|
||
return true;
|
||
|
||
if (T->isInstantiationDependentType() || T->isVariablyModifiedType() ||
|
||
T->containsUnexpandedParameterPack())
|
||
return false;
|
||
return true;
|
||
}
|
||
};
|
||
} // namespace
|
||
|
||
namespace {
|
||
|
||
// FIXME: Convert it to DynamicRecursiveASTVisitor
|
||
class HashParameterMapping : public RecursiveASTVisitor<HashParameterMapping> {
|
||
using inherited = RecursiveASTVisitor<HashParameterMapping>;
|
||
friend inherited;
|
||
|
||
Sema &SemaRef;
|
||
const MultiLevelTemplateArgumentList &TemplateArgs;
|
||
llvm::FoldingSetNodeID &ID;
|
||
llvm::SmallVector<TemplateArgument, 10> UsedTemplateArgs;
|
||
|
||
UnsignedOrNone OuterPackSubstIndex;
|
||
|
||
bool shouldVisitTemplateInstantiations() const { return true; }
|
||
|
||
public:
|
||
HashParameterMapping(Sema &SemaRef,
|
||
const MultiLevelTemplateArgumentList &TemplateArgs,
|
||
llvm::FoldingSetNodeID &ID,
|
||
UnsignedOrNone OuterPackSubstIndex)
|
||
: SemaRef(SemaRef), TemplateArgs(TemplateArgs), ID(ID),
|
||
OuterPackSubstIndex(OuterPackSubstIndex) {}
|
||
|
||
bool VisitTemplateTypeParmType(TemplateTypeParmType *T) {
|
||
// A lambda expression can introduce template parameters that don't have
|
||
// corresponding template arguments yet.
|
||
if (T->getDepth() >= TemplateArgs.getNumLevels())
|
||
return true;
|
||
|
||
// There might not be a corresponding template argument before substituting
|
||
// into the parameter mapping, e.g. a sizeof... expression.
|
||
if (!TemplateArgs.hasTemplateArgument(T->getDepth(), T->getIndex()))
|
||
return true;
|
||
|
||
TemplateArgument Arg = TemplateArgs(T->getDepth(), T->getIndex());
|
||
|
||
if (T->isParameterPack() && SemaRef.ArgPackSubstIndex) {
|
||
assert(Arg.getKind() == TemplateArgument::Pack &&
|
||
"Missing argument pack");
|
||
|
||
Arg = SemaRef.getPackSubstitutedTemplateArgument(Arg);
|
||
}
|
||
|
||
UsedTemplateArgs.push_back(
|
||
SemaRef.Context.getCanonicalTemplateArgument(Arg));
|
||
return true;
|
||
}
|
||
|
||
bool VisitDeclRefExpr(DeclRefExpr *E) {
|
||
NamedDecl *D = E->getDecl();
|
||
NonTypeTemplateParmDecl *NTTP = dyn_cast<NonTypeTemplateParmDecl>(D);
|
||
if (!NTTP)
|
||
return TraverseDecl(D);
|
||
|
||
if (NTTP->getDepth() >= TemplateArgs.getNumLevels())
|
||
return true;
|
||
|
||
if (!TemplateArgs.hasTemplateArgument(NTTP->getDepth(), NTTP->getIndex()))
|
||
return true;
|
||
|
||
TemplateArgument Arg = TemplateArgs(NTTP->getDepth(), NTTP->getPosition());
|
||
if (NTTP->isParameterPack() && SemaRef.ArgPackSubstIndex) {
|
||
assert(Arg.getKind() == TemplateArgument::Pack &&
|
||
"Missing argument pack");
|
||
Arg = SemaRef.getPackSubstitutedTemplateArgument(Arg);
|
||
}
|
||
|
||
UsedTemplateArgs.push_back(
|
||
SemaRef.Context.getCanonicalTemplateArgument(Arg));
|
||
return true;
|
||
}
|
||
|
||
bool VisitTypedefType(TypedefType *TT) {
|
||
return inherited::TraverseType(TT->desugar());
|
||
}
|
||
|
||
bool TraverseDecl(Decl *D) {
|
||
if (auto *VD = dyn_cast<ValueDecl>(D)) {
|
||
if (auto *Var = dyn_cast<VarDecl>(VD))
|
||
TraverseStmt(Var->getInit());
|
||
return TraverseType(VD->getType());
|
||
}
|
||
|
||
return inherited::TraverseDecl(D);
|
||
}
|
||
|
||
bool TraverseCallExpr(CallExpr *CE) {
|
||
inherited::TraverseStmt(CE->getCallee());
|
||
|
||
for (Expr *Arg : CE->arguments())
|
||
inherited::TraverseStmt(Arg);
|
||
|
||
return true;
|
||
}
|
||
|
||
bool TraverseTypeLoc(TypeLoc TL, bool TraverseQualifier = true) {
|
||
// We don't care about TypeLocs. So traverse Types instead.
|
||
return TraverseType(TL.getType().getCanonicalType(), TraverseQualifier);
|
||
}
|
||
|
||
bool TraverseTagType(const TagType *T, bool TraverseQualifier) {
|
||
// T's parent can be dependent while T doesn't have any template arguments.
|
||
// We should have already traversed its qualifier.
|
||
// FIXME: Add an assert to catch cases where we failed to profile the
|
||
// concept.
|
||
return true;
|
||
}
|
||
|
||
bool TraverseInjectedClassNameType(InjectedClassNameType *T,
|
||
bool TraverseQualifier) {
|
||
return TraverseTemplateArguments(T->getTemplateArgs(SemaRef.Context));
|
||
}
|
||
|
||
bool TraverseTemplateArgument(const TemplateArgument &Arg) {
|
||
if (!Arg.containsUnexpandedParameterPack() || Arg.isPackExpansion()) {
|
||
// Act as if we are fully expanding this pack, if it is a PackExpansion.
|
||
Sema::ArgPackSubstIndexRAII _1(SemaRef, std::nullopt);
|
||
llvm::SaveAndRestore<UnsignedOrNone> _2(OuterPackSubstIndex,
|
||
std::nullopt);
|
||
return inherited::TraverseTemplateArgument(Arg);
|
||
}
|
||
|
||
Sema::ArgPackSubstIndexRAII _1(SemaRef, OuterPackSubstIndex);
|
||
return inherited::TraverseTemplateArgument(Arg);
|
||
}
|
||
|
||
bool TraverseSizeOfPackExpr(SizeOfPackExpr *SOPE) {
|
||
return TraverseDecl(SOPE->getPack());
|
||
}
|
||
|
||
bool VisitSubstNonTypeTemplateParmExpr(SubstNonTypeTemplateParmExpr *E) {
|
||
return inherited::TraverseStmt(E->getReplacement());
|
||
}
|
||
|
||
bool TraverseTemplateName(TemplateName Template) {
|
||
if (auto *TTP = dyn_cast_if_present<TemplateTemplateParmDecl>(
|
||
Template.getAsTemplateDecl());
|
||
TTP && TTP->getDepth() < TemplateArgs.getNumLevels()) {
|
||
if (!TemplateArgs.hasTemplateArgument(TTP->getDepth(),
|
||
TTP->getPosition()))
|
||
return true;
|
||
|
||
TemplateArgument Arg = TemplateArgs(TTP->getDepth(), TTP->getPosition());
|
||
if (TTP->isParameterPack() && SemaRef.ArgPackSubstIndex) {
|
||
assert(Arg.getKind() == TemplateArgument::Pack &&
|
||
"Missing argument pack");
|
||
Arg = SemaRef.getPackSubstitutedTemplateArgument(Arg);
|
||
}
|
||
assert(!Arg.getAsTemplate().isNull() &&
|
||
"Null template template argument");
|
||
UsedTemplateArgs.push_back(
|
||
SemaRef.Context.getCanonicalTemplateArgument(Arg));
|
||
}
|
||
return inherited::TraverseTemplateName(Template);
|
||
}
|
||
|
||
void VisitConstraint(const NormalizedConstraintWithParamMapping &Constraint) {
|
||
if (!Constraint.hasParameterMapping()) {
|
||
for (const auto &List : TemplateArgs)
|
||
for (const TemplateArgument &Arg : List.Args)
|
||
SemaRef.Context.getCanonicalTemplateArgument(Arg).Profile(
|
||
ID, SemaRef.Context);
|
||
return;
|
||
}
|
||
|
||
llvm::ArrayRef<TemplateArgumentLoc> Mapping =
|
||
Constraint.getParameterMapping();
|
||
for (auto &ArgLoc : Mapping) {
|
||
TemplateArgument Canonical =
|
||
SemaRef.Context.getCanonicalTemplateArgument(ArgLoc.getArgument());
|
||
// We don't want sugars to impede the profile of cache.
|
||
UsedTemplateArgs.push_back(Canonical);
|
||
TraverseTemplateArgument(Canonical);
|
||
}
|
||
|
||
for (auto &Used : UsedTemplateArgs) {
|
||
llvm::FoldingSetNodeID R;
|
||
Used.Profile(R, SemaRef.Context);
|
||
ID.AddNodeID(R);
|
||
}
|
||
}
|
||
};
|
||
|
||
class ConstraintSatisfactionChecker {
|
||
Sema &S;
|
||
const NamedDecl *Template;
|
||
SourceLocation TemplateNameLoc;
|
||
UnsignedOrNone PackSubstitutionIndex;
|
||
ConstraintSatisfaction &Satisfaction;
|
||
bool BuildExpression;
|
||
|
||
private:
|
||
ExprResult
|
||
EvaluateAtomicConstraint(const Expr *AtomicExpr,
|
||
const MultiLevelTemplateArgumentList &MLTAL);
|
||
|
||
UnsignedOrNone EvaluateFoldExpandedConstraintSize(
|
||
const FoldExpandedConstraint &FE,
|
||
const MultiLevelTemplateArgumentList &MLTAL);
|
||
|
||
// XXX: It is SLOW! Use it very carefully.
|
||
std::optional<MultiLevelTemplateArgumentList> SubstitutionInTemplateArguments(
|
||
const NormalizedConstraintWithParamMapping &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL,
|
||
llvm::SmallVector<TemplateArgument> &SubstitutedOuterMost);
|
||
|
||
ExprResult EvaluateSlow(const AtomicConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL);
|
||
|
||
ExprResult Evaluate(const AtomicConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL);
|
||
|
||
ExprResult EvaluateSlow(const FoldExpandedConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL);
|
||
|
||
ExprResult Evaluate(const FoldExpandedConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL);
|
||
|
||
ExprResult EvaluateSlow(const ConceptIdConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL,
|
||
unsigned int Size);
|
||
|
||
ExprResult Evaluate(const ConceptIdConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL);
|
||
|
||
ExprResult Evaluate(const CompoundConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL);
|
||
|
||
public:
|
||
ConstraintSatisfactionChecker(Sema &SemaRef, const NamedDecl *Template,
|
||
SourceLocation TemplateNameLoc,
|
||
UnsignedOrNone PackSubstitutionIndex,
|
||
ConstraintSatisfaction &Satisfaction,
|
||
bool BuildExpression)
|
||
: S(SemaRef), Template(Template), TemplateNameLoc(TemplateNameLoc),
|
||
PackSubstitutionIndex(PackSubstitutionIndex),
|
||
Satisfaction(Satisfaction), BuildExpression(BuildExpression) {}
|
||
|
||
ExprResult Evaluate(const NormalizedConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL);
|
||
};
|
||
|
||
StringRef allocateStringFromConceptDiagnostic(const Sema &S,
|
||
const PartialDiagnostic Diag) {
|
||
SmallString<128> DiagString;
|
||
DiagString = ": ";
|
||
Diag.EmitToString(S.getDiagnostics(), DiagString);
|
||
return S.getASTContext().backupStr(DiagString);
|
||
}
|
||
|
||
} // namespace
|
||
|
||
ExprResult ConstraintSatisfactionChecker::EvaluateAtomicConstraint(
|
||
const Expr *AtomicExpr, const MultiLevelTemplateArgumentList &MLTAL) {
|
||
EnterExpressionEvaluationContext ConstantEvaluated(
|
||
S, Sema::ExpressionEvaluationContext::ConstantEvaluated,
|
||
Sema::ReuseLambdaContextDecl);
|
||
|
||
llvm::FoldingSetNodeID ID;
|
||
if (Template &&
|
||
DiagRecursiveConstraintEval(S, ID, Template, AtomicExpr, &MLTAL)) {
|
||
Satisfaction.IsSatisfied = false;
|
||
Satisfaction.ContainsErrors = true;
|
||
return ExprEmpty();
|
||
}
|
||
SatisfactionStackRAII StackRAII(S, Template, ID);
|
||
|
||
// Atomic constraint - substitute arguments and check satisfaction.
|
||
ExprResult SubstitutedExpression = const_cast<Expr *>(AtomicExpr);
|
||
{
|
||
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();
|
||
|
||
// 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: 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.
|
||
Satisfaction.Details.emplace_back(
|
||
new (S.Context) ConstraintSubstitutionDiagnostic{
|
||
SubstDiag.first,
|
||
allocateStringFromConceptDiagnostic(S, SubstDiag.second)});
|
||
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;
|
||
}
|
||
|
||
std::optional<MultiLevelTemplateArgumentList>
|
||
ConstraintSatisfactionChecker::SubstitutionInTemplateArguments(
|
||
const NormalizedConstraintWithParamMapping &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL,
|
||
llvm::SmallVector<TemplateArgument> &SubstitutedOutermost) {
|
||
|
||
if (!Constraint.hasParameterMapping())
|
||
return std::move(MLTAL);
|
||
|
||
// The mapping is empty, meaning no template arguments are needed for
|
||
// evaluation.
|
||
if (Constraint.getParameterMapping().empty())
|
||
return MultiLevelTemplateArgumentList();
|
||
|
||
TemplateDeductionInfo Info(Constraint.getBeginLoc());
|
||
Sema::InstantiatingTemplate Inst(
|
||
S, Constraint.getBeginLoc(),
|
||
Sema::InstantiatingTemplate::ConstraintSubstitution{},
|
||
// FIXME: improve const-correctness of InstantiatingTemplate
|
||
const_cast<NamedDecl *>(Template), Info, Constraint.getSourceRange());
|
||
if (Inst.isInvalid())
|
||
return std::nullopt;
|
||
|
||
Sema::SFINAETrap Trap(S);
|
||
|
||
TemplateArgumentListInfo SubstArgs;
|
||
Sema::ArgPackSubstIndexRAII SubstIndex(
|
||
S, Constraint.getPackSubstitutionIndex()
|
||
? Constraint.getPackSubstitutionIndex()
|
||
: PackSubstitutionIndex);
|
||
|
||
if (S.SubstTemplateArgumentsInParameterMapping(
|
||
Constraint.getParameterMapping(), Constraint.getBeginLoc(), MLTAL,
|
||
SubstArgs, /*BuildPackExpansionTypes=*/true)) {
|
||
Satisfaction.IsSatisfied = false;
|
||
return std::nullopt;
|
||
}
|
||
|
||
Sema::CheckTemplateArgumentInfo CTAI;
|
||
auto *TD = const_cast<TemplateDecl *>(
|
||
cast<TemplateDecl>(Constraint.getConstraintDecl()));
|
||
if (S.CheckTemplateArgumentList(TD, Constraint.getUsedTemplateParamList(),
|
||
TD->getLocation(), SubstArgs,
|
||
/*DefaultArguments=*/{},
|
||
/*PartialTemplateArgs=*/false, CTAI))
|
||
return std::nullopt;
|
||
const NormalizedConstraint::OccurenceList &Used =
|
||
Constraint.mappingOccurenceList();
|
||
// The empty MLTAL situation should only occur when evaluating non-dependent
|
||
// constraints.
|
||
if (MLTAL.getNumSubstitutedLevels())
|
||
SubstitutedOutermost =
|
||
llvm::to_vector_of<TemplateArgument>(MLTAL.getOutermost());
|
||
unsigned Offset = 0;
|
||
for (unsigned I = 0, MappedIndex = 0; I < Used.size(); I++) {
|
||
TemplateArgument Arg;
|
||
if (Used[I])
|
||
Arg = S.Context.getCanonicalTemplateArgument(
|
||
CTAI.SugaredConverted[MappedIndex++]);
|
||
if (I < SubstitutedOutermost.size()) {
|
||
SubstitutedOutermost[I] = Arg;
|
||
Offset = I + 1;
|
||
} else {
|
||
SubstitutedOutermost.push_back(Arg);
|
||
Offset = SubstitutedOutermost.size();
|
||
}
|
||
}
|
||
if (Offset < SubstitutedOutermost.size())
|
||
SubstitutedOutermost.erase(SubstitutedOutermost.begin() + Offset);
|
||
|
||
MultiLevelTemplateArgumentList SubstitutedTemplateArgs;
|
||
SubstitutedTemplateArgs.addOuterTemplateArguments(TD, SubstitutedOutermost,
|
||
/*Final=*/false);
|
||
return std::move(SubstitutedTemplateArgs);
|
||
}
|
||
|
||
ExprResult ConstraintSatisfactionChecker::EvaluateSlow(
|
||
const AtomicConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL) {
|
||
|
||
llvm::SmallVector<TemplateArgument> SubstitutedOutermost;
|
||
std::optional<MultiLevelTemplateArgumentList> SubstitutedArgs =
|
||
SubstitutionInTemplateArguments(Constraint, MLTAL, SubstitutedOutermost);
|
||
if (!SubstitutedArgs) {
|
||
Satisfaction.IsSatisfied = false;
|
||
return ExprEmpty();
|
||
}
|
||
|
||
Sema::ArgPackSubstIndexRAII SubstIndex(S, PackSubstitutionIndex);
|
||
ExprResult SubstitutedAtomicExpr = EvaluateAtomicConstraint(
|
||
Constraint.getConstraintExpr(), *SubstitutedArgs);
|
||
|
||
if (SubstitutedAtomicExpr.isInvalid())
|
||
return ExprError();
|
||
|
||
if (SubstitutedAtomicExpr.isUnset())
|
||
// 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);
|
||
Satisfaction.Details.emplace_back(
|
||
new (S.Context) ConstraintSubstitutionDiagnostic{
|
||
SubstitutedAtomicExpr.get()->getBeginLoc(),
|
||
allocateStringFromConceptDiagnostic(S, Msg)});
|
||
return SubstitutedAtomicExpr;
|
||
}
|
||
|
||
if (SubstitutedAtomicExpr.get()->isValueDependent()) {
|
||
Satisfaction.IsSatisfied = true;
|
||
Satisfaction.ContainsErrors = false;
|
||
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;
|
||
}
|
||
|
||
ExprResult ConstraintSatisfactionChecker::Evaluate(
|
||
const AtomicConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL) {
|
||
|
||
unsigned Size = Satisfaction.Details.size();
|
||
llvm::FoldingSetNodeID ID;
|
||
UnsignedOrNone OuterPackSubstIndex =
|
||
Constraint.getPackSubstitutionIndex()
|
||
? Constraint.getPackSubstitutionIndex()
|
||
: PackSubstitutionIndex;
|
||
|
||
ID.AddPointer(Constraint.getConstraintExpr());
|
||
ID.AddInteger(OuterPackSubstIndex.toInternalRepresentation());
|
||
HashParameterMapping(S, MLTAL, ID, OuterPackSubstIndex)
|
||
.VisitConstraint(Constraint);
|
||
|
||
if (auto Iter = S.UnsubstitutedConstraintSatisfactionCache.find(ID);
|
||
Iter != S.UnsubstitutedConstraintSatisfactionCache.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 = EvaluateSlow(Constraint, MLTAL);
|
||
|
||
UnsubstitutedConstraintSatisfactionCacheResult Cache;
|
||
Cache.Satisfaction.ContainsErrors = Satisfaction.ContainsErrors;
|
||
Cache.Satisfaction.IsSatisfied = Satisfaction.IsSatisfied;
|
||
Cache.Satisfaction.Details.insert(Cache.Satisfaction.Details.end(),
|
||
Satisfaction.Details.begin() + Size,
|
||
Satisfaction.Details.end());
|
||
Cache.SubstExpr = E;
|
||
S.UnsubstitutedConstraintSatisfactionCache.insert({ID, std::move(Cache)});
|
||
|
||
return E;
|
||
}
|
||
|
||
UnsignedOrNone
|
||
ConstraintSatisfactionChecker::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 ConstraintSatisfactionChecker::EvaluateSlow(
|
||
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 =
|
||
ConstraintSatisfactionChecker(S, Template, TemplateNameLoc,
|
||
UnsignedOrNone(I), Satisfaction,
|
||
/*BuildExpression=*/false)
|
||
.Evaluate(Constraint.getNormalizedPattern(), *SubstitutedArgs);
|
||
if (BuildExpression && 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(!BuildExpression || !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 ConstraintSatisfactionChecker::Evaluate(
|
||
const FoldExpandedConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL) {
|
||
|
||
llvm::FoldingSetNodeID ID;
|
||
ID.AddPointer(Constraint.getPattern());
|
||
HashParameterMapping(S, MLTAL, ID, std::nullopt).VisitConstraint(Constraint);
|
||
|
||
if (auto Iter = S.UnsubstitutedConstraintSatisfactionCache.find(ID);
|
||
Iter != S.UnsubstitutedConstraintSatisfactionCache.end()) {
|
||
|
||
auto &Cached = Iter->second.Satisfaction;
|
||
Satisfaction.ContainsErrors = Cached.ContainsErrors;
|
||
Satisfaction.IsSatisfied = Cached.IsSatisfied;
|
||
Satisfaction.Details.insert(Satisfaction.Details.end(),
|
||
Cached.Details.begin(), Cached.Details.end());
|
||
return Iter->second.SubstExpr;
|
||
}
|
||
|
||
unsigned Size = Satisfaction.Details.size();
|
||
|
||
ExprResult E = EvaluateSlow(Constraint, MLTAL);
|
||
UnsubstitutedConstraintSatisfactionCacheResult Cache;
|
||
Cache.Satisfaction.ContainsErrors = Satisfaction.ContainsErrors;
|
||
Cache.Satisfaction.IsSatisfied = Satisfaction.IsSatisfied;
|
||
Cache.Satisfaction.Details.insert(Cache.Satisfaction.Details.end(),
|
||
Satisfaction.Details.begin() + Size,
|
||
Satisfaction.Details.end());
|
||
Cache.SubstExpr = E;
|
||
S.UnsubstitutedConstraintSatisfactionCache.insert({ID, std::move(Cache)});
|
||
return E;
|
||
}
|
||
|
||
ExprResult ConstraintSatisfactionChecker::EvaluateSlow(
|
||
const ConceptIdConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL, unsigned Size) {
|
||
const ConceptReference *ConceptId = Constraint.getConceptId();
|
||
|
||
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 =
|
||
ConceptId->getTemplateArgsAsWritten();
|
||
TemplateDeductionInfo Info(TemplateNameLoc);
|
||
Sema::InstantiatingTemplate _(
|
||
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: 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.
|
||
Satisfaction.Details.insert(
|
||
Satisfaction.Details.begin() + Size,
|
||
new (S.Context) ConstraintSubstitutionDiagnostic{
|
||
SubstDiag.first,
|
||
allocateStringFromConceptDiagnostic(S, SubstDiag.second)});
|
||
return ExprError();
|
||
}
|
||
|
||
CXXScopeSpec SS;
|
||
SS.Adopt(ConceptId->getNestedNameSpecifierLoc());
|
||
|
||
ExprResult SubstitutedConceptId = S.CheckConceptTemplateId(
|
||
SS, ConceptId->getTemplateKWLoc(), ConceptId->getConceptNameInfo(),
|
||
ConceptId->getFoundDecl(), ConceptId->getNamedConcept(), &OutArgs,
|
||
/*DoCheckConstraintSatisfaction=*/false);
|
||
|
||
if (SubstitutedConceptId.isInvalid() || Trap.hasErrorOccurred())
|
||
return ExprError();
|
||
|
||
if (Size != Satisfaction.Details.size()) {
|
||
Satisfaction.Details.insert(
|
||
Satisfaction.Details.begin() + Size,
|
||
UnsatisfiedConstraintRecord(
|
||
SubstitutedConceptId.getAs<ConceptSpecializationExpr>()
|
||
->getConceptReference()));
|
||
}
|
||
return SubstitutedConceptId;
|
||
}
|
||
|
||
ExprResult ConstraintSatisfactionChecker::Evaluate(
|
||
const ConceptIdConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL) {
|
||
|
||
const ConceptReference *ConceptId = Constraint.getConceptId();
|
||
|
||
UnsignedOrNone OuterPackSubstIndex =
|
||
Constraint.getPackSubstitutionIndex()
|
||
? Constraint.getPackSubstitutionIndex()
|
||
: PackSubstitutionIndex;
|
||
|
||
Sema::InstantiatingTemplate InstTemplate(
|
||
S, ConceptId->getBeginLoc(),
|
||
Sema::InstantiatingTemplate::ConstraintsCheck{},
|
||
ConceptId->getNamedConcept(),
|
||
// We may have empty template arguments when checking non-dependent
|
||
// nested constraint expressions.
|
||
// In such cases, non-SFINAE errors would have already been diagnosed
|
||
// during parameter mapping substitution, so the instantiating template
|
||
// arguments are less useful here.
|
||
MLTAL.getNumSubstitutedLevels() ? MLTAL.getInnermost()
|
||
: ArrayRef<TemplateArgument>{},
|
||
Constraint.getSourceRange());
|
||
if (InstTemplate.isInvalid())
|
||
return ExprError();
|
||
|
||
unsigned Size = Satisfaction.Details.size();
|
||
|
||
ExprResult E = Evaluate(Constraint.getNormalizedConstraint(), MLTAL);
|
||
|
||
if (E.isInvalid()) {
|
||
Satisfaction.Details.insert(Satisfaction.Details.begin() + Size, ConceptId);
|
||
return E;
|
||
}
|
||
|
||
// ConceptIdConstraint is only relevant for diagnostics,
|
||
// so if the normalized constraint is satisfied, we should not
|
||
// substitute into the constraint.
|
||
if (Satisfaction.IsSatisfied)
|
||
return E;
|
||
|
||
llvm::FoldingSetNodeID ID;
|
||
ID.AddPointer(Constraint.getConceptId());
|
||
ID.AddInteger(OuterPackSubstIndex.toInternalRepresentation());
|
||
HashParameterMapping(S, MLTAL, ID, OuterPackSubstIndex)
|
||
.VisitConstraint(Constraint);
|
||
|
||
if (auto Iter = S.UnsubstitutedConstraintSatisfactionCache.find(ID);
|
||
Iter != S.UnsubstitutedConstraintSatisfactionCache.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 CE = EvaluateSlow(Constraint, MLTAL, Size);
|
||
if (CE.isInvalid())
|
||
return E;
|
||
UnsubstitutedConstraintSatisfactionCacheResult Cache;
|
||
Cache.Satisfaction.ContainsErrors = Satisfaction.ContainsErrors;
|
||
Cache.Satisfaction.IsSatisfied = Satisfaction.IsSatisfied;
|
||
Cache.Satisfaction.Details.insert(Cache.Satisfaction.Details.end(),
|
||
Satisfaction.Details.begin() + Size,
|
||
Satisfaction.Details.end());
|
||
Cache.SubstExpr = CE;
|
||
S.UnsubstitutedConstraintSatisfactionCache.insert({ID, std::move(Cache)});
|
||
return CE;
|
||
}
|
||
|
||
ExprResult ConstraintSatisfactionChecker::Evaluate(
|
||
const CompoundConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL) {
|
||
|
||
unsigned EffectiveDetailEndIndex = Satisfaction.Details.size();
|
||
|
||
bool Conjunction =
|
||
Constraint.getCompoundKind() == NormalizedConstraint::CCK_Conjunction;
|
||
|
||
ExprResult LHS = Evaluate(Constraint.getLHS(), MLTAL);
|
||
|
||
if (Conjunction && (!Satisfaction.IsSatisfied || Satisfaction.ContainsErrors))
|
||
return LHS;
|
||
|
||
if (!Conjunction && !LHS.isInvalid() && Satisfaction.IsSatisfied &&
|
||
!Satisfaction.ContainsErrors)
|
||
return LHS;
|
||
|
||
Satisfaction.ContainsErrors = false;
|
||
Satisfaction.IsSatisfied = false;
|
||
|
||
ExprResult RHS = Evaluate(Constraint.getRHS(), MLTAL);
|
||
|
||
if (!Conjunction && !RHS.isInvalid() && Satisfaction.IsSatisfied &&
|
||
!Satisfaction.ContainsErrors)
|
||
Satisfaction.Details.erase(Satisfaction.Details.begin() +
|
||
EffectiveDetailEndIndex,
|
||
Satisfaction.Details.end());
|
||
|
||
if (!BuildExpression)
|
||
return Satisfaction.ContainsErrors ? ExprError() : ExprEmpty();
|
||
|
||
if (!LHS.isUsable())
|
||
return RHS;
|
||
|
||
if (!RHS.isUsable())
|
||
return LHS;
|
||
|
||
return BinaryOperator::Create(S.Context, LHS.get(), RHS.get(),
|
||
Conjunction ? BinaryOperatorKind::BO_LAnd
|
||
: BinaryOperatorKind::BO_LOr,
|
||
S.Context.BoolTy, VK_PRValue, OK_Ordinary,
|
||
Constraint.getBeginLoc(), FPOptionsOverride{});
|
||
}
|
||
|
||
ExprResult ConstraintSatisfactionChecker::Evaluate(
|
||
const NormalizedConstraint &Constraint,
|
||
const MultiLevelTemplateArgumentList &MLTAL) {
|
||
switch (Constraint.getKind()) {
|
||
case NormalizedConstraint::ConstraintKind::Atomic:
|
||
return Evaluate(static_cast<const AtomicConstraint &>(Constraint), MLTAL);
|
||
|
||
case NormalizedConstraint::ConstraintKind::FoldExpanded:
|
||
return Evaluate(static_cast<const FoldExpandedConstraint &>(Constraint),
|
||
MLTAL);
|
||
|
||
case NormalizedConstraint::ConstraintKind::ConceptId:
|
||
return Evaluate(static_cast<const ConceptIdConstraint &>(Constraint),
|
||
MLTAL);
|
||
|
||
case NormalizedConstraint::ConstraintKind::Compound:
|
||
return Evaluate(static_cast<const CompoundConstraint &>(Constraint), MLTAL);
|
||
}
|
||
llvm_unreachable("Unknown ConstraintKind enum");
|
||
}
|
||
|
||
static bool CheckConstraintSatisfaction(
|
||
Sema &S, const NamedDecl *Template,
|
||
ArrayRef<AssociatedConstraint> AssociatedConstraints,
|
||
const MultiLevelTemplateArgumentList &TemplateArgsLists,
|
||
SourceRange TemplateIDRange, ConstraintSatisfaction &Satisfaction,
|
||
Expr **ConvertedExpr, const ConceptReference *TopLevelConceptId = nullptr) {
|
||
|
||
if (ConvertedExpr)
|
||
*ConvertedExpr = nullptr;
|
||
|
||
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;
|
||
}
|
||
|
||
llvm::ArrayRef<TemplateArgument> Args;
|
||
if (TemplateArgsLists.getNumLevels() != 0)
|
||
Args = TemplateArgsLists.getInnermost();
|
||
|
||
std::optional<Sema::InstantiatingTemplate> SynthesisContext;
|
||
if (!TopLevelConceptId) {
|
||
SynthesisContext.emplace(S, TemplateIDRange.getBegin(),
|
||
Sema::InstantiatingTemplate::ConstraintsCheck{},
|
||
const_cast<NamedDecl *>(Template), Args,
|
||
TemplateIDRange);
|
||
}
|
||
|
||
const NormalizedConstraint *C =
|
||
S.getNormalizedAssociatedConstraints(Template, AssociatedConstraints);
|
||
if (!C) {
|
||
Satisfaction.IsSatisfied = false;
|
||
return true;
|
||
}
|
||
|
||
if (TopLevelConceptId)
|
||
C = ConceptIdConstraint::Create(S.getASTContext(), TopLevelConceptId,
|
||
const_cast<NormalizedConstraint *>(C),
|
||
Template, /*CSE=*/nullptr,
|
||
S.ArgPackSubstIndex);
|
||
|
||
ExprResult Res = ConstraintSatisfactionChecker(
|
||
S, Template, TemplateIDRange.getBegin(),
|
||
S.ArgPackSubstIndex, Satisfaction,
|
||
/*BuildExpression=*/ConvertedExpr != nullptr)
|
||
.Evaluate(*C, TemplateArgsLists);
|
||
|
||
if (Res.isInvalid())
|
||
return true;
|
||
|
||
if (Res.isUsable() && ConvertedExpr)
|
||
*ConvertedExpr = Res.get();
|
||
|
||
return false;
|
||
}
|
||
|
||
bool Sema::CheckConstraintSatisfaction(
|
||
ConstrainedDeclOrNestedRequirement Entity,
|
||
ArrayRef<AssociatedConstraint> AssociatedConstraints,
|
||
const MultiLevelTemplateArgumentList &TemplateArgsLists,
|
||
SourceRange TemplateIDRange, ConstraintSatisfaction &OutSatisfaction,
|
||
const ConceptReference *TopLevelConceptId, Expr **ConvertedExpr) {
|
||
if (AssociatedConstraints.empty()) {
|
||
OutSatisfaction.IsSatisfied = true;
|
||
return false;
|
||
}
|
||
const auto *Template = Entity.dyn_cast<const NamedDecl *>();
|
||
if (!Template) {
|
||
return ::CheckConstraintSatisfaction(
|
||
*this, nullptr, AssociatedConstraints, TemplateArgsLists,
|
||
TemplateIDRange, OutSatisfaction, ConvertedExpr, TopLevelConceptId);
|
||
}
|
||
// 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)
|
||
for (const TemplateArgument &Arg : List.Args)
|
||
FlattenedArgs.emplace_back(Context.getCanonicalTemplateArgument(Arg));
|
||
|
||
const NamedDecl *Owner = Template;
|
||
if (TopLevelConceptId)
|
||
Owner = TopLevelConceptId->getNamedConcept();
|
||
|
||
llvm::FoldingSetNodeID ID;
|
||
ConstraintSatisfaction::Profile(ID, Context, Owner, FlattenedArgs);
|
||
void *InsertPos;
|
||
if (auto *Cached = SatisfactionCache.FindNodeOrInsertPos(ID, InsertPos)) {
|
||
OutSatisfaction = *Cached;
|
||
return false;
|
||
}
|
||
|
||
auto Satisfaction =
|
||
std::make_unique<ConstraintSatisfaction>(Owner, FlattenedArgs);
|
||
if (::CheckConstraintSatisfaction(
|
||
*this, Template, AssociatedConstraints, TemplateArgsLists,
|
||
TemplateIDRange, *Satisfaction, ConvertedExpr, TopLevelConceptId)) {
|
||
OutSatisfaction = std::move(*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;
|
||
}
|
||
|
||
static ExprResult
|
||
SubstituteConceptsInConstraintExpression(Sema &S, const NamedDecl *D,
|
||
const ConceptSpecializationExpr *CSE,
|
||
UnsignedOrNone SubstIndex) {
|
||
|
||
// [C++2c] [temp.constr.normal]
|
||
// Otherwise, to form CE, any non-dependent concept template argument Ai
|
||
// is substituted into the constraint-expression of C.
|
||
// If any such substitution results in an invalid concept-id,
|
||
// the program is ill-formed; no diagnostic is required.
|
||
|
||
ConceptDecl *Concept = CSE->getNamedConcept()->getCanonicalDecl();
|
||
Sema::ArgPackSubstIndexRAII _(S, SubstIndex);
|
||
|
||
const ASTTemplateArgumentListInfo *ArgsAsWritten =
|
||
CSE->getTemplateArgsAsWritten();
|
||
if (llvm::none_of(
|
||
ArgsAsWritten->arguments(), [&](const TemplateArgumentLoc &ArgLoc) {
|
||
return !ArgLoc.getArgument().isDependent() &&
|
||
ArgLoc.getArgument().isConceptOrConceptTemplateParameter();
|
||
})) {
|
||
return Concept->getConstraintExpr();
|
||
}
|
||
|
||
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
|
||
Concept, Concept->getLexicalDeclContext(),
|
||
/*Final=*/false, CSE->getTemplateArguments(),
|
||
/*RelativeToPrimary=*/true,
|
||
/*Pattern=*/nullptr,
|
||
/*ForConstraintInstantiation=*/true);
|
||
return S.SubstConceptTemplateArguments(CSE, Concept->getConstraintExpr(),
|
||
MLTAL);
|
||
}
|
||
|
||
bool Sema::CheckConstraintSatisfaction(
|
||
const ConceptSpecializationExpr *ConstraintExpr,
|
||
ConstraintSatisfaction &Satisfaction) {
|
||
|
||
ExprResult Res = SubstituteConceptsInConstraintExpression(
|
||
*this, nullptr, ConstraintExpr, ArgPackSubstIndex);
|
||
if (!Res.isUsable())
|
||
return true;
|
||
|
||
llvm::SmallVector<AssociatedConstraint, 1> Constraints;
|
||
Constraints.emplace_back(Res.get());
|
||
|
||
MultiLevelTemplateArgumentList MLTAL(ConstraintExpr->getNamedConcept(),
|
||
ConstraintExpr->getTemplateArguments(),
|
||
true);
|
||
|
||
return CheckConstraintSatisfaction(
|
||
ConstraintExpr->getNamedConcept(), Constraints, MLTAL,
|
||
ConstraintExpr->getSourceRange(), Satisfaction,
|
||
ConstraintExpr->getConceptReference());
|
||
}
|
||
|
||
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
|
||
// CheckFunctionTemplateConstraints 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);
|
||
}
|
||
|
||
static const Expr *SubstituteConstraintExpressionWithoutSatisfaction(
|
||
Sema &S, const Sema::TemplateCompareNewDeclInfo &DeclInfo,
|
||
const Expr *ConstrExpr) {
|
||
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
|
||
DeclInfo.getDecl(), DeclInfo.getDeclContext(), /*Final=*/false,
|
||
/*Innermost=*/std::nullopt,
|
||
/*RelativeToPrimary=*/true,
|
||
/*Pattern=*/nullptr, /*ForConstraintInstantiation=*/true,
|
||
/*SkipForSpecialization*/ false);
|
||
|
||
if (MLTAL.getNumSubstitutedLevels() == 0)
|
||
return ConstrExpr;
|
||
|
||
Sema::SFINAETrap SFINAE(S);
|
||
|
||
Sema::InstantiatingTemplate Inst(
|
||
S, DeclInfo.getLocation(),
|
||
Sema::InstantiatingTemplate::ConstraintNormalization{},
|
||
const_cast<NamedDecl *>(DeclInfo.getDecl()), SourceRange{});
|
||
if (Inst.isInvalid())
|
||
return nullptr;
|
||
|
||
// Set up a dummy 'instantiation' scope in the case of reference to function
|
||
// parameters that the surrounding function hasn't been instantiated yet. Note
|
||
// this may happen while we're comparing two templates' constraint
|
||
// equivalence.
|
||
std::optional<LocalInstantiationScope> ScopeForParameters;
|
||
if (const NamedDecl *ND = DeclInfo.getDecl();
|
||
ND && ND->isFunctionOrFunctionTemplate()) {
|
||
ScopeForParameters.emplace(S, /*CombineWithOuterScope=*/true);
|
||
const FunctionDecl *FD = ND->getAsFunction();
|
||
if (FunctionTemplateDecl *Template = FD->getDescribedFunctionTemplate();
|
||
Template && Template->getInstantiatedFromMemberTemplate())
|
||
FD = Template->getInstantiatedFromMemberTemplate()->getTemplatedDecl();
|
||
for (auto *PVD : FD->parameters()) {
|
||
if (ScopeForParameters->getInstantiationOfIfExists(PVD))
|
||
continue;
|
||
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;
|
||
}
|
||
|
||
static bool CheckFunctionConstraintsWithoutInstantiation(
|
||
Sema &SemaRef, SourceLocation PointOfInstantiation,
|
||
FunctionTemplateDecl *Template, ArrayRef<TemplateArgument> TemplateArgs,
|
||
ConstraintSatisfaction &Satisfaction) {
|
||
SmallVector<AssociatedConstraint, 3> TemplateAC;
|
||
Template->getAssociatedConstraints(TemplateAC);
|
||
if (TemplateAC.empty()) {
|
||
Satisfaction.IsSatisfied = true;
|
||
return false;
|
||
}
|
||
|
||
LocalInstantiationScope Scope(SemaRef);
|
||
|
||
FunctionDecl *FD = Template->getTemplatedDecl();
|
||
// 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.
|
||
|
||
MultiLevelTemplateArgumentList MLTAL;
|
||
{
|
||
// getTemplateInstantiationArgs uses this instantiation context to find out
|
||
// template arguments for uninstantiated functions.
|
||
// We don't want this RAII object to persist, because there would be
|
||
// otherwise duplicate diagnostic notes.
|
||
Sema::InstantiatingTemplate Inst(
|
||
SemaRef, PointOfInstantiation,
|
||
Sema::InstantiatingTemplate::ConstraintsCheck{}, Template, TemplateArgs,
|
||
PointOfInstantiation);
|
||
if (Inst.isInvalid())
|
||
return true;
|
||
MLTAL = SemaRef.getTemplateInstantiationArgs(
|
||
/*D=*/FD, FD,
|
||
/*Final=*/false, /*Innermost=*/{}, /*RelativeToPrimary=*/true,
|
||
/*Pattern=*/nullptr, /*ForConstraintInstantiation=*/true);
|
||
}
|
||
|
||
Sema::ContextRAII SavedContext(SemaRef, FD);
|
||
return SemaRef.CheckConstraintSatisfaction(
|
||
Template, TemplateAC, MLTAL, PointOfInstantiation, Satisfaction);
|
||
}
|
||
|
||
bool Sema::CheckFunctionTemplateConstraints(
|
||
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();
|
||
|
||
if (!Template)
|
||
return ::CheckFunctionConstraintsWithoutInstantiation(
|
||
*this, PointOfInstantiation, Decl->getDescribedFunctionTemplate(),
|
||
TemplateArgs, Satisfaction);
|
||
|
||
// 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, 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();
|
||
S.DiagnoseUnsatisfiedConstraint(ConstraintExpr);
|
||
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 diagnoseUnsatisfiedConceptIdExpr(Sema &S,
|
||
const ConceptReference *Concept,
|
||
SourceLocation Loc, bool First) {
|
||
if (Concept->getTemplateArgsAsWritten()->NumTemplateArgs == 1) {
|
||
S.Diag(
|
||
Loc,
|
||
diag::
|
||
note_single_arg_concept_specialization_constraint_evaluated_to_false)
|
||
<< (int)First
|
||
<< Concept->getTemplateArgsAsWritten()->arguments()[0].getArgument()
|
||
<< Concept->getNamedConcept();
|
||
} else {
|
||
S.Diag(Loc, diag::note_concept_specialization_constraint_evaluated_to_false)
|
||
<< (int)First << Concept;
|
||
}
|
||
}
|
||
|
||
static void diagnoseUnsatisfiedConstraintExpr(
|
||
Sema &S, const UnsatisfiedConstraintRecord &Record, SourceLocation Loc,
|
||
bool First, concepts::NestedRequirement *Req = nullptr);
|
||
|
||
static void DiagnoseUnsatisfiedConstraint(
|
||
Sema &S, ArrayRef<UnsatisfiedConstraintRecord> Records, SourceLocation Loc,
|
||
bool First = true, concepts::NestedRequirement *Req = nullptr) {
|
||
for (auto &Record : Records) {
|
||
diagnoseUnsatisfiedConstraintExpr(S, Record, Loc, First, Req);
|
||
Loc = {};
|
||
First = isa<const ConceptReference *>(Record);
|
||
}
|
||
}
|
||
|
||
static void diagnoseUnsatisfiedRequirement(Sema &S,
|
||
concepts::NestedRequirement *Req,
|
||
bool First) {
|
||
DiagnoseUnsatisfiedConstraint(S, Req->getConstraintSatisfaction().records(),
|
||
Req->hasInvalidConstraint()
|
||
? SourceLocation()
|
||
: Req->getConstraintExpr()->getExprLoc(),
|
||
First, Req);
|
||
}
|
||
|
||
static void diagnoseWellFormedUnsatisfiedConstraintExpr(Sema &S,
|
||
const Expr *SubstExpr,
|
||
bool First) {
|
||
SubstExpr = SubstExpr->IgnoreParenImpCasts();
|
||
if (const 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 *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 *CSE = dyn_cast<ConceptSpecializationExpr>(SubstExpr)) {
|
||
// Drill down concept ids treated as atomic constraints
|
||
S.DiagnoseUnsatisfiedConstraint(CSE, First);
|
||
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;
|
||
S.DiagnoseTypeTraitDetails(SubstExpr);
|
||
}
|
||
|
||
static void diagnoseUnsatisfiedConstraintExpr(
|
||
Sema &S, const UnsatisfiedConstraintRecord &Record, SourceLocation Loc,
|
||
bool First, concepts::NestedRequirement *Req) {
|
||
if (auto *Diag =
|
||
Record
|
||
.template dyn_cast<const ConstraintSubstitutionDiagnostic *>()) {
|
||
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;
|
||
}
|
||
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, SourceLocation Loc,
|
||
bool First) {
|
||
|
||
assert(!Satisfaction.IsSatisfied &&
|
||
"Attempted to diagnose a satisfied constraint");
|
||
::DiagnoseUnsatisfiedConstraint(*this, Satisfaction.Details, Loc, First);
|
||
}
|
||
|
||
void Sema::DiagnoseUnsatisfiedConstraint(
|
||
const ConceptSpecializationExpr *ConstraintExpr, bool First) {
|
||
|
||
const ASTConstraintSatisfaction &Satisfaction =
|
||
ConstraintExpr->getSatisfaction();
|
||
|
||
assert(!Satisfaction.IsSatisfied &&
|
||
"Attempted to diagnose a satisfied constraint");
|
||
|
||
::DiagnoseUnsatisfiedConstraint(*this, Satisfaction.records(),
|
||
ConstraintExpr->getBeginLoc(), First);
|
||
}
|
||
|
||
namespace {
|
||
|
||
class SubstituteParameterMappings {
|
||
Sema &SemaRef;
|
||
|
||
const MultiLevelTemplateArgumentList *MLTAL;
|
||
const ASTTemplateArgumentListInfo *ArgsAsWritten;
|
||
|
||
bool InFoldExpr;
|
||
|
||
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);
|
||
};
|
||
|
||
void SubstituteParameterMappings::buildParameterMapping(
|
||
NormalizedConstraintWithParamMapping &N) {
|
||
TemplateParameterList *TemplateParams =
|
||
cast<TemplateDecl>(N.getConstraintDecl())->getTemplateParameters();
|
||
|
||
llvm::SmallBitVector OccurringIndices(TemplateParams->size());
|
||
llvm::SmallBitVector OccurringIndicesForSubsumption(TemplateParams->size());
|
||
|
||
if (N.getKind() == NormalizedConstraint::ConstraintKind::Atomic) {
|
||
SemaRef.MarkUsedTemplateParameters(
|
||
static_cast<AtomicConstraint &>(N).getConstraintExpr(),
|
||
/*OnlyDeduced=*/false,
|
||
/*Depth=*/0, OccurringIndices);
|
||
|
||
SemaRef.MarkUsedTemplateParametersForSubsumptionParameterMapping(
|
||
static_cast<AtomicConstraint &>(N).getConstraintExpr(),
|
||
/*Depth=*/0, OccurringIndicesForSubsumption);
|
||
|
||
} 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);
|
||
}
|
||
unsigned Size = OccurringIndices.count();
|
||
// When the constraint is independent of any template parameters,
|
||
// we build an empty mapping so that we can distinguish these cases
|
||
// from cases where no mapping exists at all, e.g. when there are only atomic
|
||
// constraints.
|
||
TemplateArgumentLoc *TempArgs =
|
||
new (SemaRef.Context) TemplateArgumentLoc[Size];
|
||
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(
|
||
std::move(OccurringIndices), std::move(OccurringIndicesForSubsumption),
|
||
MutableArrayRef<TemplateArgumentLoc>{TempArgs, Size}, UsedList);
|
||
}
|
||
|
||
bool SubstituteParameterMappings::substitute(
|
||
NormalizedConstraintWithParamMapping &N) {
|
||
if (!N.hasParameterMapping())
|
||
buildParameterMapping(N);
|
||
|
||
// If the parameter mapping is empty, there is nothing to substitute.
|
||
if (N.getParameterMapping().empty())
|
||
return false;
|
||
|
||
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(
|
||
SemaRef, InstLocBegin,
|
||
Sema::InstantiatingTemplate::ParameterMappingSubstitution{},
|
||
const_cast<NamedDecl *>(N.getConstraintDecl()),
|
||
{InstLocBegin, InstLocEnd});
|
||
if (Inst.isInvalid())
|
||
return true;
|
||
|
||
// 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 (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(),
|
||
N.mappingOccurenceListForSubsumption(), Mapping,
|
||
N.getUsedTemplateParamList());
|
||
return false;
|
||
}
|
||
|
||
bool SubstituteParameterMappings::substitute(ConceptIdConstraint &CC) {
|
||
assert(CC.getConstraintDecl() && MLTAL && ArgsAsWritten);
|
||
|
||
if (substitute(static_cast<NormalizedConstraintWithParamMapping &>(CC)))
|
||
return true;
|
||
|
||
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 {
|
||
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;
|
||
|
||
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(CSE->getNamedConcept(),
|
||
CTAI.SugaredConverted);
|
||
return SubstituteParameterMappings(SemaRef, &TemplateArgs, ArgsAsWritten,
|
||
InFoldExpr)
|
||
.substitute(CC.getNormalizedConstraint());
|
||
}
|
||
|
||
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);
|
||
|
||
return SubstituteParameterMappings(
|
||
SemaRef, &MLTAL, CSE->getTemplateArgsAsWritten(), InFoldExpr)
|
||
.substitute(CC.getNormalizedConstraint());
|
||
}
|
||
case NormalizedConstraint::ConstraintKind::Compound: {
|
||
auto &Compound = static_cast<CompoundConstraint &>(N);
|
||
if (substitute(Compound.getLHS()))
|
||
return true;
|
||
return substitute(Compound.getRHS());
|
||
}
|
||
}
|
||
llvm_unreachable("Unknown ConstraintKind enum");
|
||
}
|
||
|
||
} // namespace
|
||
|
||
NormalizedConstraint *NormalizedConstraint::fromAssociatedConstraints(
|
||
Sema &S, const NamedDecl *D, ArrayRef<AssociatedConstraint> ACs) {
|
||
assert(ACs.size() != 0);
|
||
auto *Conjunction =
|
||
fromConstraintExpr(S, D, ACs[0].ConstraintExpr, ACs[0].ArgPackSubstIndex);
|
||
if (!Conjunction)
|
||
return nullptr;
|
||
for (unsigned I = 1; I < ACs.size(); ++I) {
|
||
auto *Next = fromConstraintExpr(S, D, ACs[I].ConstraintExpr,
|
||
ACs[I].ArgPackSubstIndex);
|
||
if (!Next)
|
||
return nullptr;
|
||
Conjunction = CompoundConstraint::CreateConjunction(S.getASTContext(),
|
||
Conjunction, Next);
|
||
}
|
||
return Conjunction;
|
||
}
|
||
|
||
NormalizedConstraint *NormalizedConstraint::fromConstraintExpr(
|
||
Sema &S, const NamedDecl *D, const Expr *E, UnsignedOrNone SubstIndex) {
|
||
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();
|
||
|
||
llvm::FoldingSetNodeID ID;
|
||
if (D && DiagRecursiveConstraintEval(S, ID, D, E)) {
|
||
return nullptr;
|
||
}
|
||
SatisfactionStackRAII StackRAII(S, D, ID);
|
||
|
||
// 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(), SubstIndex);
|
||
if (!LHS)
|
||
return nullptr;
|
||
auto *RHS = fromConstraintExpr(S, D, BO.getRHS(), SubstIndex);
|
||
if (!RHS)
|
||
return nullptr;
|
||
|
||
return CompoundConstraint::Create(
|
||
S.Context, LHS, BO.isAnd() ? CCK_Conjunction : CCK_Disjunction, RHS);
|
||
} else if (auto *CSE = dyn_cast<const ConceptSpecializationExpr>(E)) {
|
||
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 nullptr;
|
||
// C++ [temp.constr.normal]p1.1
|
||
// [...]
|
||
// The normal form of an id-expression of the form C<A1, A2, ..., AN>,
|
||
// where C names a concept, is the normal form of the
|
||
// constraint-expression of C, after substituting A1, A2, ..., AN for C’s
|
||
// respective template parameters in the parameter mappings in each atomic
|
||
// constraint. If any such substitution results in an invalid type or
|
||
// expression, the program is ill-formed; no diagnostic is required.
|
||
// [...]
|
||
|
||
// Use canonical declarations to merge ConceptDecls across
|
||
// different modules.
|
||
ConceptDecl *CD = CSE->getNamedConcept()->getCanonicalDecl();
|
||
|
||
ExprResult Res =
|
||
SubstituteConceptsInConstraintExpression(S, D, CSE, SubstIndex);
|
||
if (!Res.isUsable())
|
||
return nullptr;
|
||
|
||
SubNF = NormalizedConstraint::fromAssociatedConstraints(
|
||
S, CD, AssociatedConstraint(Res.get(), SubstIndex));
|
||
|
||
if (!SubNF)
|
||
return nullptr;
|
||
}
|
||
|
||
return ConceptIdConstraint::Create(S.getASTContext(),
|
||
CSE->getConceptReference(), SubNF, D,
|
||
CSE, SubstIndex);
|
||
|
||
} else if (auto *FE = dyn_cast<const CXXFoldExpr>(E);
|
||
FE && S.getLangOpts().CPlusPlus26 &&
|
||
(FE->getOperator() == BinaryOperatorKind::BO_LAnd ||
|
||
FE->getOperator() == BinaryOperatorKind::BO_LOr)) {
|
||
|
||
// 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(), SubstIndex);
|
||
auto *RHS = fromConstraintExpr(S, D, FE->getRHS(), SubstIndex);
|
||
if (!LHS || !RHS)
|
||
return nullptr;
|
||
|
||
if (FE->isRightFold())
|
||
LHS = FoldExpandedConstraint::Create(S.getASTContext(),
|
||
FE->getPattern(), D, Kind, LHS);
|
||
else
|
||
RHS = FoldExpandedConstraint::Create(S.getASTContext(),
|
||
FE->getPattern(), D, Kind, RHS);
|
||
|
||
return CompoundConstraint::Create(
|
||
S.getASTContext(), LHS,
|
||
(FE->getOperator() == BinaryOperatorKind::BO_LAnd ? CCK_Conjunction
|
||
: CCK_Disjunction),
|
||
RHS);
|
||
}
|
||
auto *Sub = fromConstraintExpr(S, D, FE->getPattern(), SubstIndex);
|
||
if (!Sub)
|
||
return nullptr;
|
||
return FoldExpandedConstraint::Create(S.getASTContext(), FE->getPattern(),
|
||
D, Kind, Sub);
|
||
}
|
||
return AtomicConstraint::Create(S.getASTContext(), E, D, SubstIndex);
|
||
}
|
||
|
||
const NormalizedConstraint *Sema::getNormalizedAssociatedConstraints(
|
||
ConstrainedDeclOrNestedRequirement ConstrainedDeclOrNestedReq,
|
||
ArrayRef<AssociatedConstraint> AssociatedConstraints) {
|
||
if (!ConstrainedDeclOrNestedReq) {
|
||
auto *Normalized = NormalizedConstraint::fromAssociatedConstraints(
|
||
*this, nullptr, AssociatedConstraints);
|
||
if (!Normalized ||
|
||
SubstituteParameterMappings(*this).substitute(*Normalized))
|
||
return nullptr;
|
||
|
||
return Normalized;
|
||
}
|
||
|
||
// FIXME: ConstrainedDeclOrNestedReq is never a NestedRequirement!
|
||
const NamedDecl *ND =
|
||
ConstrainedDeclOrNestedReq.dyn_cast<const NamedDecl *>();
|
||
auto CacheEntry = NormalizationCache.find(ConstrainedDeclOrNestedReq);
|
||
if (CacheEntry == NormalizationCache.end()) {
|
||
auto *Normalized = NormalizedConstraint::fromAssociatedConstraints(
|
||
*this, ND, AssociatedConstraints);
|
||
if (!Normalized) {
|
||
NormalizationCache.try_emplace(ConstrainedDeclOrNestedReq, nullptr);
|
||
return nullptr;
|
||
}
|
||
// substitute() can invalidate iterators of NormalizationCache.
|
||
bool Failed = SubstituteParameterMappings(*this).substitute(*Normalized);
|
||
CacheEntry =
|
||
NormalizationCache.try_emplace(ConstrainedDeclOrNestedReq, Normalized)
|
||
.first;
|
||
if (Failed)
|
||
return nullptr;
|
||
}
|
||
return CacheEntry->second;
|
||
}
|
||
|
||
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.getPattern()),
|
||
APacks);
|
||
Sema::collectUnexpandedParameterPacks(const_cast<Expr *>(B.getPattern()),
|
||
BPacks);
|
||
|
||
for (const UnexpandedParameterPack &APack : APacks) {
|
||
auto ADI = getDepthAndIndex(APack);
|
||
if (!ADI)
|
||
continue;
|
||
auto It = llvm::find_if(BPacks, [&](const UnexpandedParameterPack &BPack) {
|
||
return getDepthAndIndex(BPack) == ADI;
|
||
});
|
||
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.getConstraintExpr(), *EB = B.getConstraintExpr();
|
||
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;
|
||
}
|
||
|
||
//
|
||
//
|
||
// ------------------------ Subsumption -----------------------------------
|
||
//
|
||
//
|
||
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(const AtomicConstraint *Ori) -> Literal {
|
||
auto &Elems = AtomicMap[Ori->getConstraintExpr()];
|
||
// 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;
|
||
ID.AddBoolean(Ori->hasParameterMapping());
|
||
if (Ori->hasParameterMapping()) {
|
||
const auto &Mapping = Ori->getParameterMapping();
|
||
const NormalizedConstraint::OccurenceList &Indexes =
|
||
Ori->mappingOccurenceListForSubsumption();
|
||
for (auto [Idx, TAL] : llvm::enumerate(Mapping)) {
|
||
if (Indexes[Idx])
|
||
SemaRef.getASTContext()
|
||
.getCanonicalTemplateArgument(TAL.getArgument())
|
||
.Profile(ID, SemaRef.getASTContext());
|
||
}
|
||
}
|
||
auto It = Elems.find(ID);
|
||
if (It == Elems.end()) {
|
||
It = Elems
|
||
.insert({ID,
|
||
MappedAtomicConstraint{
|
||
Ori, {getNewLiteralId(), Literal::Atomic}}})
|
||
.first;
|
||
ReverseMap[It->second.ID.Value] = Ori;
|
||
}
|
||
return It->getSecond().ID;
|
||
}
|
||
|
||
auto SubsumptionChecker::find(const FoldExpandedConstraint *Ori) -> Literal {
|
||
auto &Elems = FoldMap[Ori->getPattern()];
|
||
|
||
FoldExpendedConstraintKey K;
|
||
K.Kind = Ori->getFoldOperator();
|
||
|
||
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));
|
||
};
|
||
|
||
switch (NC.getKind()) {
|
||
case NormalizedConstraint::ConstraintKind::Atomic:
|
||
return {{find(&static_cast<const AtomicConstraint &>(NC))}};
|
||
|
||
case NormalizedConstraint::ConstraintKind::FoldExpanded:
|
||
return {{find(&static_cast<const FoldExpandedConstraint &>(NC))}};
|
||
|
||
case NormalizedConstraint::ConstraintKind::ConceptId:
|
||
return Normalize<FormulaType>(
|
||
static_cast<const ConceptIdConstraint &>(NC).getNormalizedConstraint());
|
||
|
||
case NormalizedConstraint::ConstraintKind::Compound: {
|
||
const auto &Compound = static_cast<const CompoundConstraint &>(NC);
|
||
FormulaType Left, Right;
|
||
SemaRef.runWithSufficientStackSpace(SourceLocation(), [&] {
|
||
Left = Normalize<FormulaType>(Compound.getLHS());
|
||
Right = Normalize<FormulaType>(Compound.getRHS());
|
||
});
|
||
|
||
if (Compound.getCompoundKind() == FormulaType::Kind) {
|
||
unsigned SizeLeft = Left.size();
|
||
Res = std::move(Left);
|
||
Res.reserve(SizeLeft + 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 <ransform : 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;
|
||
}
|
||
}
|
||
llvm_unreachable("Unknown ConstraintKind enum");
|
||
}
|
||
|
||
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 =
|
||
SemaRef.getNormalizedAssociatedConstraints(DP, P);
|
||
if (!PNormalized)
|
||
return std::nullopt;
|
||
|
||
const NormalizedConstraint *QNormalized =
|
||
SemaRef.getNormalizedAssociatedConstraints(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->getFoldOperator() == B->getFoldOperator() &&
|
||
FoldExpandedConstraint::AreCompatibleForSubsumption(*A, *B) &&
|
||
Subsumes(&A->getNormalizedPattern(), &B->getNormalizedPattern());
|
||
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");
|
||
}
|