[clang] [Clang][Sema] Mark partial specializations invalid when not more specialized than primary (PR #181614)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Feb 16 00:57:59 PST 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Giovanni B. (Z3rox-dev)
<details>
<summary>Changes</summary>
## Summary
`checkMoreSpecializedThanPrimary()` emits `ext_partial_spec_not_more_specialized_than_primary` but did not mark the partial specialization as invalid. Additionally, `ActOnClassTemplateSpecialization()` unconditionally called `Specialization->setInvalidDecl(Invalid)` where `Invalid` was `false` for partial specializations, clearing any invalid flag set by `CheckTemplatePartialSpecialization`.
This caused the invalid partial specialization to still be selected during template argument deduction. Using it for instantiation produced dependent expressions (`CXXUnresolvedConstructExpr`) in non-dependent contexts, leading to an assertion failure:
```
assert(!Init->isValueDependent()) in VarDecl::evaluateValueImpl
```
## Root Cause
Two-part issue in `SemaTemplate.cpp`:
1. **Missing invalidation**: `checkMoreSpecializedThanPrimary()` emitted the diagnostic but never called `Partial->setInvalidDecl()`.
2. **Flag clearing**: `ActOnClassTemplateSpecialization()` called `Specialization->setInvalidDecl(Invalid)` at the end, where `Invalid` is `false` for partial specializations. Since `Specialization` and `Partial` are the same pointer (set at line 8987), this unconditionally cleared the invalid flag even if `CheckTemplatePartialSpecialization` had just set it.
The guard in `DeduceTemplateArguments()` (SemaTemplateDeduction.cpp:3406) already checks `Partial->isInvalidDecl()` and returns `TDK_Invalid` — so once the partial spec is properly marked invalid, it is excluded from deduction.
## Fix
1. Add `Partial->setInvalidDecl()` in `checkMoreSpecializedThanPrimary()` after the diagnostic.
2. Change `ActOnClassTemplateSpecialization()` to use `if (Invalid) Specialization->setInvalidDecl()` — only sets the flag, never clears it.
## Test Plan
- New regression test: `clang/test/SemaTemplate/partial-spec-not-more-specialized-invalid.cpp` (exact reproducer from #<!-- -->181410)
- Updated 2 existing tests to match new behavior (invalid partial specs are now excluded from deduction)
- Full `check-clang`: **50,534 tests, 0 failures** (46,308 passed, 28 expectedly failed)
> AI tools were used to assist with root cause analysis and tracing the code paths in this fix.
Fixes #<!-- -->181410
---
Full diff: https://github.com/llvm/llvm-project/pull/181614.diff
4 Files Affected:
- (modified) clang/lib/Sema/SemaTemplate.cpp (+11-1)
- (modified) clang/test/CXX/temp/temp.decls/temp.variadic/fixed-expansion.cpp (+3-3)
- (added) clang/test/SemaTemplate/partial-spec-not-more-specialized-invalid.cpp (+30)
- (modified) clang/test/SemaTemplate/temp_arg_nontype.cpp (+1-4)
``````````diff
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 3497ff7856eed..5cdfa97c6c35e 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -4229,6 +4229,12 @@ static void checkMoreSpecializedThanPrimary(Sema &S, PartialSpecDecl *Partial) {
diag::ext_partial_spec_not_more_specialized_than_primary)
<< isa<VarTemplateDecl>(Template);
+ // Mark the partial specialization as invalid so it won't be selected
+ // during template argument deduction. Using an invalid partial
+ // specialization for instantiation can produce dependent expressions
+ // in non-dependent contexts, leading to crashes in later phases.
+ Partial->setInvalidDecl();
+
if (Info.hasSFINAEDiagnostic()) {
PartialDiagnosticAt Diag = {SourceLocation(),
PartialDiagnostic::NullDiagnostic()};
@@ -9108,7 +9114,11 @@ DeclResult Sema::ActOnClassTemplateSpecialization(
if (SkipBody && SkipBody->ShouldSkip)
return SkipBody->Previous;
- Specialization->setInvalidDecl(Invalid);
+ // Only mark invalid if determined here; don't clear a flag already set
+ // by CheckTemplatePartialSpecialization (e.g., for a partial specialization
+ // that is not more specialized than the primary template).
+ if (Invalid)
+ Specialization->setInvalidDecl();
inferGslOwnerPointerAttribute(Specialization);
return Specialization;
}
diff --git a/clang/test/CXX/temp/temp.decls/temp.variadic/fixed-expansion.cpp b/clang/test/CXX/temp/temp.decls/temp.variadic/fixed-expansion.cpp
index ab4c663d24c7d..0bc69909b51c5 100644
--- a/clang/test/CXX/temp/temp.decls/temp.variadic/fixed-expansion.cpp
+++ b/clang/test/CXX/temp/temp.decls/temp.variadic/fixed-expansion.cpp
@@ -108,15 +108,15 @@ namespace PR9021b {
namespace PartialSpecialization {
template<typename T, typename U, typename V = U>
- struct X0; // expected-note 2{{template is declared here}}
+ struct X0; // expected-note 4{{template is declared here}}
template<typename ...Ts>
struct X0<Ts...> { // expected-error {{class template partial specialization is not more specialized than the primary template}}
};
X0<int> x0i; // expected-error{{too few template arguments for class template 'X0'}}
- X0<int, float> x0if;
- X0<int, float, double> x0ifd;
+ X0<int, float> x0if; // expected-error {{implicit instantiation of undefined template 'PartialSpecialization::X0<int, float>'}}
+ X0<int, float, double> x0ifd; // expected-error {{implicit instantiation of undefined template 'PartialSpecialization::X0<int, float, double>'}}
}
namespace FixedAliasTemplate {
diff --git a/clang/test/SemaTemplate/partial-spec-not-more-specialized-invalid.cpp b/clang/test/SemaTemplate/partial-spec-not-more-specialized-invalid.cpp
new file mode 100644
index 0000000000000..5439081a5dedf
--- /dev/null
+++ b/clang/test/SemaTemplate/partial-spec-not-more-specialized-invalid.cpp
@@ -0,0 +1,30 @@
+// RUN: %clang_cc1 -std=c++20 -verify -emit-llvm-only %s
+// Regression test for https://github.com/llvm/llvm-project/issues/181410
+//
+// A class template partial specialization diagnosed as "not more specialized
+// than the primary template" was not marked invalid, so it was still selected
+// during template argument deduction. Using it for instantiation produced
+// dependent expressions in non-dependent contexts, causing an assertion
+// failure in CodeGen: assert(!Init->isValueDependent()).
+
+template <int>
+struct integer_sequence {};
+
+template <int>
+struct array {};
+
+template <int*>
+struct MetaValuesHelper; // expected-note 2{{template is declared here}}
+
+template <typename TupleName, TupleName kValues>
+struct MetaValuesHelper<kValues> { // expected-error {{class template partial specialization is not more specialized than the primary template}}
+ template <int... Is>
+ static array<stdget<Is>(kValues)...> MetaValuesFunc(integer_sequence<Is...>);
+};
+
+int kBaseIndexRegistersUsed;
+
+// Previously this crashed with: assert(!Init->isValueDependent())
+// Now the invalid partial specialization is excluded from deduction,
+// and the primary template (which is only forward-declared) is used instead.
+array<0> u = decltype(MetaValuesHelper<&kBaseIndexRegistersUsed>::MetaValuesFunc(integer_sequence<0>{})){}; // expected-error {{implicit instantiation of undefined template 'MetaValuesHelper<&kBaseIndexRegistersUsed>'}}
diff --git a/clang/test/SemaTemplate/temp_arg_nontype.cpp b/clang/test/SemaTemplate/temp_arg_nontype.cpp
index bd0bf3cfdbc59..e94bd4e8aa1ee 100644
--- a/clang/test/SemaTemplate/temp_arg_nontype.cpp
+++ b/clang/test/SemaTemplate/temp_arg_nontype.cpp
@@ -391,10 +391,7 @@ namespace partial_order_different_types {
template<int N, typename T, typename U, T V> struct A<0, N, T, U, V> {}; // #P1
template<int N, typename T, typename U, U V> struct A<0, N, T, U, V>; // #P2
// expected-error at -1 {{class template partial specialization is not more specialized than the primary template}}
- A<0, 0, int, int, 0> a;
- // expected-error at -1 {{ambiguous partial specializations}}
- // expected-note@#P1 {{partial specialization matches}}
- // expected-note@#P2 {{partial specialization matches}}
+ A<0, 0, int, int, 0> a; // OK: #P2 is invalid and excluded; only #P1 matches.
}
namespace partial_order_references {
``````````
</details>
https://github.com/llvm/llvm-project/pull/181614
More information about the cfe-commits
mailing list