[clang] [clang] Fix assertion when substituting an incomplete template argument list into a member access qualified by a template parameter (PR #225088)

Akash Manna via cfe-commits cfe-commits at lists.llvm.org
Mon Sep 21 06:51:42 PDT 2026


https://github.com/akash-manna-sky created https://github.com/llvm/llvm-project/pull/225088

Fixes #204059

When a function template's return type contains a member access whose nested-name-specifier starts with a template type parameter, e.g. `decltype(t.U::template B<>::MEM)`, the parser records `U` as the first qualifier found in scope. During substitution, `TransformFirstQualifierInScope` fetched `U`'s template argument without checking that one exists. Two callers legitimately substitute with an incomplete list: explicit template argument substitution before deduction (`k<S>(S{})`), and partial deduction for code completion signature help (`k(`). The first hit an out-of-bounds index, the second a null argument, and both tripped an assertion.

Every other parameter handler in the instantiator checks `hasTemplateArgument` first and records the substitution as incomplete. This does the same here, leaving the qualifier dependent so the expression is rebuilt as a dependent member access. Signature help then shows the candidate with `U` unresolved, and the explicit call correctly reports that `U` could not be inferred.


>From fd37024356fb53bbf655d56662974a0dbed0e119 Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Mon, 21 Sep 2026 19:19:43 +0530
Subject: [PATCH] [clang] Fix assertion when substituting an incomplete
 template argument list into a member access qualified by a template parameter

TemplateInstantiator::TransformFirstQualifierInScope fetched the template
argument for a template type parameter that begins the nested-name-specifier
of a dependent member access without checking that the argument exists.
Explicit template argument substitution and partial deduction for signature
help both substitute with an incomplete list on purpose, so the lookup hit an
out-of-bounds index or a null argument.

Check hasTemplateArgument first, like every other parameter handler in the
instantiator, mark the substitution incomplete and keep the qualifier
dependent.

Fixes #204059
---
 clang/docs/ReleaseNotes.md                  |  6 ++++++
 clang/lib/Sema/SemaTemplateInstantiate.cpp  |  5 +++++
 clang/test/CodeCompletion/GH204059.cpp      | 10 ++++++++++
 clang/test/SemaTemplate/dependent-names.cpp |  9 +++++++++
 4 files changed, 30 insertions(+)
 create mode 100644 clang/test/CodeCompletion/GH204059.cpp

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index f4a34a37aff52..5dc50122e01da 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -724,6 +724,12 @@ features cannot lower the translation-unit ABI level;
 - Fixed an issue where an explicit specialization of a constexpr variable would
   result in a link error. (#GH219796)
 
+- Fixed an assertion when substituting an incomplete set of template arguments
+  (explicitly specified, or partially deduced during code completion) into a
+  member access whose nested-name-specifier starts with a template parameter
+  that has no corresponding argument yet, such as
+  `decltype(t.U::template B<>::MEM)`. (#GH204059)
+
 #### Bug Fixes to AST Handling
 
 - Fixed a non-deterministic ordering of unused local typedefs that made
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 20b16c3e49bea..6aeb5d06062cd 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -2072,6 +2072,11 @@ TemplateInstantiator::TransformFirstQualifierInScope(NamedDecl *D,
       = cast<TemplateTypeParmType>(getSema().Context.getTypeDeclType(TTPD));
 
     if (TTP->getDepth() < TemplateArgs.getNumLevels()) {
+      if (!TemplateArgs.hasTemplateArgument(TTP->getDepth(), TTP->getIndex())) {
+        IsIncomplete = true;
+        return BailOutOnIncomplete ? nullptr : D;
+      }
+
       // FIXME: This needs testing w/ member access expressions.
       TemplateArgument Arg = TemplateArgs(TTP->getDepth(), TTP->getIndex());
 
diff --git a/clang/test/CodeCompletion/GH204059.cpp b/clang/test/CodeCompletion/GH204059.cpp
new file mode 100644
index 0000000000000..cc7648bd5107b
--- /dev/null
+++ b/clang/test/CodeCompletion/GH204059.cpp
@@ -0,0 +1,10 @@
+template <class T, class U>
+auto k(T t) -> decltype(t.U::template B<>::MEM);
+
+struct S {};
+
+void f() {
+  k(S{});
+  // RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-1):5 %s -o - | FileCheck %s
+  // CHECK: OVERLOAD: [#decltype(t.U::template B<>::MEM)#]k(<#T t#>)
+}
diff --git a/clang/test/SemaTemplate/dependent-names.cpp b/clang/test/SemaTemplate/dependent-names.cpp
index d6bd670841c08..bb838ece6ccc5 100644
--- a/clang/test/SemaTemplate/dependent-names.cpp
+++ b/clang/test/SemaTemplate/dependent-names.cpp
@@ -481,3 +481,12 @@ namespace TransformNestedName {
   template <typename T::template X<N<T>::State::kA>>
   inline void N<T>::F() {}
 } // namespace TransformNestedName
+
+namespace GH204059 {
+  template <class T, class U>
+  auto k(T t) -> decltype(t.U::template B<>::MEM); // expected-note {{candidate template ignored: couldn't infer template argument 'U'}}
+  struct S {};
+  void f() {
+    k<S>(S{}); // expected-error {{no matching function for call to 'k'}}
+  }
+} // namespace GH204059



More information about the cfe-commits mailing list