Skip to content

Conversation

@HamzaHassanain
Copy link
Contributor

@HamzaHassanain HamzaHassanain commented Jan 3, 2026

#173349

The Problem

Clang crashes when encountering a static data member declared with an alias template but lacking template arguments (e.g., static E u;).

Because of C++ CTAD rules, Clang's parser assigns this a placeholder AutoType. If this member is later referenced, the backend (CodeGen) attempts to emit the variable. Since the type remains undeduced and no initializer exists, the compiler hits an llvm_unreachable in CodeGenTypes.cpp.

The Fix

The fix is implemented in Sema::ActOnUninitializedDecl. We now explicitly check if a static data member still possesses an undeduced placeholder type after all deduction attempts have failed.

  • Logic: If the type contains an undeduced type, and the variable is a static member without an initializer, we emit err_auto_var_requires_init.

Changes

  • SemaDecl.cpp: Added validation logic to ActOnUninitializedDecl to catch undeduced static members.
  • alias-template-static-member.cpp: Added a regression test to ensure this scenario results in a diagnostic rather than a crash.

Notes on formatting

When I clang-format the SemaDecl.cpp after applying my changes, I found the whole file got changed, so I undid the formatting.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jan 3, 2026
@llvmbot
Copy link
Member

llvmbot commented Jan 3, 2026

@llvm/pr-subscribers-clang

Author: Hamza Hassanain (HamzaHassanain)

Changes

The Problem

Clang crashes when encountering a static data member declared with an alias template but lacking template arguments (e.g., static E u;).

Because of C++ CTAD rules, Clang's parser assigns this a placeholder AutoType. If this member is later referenced, the backend (CodeGen) attempts to emit the variable. Since the type remains undeduced and no initializer exists, the compiler hits an llvm_unreachable in CodeGenTypes.cpp.

The Fix

The fix is implemented in Sema::ActOnUninitializedDecl. We now explicitly check if a static data member still possesses an undeduced placeholder type after all deduction attempts have failed.

  • Logic: If the type contains an undeduced type, and the variable is a static member without an initializer, we emit err_auto_var_requires_init.

Changes

  • SemaDecl.cpp: Added validation logic to ActOnUninitializedDecl to catch undeduced static members.
  • alias-template-static-member.cpp: Added a regression test to ensure this scenario results in a diagnostic rather than a crash.

Notes on formatting

When I clang-format the SemaDecl.cpp after applying my changes, I found the whole file got changed, so I undid the formatting.


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

2 Files Affected:

  • (modified) clang/lib/Sema/SemaDecl.cpp (+10)
  • (added) clang/test/SemaCXX/alias-template-static-member.cpp (+11)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 11323803e1910..50a6a4176d1ec 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -14337,8 +14337,18 @@ void Sema::ActOnUninitializedDecl(Decl *RealDecl) {
         DeduceVariableDeclarationType(Var, false, nullptr))
       return;
 
+    Type = Var->getType();
     this->CheckAttributesOnDeducedType(RealDecl);
 
+    if (auto *Deduced = Type->getContainedDeducedType()) {
+      if (Var->isStaticDataMember() && Deduced->getDeducedType().isNull()) {
+        Diag(Var->getLocation(), diag::err_auto_var_requires_init)
+            << Var->getDeclName() << Type;
+        Var->setInvalidDecl();
+        return;
+      }
+    }
+
     // C++11 [class.static.data]p3: A static data member can be declared with
     // the constexpr specifier; if so, its declaration shall specify
     // a brace-or-equal-initializer.
diff --git a/clang/test/SemaCXX/alias-template-static-member.cpp b/clang/test/SemaCXX/alias-template-static-member.cpp
new file mode 100644
index 0000000000000..0089aea33cc80
--- /dev/null
+++ b/clang/test/SemaCXX/alias-template-static-member.cpp
@@ -0,0 +1,11 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++17 %s
+
+template <class T>
+struct A {
+  template <class U>
+  using E = U;
+
+  static E u; // expected-error {{declaration of variable 'u' with deduced type 'E' requires an initializer}}
+};
+
+decltype(A<int>::u) a;
\ No newline at end of file

@zwuis
Copy link
Contributor

zwuis commented Jan 3, 2026

Please add a release note entry to "clang/docs/ReleaseNotes.rst".


We already diagnose correctly if we move E and u out of templates. Can you merge these logic?

@HamzaHassanain
Copy link
Contributor Author

@zwuis

Switched from checking Type->isUndeducedType() to Type->getContainedDeducedType()
.

As far as I understand:

isUndeducedType() looks at E, sees it's an alias, and returns false because E itself isn't technically a "deduced type" node until you unwrap it.

So, should I leave it as Type->isUndeducedType() and find another condition to force calling Sema::DeduceVariableDeclarationType (so as to call deduceVarTypeFromInitializer to hit the place where we handle E, U)

static E u; // expected-error {{declaration of variable 'u' with deduced type 'E' requires an initializer}}
};

decltype(A<int>::u) a; No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing EOF - also, can we put that test in an existing file ? test/SemaCXX/alias-template.cpp would be a good candidate

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I added that!

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.

While we are here, could you handle the case that u has initializer?


So, should I leave it as Type->isUndeducedType() and find another condition to force calling Sema::DeduceVariableDeclarationType (so as to call deduceVarTypeFromInitializer to hit the place where we handle E, U)

I'm not familiar enough with this part of code. Please wait for other reviewers.

template <class U>
using E = U;

static E u; // expected-error {{declaration of variable 'u' with deduced type 'E' requires an initializer}}
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be nice to emit same diagnostic messages as the case that E and u are out of the definition of A.

template <class U>
using E = U;

static E u;

@HamzaHassanain
Copy link
Contributor Author

While we are here, could you handle the case that u has an initializer?

It is already handled

@HamzaHassanain
Copy link
Contributor Author

@cor3ntin

About the current diagnostic, expected-error {{declaration of variable 'u' with deduced type 'E' requires an initializer}} is this the correct diagnostic? (err_auto_var_requires_init) Or should it be:

expected-error {{alias template 'E' requires template arguments; argument deduction only allowed for class templates or alias templates}} (err_deduced_non_class_or_alias_template_specialization_type) ?

I believe the first one is fine. If not, could you please tell me, should I try to handle it inside

SemaDecl/Sema::deduceVarTypeFromInitializer

Or would it be inside

SemaInit.cpp/Sema::DeduceTemplateSpecializationFromInitializer

Also, about moving to a separate file, the file you mentioned uses c++14, while the whole issue is with C++17, C++ 20, C++23., So I believe we should keep it in a separate file?

I am very confused also with @zwuis comments, could you please explain a bit more? Or at least give me a little clue?

@zwuis
Copy link
Contributor

zwuis commented Jan 6, 2026

While we are here, could you handle the case that u has an initializer?

It is already handled

We can refer to how it is handled.

template <typename> struct A {
  template <typename U>
  using E = U;

  // both of them should be rejected
  static E u1;
  static E u2 = 0;
};

Also, about moving to a separate file, the file you mentioned uses c++14, while the whole issue is with C++17, C++ 20, C++23., So I believe we should keep it in a separate file?

We can write multiple // RUN in one test file, each // RUN specifies different C++ versions.

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.

While we are here, could you handle the case that u has an initializer?

It is already handled

Oh, I see. We already emit diagnostics for static E u2 = 0; during template instantiation. With this patch, we emit diagnostics for static E u; before template instantiation. It would be better to handle the former case before template instantiation.

// RUN: %clang_cc1 -verify -std=c++14 -fcxx-exceptions %s
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s

#if __cplusplus < 202002L
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we remove this #if?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How then can I make some of the file be tested only with c++ 14, and my tests only run with c++ 20 ?

@HamzaHassanain HamzaHassanain deleted the clang-crash-decltype-static branch January 6, 2026 17:32
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.

4 participants