[PATCH] D98068: Exampt asserts for recursive lambdas about LocalInstantiationScope
Richard Smith - zygoloid via Phabricator via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 14 12:59:45 PDT 2021
rsmith added a comment.
I think this is the wrong solution; I think the assertion is correct and is observing a problem elsewhere. Imagine if we had something like:
template<typename T>
void f() {
auto a = [] (auto recurse, auto x) {
auto b = [] (auto) {
refer to local decl in a
};
if constexpr (recurse)
a(false_type(), b)
else
x(0)
}
a(true_type(), a)
}
We would then have a local instantiation scope stack that looks like:
f<int>
f<int>::a::operator()<true_type, f<int>::a>
f<int>::a::operator()<false_type, f<int>::a::operator()<true_type, f<int>::a>::b>
f<int>::a::operator()<true_type, f<int>::a>::operator()::b::operator()<int>
... so the reference from the `true_type` version of `b` to the local decl in `a` would find the local from the enclosing `false_type` class, which is wrong. That's the kind of bug that I think this assertion is defending against, and it looks like we have that bug here. (As it happens, this will never actually cause problems, because all references from b to enclosing locals already got substituted when instantiating the `operator()` function template, but that's actually kind of the point -- the local instantiation scope stack is wrong.)
I think the real problem is that the local instantiation scope for the instantiation of a generic lambda's call operator from the instantiated call operator template should never be merged with its parent. Merging the contexts cannot be necessary -- we can have left the parent instantiation before we instantiate a specialization of the call operator template, so there cannot be any references from the call operator template to enclosing locals that require substitution. I think the bug is in `InstantiateFunctionDefinition`, somewhere around line 4863:
// Introduce a new scope where local variable instantiations will be
// recorded, unless we're actually a member function within a local
// class, in which case we need to merge our results with the parent
// scope (of the enclosing function).
bool MergeWithParentScope = false;
if (CXXRecordDecl *Rec = dyn_cast<CXXRecordDecl>(Function->getDeclContext()))
MergeWithParentScope = Rec->isLocalClass();
LocalInstantiationScope Scope(*this, MergeWithParentScope);
While we do need the enclosing local instantiation scope when we're instantiating a non-template member of a local class, or when we're instantiating the definition of a member function template as a member function template (that is, when instantiating a generic lambda to form a generic lambda), we do not need the enclosing local instantiation scope when re-substituting into the result of such a local substitution. That is, we do not want a local instantiation scope if we're instantiating a function template specialization, because the template we're instantiating already had references to locals properly substituted. I'd try a change like this to the above code:
- MergeWithParentScope = Rec->isLocalClass();
+ MergeWithParentScope = Rec->isLocalClass() && !Function->isFunctionTemplateSpecialization();
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D98068/new/
https://reviews.llvm.org/D98068
More information about the cfe-commits
mailing list