PR27015 (variable template initialized with a generic lambda expresssion)

I was wondering whether someone could answer a few questions about variable templates. I’m trying to come up with a patch that fixes the crash described in PR27015.

https://llvm.org/bugs/show_bug.cgi?id=27015

The crash happens when clang compiles a code that has a variable template initialized with a generic lambda. For example,

$ cat test1.c

template auto fn = (auto a) { return a + T(1); };

template
int func() {
X a = 0x61;
fn(a);
return 0;
}

int main() {
func();
}

First question, is this legal c++14 code? I didn’t find anything that suggests it isn’t legal, but I haven’t found any code that uses variable templates like this either other than the provided test case.

Second question, what would the closure type look like in this case? My understanding is that the closure type for generic lambda without template parameters looks like this:

class Class {
template
AT operator()(AT a) { … }

};

With template parameter, would it look like this?

template

class Class {
template
AT operator()(AT a) { return a + T(1); }

};

I was wondering whether someone could answer a few questions about
variable templates. I'm trying to come up with a patch that fixes the crash
described in PR27015.

https://llvm.org/bugs/show_bug.cgi?id=27015

The crash happens when clang compiles a code that has a variable template
initialized with a generic lambda. For example,

$ cat test1.c

template<typename T> auto fn = (auto a) { return a + T(1); };

template <typename X>
int func() {
  X a = 0x61;
  fn<char>(a);
  return 0;
}

int main() {
  func<int>();
}

First question, is this legal c++14 code? I didn't find anything that
suggests it isn't legal, but I haven't found any code that uses variable
templates like this either other than the provided test case.

Yes, sadly, this is valid C++14 code.

Second question, what would the closure type look like in this case? My
understanding is that the closure type for generic lambda without template
parameters looks like this:

class Class {
template<typename AT>
  AT operator()(AT a) { ... }
  ...
};

With template parameter, would it look like this?

template<typename T>
class Class {
template<typename AT>
  AT operator()(AT a) { return a + T(1); }
  ...
};

No. Each instantiation of 'fn' gets its own closure type. The closure type
from the template itself should be treated as being in a dependent context,
even though there is no dependent DeclContext to contain it.

After the template declaration for func is parsed,
VarTemplateSpecialization for fn looks like this in the AST:

`-*VarTemplateSpecializationDecl* 0x10d001a00 <line:23:22, col:62> col:27
referenced* fn* 'auto' cinit

I think this is not correct as the type of the
VarTemplateSpecializationDecl at this point should be the closure type for
fn<char> instead of 'auto'. Is that correct?

I'm still trying to understand how instantiation of variable templates
works, but it seems like one of the problem is that
Sema::createLambdaClosureType is returning a type that is dependent
(CXXRecordDecl::isDependentType() returns true), which looks like is
preventing the type of VarTemplateSpecialization from being replaced.

I was wondering whether someone could answer a few questions about
variable templates. I'm trying to come up with a patch that fixes the crash
described in PR27015.

https://llvm.org/bugs/show_bug.cgi?id=27015

The crash happens when clang compiles a code that has a variable
template initialized with a generic lambda. For example,

$ cat test1.c

template<typename T> auto fn = (auto a) { return a + T(1); };

template <typename X>
int func() {
  X a = 0x61;
  fn<char>(a);
  return 0;
}

int main() {
  func<int>();
}

First question, is this legal c++14 code? I didn't find anything that
suggests it isn't legal, but I haven't found any code that uses variable
templates like this either other than the provided test case.

Yes, sadly, this is valid C++14 code.

Second question, what would the closure type look like in this case? My
understanding is that the closure type for generic lambda without template
parameters looks like this:

class Class {
template<typename AT>
  AT operator()(AT a) { ... }
  ...
};

With template parameter, would it look like this?

template<typename T>
class Class {
template<typename AT>
  AT operator()(AT a) { return a + T(1); }
  ...
};

No. Each instantiation of 'fn' gets its own closure type. The closure
type from the template itself should be treated as being in a dependent
context, even though there is no dependent DeclContext to contain it.

After the template declaration for func is parsed,
VarTemplateSpecialization for fn looks like this in the AST:

`-*VarTemplateSpecializationDecl* 0x10d001a00 <line:23:22, col:62> col:27
referenced* fn* 'auto' cinit

I think this is not correct as the type of the
VarTemplateSpecializationDecl at this point should be the closure type for
fn<char> instead of 'auto'. Is that correct?

Yes.

I'm still trying to understand how instantiation of variable templates
works, but it seems like one of the problem is that
Sema::createLambdaClosureType is returning a type that is dependent
(CXXRecordDecl::isDependentType() returns true), which looks like is
preventing the type of VarTemplateSpecialization from being replaced.

The closure type created in the instantiation should not be considered
dependent. If it is, that's a bug.

Thanks, I was about to send an update.

I made a few changes to make sure the lambda class gets the right parent DeclContext, which I think is the translation unit (type Decl::TranslationUnit) in my example. Currently, when the variable template is instantiated (fn), function “func” is passed as the DeclContext to CXXRecordDecl::CreateLambda, which is causing CXXRecordDecl::isDependentType() to return true.

With this change, clang doesn’t crash anymore.

Does it sound like I’m headed in the right direction? I can send my WIP patch for review to cfe-commits if that makes it easier to discuss my current approach.

Thanks, I was about to send an update.

I made a few changes to make sure the lambda class gets the right parent
DeclContext, which I think is the translation unit (type
Decl::TranslationUnit) in my example. Currently, when the variable template
is instantiated (fn<char>), function "func" is passed as the DeclContext to
CXXRecordDecl::CreateLambda, which is causing
CXXRecordDecl::isDependentType() to return true.

With this change, clang doesn't crash anymore.

Does it sound like I'm headed in the right direction? I can send my WIP
patch for review to cfe-commits if that makes it easier to discuss my
current approach.

Yes, that sounds like you've found the root cause and are heading in the
right direction.