Skip to content

Commit 50e5411

Browse files
authored
[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
1 parent a5cd5d3 commit 50e5411

File tree

6 files changed

+191
-7
lines changed

6 files changed

+191
-7
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,8 @@ Bug Fixes to C++ Support
451451
diagnosing a failed cast caused indirectly by a failed implicit conversion to the type of the constructor parameter.
452452
- Fixed an assertion failure by adjusting integral to boolean vector conversions (#GH108326)
453453
- Mangle friend function templates with a constraint that depends on a template parameter from an enclosing template as members of the enclosing class. (#GH110247)
454+
- Fixed an issue in constraint evaluation, where type constraints on the lambda expression
455+
containing outer unexpanded parameters were not correctly expanded. (#GH101754)
454456

455457
Bug Fixes to AST Handling
456458
^^^^^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/Sema/Sema.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11253,6 +11253,7 @@ class Sema final : public SemaBase {
1125311253
ConceptDecl *NamedConcept, NamedDecl *FoundDecl,
1125411254
const TemplateArgumentListInfo *TemplateArgs,
1125511255
TemplateTypeParmDecl *ConstrainedParameter,
11256+
QualType ConstrainedType,
1125611257
SourceLocation EllipsisLoc);
1125711258

1125811259
bool AttachTypeConstraint(AutoTypeLoc TL,

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,7 +1134,8 @@ bool Sema::BuildTypeConstraint(const CXXScopeSpec &SS,
11341134
SS.isSet() ? SS.getWithLocInContext(Context) : NestedNameSpecifierLoc(),
11351135
ConceptName, CD, /*FoundDecl=*/USD ? cast<NamedDecl>(USD) : CD,
11361136
TypeConstr->LAngleLoc.isValid() ? &TemplateArgs : nullptr,
1137-
ConstrainedParameter, EllipsisLoc);
1137+
ConstrainedParameter, Context.getTypeDeclType(ConstrainedParameter),
1138+
EllipsisLoc);
11381139
}
11391140

11401141
template <typename ArgumentLocAppender>
@@ -1191,6 +1192,7 @@ bool Sema::AttachTypeConstraint(NestedNameSpecifierLoc NS,
11911192
ConceptDecl *NamedConcept, NamedDecl *FoundDecl,
11921193
const TemplateArgumentListInfo *TemplateArgs,
11931194
TemplateTypeParmDecl *ConstrainedParameter,
1195+
QualType ConstrainedType,
11941196
SourceLocation EllipsisLoc) {
11951197
// C++2a [temp.param]p4:
11961198
// [...] If Q is of the form C<A1, ..., An>, then let E' be
@@ -1199,7 +1201,7 @@ bool Sema::AttachTypeConstraint(NestedNameSpecifierLoc NS,
11991201
TemplateArgs ? ASTTemplateArgumentListInfo::Create(Context,
12001202
*TemplateArgs) : nullptr;
12011203

1202-
QualType ParamAsArgument(ConstrainedParameter->getTypeForDecl(), 0);
1204+
QualType ParamAsArgument = ConstrainedType;
12031205

12041206
ExprResult ImmediatelyDeclaredConstraint = formImmediatelyDeclaredConstraint(
12051207
*this, NS, NameInfo, NamedConcept, FoundDecl,

clang/lib/Sema/SemaTemplateInstantiate.cpp

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1655,6 +1655,21 @@ namespace {
16551655
SubstTemplateTypeParmPackTypeLoc TL,
16561656
bool SuppressObjCLifetime);
16571657

1658+
QualType
1659+
TransformSubstTemplateTypeParmType(TypeLocBuilder &TLB,
1660+
SubstTemplateTypeParmTypeLoc TL) {
1661+
if (SemaRef.CodeSynthesisContexts.back().Kind !=
1662+
Sema::CodeSynthesisContext::ConstraintSubstitution)
1663+
return inherited::TransformSubstTemplateTypeParmType(TLB, TL);
1664+
1665+
auto PackIndex = TL.getTypePtr()->getPackIndex();
1666+
std::optional<Sema::ArgumentPackSubstitutionIndexRAII> SubstIndex;
1667+
if (SemaRef.ArgumentPackSubstitutionIndex == -1 && PackIndex)
1668+
SubstIndex.emplace(SemaRef, *PackIndex);
1669+
1670+
return inherited::TransformSubstTemplateTypeParmType(TLB, TL);
1671+
}
1672+
16581673
CXXRecordDecl::LambdaDependencyKind
16591674
ComputeLambdaDependency(LambdaScopeInfo *LSI) {
16601675
if (auto TypeAlias =
@@ -3078,6 +3093,58 @@ namespace {
30783093

30793094
} // namespace
30803095

3096+
namespace {
3097+
3098+
struct ExpandPackedTypeConstraints
3099+
: TreeTransform<ExpandPackedTypeConstraints> {
3100+
3101+
using inherited = TreeTransform<ExpandPackedTypeConstraints>;
3102+
3103+
ExpandPackedTypeConstraints(Sema &SemaRef) : inherited(SemaRef) {}
3104+
3105+
using inherited::TransformTemplateTypeParmType;
3106+
3107+
QualType TransformTemplateTypeParmType(TypeLocBuilder &TLB,
3108+
TemplateTypeParmTypeLoc TL, bool) {
3109+
const TemplateTypeParmType *T = TL.getTypePtr();
3110+
if (!T->isParameterPack()) {
3111+
TemplateTypeParmTypeLoc NewTL =
3112+
TLB.push<TemplateTypeParmTypeLoc>(TL.getType());
3113+
NewTL.setNameLoc(TL.getNameLoc());
3114+
return TL.getType();
3115+
}
3116+
3117+
assert(SemaRef.ArgumentPackSubstitutionIndex != -1);
3118+
3119+
QualType Result = SemaRef.Context.getSubstTemplateTypeParmType(
3120+
TL.getType(), T->getDecl(), T->getIndex(),
3121+
SemaRef.ArgumentPackSubstitutionIndex);
3122+
SubstTemplateTypeParmTypeLoc NewTL =
3123+
TLB.push<SubstTemplateTypeParmTypeLoc>(Result);
3124+
NewTL.setNameLoc(TL.getNameLoc());
3125+
return Result;
3126+
}
3127+
3128+
QualType TransformSubstTemplateTypeParmType(TypeLocBuilder &TLB,
3129+
SubstTemplateTypeParmTypeLoc TL) {
3130+
const SubstTemplateTypeParmType *T = TL.getTypePtr();
3131+
if (T->getPackIndex()) {
3132+
SubstTemplateTypeParmTypeLoc TypeLoc =
3133+
TLB.push<SubstTemplateTypeParmTypeLoc>(TL.getType());
3134+
TypeLoc.setNameLoc(TL.getNameLoc());
3135+
return TypeLoc.getType();
3136+
}
3137+
return inherited::TransformSubstTemplateTypeParmType(TLB, TL);
3138+
}
3139+
3140+
bool SubstTemplateArguments(ArrayRef<TemplateArgumentLoc> Args,
3141+
TemplateArgumentListInfo &Out) {
3142+
return inherited::TransformTemplateArguments(Args.begin(), Args.end(), Out);
3143+
}
3144+
};
3145+
3146+
} // namespace
3147+
30813148
bool Sema::SubstTypeConstraint(
30823149
TemplateTypeParmDecl *Inst, const TypeConstraint *TC,
30833150
const MultiLevelTemplateArgumentList &TemplateArgs,
@@ -3086,9 +3153,62 @@ bool Sema::SubstTypeConstraint(
30863153
TC->getTemplateArgsAsWritten();
30873154

30883155
if (!EvaluateConstraints) {
3089-
Inst->setTypeConstraint(TC->getConceptReference(),
3090-
TC->getImmediatelyDeclaredConstraint());
3091-
return false;
3156+
bool ShouldExpandExplicitTemplateArgs =
3157+
TemplArgInfo && ArgumentPackSubstitutionIndex != -1 &&
3158+
llvm::any_of(TemplArgInfo->arguments(), [](auto &Arg) {
3159+
return Arg.getArgument().containsUnexpandedParameterPack();
3160+
});
3161+
3162+
// We want to transform the packs into Subst* nodes for type constraints
3163+
// inside a pack expansion. For example,
3164+
//
3165+
// template <class... Ts> void foo() {
3166+
// bar([](C<Ts> auto value) {}...);
3167+
// }
3168+
//
3169+
// As we expand Ts in the process of instantiating foo(), and retain
3170+
// the original template depths of Ts until the constraint evaluation, we
3171+
// would otherwise have no chance to expand Ts by the time of evaluating
3172+
// C<auto, Ts>.
3173+
//
3174+
// So we form a Subst* node for Ts along with a proper substitution index
3175+
// here, and substitute the node with a complete MLTAL later in evaluation.
3176+
if (ShouldExpandExplicitTemplateArgs) {
3177+
TemplateArgumentListInfo InstArgs;
3178+
InstArgs.setLAngleLoc(TemplArgInfo->LAngleLoc);
3179+
InstArgs.setRAngleLoc(TemplArgInfo->RAngleLoc);
3180+
if (ExpandPackedTypeConstraints(*this).SubstTemplateArguments(
3181+
TemplArgInfo->arguments(), InstArgs))
3182+
return true;
3183+
3184+
// The type of the original parameter.
3185+
auto *ConstraintExpr = TC->getImmediatelyDeclaredConstraint();
3186+
QualType ConstrainedType;
3187+
3188+
if (auto *FE = dyn_cast<CXXFoldExpr>(ConstraintExpr)) {
3189+
assert(FE->getLHS());
3190+
ConstraintExpr = FE->getLHS();
3191+
}
3192+
auto *CSE = cast<ConceptSpecializationExpr>(ConstraintExpr);
3193+
assert(!CSE->getTemplateArguments().empty() &&
3194+
"Empty template arguments?");
3195+
ConstrainedType = CSE->getTemplateArguments()[0].getAsType();
3196+
assert(!ConstrainedType.isNull() &&
3197+
"Failed to extract the original ConstrainedType?");
3198+
3199+
return AttachTypeConstraint(
3200+
TC->getNestedNameSpecifierLoc(), TC->getConceptNameInfo(),
3201+
TC->getNamedConcept(),
3202+
/*FoundDecl=*/TC->getConceptReference()->getFoundDecl(), &InstArgs,
3203+
Inst, ConstrainedType,
3204+
Inst->isParameterPack()
3205+
? cast<CXXFoldExpr>(TC->getImmediatelyDeclaredConstraint())
3206+
->getEllipsisLoc()
3207+
: SourceLocation());
3208+
}
3209+
Inst->setTypeConstraint(TC->getConceptReference(),
3210+
TC->getImmediatelyDeclaredConstraint());
3211+
return false;
30923212
}
30933213

30943214
TemplateArgumentListInfo InstArgs;
@@ -3104,6 +3224,7 @@ bool Sema::SubstTypeConstraint(
31043224
TC->getNestedNameSpecifierLoc(), TC->getConceptNameInfo(),
31053225
TC->getNamedConcept(),
31063226
/*FoundDecl=*/TC->getConceptReference()->getFoundDecl(), &InstArgs, Inst,
3227+
Context.getTypeDeclType(Inst),
31073228
Inst->isParameterPack()
31083229
? cast<CXXFoldExpr>(TC->getImmediatelyDeclaredConstraint())
31093230
->getEllipsisLoc()

clang/lib/Sema/SemaType.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3035,7 +3035,9 @@ InventTemplateParameter(TypeProcessingState &state, QualType T,
30353035
AutoLoc.getNestedNameSpecifierLoc(), AutoLoc.getConceptNameInfo(),
30363036
AutoLoc.getNamedConcept(), /*FoundDecl=*/AutoLoc.getFoundDecl(),
30373037
AutoLoc.hasExplicitTemplateArgs() ? &TAL : nullptr,
3038-
InventedTemplateParam, D.getEllipsisLoc());
3038+
InventedTemplateParam,
3039+
S.Context.getTypeDeclType(InventedTemplateParam),
3040+
D.getEllipsisLoc());
30393041
}
30403042
} else {
30413043
// The 'auto' appears in the decl-specifiers; we've not finished forming
@@ -3072,7 +3074,9 @@ InventTemplateParameter(TypeProcessingState &state, QualType T,
30723074
/*FoundDecl=*/
30733075
USD ? cast<NamedDecl>(USD) : CD,
30743076
TemplateId->LAngleLoc.isValid() ? &TemplateArgsInfo : nullptr,
3075-
InventedTemplateParam, D.getEllipsisLoc());
3077+
InventedTemplateParam,
3078+
S.Context.getTypeDeclType(InventedTemplateParam),
3079+
D.getEllipsisLoc());
30763080
}
30773081
}
30783082
}

clang/test/SemaCXX/fold_lambda_with_variadics.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,57 @@ void foo() {
179179
}
180180

181181
} // namespace GH99877
182+
183+
namespace GH101754 {
184+
185+
template <typename... Ts> struct Overloaded : Ts... {
186+
using Ts::operator()...;
187+
};
188+
189+
template <typename... Ts> Overloaded(Ts...) -> Overloaded<Ts...>;
190+
191+
template <class T, class U>
192+
concept same_as = __is_same(T, U); // #same_as
193+
194+
template <typename... Ts> constexpr auto foo() {
195+
return Overloaded{[](same_as<Ts> auto value) { return value; }...}; // #lambda
196+
}
197+
198+
static_assert(foo<int, double>()(123) == 123);
199+
static_assert(foo<int, double>()(2.718) == 2.718);
200+
201+
static_assert(foo<int, double>()('c'));
202+
// expected-error@-1 {{no matching function}}
203+
204+
// expected-note@#lambda {{constraints not satisfied}}
205+
// expected-note@#lambda {{'same_as<char, int>' evaluated to false}}
206+
// expected-note@#same_as {{evaluated to false}}
207+
208+
// expected-note@#lambda {{constraints not satisfied}}
209+
// expected-note@#lambda {{'same_as<char, double>' evaluated to false}}
210+
// expected-note@#same_as {{evaluated to false}}
211+
212+
template <class T, class U, class V>
213+
concept C = same_as<T, U> && same_as<U, V>; // #C
214+
215+
template <typename... Ts> constexpr auto bar() {
216+
return ([]<class Up>() {
217+
return Overloaded{[](C<Up, Ts> auto value) { // #bar
218+
return value;
219+
}...};
220+
}.template operator()<Ts>(), ...);
221+
}
222+
static_assert(bar<int, float>()(3.14f)); // OK, bar() returns the last overload i.e. <float>.
223+
224+
static_assert(bar<int, float>()(123));
225+
// expected-error@-1 {{no matching function}}
226+
// expected-note@#bar {{constraints not satisfied}}
227+
// expected-note@#bar {{'C<int, float, int>' evaluated to false}}
228+
// expected-note@#C {{evaluated to false}}
229+
230+
// expected-note@#bar {{constraints not satisfied}}
231+
// expected-note@#bar {{'C<int, float, float>' evaluated to false}}
232+
// expected-note@#C {{evaluated to false}}
233+
// expected-note@#same_as 2{{evaluated to false}}
234+
235+
} // namespace GH101754

0 commit comments

Comments
 (0)