blob: 466e37831f6615b3bae5119c6e888ffac5eb4a1d [file] [log] [blame]
//===-- 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 "clang/Sema/Sema.h"
#include "clang/Sema/SemaInternal.h"
#include "clang/Sema/SemaDiagnostic.h"
#include "clang/Sema/TemplateDeduction.h"
#include "clang/Sema/Template.h"
#include "clang/Sema/Overload.h"
#include "clang/Sema/Initialization.h"
#include "clang/Sema/SemaInternal.h"
#include "clang/AST/ExprConcepts.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/OperatorPrecedence.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/PointerUnion.h"
#include "llvm/ADT/StringExtras.h"
using namespace clang;
using namespace sema;
namespace {
class LogicalBinOp {
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();
} 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);
}
}
}
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; }
};
}
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)
*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.
(NextToken.is(tok::l_paren) &&
(IsTrailingRequiresClause ||
(Type->isDependentType() &&
isa<UnresolvedLookupExpr>(ConstraintExpression)) ||
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;
}
template <typename AtomicEvaluator>
static bool
calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
ConstraintSatisfaction &Satisfaction,
AtomicEvaluator &&Evaluator) {
ConstraintExpr = ConstraintExpr->IgnoreParenImpCasts();
if (LogicalBinOp BO = ConstraintExpr) {
if (calculateConstraintSatisfaction(S, BO.getLHS(), Satisfaction,
Evaluator))
return true;
bool IsLHSSatisfied = Satisfaction.IsSatisfied;
if (BO.isOr() && IsLHSSatisfied)
// [temp.constr.op] p3
// A disjunction is a constraint taking two operands. To determine if
// a disjunction is satisfied, the satisfaction of the first operand
// is checked. If that is satisfied, the disjunction is satisfied.
// Otherwise, the disjunction is satisfied if and only if the second
// operand is satisfied.
return false;
if (BO.isAnd() && !IsLHSSatisfied)
// [temp.constr.op] p2
// A conjunction is a constraint taking two operands. To determine if
// a conjunction is satisfied, the satisfaction of the first operand
// is checked. If that is not satisfied, the conjunction is not
// satisfied. Otherwise, the conjunction is satisfied if and only if
// the second operand is satisfied.
return false;
return calculateConstraintSatisfaction(
S, BO.getRHS(), Satisfaction, std::forward<AtomicEvaluator>(Evaluator));
} else if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpr)) {
return calculateConstraintSatisfaction(S, C->getSubExpr(), Satisfaction,
std::forward<AtomicEvaluator>(Evaluator));
}
// An atomic constraint expression
ExprResult SubstitutedAtomicExpr = Evaluator(ConstraintExpr);
if (SubstitutedAtomicExpr.isInvalid())
return true;
if (!SubstitutedAtomicExpr.isUsable())
// Evaluator has decided satisfaction without yielding an expression.
return false;
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 true;
}
assert(EvalResult.Val.isInt() &&
"evaluating bool expression didn't produce int");
Satisfaction.IsSatisfied = EvalResult.Val.getInt().getBoolValue();
if (!Satisfaction.IsSatisfied)
Satisfaction.Details.emplace_back(ConstraintExpr,
SubstitutedAtomicExpr.get());
return false;
}
static bool calculateConstraintSatisfaction(
Sema &S, const NamedDecl *Template, ArrayRef<TemplateArgument> TemplateArgs,
SourceLocation TemplateNameLoc, MultiLevelTemplateArgumentList &MLTAL,
const Expr *ConstraintExpr, ConstraintSatisfaction &Satisfaction) {
return calculateConstraintSatisfaction(
S, ConstraintExpr, Satisfaction, [&](const Expr *AtomicExpr) {
EnterExpressionEvaluationContext ConstantEvaluated(
S, Sema::ExpressionEvaluationContext::ConstantEvaluated);
// Atomic constraint - substitute arguments and check satisfaction.
ExprResult SubstitutedExpression;
{
TemplateDeductionInfo Info(TemplateNameLoc);
Sema::InstantiatingTemplate Inst(S, AtomicExpr->getBeginLoc(),
Sema::InstantiatingTemplate::ConstraintSubstitution{},
const_cast<NamedDecl *>(Template), Info,
AtomicExpr->getSourceRange());
if (Inst.isInvalid())
return ExprError();
// We do not want error diagnostics escaping here.
Sema::SFINAETrap Trap(S);
SubstitutedExpression = S.SubstExpr(const_cast<Expr *>(AtomicExpr),
MLTAL);
// Substitution might have stripped off a contextual conversion to
// bool if this is the operand of an '&&' or '||'. For example, we
// might lose an lvalue-to-rvalue conversion here. If so, put it back
// before we try to evaluate.
if (!SubstitutedExpression.isInvalid())
SubstitutedExpression =
S.PerformContextuallyConvertToBool(SubstitutedExpression.get());
if (SubstitutedExpression.isInvalid() || Trap.hasErrorOccurred()) {
// C++2a [temp.constr.atomic]p1
// ...If substitution results in an invalid type or expression, the
// constraint is not satisfied.
if (!Trap.hasErrorOccurred())
// A non-SFINAE error has occurred as a result of this
// substitution.
return ExprError();
PartialDiagnosticAt SubstDiag{SourceLocation(),
PartialDiagnostic::NullDiagnostic()};
Info.takeSFINAEDiagnostic(SubstDiag);
// FIXME: Concepts: This is an unfortunate consequence of there
// being no serialization code for PartialDiagnostics and the fact
// that serializing them would likely take a lot more storage than
// just storing them as strings. We would still like, in the
// future, to serialize the proper PartialDiagnostic as serializing
// it as a string defeats the purpose of the diagnostic mechanism.
SmallString<128> DiagString;
DiagString = ": ";
SubstDiag.second.EmitToString(S.getDiagnostics(), DiagString);
unsigned MessageSize = DiagString.size();
char *Mem = new (S.Context) char[MessageSize];
memcpy(Mem, DiagString.c_str(), MessageSize);
Satisfaction.Details.emplace_back(
AtomicExpr,
new (S.Context) ConstraintSatisfaction::SubstitutionDiagnostic{
SubstDiag.first, StringRef(Mem, MessageSize)});
Satisfaction.IsSatisfied = false;
return ExprEmpty();
}
}
if (!S.CheckConstraintExpression(SubstitutedExpression.get()))
return ExprError();
return SubstitutedExpression;
});
}
static bool CheckConstraintSatisfaction(Sema &S, const NamedDecl *Template,
ArrayRef<const Expr *> ConstraintExprs,
ArrayRef<TemplateArgument> TemplateArgs,
SourceRange TemplateIDRange,
ConstraintSatisfaction &Satisfaction) {
if (ConstraintExprs.empty()) {
Satisfaction.IsSatisfied = true;
return false;
}
for (auto& Arg : TemplateArgs)
if (Arg.isInstantiationDependent()) {
// No need to check satisfaction for dependent constraint expressions.
Satisfaction.IsSatisfied = true;
return false;
}
Sema::InstantiatingTemplate Inst(S, TemplateIDRange.getBegin(),
Sema::InstantiatingTemplate::ConstraintsCheck{},
const_cast<NamedDecl *>(Template), TemplateArgs, TemplateIDRange);
if (Inst.isInvalid())
return true;
MultiLevelTemplateArgumentList MLTAL;
MLTAL.addOuterTemplateArguments(TemplateArgs);
for (const Expr *ConstraintExpr : ConstraintExprs) {
if (calculateConstraintSatisfaction(S, Template, TemplateArgs,
TemplateIDRange.getBegin(), MLTAL,
ConstraintExpr, Satisfaction))
return true;
if (!Satisfaction.IsSatisfied)
// [temp.constr.op] p2
// [...] To determine if a conjunction is satisfied, the satisfaction
// of the first operand is checked. If that is not satisfied, the
// conjunction is not satisfied. [...]
return false;
}
return false;
}
bool Sema::CheckConstraintSatisfaction(
const NamedDecl *Template, ArrayRef<const Expr *> ConstraintExprs,
ArrayRef<TemplateArgument> TemplateArgs, SourceRange TemplateIDRange,
ConstraintSatisfaction &OutSatisfaction) {
if (ConstraintExprs.empty()) {
OutSatisfaction.IsSatisfied = true;
return false;
}
llvm::FoldingSetNodeID ID;
void *InsertPos;
ConstraintSatisfaction *Satisfaction = nullptr;
bool ShouldCache = LangOpts.ConceptSatisfactionCaching && Template;
if (ShouldCache) {
ConstraintSatisfaction::Profile(ID, Context, Template, TemplateArgs);
Satisfaction = SatisfactionCache.FindNodeOrInsertPos(ID, InsertPos);
if (Satisfaction) {
OutSatisfaction = *Satisfaction;
return false;
}
Satisfaction = new ConstraintSatisfaction(Template, TemplateArgs);
} else {
Satisfaction = &OutSatisfaction;
}
if (::CheckConstraintSatisfaction(*this, Template, ConstraintExprs,
TemplateArgs, TemplateIDRange,
*Satisfaction)) {
if (ShouldCache)
delete Satisfaction;
return true;
}
if (ShouldCache) {
// We cannot use InsertNode here because CheckConstraintSatisfaction might
// have invalidated it.
SatisfactionCache.InsertNode(Satisfaction);
OutSatisfaction = *Satisfaction;
}
return false;
}
bool Sema::CheckConstraintSatisfaction(const Expr *ConstraintExpr,
ConstraintSatisfaction &Satisfaction) {
return calculateConstraintSatisfaction(
*this, ConstraintExpr, Satisfaction,
[](const Expr *AtomicExpr) -> ExprResult {
return ExprResult(const_cast<Expr *>(AtomicExpr));
});
}
bool Sema::CheckFunctionConstraints(const FunctionDecl *FD,
ConstraintSatisfaction &Satisfaction,
SourceLocation UsageLoc) {
const Expr *RC = FD->getTrailingRequiresClause();
if (RC->isInstantiationDependent()) {
Satisfaction.IsSatisfied = true;
return false;
}
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);
// We substitute with empty arguments in order to rebuild the atomic
// constraint in a constant-evaluated context.
// FIXME: Should this be a dedicated TreeTransform?
return CheckConstraintSatisfaction(
FD, {RC}, /*TemplateArgs=*/{},
SourceRange(UsageLoc.isValid() ? UsageLoc : FD->getLocation()),
Satisfaction);
}
bool Sema::EnsureTemplateArgumentListConstraints(
TemplateDecl *TD, ArrayRef<TemplateArgument> TemplateArgs,
SourceRange TemplateIDRange) {
ConstraintSatisfaction Satisfaction;
llvm::SmallVector<const Expr *, 3> AssociatedConstraints;
TD->getAssociatedConstraints(AssociatedConstraints);
if (CheckConstraintSatisfaction(TD, AssociatedConstraints, TemplateArgs,
TemplateIDRange, Satisfaction))
return true;
if (!Satisfaction.IsSatisfied) {
SmallString<128> TemplateArgString;
TemplateArgString = " ";
TemplateArgString += getTemplateArgumentBindingsText(
TD->getTemplateParameters(), TemplateArgs.data(), TemplateArgs.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 void diagnoseUnsatisfiedRequirement(Sema &S,
concepts::ExprRequirement *Req,
bool First) {
assert(!Req->isSatisfied()
&& "Diagnose() can only be used on an unsatisfied requirement");
switch (Req->getSatisfactionStatus()) {
case concepts::ExprRequirement::SS_Dependent:
llvm_unreachable("Diagnosing a dependent requirement");
break;
case concepts::ExprRequirement::SS_ExprSubstitutionFailure: {
auto *SubstDiag = Req->getExprSubstitutionDiagnostic();
if (!SubstDiag->DiagMessage.empty())
S.Diag(SubstDiag->DiagLoc,
diag::note_expr_requirement_expr_substitution_error)
<< (int)First << SubstDiag->SubstitutedEntity
<< SubstDiag->DiagMessage;
else
S.Diag(SubstDiag->DiagLoc,
diag::note_expr_requirement_expr_unknown_substitution_error)
<< (int)First << SubstDiag->SubstitutedEntity;
break;
}
case concepts::ExprRequirement::SS_NoexceptNotMet:
S.Diag(Req->getNoexceptLoc(),
diag::note_expr_requirement_noexcept_not_met)
<< (int)First << Req->getExpr();
break;
case concepts::ExprRequirement::SS_TypeRequirementSubstitutionFailure: {
auto *SubstDiag =
Req->getReturnTypeRequirement().getSubstitutionDiagnostic();
if (!SubstDiag->DiagMessage.empty())
S.Diag(SubstDiag->DiagLoc,
diag::note_expr_requirement_type_requirement_substitution_error)
<< (int)First << SubstDiag->SubstitutedEntity
<< SubstDiag->DiagMessage;
else
S.Diag(SubstDiag->DiagLoc,
diag::note_expr_requirement_type_requirement_unknown_substitution_error)
<< (int)First << SubstDiag->SubstitutedEntity;
break;
}
case concepts::ExprRequirement::SS_ConstraintsNotSatisfied: {
ConceptSpecializationExpr *ConstraintExpr =
Req->getReturnTypeRequirementSubstitutedConstraintExpr();
if (ConstraintExpr->getTemplateArgsAsWritten()->NumTemplateArgs == 1) {
// A simple case - expr type is the type being constrained and the concept
// was not provided arguments.
Expr *e = Req->getExpr();
S.Diag(e->getBeginLoc(),
diag::note_expr_requirement_constraints_not_satisfied_simple)
<< (int)First << S.Context.getReferenceQualifiedType(e)
<< ConstraintExpr->getNamedConcept();
} else {
S.Diag(ConstraintExpr->getBeginLoc(),
diag::note_expr_requirement_constraints_not_satisfied)
<< (int)First << ConstraintExpr;
}
S.DiagnoseUnsatisfiedConstraint(ConstraintExpr->getSatisfaction());
break;
}
case concepts::ExprRequirement::SS_Satisfied:
llvm_unreachable("We checked this above");
}
}
static void diagnoseUnsatisfiedRequirement(Sema &S,
concepts::TypeRequirement *Req,
bool First) {
assert(!Req->isSatisfied()
&& "Diagnose() can only be used on an unsatisfied requirement");
switch (Req->getSatisfactionStatus()) {
case concepts::TypeRequirement::SS_Dependent:
llvm_unreachable("Diagnosing a dependent requirement");
return;
case concepts::TypeRequirement::SS_SubstitutionFailure: {
auto *SubstDiag = Req->getSubstitutionDiagnostic();
if (!SubstDiag->DiagMessage.empty())
S.Diag(SubstDiag->DiagLoc,
diag::note_type_requirement_substitution_error) << (int)First
<< SubstDiag->SubstitutedEntity << SubstDiag->DiagMessage;
else
S.Diag(SubstDiag->DiagLoc,
diag::note_type_requirement_unknown_substitution_error)
<< (int)First << SubstDiag->SubstitutedEntity;
return;
}
default:
llvm_unreachable("Unknown satisfaction status");
return;
}
}
static void diagnoseUnsatisfiedRequirement(Sema &S,
concepts::NestedRequirement *Req,
bool First) {
if (Req->isSubstitutionFailure()) {
concepts::Requirement::SubstitutionDiagnostic *SubstDiag =
Req->getSubstitutionDiagnostic();
if (!SubstDiag->DiagMessage.empty())
S.Diag(SubstDiag->DiagLoc,
diag::note_nested_requirement_substitution_error)
<< (int)First << SubstDiag->SubstitutedEntity
<< SubstDiag->DiagMessage;
else
S.Diag(SubstDiag->DiagLoc,
diag::note_nested_requirement_unknown_substitution_error)
<< (int)First << SubstDiag->SubstitutedEntity;
return;
}
S.DiagnoseUnsatisfiedConstraint(Req->getConstraintSatisfaction(), First);
}
static void diagnoseWellFormedUnsatisfiedConstraintExpr(Sema &S,
Expr *SubstExpr,
bool First = true) {
SubstExpr = SubstExpr->IgnoreParenImpCasts();
if (BinaryOperator *BO = dyn_cast<BinaryOperator>(SubstExpr)) {
switch (BO->getOpcode()) {
// These two cases will in practice only be reached when using fold
// expressions with || and &&, since otherwise the || and && will have been
// broken down into atomic constraints during satisfaction checking.
case BO_LOr:
// Or evaluated to false - meaning both RHS and LHS evaluated to false.
diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getLHS(), First);
diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getRHS(),
/*First=*/false);
return;
case BO_LAnd: {
bool LHSSatisfied =
BO->getLHS()->EvaluateKnownConstInt(S.Context).getBoolValue();
if (LHSSatisfied) {
// LHS is true, so RHS must be false.
diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getRHS(), First);
return;
}
// LHS is false
diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getLHS(), First);
// RHS might also be false
bool RHSSatisfied =
BO->getRHS()->EvaluateKnownConstInt(S.Context).getBoolValue();
if (!RHSSatisfied)
diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getRHS(),
/*First=*/false);
return;
}
case BO_GE:
case BO_LE:
case BO_GT:
case BO_LT:
case BO_EQ:
case BO_NE:
if (BO->getLHS()->getType()->isIntegerType() &&
BO->getRHS()->getType()->isIntegerType()) {
Expr::EvalResult SimplifiedLHS;
Expr::EvalResult SimplifiedRHS;
BO->getLHS()->EvaluateAsInt(SimplifiedLHS, S.Context,
Expr::SE_NoSideEffects,
/*InConstantContext=*/true);
BO->getRHS()->EvaluateAsInt(SimplifiedRHS, S.Context,
Expr::SE_NoSideEffects,
/*InConstantContext=*/true);
if (!SimplifiedLHS.Diag && ! SimplifiedRHS.Diag) {
S.Diag(SubstExpr->getBeginLoc(),
diag::note_atomic_constraint_evaluated_to_false_elaborated)
<< (int)First << SubstExpr
<< toString(SimplifiedLHS.Val.getInt(), 10)
<< BinaryOperator::getOpcodeStr(BO->getOpcode())
<< toString(SimplifiedRHS.Val.getInt(), 10);
return;
}
}
break;
default:
break;
}
} else if (auto *CSE = dyn_cast<ConceptSpecializationExpr>(SubstExpr)) {
if (CSE->getTemplateArgsAsWritten()->NumTemplateArgs == 1) {
S.Diag(
CSE->getSourceRange().getBegin(),
diag::
note_single_arg_concept_specialization_constraint_evaluated_to_false)
<< (int)First
<< CSE->getTemplateArgsAsWritten()->arguments()[0].getArgument()
<< CSE->getNamedConcept();
} else {
S.Diag(SubstExpr->getSourceRange().getBegin(),
diag::note_concept_specialization_constraint_evaluated_to_false)
<< (int)First << CSE;
}
S.DiagnoseUnsatisfiedConstraint(CSE->getSatisfaction());
return;
} else if (auto *RE = dyn_cast<RequiresExpr>(SubstExpr)) {
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;
}
S.Diag(SubstExpr->getSourceRange().getBegin(),
diag::note_atomic_constraint_evaluated_to_false)
<< (int)First << SubstExpr;
}
template<typename SubstitutionDiagnostic>
static void diagnoseUnsatisfiedConstraintExpr(
Sema &S, const Expr *E,
const llvm::PointerUnion<Expr *, SubstitutionDiagnostic *> &Record,
bool First = true) {
if (auto *Diag = Record.template dyn_cast<SubstitutionDiagnostic *>()){
S.Diag(Diag->first, diag::note_substituted_constraint_expr_is_ill_formed)
<< Diag->second;
return;
}
diagnoseWellFormedUnsatisfiedConstraintExpr(S,
Record.template get<Expr *>(), First);
}
void
Sema::DiagnoseUnsatisfiedConstraint(const ConstraintSatisfaction& Satisfaction,
bool First) {
assert(!Satisfaction.IsSatisfied &&
"Attempted to diagnose a satisfied constraint");
for (auto &Pair : Satisfaction.Details) {
diagnoseUnsatisfiedConstraintExpr(*this, Pair.first, Pair.second, First);
First = false;
}
}
void Sema::DiagnoseUnsatisfiedConstraint(
const ASTConstraintSatisfaction &Satisfaction,
bool First) {
assert(!Satisfaction.IsSatisfied &&
"Attempted to diagnose a satisfied constraint");
for (auto &Pair : Satisfaction) {
diagnoseUnsatisfiedConstraintExpr(*this, Pair.first, Pair.second, First);
First = false;
}
}
const NormalizedConstraint *
Sema::getNormalizedAssociatedConstraints(
NamedDecl *ConstrainedDecl, ArrayRef<const Expr *> AssociatedConstraints) {
auto CacheEntry = NormalizationCache.find(ConstrainedDecl);
if (CacheEntry == NormalizationCache.end()) {
auto Normalized =
NormalizedConstraint::fromConstraintExprs(*this, ConstrainedDecl,
AssociatedConstraints);
CacheEntry =
NormalizationCache
.try_emplace(ConstrainedDecl,
Normalized
? new (Context) NormalizedConstraint(
std::move(*Normalized))
: nullptr)
.first;
}
return CacheEntry->second;
}
static bool substituteParameterMappings(Sema &S, NormalizedConstraint &N,
ConceptDecl *Concept, ArrayRef<TemplateArgument> TemplateArgs,
const ASTTemplateArgumentListInfo *ArgsAsWritten) {
if (!N.isAtomic()) {
if (substituteParameterMappings(S, N.getLHS(), Concept, TemplateArgs,
ArgsAsWritten))
return true;
return substituteParameterMappings(S, N.getRHS(), Concept, TemplateArgs,
ArgsAsWritten);
}
TemplateParameterList *TemplateParams = Concept->getTemplateParameters();
AtomicConstraint &Atomic = *N.getAtomicConstraint();
TemplateArgumentListInfo SubstArgs;
MultiLevelTemplateArgumentList MLTAL;
MLTAL.addOuterTemplateArguments(TemplateArgs);
if (!Atomic.ParameterMapping) {
llvm::SmallBitVector OccurringIndices(TemplateParams->size());
S.MarkUsedTemplateParameters(Atomic.ConstraintExpr, /*OnlyDeduced=*/false,
/*Depth=*/0, OccurringIndices);
Atomic.ParameterMapping.emplace(
MutableArrayRef<TemplateArgumentLoc>(
new (S.Context) TemplateArgumentLoc[OccurringIndices.count()],
OccurringIndices.count()));
for (unsigned I = 0, J = 0, C = TemplateParams->size(); I != C; ++I)
if (OccurringIndices[I])
new (&(*Atomic.ParameterMapping)[J++]) TemplateArgumentLoc(
S.getIdentityTemplateArgumentLoc(TemplateParams->begin()[I],
// Here we assume we do not support things like
// template<typename A, typename B>
// concept C = ...;
//
// template<typename... Ts> requires C<Ts...>
// struct S { };
// The above currently yields a diagnostic.
// We still might have default arguments for concept parameters.
ArgsAsWritten->NumTemplateArgs > I ?
ArgsAsWritten->arguments()[I].getLocation() :
SourceLocation()));
}
Sema::InstantiatingTemplate Inst(
S, ArgsAsWritten->arguments().front().getSourceRange().getBegin(),
Sema::InstantiatingTemplate::ParameterMappingSubstitution{}, Concept,
SourceRange(ArgsAsWritten->arguments()[0].getSourceRange().getBegin(),
ArgsAsWritten->arguments().back().getSourceRange().getEnd()));
if (S.SubstTemplateArguments(*Atomic.ParameterMapping, MLTAL, SubstArgs))
return true;
Atomic.ParameterMapping.emplace(
MutableArrayRef<TemplateArgumentLoc>(
new (S.Context) TemplateArgumentLoc[SubstArgs.size()],
SubstArgs.size()));
std::copy(SubstArgs.arguments().begin(), SubstArgs.arguments().end(),
N.getAtomicConstraint()->ParameterMapping->begin());
return false;
}
Optional<NormalizedConstraint>
NormalizedConstraint::fromConstraintExprs(Sema &S, NamedDecl *D,
ArrayRef<const Expr *> E) {
assert(E.size() != 0);
auto Conjunction = fromConstraintExpr(S, D, E[0]);
if (!Conjunction)
return None;
for (unsigned I = 1; I < E.size(); ++I) {
auto Next = fromConstraintExpr(S, D, E[I]);
if (!Next)
return None;
*Conjunction = NormalizedConstraint(S.Context, std::move(*Conjunction),
std::move(*Next), CCK_Conjunction);
}
return Conjunction;
}
llvm::Optional<NormalizedConstraint>
NormalizedConstraint::fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E) {
assert(E != nullptr);
// C++ [temp.constr.normal]p1.1
// [...]
// - The normal form of an expression (E) is the normal form of E.
// [...]
E = E->IgnoreParenImpCasts();
if (LogicalBinOp BO = E) {
auto LHS = fromConstraintExpr(S, D, BO.getLHS());
if (!LHS)
return None;
auto RHS = fromConstraintExpr(S, D, BO.getRHS());
if (!RHS)
return None;
return NormalizedConstraint(S.Context, std::move(*LHS), std::move(*RHS),
BO.isAnd() ? CCK_Conjunction : CCK_Disjunction);
} else if (auto *CSE = dyn_cast<const ConceptSpecializationExpr>(E)) {
const NormalizedConstraint *SubNF;
{
Sema::InstantiatingTemplate Inst(
S, CSE->getExprLoc(),
Sema::InstantiatingTemplate::ConstraintNormalization{}, D,
CSE->getSourceRange());
// 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.
// [...]
ConceptDecl *CD = CSE->getNamedConcept();
SubNF = S.getNormalizedAssociatedConstraints(CD,
{CD->getConstraintExpr()});
if (!SubNF)
return None;
}
Optional<NormalizedConstraint> New;
New.emplace(S.Context, *SubNF);
if (substituteParameterMappings(
S, *New, CSE->getNamedConcept(),
CSE->getTemplateArguments(), CSE->getTemplateArgsAsWritten()))
return None;
return New;
}
return NormalizedConstraint{new (S.Context) AtomicConstraint(S, E)};
}
using NormalForm =
llvm::SmallVector<llvm::SmallVector<AtomicConstraint *, 2>, 4>;
static NormalForm makeCNF(const NormalizedConstraint &Normalized) {
if (Normalized.isAtomic())
return {{Normalized.getAtomicConstraint()}};
NormalForm LCNF = makeCNF(Normalized.getLHS());
NormalForm RCNF = makeCNF(Normalized.getRHS());
if (Normalized.getCompoundKind() == NormalizedConstraint::CCK_Conjunction) {
LCNF.reserve(LCNF.size() + RCNF.size());
while (!RCNF.empty())
LCNF.push_back(RCNF.pop_back_val());
return LCNF;
}
// Disjunction
NormalForm Res;
Res.reserve(LCNF.size() * RCNF.size());
for (auto &LDisjunction : LCNF)
for (auto &RDisjunction : RCNF) {
NormalForm::value_type Combined;
Combined.reserve(LDisjunction.size() + RDisjunction.size());
std::copy(LDisjunction.begin(), LDisjunction.end(),
std::back_inserter(Combined));
std::copy(RDisjunction.begin(), RDisjunction.end(),
std::back_inserter(Combined));
Res.emplace_back(Combined);
}
return Res;
}
static NormalForm makeDNF(const NormalizedConstraint &Normalized) {
if (Normalized.isAtomic())
return {{Normalized.getAtomicConstraint()}};
NormalForm LDNF = makeDNF(Normalized.getLHS());
NormalForm RDNF = makeDNF(Normalized.getRHS());
if (Normalized.getCompoundKind() == NormalizedConstraint::CCK_Disjunction) {
LDNF.reserve(LDNF.size() + RDNF.size());
while (!RDNF.empty())
LDNF.push_back(RDNF.pop_back_val());
return LDNF;
}
// Conjunction
NormalForm Res;
Res.reserve(LDNF.size() * RDNF.size());
for (auto &LConjunction : LDNF) {
for (auto &RConjunction : RDNF) {
NormalForm::value_type Combined;
Combined.reserve(LConjunction.size() + RConjunction.size());
std::copy(LConjunction.begin(), LConjunction.end(),
std::back_inserter(Combined));
std::copy(RConjunction.begin(), RConjunction.end(),
std::back_inserter(Combined));
Res.emplace_back(Combined);
}
}
return Res;
}
template<typename AtomicSubsumptionEvaluator>
static bool subsumes(NormalForm PDNF, NormalForm QCNF,
AtomicSubsumptionEvaluator E) {
// C++ [temp.constr.order] p2
// Then, P subsumes Q if and only if, for every disjunctive clause Pi in the
// disjunctive normal form of P, Pi subsumes every conjunctive clause Qj in
// the conjuctive normal form of Q, where [...]
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.
bool Found = false;
for (const AtomicConstraint *Pia : Pi) {
for (const AtomicConstraint *Qjb : Qj) {
if (E(*Pia, *Qjb)) {
Found = true;
break;
}
}
if (Found)
break;
}
if (!Found)
return false;
}
}
return true;
}
template<typename AtomicSubsumptionEvaluator>
static bool subsumes(Sema &S, NamedDecl *DP, ArrayRef<const Expr *> P,
NamedDecl *DQ, ArrayRef<const Expr *> Q, bool &Subsumes,
AtomicSubsumptionEvaluator E) {
// C++ [temp.constr.order] p2
// In order to determine if a constraint P subsumes a constraint Q, P is
// transformed into disjunctive normal form, and Q is transformed into
// conjunctive normal form. [...]
auto *PNormalized = S.getNormalizedAssociatedConstraints(DP, P);
if (!PNormalized)
return true;
const NormalForm PDNF = makeDNF(*PNormalized);
auto *QNormalized = S.getNormalizedAssociatedConstraints(DQ, Q);
if (!QNormalized)
return true;
const NormalForm QCNF = makeCNF(*QNormalized);
Subsumes = subsumes(PDNF, QCNF, E);
return false;
}
bool Sema::IsAtLeastAsConstrained(NamedDecl *D1, ArrayRef<const Expr *> AC1,
NamedDecl *D2, ArrayRef<const Expr *> AC2,
bool &Result) {
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<NamedDecl *, NamedDecl *> Key{D1, D2};
auto CacheEntry = SubsumptionCache.find(Key);
if (CacheEntry != SubsumptionCache.end()) {
Result = CacheEntry->second;
return false;
}
if (subsumes(*this, D1, AC1, D2, AC2, Result,
[this] (const AtomicConstraint &A, const AtomicConstraint &B) {
return A.subsumes(Context, B);
}))
return true;
SubsumptionCache.try_emplace(Key, Result);
return false;
}
bool Sema::MaybeEmitAmbiguousAtomicConstraintsDiagnostic(NamedDecl *D1,
ArrayRef<const Expr *> AC1, NamedDecl *D2, ArrayRef<const Expr *> AC2) {
if (isSFINAEContext())
// No need to work here because our notes would be discarded.
return false;
if (AC1.empty() || AC2.empty())
return false;
auto NormalExprEvaluator =
[this] (const AtomicConstraint &A, const AtomicConstraint &B) {
return A.subsumes(Context, B);
};
const Expr *AmbiguousAtomic1 = nullptr, *AmbiguousAtomic2 = nullptr;
auto IdenticalExprEvaluator =
[&] (const AtomicConstraint &A, const AtomicConstraint &B) {
if (!A.hasMatchingParameterMapping(Context, B))
return false;
const Expr *EA = A.ConstraintExpr, *EB = B.ConstraintExpr;
if (EA == EB)
return true;
// Not the same source level expression - are the expressions
// identical?
llvm::FoldingSetNodeID IDA, IDB;
EA->Profile(IDA, Context, /*Canonical=*/true);
EB->Profile(IDB, Context, /*Canonical=*/true);
if (IDA != IDB)
return false;
AmbiguousAtomic1 = EA;
AmbiguousAtomic2 = EB;
return true;
};
{
// The subsumption checks might cause diagnostics
SFINAETrap Trap(*this);
auto *Normalized1 = getNormalizedAssociatedConstraints(D1, AC1);
if (!Normalized1)
return false;
const NormalForm DNF1 = makeDNF(*Normalized1);
const NormalForm CNF1 = makeCNF(*Normalized1);
auto *Normalized2 = getNormalizedAssociatedConstraints(D2, AC2);
if (!Normalized2)
return false;
const NormalForm DNF2 = makeDNF(*Normalized2);
const NormalForm CNF2 = makeCNF(*Normalized2);
bool Is1AtLeastAs2Normally = subsumes(DNF1, CNF2, NormalExprEvaluator);
bool Is2AtLeastAs1Normally = subsumes(DNF2, CNF1, NormalExprEvaluator);
bool Is1AtLeastAs2 = subsumes(DNF1, CNF2, IdenticalExprEvaluator);
bool Is2AtLeastAs1 = subsumes(DNF2, CNF1, IdenticalExprEvaluator);
if (Is1AtLeastAs2 == Is1AtLeastAs2Normally &&
Is2AtLeastAs1 == Is2AtLeastAs1Normally)
// Same result - no ambiguity was caused by identical atomic expressions.
return false;
}
// A different result! Some ambiguous atomic constraint(s) caused a difference
assert(AmbiguousAtomic1 && AmbiguousAtomic2);
Diag(AmbiguousAtomic1->getBeginLoc(), diag::note_ambiguous_atomic_constraints)
<< AmbiguousAtomic1->getSourceRange();
Diag(AmbiguousAtomic2->getBeginLoc(),
diag::note_ambiguous_atomic_constraints_similar_expression)
<< AmbiguousAtomic2->getSourceRange();
return true;
}
concepts::ExprRequirement::ExprRequirement(
Expr *E, bool IsSimple, SourceLocation NoexceptLoc,
ReturnTypeRequirement Req, SatisfactionStatus Status,
ConceptSpecializationExpr *SubstitutedConstraintExpr) :
Requirement(IsSimple ? RK_Simple : RK_Compound, Status == SS_Dependent,
Status == SS_Dependent &&
(E->containsUnexpandedParameterPack() ||
Req.containsUnexpandedParameterPack()),
Status == SS_Satisfied), Value(E), NoexceptLoc(NoexceptLoc),
TypeReq(Req), SubstitutedConstraintExpr(SubstitutedConstraintExpr),
Status(Status) {
assert((!IsSimple || (Req.isEmpty() && NoexceptLoc.isInvalid())) &&
"Simple requirement must not have a return type requirement or a "
"noexcept specification");
assert((Status > SS_TypeRequirementSubstitutionFailure && Req.isTypeConstraint()) ==
(SubstitutedConstraintExpr != nullptr));
}
concepts::ExprRequirement::ExprRequirement(
SubstitutionDiagnostic *ExprSubstDiag, bool IsSimple,
SourceLocation NoexceptLoc, ReturnTypeRequirement Req) :
Requirement(IsSimple ? RK_Simple : RK_Compound, Req.isDependent(),
Req.containsUnexpandedParameterPack(), /*IsSatisfied=*/false),
Value(ExprSubstDiag), NoexceptLoc(NoexceptLoc), TypeReq(Req),
Status(SS_ExprSubstitutionFailure) {
assert((!IsSimple || (Req.isEmpty() && NoexceptLoc.isInvalid())) &&
"Simple requirement must not have a return type requirement or a "
"noexcept specification");
}
concepts::ExprRequirement::ReturnTypeRequirement::
ReturnTypeRequirement(TemplateParameterList *TPL) :
TypeConstraintInfo(TPL, 0) {
assert(TPL->size() == 1);
const TypeConstraint *TC =
cast<TemplateTypeParmDecl>(TPL->getParam(0))->getTypeConstraint();
assert(TC &&
"TPL must have a template type parameter with a type constraint");
auto *Constraint =
cast<ConceptSpecializationExpr>(TC->getImmediatelyDeclaredConstraint());
bool Dependent =
Constraint->getTemplateArgsAsWritten() &&
TemplateSpecializationType::anyInstantiationDependentTemplateArguments(
Constraint->getTemplateArgsAsWritten()->arguments().drop_front(1));
TypeConstraintInfo.setInt(Dependent ? 1 : 0);
}
concepts::TypeRequirement::TypeRequirement(TypeSourceInfo *T) :
Requirement(RK_Type, T->getType()->isInstantiationDependentType(),
T->getType()->containsUnexpandedParameterPack(),
// We reach this ctor with either dependent types (in which
// IsSatisfied doesn't matter) or with non-dependent type in
// which the existence of the type indicates satisfaction.
/*IsSatisfied=*/true),
Value(T),
Status(T->getType()->isInstantiationDependentType() ? SS_Dependent
: SS_Satisfied) {}