Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix missing initializer for inline static template member with auto caused by delayed template instantiation. #138122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

dty2
Copy link
Contributor

@dty2 dty2 commented May 1, 2025

Try to fix missing initializer for inline static template member with auto caused by delayed template instantiation.

Bug Fix: Bug 135032
Problem Description:
Due to nested templates, when instantiating the outer layer (the template class), the inner layer (the template variable) uses delayed instantiation.
This causes the declaration (VarDecl) of the template variable to retain the type from the original template declaration (i.e., auto), and it loses the initializer.
Later, when instantiating the template variable, its VarTemplateSpecializationDecl type depends on the VarDecl type.
Thus, the VarTemplateSpecializationDecl also has no initializer, and its type remains auto.
Ultimately, when building the reference expression in Sema::BuildDeclarationNameExpr, the expression's type is auto and stays as auto until code generation, triggering llvm_unreachable in CodeGenTypes::ConvertType.

Reproduction Steps:
Test Code:

template <typename T> struct B {
  template <typename G> inline static auto var = 5;
};

int main() {
  int bb = B<int>::var<int>;
  return 0;
}

GDB Breakpoints for Debugging:
Annotations like #(1) mark the call order in a tree structure. For example, two #(3) entries mean Sema::CheckVarTemplateId calls Sema::BuildDeclarationNameExpr after its own execution.

#(1)
b Sema::BuildTemplateIdExpr
#(2)
b Sema::CheckVarTemplateId
#(3)
b Sema::CheckVarTemplateId
#(4)
b Sema::BuildVarTemplateInstantiation
#(5)
b TemplateDeclInstantiator::VisitVarTemplateSpecializationDecl
#(6)
b VarTemplateSpecializationDecl::Create
#(7)
b VarTemplateSpecializationDecl::VarTemplateSpecializationDecl
#(8)
b VarDecl::VarDecl
#(6)
b Sema::BuildVariableInstantiation
#(7)
b Sema::InstantiateVariableInitializer
#(3)
b Sema::BuildDeclarationNameExpr
#(4)
b Sema::BuildDeclRefExpr

Key Observations:
While debugging, I generated the AST for the test code and noticed a contradiction:

The AST shows VarTemplateSpecializationDecl as int (correct), but GDB reveals its type is actually auto during execution.

Example from AST:

DeclRefExpr 0x598f651f5a60 <col:9, col:27> 'auto' lvalue VarTemplateSpecialization 0x598f651f5980 'var' 'int' (VarTemplate 0x598f651d1938 'var')
The DeclRefExpr has type auto, while its referenced VarTemplateSpecialization claims to be int—a clear inconsistency.

Solution:
Since I noticed that the deduction of auto type is caused by the initializer
I plan to do special processing for template variables of type auto, that is, to prevent their delayed instantiation
so that their initializers will not be lost when the outer template class is instantiated

…h auto caused by delayed template instantiation.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels May 1, 2025
@llvmbot
Copy link
Member

llvmbot commented May 1, 2025

@llvm/pr-subscribers-clang

Author: 执着 (dty2)

Changes

Try to fix missing initializer for inline static template member with auto caused by delayed template instantiation.

Bug Fix: Bug 135032
Problem Description:
Due to nested templates, when instantiating the outer layer (the template class), the inner layer (the template variable) uses delayed instantiation.
This causes the declaration (VarDecl) of the template variable to retain the type from the original template declaration (i.e., auto), and it loses the initializer.
Later, when instantiating the template variable, its VarTemplateSpecializationDecl type depends on the VarDecl type.
Thus, the VarTemplateSpecializationDecl also has no initializer, and its type remains auto.
Ultimately, when building the reference expression in Sema::BuildDeclarationNameExpr, the expression's type is auto and stays as auto until code generation, triggering llvm_unreachable in CodeGenTypes::ConvertType.

Reproduction Steps:
Test Code:

template &lt;typename T&gt; struct B {
  template &lt;typename G&gt; inline static auto var = 5;
};

int main() {
  int bb = B&lt;int&gt;::var&lt;int&gt;;
  return 0;
}

GDB Breakpoints for Debugging:
Annotations like #(1) mark the call order in a tree structure. For example, two #(3) entries mean Sema::CheckVarTemplateId calls Sema::BuildDeclarationNameExpr after its own execution.

#(1)
b Sema::BuildTemplateIdExpr
#(2)
b Sema::CheckVarTemplateId
#(3)
b Sema::CheckVarTemplateId
#(4)
b Sema::BuildVarTemplateInstantiation
#(5)
b TemplateDeclInstantiator::VisitVarTemplateSpecializationDecl
#(6)
b VarTemplateSpecializationDecl::Create
#(7)
b VarTemplateSpecializationDecl::VarTemplateSpecializationDecl
#(8)
b VarDecl::VarDecl
#(6)
b Sema::BuildVariableInstantiation
#(7)
b Sema::InstantiateVariableInitializer
#(3)
b Sema::BuildDeclarationNameExpr
#(4)
b Sema::BuildDeclRefExpr

Key Observations:
While debugging, I generated the AST for the test code and noticed a contradiction:

The AST shows VarTemplateSpecializationDecl as int (correct), but GDB reveals its type is actually auto during execution.

Example from AST:

DeclRefExpr 0x598f651f5a60 <col:9, col:27> 'auto' lvalue VarTemplateSpecialization 0x598f651f5980 'var' 'int' (VarTemplate 0x598f651d1938 'var')
The DeclRefExpr has type auto, while its referenced VarTemplateSpecialization claims to be int—a clear inconsistency.

Solution:
Since I noticed that the deduction of auto type is caused by the initializer
I plan to do special processing for template variables of type auto, that is, to prevent their delayed instantiation
so that their initializers will not be lost when the outer template class is instantiated


Full diff: https://github.com/llvm/llvm-project/pull/138122.diff

3 Files Affected:

  • (modified) clang/lib/Sema/SemaTemplateInstantiateDecl.cpp (+7-1)
  • (modified) clang/test/CodeGenCXX/cxx1z-inline-variables.cpp (+8)
  • (modified) clang/test/SemaTemplate/cxx17-inline-variables.cpp (+6)
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 76c055d28f091..eb66ec34a956f 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -6027,8 +6027,14 @@ void Sema::BuildVariableInstantiation(
   Context.setManglingNumber(NewVar, Context.getManglingNumber(OldVar));
   Context.setStaticLocalNumber(NewVar, Context.getStaticLocalNumber(OldVar));
 
+  bool VarTemplateWithAutoType = false;
+  QualType VarSourceType = OldVar->getTypeSourceInfo()->getType();
+  if (VarSourceType->getAs<AutoType>()) {
+    VarTemplateWithAutoType = true;
+  }
+
   // Figure out whether to eagerly instantiate the initializer.
-  if (InstantiatingVarTemplate || InstantiatingVarTemplatePartialSpec) {
+  if ((InstantiatingVarTemplate && !VarTemplateWithAutoType) || InstantiatingVarTemplatePartialSpec) {
     // We're producing a template. Don't instantiate the initializer yet.
   } else if (NewVar->getType()->isUndeducedType()) {
     // We need the type to complete the declaration of the variable.
diff --git a/clang/test/CodeGenCXX/cxx1z-inline-variables.cpp b/clang/test/CodeGenCXX/cxx1z-inline-variables.cpp
index 812e438f30c9a..b1d8e376b826f 100644
--- a/clang/test/CodeGenCXX/cxx1z-inline-variables.cpp
+++ b/clang/test/CodeGenCXX/cxx1z-inline-variables.cpp
@@ -1,5 +1,13 @@
 // RUN: %clang_cc1 -std=c++1z %s -emit-llvm -o - -triple x86_64-linux-gnu | FileCheck %s
 
+template <typename T> struct B {
+  template <typename G> inline static auto var = 5;
+};
+
+int inlinevartemplate = B<int>::var<int>;
+// CHECK: @_ZN1BIiE3varIiEE = {{.*}}global i32 5{{.*}}comdat
+// CHECK-NOT: @_ZN1BIiE3varIfEE
+
 struct Q {
   // CHECK: @_ZN1Q1kE = linkonce_odr constant i32 5, comdat
   static constexpr int k = 5;
diff --git a/clang/test/SemaTemplate/cxx17-inline-variables.cpp b/clang/test/SemaTemplate/cxx17-inline-variables.cpp
index 7fc0aa8eeeb0c..27067c8e1b5e4 100644
--- a/clang/test/SemaTemplate/cxx17-inline-variables.cpp
+++ b/clang/test/SemaTemplate/cxx17-inline-variables.cpp
@@ -27,3 +27,9 @@ template <typename T> constexpr int A<T>::n = sizeof(A) + sizeof(T);
 template <typename T> inline constexpr int A<T>::m = sizeof(A) + sizeof(T);
 static_assert(A<int>().f() == 5);
 static_assert(A<int>().g() == 5);
+
+template <typename T> struct B {
+  template <typename G> inline static auto var = 5;
+};
+
+int b = B<int>::var<int>;

Copy link

github-actions bot commented May 1, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

…h auto caused by delayed template instantiation.
Comment on lines +6030 to +6035
bool VarTemplateWithAutoType = false;
QualType VarSourceType = OldVar->getTypeSourceInfo()->getType();
if (VarSourceType->getAs<AutoType>()) {
VarTemplateWithAutoType = true;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bool VarTemplateWithAutoType = false;
QualType VarSourceType = OldVar->getTypeSourceInfo()->getType();
if (VarSourceType->getAs<AutoType>()) {
VarTemplateWithAutoType = true;
}
bool VarTemplateWithAutoType = OldVar->getTypeSourceInfo()->getType()->getAs<AutoType>();

Can we avoid getTypeSourceInfo?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your suggestion is correct, I will adopt it.

@zyn0217
Copy link
Contributor

zyn0217 commented May 1, 2025

Also, can you help me test if this fixes #134526? Thanks

@dty2
Copy link
Contributor Author

dty2 commented May 1, 2025

Also, can you help me test if this fixes #134526? Thanks

sure, no problem.

@dty2
Copy link
Contributor Author

dty2 commented May 1, 2025

@zyn0217
I can compile it successfuly, and the output is 1.

#include <iostream>
template <class T>
struct S {
  template <class U> static const auto var = T();
  template <class U> static const auto foo = var<T>;
};

int main() {
  const int p = S<int>::var<int> + S<int>::foo<int> + 1;
  std::cout << p;
  return 0;
}

Copy link
Contributor

@zwuis zwuis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a release note in clang/docs/ReleaseNotes.rst .

@@ -6027,8 +6027,15 @@ void Sema::BuildVariableInstantiation(
Context.setManglingNumber(NewVar, Context.getManglingNumber(OldVar));
Context.setStaticLocalNumber(NewVar, Context.getStaticLocalNumber(OldVar));

bool VarTemplateWithAutoType = false;
QualType VarSourceType = OldVar->getTypeSourceInfo()->getType();
if (VarSourceType->getAs<AutoType>()) {
Copy link
Contributor

@zwuis zwuis May 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use ->isUndeducedType() to handle this case

template <typename T> struct S { S(T); };
template <typename T> struct B { 
   template <typename G> inline static S var = 5; 
 };

?

Update: And we need to change OldVar-> to NewVar->.

Comment on lines +6037 to 6041
if ((InstantiatingVarTemplate && !VarTemplateWithAutoType) ||
InstantiatingVarTemplatePartialSpec) {
// We're producing a template. Don't instantiate the initializer yet.
} else if (NewVar->getType()->isUndeducedType()) {
// We need the type to complete the declaration of the variable.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that we can just swap these two if statements. We need to handle partial specializations as well. E.g.

template <typename> struct B {
  template <typename, typrname> inline static auto var = 6;
  template <typename T> inline static auto var<int, T> = 7;
};

int b = B<int>::var<int, int>;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[clang] Crash instantiating static variable template specialization member of class template
4 participants