Implement DR2565: Invalid types in the parameter-declaration-clause of a

requires-expression

As reported: https://github.com/llvm/llvm-project/issues/57487

We properly treated a failed instantiation of a concept as a
unsatisified constraint, however, we need to do this at the 'requires
clause' level as well.  This ensures that the parameters on a requires
clause that fail instantiation will cause a satisfaction failure.

This patch implements this by running requires parameter clause
instantiation under a SFINAE trap, then stores any such failure as a
requirement failure, so it can be diagnosed later.
This commit is contained in:
Erich Keane 2022-09-29 11:38:52 -07:00
parent 7d85f6b1af
commit 3d7946c580
9 changed files with 169 additions and 13 deletions

View File

@ -172,7 +172,10 @@ Bug Fixes
- The template arguments of a variable template being accessed as a
member will now be represented in the AST.
- Fix incorrect handling of inline builtins with asm labels.
- Finished implementing C++ DR2565, which results in a requirement becoming
not satisfied in the event of an instantiation failures in a requires expression's
parameter list. We previously handled this correctly in a constraint evaluation
context, but not in a requires clause evaluated as a boolean.
Improvements to Clang's diagnostics
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -5235,6 +5235,8 @@ def note_template_exception_spec_instantiation_here : Note<
"in instantiation of exception specification for %0 requested here">;
def note_template_requirement_instantiation_here : Note<
"in instantiation of requirement here">;
def note_template_requirement_params_instantiation_here : Note<
"in instantiation of requirement parameters here">;
def warn_var_template_missing : Warning<"instantiation of variable %q0 "
"required here, but no definition is available">,
InGroup<UndefinedVarTemplate>;

View File

@ -9135,6 +9135,9 @@ public:
// We are normalizing a constraint expression.
ConstraintNormalization,
// Instantiating a Requires Expression parameter clause.
RequirementParameterInstantiation,
// We are substituting into the parameter mapping of an atomic constraint
// during normalization.
ParameterMappingSubstitution,
@ -9464,6 +9467,11 @@ public:
concepts::NestedRequirement *Req, ConstraintsCheck,
SourceRange InstantiationRange = SourceRange());
/// \brief Note that we are checking a requires clause.
InstantiatingTemplate(Sema &SemaRef, SourceLocation PointOfInstantiation,
const RequiresExpr *E,
sema::TemplateDeductionInfo &DeductionInfo,
SourceRange InstantiationRange);
/// Note that we have finished instantiating this template.
void Clear();

View File

@ -470,6 +470,8 @@ private:
return "ConstraintSubstitution";
case CodeSynthesisContext::ConstraintNormalization:
return "ConstraintNormalization";
case CodeSynthesisContext::RequirementParameterInstantiation:
return "RequirementParameterInstantiation";
case CodeSynthesisContext::ParameterMappingSubstitution:
return "ParameterMappingSubstitution";
case CodeSynthesisContext::RequirementInstantiation:

View File

@ -320,6 +320,7 @@ bool Sema::CodeSynthesisContext::isInstantiationRecord() const {
return true;
case RequirementInstantiation:
case RequirementParameterInstantiation:
case DefaultTemplateArgumentChecking:
case DeclaringSpecialMember:
case DeclaringImplicitEqualityComparison:
@ -505,6 +506,13 @@ Sema::InstantiatingTemplate::InstantiatingTemplate(
PointOfInstantiation, InstantiationRange, /*Entity=*/nullptr,
/*Template=*/nullptr, /*TemplateArgs=*/None) {}
Sema::InstantiatingTemplate::InstantiatingTemplate(
Sema &SemaRef, SourceLocation PointOfInstantiation, const RequiresExpr *RE,
sema::TemplateDeductionInfo &DeductionInfo, SourceRange InstantiationRange)
: InstantiatingTemplate(
SemaRef, CodeSynthesisContext::RequirementParameterInstantiation,
PointOfInstantiation, InstantiationRange, /*Entity=*/nullptr,
/*Template=*/nullptr, /*TemplateArgs=*/None, &DeductionInfo) {}
Sema::InstantiatingTemplate::InstantiatingTemplate(
Sema &SemaRef, SourceLocation PointOfInstantiation,
@ -540,6 +548,7 @@ Sema::InstantiatingTemplate::InstantiatingTemplate(
SemaRef, CodeSynthesisContext::ParameterMappingSubstitution,
PointOfInstantiation, InstantiationRange, Template) {}
void Sema::pushCodeSynthesisContext(CodeSynthesisContext Ctx) {
Ctx.SavedInNonInstantiationSFINAEContext = InNonInstantiationSFINAEContext;
InNonInstantiationSFINAEContext = false;
@ -845,6 +854,12 @@ void Sema::PrintInstantiationStack() {
diag::note_template_requirement_instantiation_here)
<< Active->InstantiationRange;
break;
case CodeSynthesisContext::RequirementParameterInstantiation:
assert("how do we get here?!");
Diags.Report(Active->PointOfInstantiation,
diag::note_template_requirement_params_instantiation_here)
<< Active->InstantiationRange;
break;
case CodeSynthesisContext::NestedRequirementConstraintsCheck:
Diags.Report(Active->PointOfInstantiation,
@ -1003,6 +1018,7 @@ Optional<TemplateDeductionInfo *> Sema::isSFINAEContext() const {
case CodeSynthesisContext::DeducedTemplateArgumentSubstitution:
case CodeSynthesisContext::ConstraintSubstitution:
case CodeSynthesisContext::RequirementInstantiation:
case CodeSynthesisContext::RequirementParameterInstantiation:
// We're either substituting explicitly-specified template arguments,
// deduced template arguments, a constraint expression or a requirement
// in a requires expression, so SFINAE applies.
@ -1348,6 +1364,12 @@ namespace {
TransformExprRequirement(concepts::ExprRequirement *Req);
concepts::NestedRequirement *
TransformNestedRequirement(concepts::NestedRequirement *Req);
ExprResult TransformRequiresTypeParams(
SourceLocation KWLoc, SourceLocation RBraceLoc, const RequiresExpr *RE,
RequiresExprBodyDecl *Body, ArrayRef<ParmVarDecl *> Params,
SmallVectorImpl<QualType> &PTypes,
SmallVectorImpl<ParmVarDecl *> &TransParams,
Sema::ExtParameterInfoBuilder &PInfos);
private:
ExprResult transformNonTypeTemplateParmRef(NonTypeTemplateParmDecl *parm,
@ -2065,6 +2087,37 @@ createSubstDiag(Sema &S, TemplateDeductionInfo &Info, EntityPrinter Printer) {
StringRef(MessageBuf, Message.size())};
}
ExprResult TemplateInstantiator::TransformRequiresTypeParams(
SourceLocation KWLoc, SourceLocation RBraceLoc, const RequiresExpr *RE,
RequiresExprBodyDecl *Body, ArrayRef<ParmVarDecl *> Params,
SmallVectorImpl<QualType> &PTypes,
SmallVectorImpl<ParmVarDecl *> &TransParams,
Sema::ExtParameterInfoBuilder &PInfos) {
TemplateDeductionInfo Info(KWLoc);
Sema::InstantiatingTemplate TypeInst(SemaRef, KWLoc,
RE, Info,
SourceRange{KWLoc, RBraceLoc});
Sema::SFINAETrap Trap(SemaRef);
unsigned ErrorIdx;
if (getDerived().TransformFunctionTypeParams(
KWLoc, Params, /*ParamTypes=*/nullptr, /*ParamInfos=*/nullptr, PTypes,
&TransParams, PInfos, &ErrorIdx) ||
Trap.hasErrorOccurred()) {
SmallVector<concepts::Requirement *, 4> TransReqs;
ParmVarDecl *FailedDecl = Params[ErrorIdx];
// Add a 'failed' Requirement to contain the error that caused the failure
// here.
TransReqs.push_back(RebuildTypeRequirement(createSubstDiag(
SemaRef, Info, [&](llvm::raw_ostream &OS) { OS << *FailedDecl; })));
return getDerived().RebuildRequiresExpr(KWLoc, Body, TransParams, TransReqs,
RBraceLoc);
}
return ExprResult{};
}
concepts::TypeRequirement *
TemplateInstantiator::TransformTypeRequirement(concepts::TypeRequirement *Req) {
if (!Req->isDependent() && !AlwaysRebuild())

View File

@ -671,13 +671,49 @@ public:
/// The result vectors should be kept in sync; null entries in the
/// variables vector are acceptable.
///
/// LastParamTransformed, if non-null, will be set to the index of the last
/// parameter on which transfromation was started. In the event of an error,
/// this will contain the parameter which failed to instantiate.
///
/// Return true on error.
bool TransformFunctionTypeParams(
SourceLocation Loc, ArrayRef<ParmVarDecl *> Params,
const QualType *ParamTypes,
const FunctionProtoType::ExtParameterInfo *ParamInfos,
SmallVectorImpl<QualType> &PTypes, SmallVectorImpl<ParmVarDecl *> *PVars,
Sema::ExtParameterInfoBuilder &PInfos);
Sema::ExtParameterInfoBuilder &PInfos, unsigned *LastParamTransformed);
bool TransformFunctionTypeParams(
SourceLocation Loc, ArrayRef<ParmVarDecl *> Params,
const QualType *ParamTypes,
const FunctionProtoType::ExtParameterInfo *ParamInfos,
SmallVectorImpl<QualType> &PTypes, SmallVectorImpl<ParmVarDecl *> *PVars,
Sema::ExtParameterInfoBuilder &PInfos) {
return getDerived().TransformFunctionTypeParams(
Loc, Params, ParamTypes, ParamInfos, PTypes, PVars, PInfos, nullptr);
}
/// Transforms the parameters of a requires expresison into the given vectors.
///
/// The result vectors should be kept in sync; null entries in the
/// variables vector are acceptable.
///
/// Returns an unset ExprResult on success. Returns an ExprResult the 'not
/// satisfied' RequiresExpr if subsitution failed, OR an ExprError, both of
/// which are cases where transformation shouldn't continue.
ExprResult TransformRequiresTypeParams(
SourceLocation KWLoc, SourceLocation RBraceLoc, const RequiresExpr *RE,
RequiresExprBodyDecl *Body, ArrayRef<ParmVarDecl *> Params,
SmallVectorImpl<QualType> &PTypes,
SmallVectorImpl<ParmVarDecl *> &TransParams,
Sema::ExtParameterInfoBuilder &PInfos) {
if (getDerived().TransformFunctionTypeParams(
KWLoc, Params, /*ParamTypes=*/nullptr,
/*ParamInfos=*/nullptr, PTypes, &TransParams, PInfos))
return ExprError();
return ExprResult{};
}
/// Transforms a single function-type parameter. Return null
/// on error.
@ -5661,11 +5697,14 @@ bool TreeTransform<Derived>::TransformFunctionTypeParams(
const FunctionProtoType::ExtParameterInfo *ParamInfos,
SmallVectorImpl<QualType> &OutParamTypes,
SmallVectorImpl<ParmVarDecl *> *PVars,
Sema::ExtParameterInfoBuilder &PInfos) {
Sema::ExtParameterInfoBuilder &PInfos,
unsigned *LastParamTransformed) {
int indexAdjustment = 0;
unsigned NumParams = Params.size();
for (unsigned i = 0; i != NumParams; ++i) {
if (LastParamTransformed)
*LastParamTransformed = i;
if (ParmVarDecl *OldParm = Params[i]) {
assert(OldParm->getFunctionScopeIndex() == i);
@ -5783,6 +5822,7 @@ bool TreeTransform<Derived>::TransformFunctionTypeParams(
// Deal with the possibility that we don't have a parameter
// declaration for this parameter.
assert(ParamTypes);
QualType OldType = ParamTypes[i];
bool IsPackExpansion = false;
Optional<unsigned> NumExpansions;
@ -12581,16 +12621,20 @@ TreeTransform<Derived>::TransformRequiresExpr(RequiresExpr *E) {
Sema::ContextRAII SavedContext(getSema(), Body, /*NewThisContext*/false);
if (getDerived().TransformFunctionTypeParams(E->getRequiresKWLoc(),
E->getLocalParameters(),
/*ParamTypes=*/nullptr,
/*ParamInfos=*/nullptr,
TransParamTypes, &TransParams,
ExtParamInfos))
return ExprError();
ExprResult TypeParamResult = getDerived().TransformRequiresTypeParams(
E->getRequiresKWLoc(), E->getRBraceLoc(), E, Body,
E->getLocalParameters(), TransParamTypes, TransParams, ExtParamInfos);
for (ParmVarDecl *Param : TransParams)
Param->setDeclContext(Body);
if (Param)
Param->setDeclContext(Body);
// On failure to transform, TransformRequiresTypeParams returns an expression
// in the event that the transformation of the type params failed in some way.
// It is expected that this will result in a 'not satisfied' Requires clause
// when instantiating.
if (!TypeParamResult.isUnset())
return TypeParamResult;
SmallVector<concepts::Requirement *, 4> TransReqs;
if (getDerived().TransformRequiresExprRequirements(E->getRequirements(),

View File

@ -0,0 +1,44 @@
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-unknown %s -verify
namespace dr2565 { // dr252: 16
template<typename T>
concept C = requires (typename T::type x) {
x + 1;
};
static_assert(!C<int>);
// Variant of this as reported in GH57487.
template<bool B> struct bool_constant
{ static constexpr bool value = B; };
template<typename T>
using is_referenceable
= bool_constant<requires (T&) { true; }>;
static_assert(!is_referenceable<void>::value);
static_assert(is_referenceable<int>::value);
template<typename T, typename U>
concept TwoParams = requires (T *a, U b){ true;}; // #TPC
template<typename T, typename U>
requires TwoParams<T, U> // #TPSREQ
struct TwoParamsStruct{};
using TPSU = TwoParamsStruct<void, void>;
// expected-error@-1{{constraints not satisfied for class template 'TwoParamsStruct'}}
// expected-note@#TPSREQ{{because 'TwoParams<void, void>' evaluated to false}}
// expected-note@#TPC{{because 'b' would be invalid: argument may not have 'void' type}}
template<typename T, typename ...U>
concept Variadic = requires (U* ... a, T b){ true;}; // #VC
template<typename T, typename ...U>
requires Variadic<T, U...> // #VSREQ
struct VariadicStruct{};
using VSU = VariadicStruct<void, int, char, double>;
// expected-error@-1{{constraints not satisfied for class template 'VariadicStruct'}}
// expected-note@#VSREQ{{because 'Variadic<void, int, char, double>' evaluated to false}}
// expected-note@#VC{{because 'b' would be invalid: argument may not have 'void' type}}
}

View File

@ -88,7 +88,7 @@ using r7i1 = r7<int>;
// C++ [expr.prim.req.simple] Example
namespace std_example {
template<typename T> concept C =
requires (T a, T b) { // expected-note{{because substituted constraint expression is ill-formed: argument may not have 'void' type}}
requires (T a, T b) { // expected-note{{because 'a' would be invalid: argument may not have 'void' type}}
a + b; // expected-note{{because 'a + b' would be invalid: invalid operands to binary expression ('int *' and 'int *')}}
};

View File

@ -15198,7 +15198,7 @@ and <I>POD class</I></td>
<td><a href="https://wg21.link/cwg2565">2565</a></td>
<td>open</td>
<td>Invalid types in the <I>parameter-declaration-clause</I> of a <I>requires-expression</I></td>
<td align="center">Not resolved</td>
<td class="full" align="center">Clang 16</td>
</tr>
<tr class="open" id="2566">
<td><a href="https://wg21.link/cwg2566">2566</a></td>