[Clang][Sema] Retain the expanding index for unevaluated type constraints (#109518)
(This continues the effort of #86265, fixing another piece of issue in constraint evaluation on variadic lambdas.) We need the depth of the primary template parameters for constraint substitution. To that end, we avoided substituting type constraints by copying the constraint expression when instantiating a template. This, however, has left an issue in that for lambda's parameters, they can reference outer template packs that would be expanded in the process of an instantiation, where these parameters would make their way into the constraint evaluation, wherein we have no other way to expand them later in evaluation. For example, template <class... Ts> void foo() { bar([](C<Ts> auto value) {}...); } The lambda references a pack `Ts` that should be expanded when instantiating `foo()`. The `Ts` along with the constraint expression would not be transformed until constraint evaluation, and at that point, we would have no chance to expand `Ts` anyhow. This patch takes an approach that transforms `Ts` from an unexpanded TemplateTypeParmType into a SubstTemplateTypeParmType with the current pack substitution index, such that we could use that to expand the type during evaluation. Fixes #101754
This commit is contained in:
@@ -451,6 +451,8 @@ Bug Fixes to C++ Support
|
||||
diagnosing a failed cast caused indirectly by a failed implicit conversion to the type of the constructor parameter.
|
||||
- Fixed an assertion failure by adjusting integral to boolean vector conversions (#GH108326)
|
||||
- Mangle friend function templates with a constraint that depends on a template parameter from an enclosing template as members of the enclosing class. (#GH110247)
|
||||
- Fixed an issue in constraint evaluation, where type constraints on the lambda expression
|
||||
containing outer unexpanded parameters were not correctly expanded. (#GH101754)
|
||||
|
||||
Bug Fixes to AST Handling
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -11253,6 +11253,7 @@ public:
|
||||
ConceptDecl *NamedConcept, NamedDecl *FoundDecl,
|
||||
const TemplateArgumentListInfo *TemplateArgs,
|
||||
TemplateTypeParmDecl *ConstrainedParameter,
|
||||
QualType ConstrainedType,
|
||||
SourceLocation EllipsisLoc);
|
||||
|
||||
bool AttachTypeConstraint(AutoTypeLoc TL,
|
||||
|
||||
@@ -1134,7 +1134,8 @@ bool Sema::BuildTypeConstraint(const CXXScopeSpec &SS,
|
||||
SS.isSet() ? SS.getWithLocInContext(Context) : NestedNameSpecifierLoc(),
|
||||
ConceptName, CD, /*FoundDecl=*/USD ? cast<NamedDecl>(USD) : CD,
|
||||
TypeConstr->LAngleLoc.isValid() ? &TemplateArgs : nullptr,
|
||||
ConstrainedParameter, EllipsisLoc);
|
||||
ConstrainedParameter, Context.getTypeDeclType(ConstrainedParameter),
|
||||
EllipsisLoc);
|
||||
}
|
||||
|
||||
template <typename ArgumentLocAppender>
|
||||
@@ -1191,6 +1192,7 @@ bool Sema::AttachTypeConstraint(NestedNameSpecifierLoc NS,
|
||||
ConceptDecl *NamedConcept, NamedDecl *FoundDecl,
|
||||
const TemplateArgumentListInfo *TemplateArgs,
|
||||
TemplateTypeParmDecl *ConstrainedParameter,
|
||||
QualType ConstrainedType,
|
||||
SourceLocation EllipsisLoc) {
|
||||
// C++2a [temp.param]p4:
|
||||
// [...] If Q is of the form C<A1, ..., An>, then let E' be
|
||||
@@ -1199,7 +1201,7 @@ bool Sema::AttachTypeConstraint(NestedNameSpecifierLoc NS,
|
||||
TemplateArgs ? ASTTemplateArgumentListInfo::Create(Context,
|
||||
*TemplateArgs) : nullptr;
|
||||
|
||||
QualType ParamAsArgument(ConstrainedParameter->getTypeForDecl(), 0);
|
||||
QualType ParamAsArgument = ConstrainedType;
|
||||
|
||||
ExprResult ImmediatelyDeclaredConstraint = formImmediatelyDeclaredConstraint(
|
||||
*this, NS, NameInfo, NamedConcept, FoundDecl,
|
||||
|
||||
@@ -1655,6 +1655,21 @@ namespace {
|
||||
SubstTemplateTypeParmPackTypeLoc TL,
|
||||
bool SuppressObjCLifetime);
|
||||
|
||||
QualType
|
||||
TransformSubstTemplateTypeParmType(TypeLocBuilder &TLB,
|
||||
SubstTemplateTypeParmTypeLoc TL) {
|
||||
if (SemaRef.CodeSynthesisContexts.back().Kind !=
|
||||
Sema::CodeSynthesisContext::ConstraintSubstitution)
|
||||
return inherited::TransformSubstTemplateTypeParmType(TLB, TL);
|
||||
|
||||
auto PackIndex = TL.getTypePtr()->getPackIndex();
|
||||
std::optional<Sema::ArgumentPackSubstitutionIndexRAII> SubstIndex;
|
||||
if (SemaRef.ArgumentPackSubstitutionIndex == -1 && PackIndex)
|
||||
SubstIndex.emplace(SemaRef, *PackIndex);
|
||||
|
||||
return inherited::TransformSubstTemplateTypeParmType(TLB, TL);
|
||||
}
|
||||
|
||||
CXXRecordDecl::LambdaDependencyKind
|
||||
ComputeLambdaDependency(LambdaScopeInfo *LSI) {
|
||||
if (auto TypeAlias =
|
||||
@@ -3078,6 +3093,58 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
|
||||
struct ExpandPackedTypeConstraints
|
||||
: TreeTransform<ExpandPackedTypeConstraints> {
|
||||
|
||||
using inherited = TreeTransform<ExpandPackedTypeConstraints>;
|
||||
|
||||
ExpandPackedTypeConstraints(Sema &SemaRef) : inherited(SemaRef) {}
|
||||
|
||||
using inherited::TransformTemplateTypeParmType;
|
||||
|
||||
QualType TransformTemplateTypeParmType(TypeLocBuilder &TLB,
|
||||
TemplateTypeParmTypeLoc TL, bool) {
|
||||
const TemplateTypeParmType *T = TL.getTypePtr();
|
||||
if (!T->isParameterPack()) {
|
||||
TemplateTypeParmTypeLoc NewTL =
|
||||
TLB.push<TemplateTypeParmTypeLoc>(TL.getType());
|
||||
NewTL.setNameLoc(TL.getNameLoc());
|
||||
return TL.getType();
|
||||
}
|
||||
|
||||
assert(SemaRef.ArgumentPackSubstitutionIndex != -1);
|
||||
|
||||
QualType Result = SemaRef.Context.getSubstTemplateTypeParmType(
|
||||
TL.getType(), T->getDecl(), T->getIndex(),
|
||||
SemaRef.ArgumentPackSubstitutionIndex);
|
||||
SubstTemplateTypeParmTypeLoc NewTL =
|
||||
TLB.push<SubstTemplateTypeParmTypeLoc>(Result);
|
||||
NewTL.setNameLoc(TL.getNameLoc());
|
||||
return Result;
|
||||
}
|
||||
|
||||
QualType TransformSubstTemplateTypeParmType(TypeLocBuilder &TLB,
|
||||
SubstTemplateTypeParmTypeLoc TL) {
|
||||
const SubstTemplateTypeParmType *T = TL.getTypePtr();
|
||||
if (T->getPackIndex()) {
|
||||
SubstTemplateTypeParmTypeLoc TypeLoc =
|
||||
TLB.push<SubstTemplateTypeParmTypeLoc>(TL.getType());
|
||||
TypeLoc.setNameLoc(TL.getNameLoc());
|
||||
return TypeLoc.getType();
|
||||
}
|
||||
return inherited::TransformSubstTemplateTypeParmType(TLB, TL);
|
||||
}
|
||||
|
||||
bool SubstTemplateArguments(ArrayRef<TemplateArgumentLoc> Args,
|
||||
TemplateArgumentListInfo &Out) {
|
||||
return inherited::TransformTemplateArguments(Args.begin(), Args.end(), Out);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Sema::SubstTypeConstraint(
|
||||
TemplateTypeParmDecl *Inst, const TypeConstraint *TC,
|
||||
const MultiLevelTemplateArgumentList &TemplateArgs,
|
||||
@@ -3086,9 +3153,62 @@ bool Sema::SubstTypeConstraint(
|
||||
TC->getTemplateArgsAsWritten();
|
||||
|
||||
if (!EvaluateConstraints) {
|
||||
Inst->setTypeConstraint(TC->getConceptReference(),
|
||||
TC->getImmediatelyDeclaredConstraint());
|
||||
return false;
|
||||
bool ShouldExpandExplicitTemplateArgs =
|
||||
TemplArgInfo && ArgumentPackSubstitutionIndex != -1 &&
|
||||
llvm::any_of(TemplArgInfo->arguments(), [](auto &Arg) {
|
||||
return Arg.getArgument().containsUnexpandedParameterPack();
|
||||
});
|
||||
|
||||
// We want to transform the packs into Subst* nodes for type constraints
|
||||
// inside a pack expansion. For example,
|
||||
//
|
||||
// template <class... Ts> void foo() {
|
||||
// bar([](C<Ts> auto value) {}...);
|
||||
// }
|
||||
//
|
||||
// As we expand Ts in the process of instantiating foo(), and retain
|
||||
// the original template depths of Ts until the constraint evaluation, we
|
||||
// would otherwise have no chance to expand Ts by the time of evaluating
|
||||
// C<auto, Ts>.
|
||||
//
|
||||
// So we form a Subst* node for Ts along with a proper substitution index
|
||||
// here, and substitute the node with a complete MLTAL later in evaluation.
|
||||
if (ShouldExpandExplicitTemplateArgs) {
|
||||
TemplateArgumentListInfo InstArgs;
|
||||
InstArgs.setLAngleLoc(TemplArgInfo->LAngleLoc);
|
||||
InstArgs.setRAngleLoc(TemplArgInfo->RAngleLoc);
|
||||
if (ExpandPackedTypeConstraints(*this).SubstTemplateArguments(
|
||||
TemplArgInfo->arguments(), InstArgs))
|
||||
return true;
|
||||
|
||||
// The type of the original parameter.
|
||||
auto *ConstraintExpr = TC->getImmediatelyDeclaredConstraint();
|
||||
QualType ConstrainedType;
|
||||
|
||||
if (auto *FE = dyn_cast<CXXFoldExpr>(ConstraintExpr)) {
|
||||
assert(FE->getLHS());
|
||||
ConstraintExpr = FE->getLHS();
|
||||
}
|
||||
auto *CSE = cast<ConceptSpecializationExpr>(ConstraintExpr);
|
||||
assert(!CSE->getTemplateArguments().empty() &&
|
||||
"Empty template arguments?");
|
||||
ConstrainedType = CSE->getTemplateArguments()[0].getAsType();
|
||||
assert(!ConstrainedType.isNull() &&
|
||||
"Failed to extract the original ConstrainedType?");
|
||||
|
||||
return AttachTypeConstraint(
|
||||
TC->getNestedNameSpecifierLoc(), TC->getConceptNameInfo(),
|
||||
TC->getNamedConcept(),
|
||||
/*FoundDecl=*/TC->getConceptReference()->getFoundDecl(), &InstArgs,
|
||||
Inst, ConstrainedType,
|
||||
Inst->isParameterPack()
|
||||
? cast<CXXFoldExpr>(TC->getImmediatelyDeclaredConstraint())
|
||||
->getEllipsisLoc()
|
||||
: SourceLocation());
|
||||
}
|
||||
Inst->setTypeConstraint(TC->getConceptReference(),
|
||||
TC->getImmediatelyDeclaredConstraint());
|
||||
return false;
|
||||
}
|
||||
|
||||
TemplateArgumentListInfo InstArgs;
|
||||
@@ -3104,6 +3224,7 @@ bool Sema::SubstTypeConstraint(
|
||||
TC->getNestedNameSpecifierLoc(), TC->getConceptNameInfo(),
|
||||
TC->getNamedConcept(),
|
||||
/*FoundDecl=*/TC->getConceptReference()->getFoundDecl(), &InstArgs, Inst,
|
||||
Context.getTypeDeclType(Inst),
|
||||
Inst->isParameterPack()
|
||||
? cast<CXXFoldExpr>(TC->getImmediatelyDeclaredConstraint())
|
||||
->getEllipsisLoc()
|
||||
|
||||
@@ -3035,7 +3035,9 @@ InventTemplateParameter(TypeProcessingState &state, QualType T,
|
||||
AutoLoc.getNestedNameSpecifierLoc(), AutoLoc.getConceptNameInfo(),
|
||||
AutoLoc.getNamedConcept(), /*FoundDecl=*/AutoLoc.getFoundDecl(),
|
||||
AutoLoc.hasExplicitTemplateArgs() ? &TAL : nullptr,
|
||||
InventedTemplateParam, D.getEllipsisLoc());
|
||||
InventedTemplateParam,
|
||||
S.Context.getTypeDeclType(InventedTemplateParam),
|
||||
D.getEllipsisLoc());
|
||||
}
|
||||
} else {
|
||||
// The 'auto' appears in the decl-specifiers; we've not finished forming
|
||||
@@ -3072,7 +3074,9 @@ InventTemplateParameter(TypeProcessingState &state, QualType T,
|
||||
/*FoundDecl=*/
|
||||
USD ? cast<NamedDecl>(USD) : CD,
|
||||
TemplateId->LAngleLoc.isValid() ? &TemplateArgsInfo : nullptr,
|
||||
InventedTemplateParam, D.getEllipsisLoc());
|
||||
InventedTemplateParam,
|
||||
S.Context.getTypeDeclType(InventedTemplateParam),
|
||||
D.getEllipsisLoc());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,3 +179,57 @@ void foo() {
|
||||
}
|
||||
|
||||
} // namespace GH99877
|
||||
|
||||
namespace GH101754 {
|
||||
|
||||
template <typename... Ts> struct Overloaded : Ts... {
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
template <typename... Ts> Overloaded(Ts...) -> Overloaded<Ts...>;
|
||||
|
||||
template <class T, class U>
|
||||
concept same_as = __is_same(T, U); // #same_as
|
||||
|
||||
template <typename... Ts> constexpr auto foo() {
|
||||
return Overloaded{[](same_as<Ts> auto value) { return value; }...}; // #lambda
|
||||
}
|
||||
|
||||
static_assert(foo<int, double>()(123) == 123);
|
||||
static_assert(foo<int, double>()(2.718) == 2.718);
|
||||
|
||||
static_assert(foo<int, double>()('c'));
|
||||
// expected-error@-1 {{no matching function}}
|
||||
|
||||
// expected-note@#lambda {{constraints not satisfied}}
|
||||
// expected-note@#lambda {{'same_as<char, int>' evaluated to false}}
|
||||
// expected-note@#same_as {{evaluated to false}}
|
||||
|
||||
// expected-note@#lambda {{constraints not satisfied}}
|
||||
// expected-note@#lambda {{'same_as<char, double>' evaluated to false}}
|
||||
// expected-note@#same_as {{evaluated to false}}
|
||||
|
||||
template <class T, class U, class V>
|
||||
concept C = same_as<T, U> && same_as<U, V>; // #C
|
||||
|
||||
template <typename... Ts> constexpr auto bar() {
|
||||
return ([]<class Up>() {
|
||||
return Overloaded{[](C<Up, Ts> auto value) { // #bar
|
||||
return value;
|
||||
}...};
|
||||
}.template operator()<Ts>(), ...);
|
||||
}
|
||||
static_assert(bar<int, float>()(3.14f)); // OK, bar() returns the last overload i.e. <float>.
|
||||
|
||||
static_assert(bar<int, float>()(123));
|
||||
// expected-error@-1 {{no matching function}}
|
||||
// expected-note@#bar {{constraints not satisfied}}
|
||||
// expected-note@#bar {{'C<int, float, int>' evaluated to false}}
|
||||
// expected-note@#C {{evaluated to false}}
|
||||
|
||||
// expected-note@#bar {{constraints not satisfied}}
|
||||
// expected-note@#bar {{'C<int, float, float>' evaluated to false}}
|
||||
// expected-note@#C {{evaluated to false}}
|
||||
// expected-note@#same_as 2{{evaluated to false}}
|
||||
|
||||
} // namespace GH101754
|
||||
|
||||
Reference in New Issue
Block a user