mirror of
https://github.com/intel/llvm.git
synced 2026-01-19 17:45:07 +08:00
Evaluating concepts can take quite a bit of time and it's not necessarily obvious what's part of that and what not. This scope makes it clear which parts are concept evaluation and where they are invoked.
2827 lines
108 KiB
C++
2827 lines
108 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"
|
||
#include "llvm/Support/TimeProfiler.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), AtomicExpr->getSourceRange());
|
||
if (Inst.isInvalid())
|
||
return ExprError();
|
||
|
||
// We do not want error diagnostics escaping here.
|
||
Sema::SFINAETrap Trap(S, Info);
|
||
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::SFINAETrap Trap(S, Info);
|
||
Sema::InstantiatingTemplate Inst(
|
||
S, Constraint.getBeginLoc(),
|
||
Sema::InstantiatingTemplate::ConstraintSubstitution{},
|
||
// FIXME: improve const-correctness of InstantiatingTemplate
|
||
const_cast<NamedDecl *>(Template), Constraint.getSourceRange());
|
||
if (Inst.isInvalid())
|
||
return std::nullopt;
|
||
|
||
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) {
|
||
|
||
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, /*Diagnose=*/false) ||
|
||
!Expand || RetainExpansion)
|
||
return std::nullopt;
|
||
|
||
if (NumExpansions && S.getLangOpts().BracketDepth < *NumExpansions)
|
||
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::ArgPackSubstIndexRAII SubstIndex(
|
||
S, Constraint.getPackSubstitutionIndex()
|
||
? Constraint.getPackSubstitutionIndex()
|
||
: PackSubstitutionIndex);
|
||
|
||
const ASTTemplateArgumentListInfo *Ori =
|
||
ConceptId->getTemplateArgsAsWritten();
|
||
TemplateDeductionInfo Info(TemplateNameLoc);
|
||
Sema::SFINAETrap Trap(S, Info);
|
||
Sema::InstantiatingTemplate _2(
|
||
S, TemplateNameLoc, Sema::InstantiatingTemplate::ConstraintSubstitution{},
|
||
const_cast<NamedDecl *>(Template), 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();
|
||
|
||
struct SynthesisContextPair {
|
||
Sema::InstantiatingTemplate Inst;
|
||
Sema::NonSFINAEContext NSC;
|
||
SynthesisContextPair(Sema &S, NamedDecl *Template,
|
||
ArrayRef<TemplateArgument> TemplateArgs,
|
||
SourceRange InstantiationRange)
|
||
: Inst(S, InstantiationRange.getBegin(),
|
||
Sema::InstantiatingTemplate::ConstraintsCheck{}, Template,
|
||
TemplateArgs, InstantiationRange),
|
||
NSC(S) {}
|
||
};
|
||
std::optional<SynthesisContextPair> SynthesisContext;
|
||
if (!TopLevelConceptId)
|
||
SynthesisContext.emplace(S, 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) {
|
||
llvm::TimeTraceScope TimeScope(
|
||
"CheckConstraintSatisfaction", [TemplateIDRange, this] {
|
||
return TemplateIDRange.printToString(getSourceManager());
|
||
});
|
||
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::NonSFINAEContext _(S);
|
||
Sema::InstantiatingTemplate Inst(
|
||
S, DeclInfo.getLocation(),
|
||
Sema::InstantiatingTemplate::ConstraintNormalization{},
|
||
const_cast<NamedDecl *>(DeclInfo.getDecl()), SourceRange{});
|
||
if (Inst.isInvalid())
|
||
return nullptr;
|
||
|
||
// Set up a dummy 'instantiation' scope in the case of reference to function
|
||
// parameters that the surrounding function hasn't been instantiated yet. Note
|
||
// this may happen while we're comparing two templates' constraint
|
||
// 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 (!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::NonSFINAEContext _(SemaRef);
|
||
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();
|
||
}
|
||
Sema::NonSFINAEContext _(SemaRef);
|
||
// 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::NonSFINAEContext _(S);
|
||
Sema::InstantiatingTemplate Inst(
|
||
S, CSE->getExprLoc(),
|
||
Sema::InstantiatingTemplate::ConstraintNormalization{},
|
||
// FIXME: improve const-correctness of InstantiatingTemplate
|
||
const_cast<NamedDecl *>(D), CSE->getSourceRange());
|
||
if (Inst.isInvalid())
|
||
return nullptr;
|
||
// C++ [temp.constr.normal]p1.1
|
||
// [...]
|
||
// The normal form of an id-expression of the form C<A1, A2, ..., AN>,
|
||
// where C names a concept, is the normal form of the
|
||
// constraint-expression of C, after substituting A1, A2, ..., AN for C’s
|
||
// respective template parameters in the parameter mappings in each atomic
|
||
// constraint. If any such substitution results in an invalid type or
|
||
// expression, the program is ill-formed; no diagnostic is required.
|
||
// [...]
|
||
|
||
// Use canonical declarations to merge ConceptDecls across
|
||
// different modules.
|
||
ConceptDecl *CD = CSE->getNamedConcept()->getCanonicalDecl();
|
||
|
||
ExprResult Res =
|
||
SubstituteConceptsInConstraintExpression(S, D, CSE, SubstIndex);
|
||
if (!Res.isUsable())
|
||
return nullptr;
|
||
|
||
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;
|
||
};
|
||
|
||
{
|
||
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");
|
||
}
|