https://github.com/likeamahoney updated 
https://github.com/llvm/llvm-project/pull/205762

>From 81b67925efc2ed6b60220e5aa841343a75d8af68 Mon Sep 17 00:00:00 2001
From: likeamahoney <[email protected]>
Date: Thu, 25 Jun 2026 12:41:16 +0300
Subject: [PATCH] [Clang][Sema] Keep intermediate dependent members dependent
 in implicit-typename qualifiers

When a dependent qualified type is written without the `typename` keyword
(the C++20 / extension "implicit typename" recovery), its nested-name-specifier
is parsed while "entering" a context. Whenever the declaration sits in a class 
or
at namespace scope the parser cannot yet tell a type-specifier apart from an
out-of-line member definition, so it parses the prefix in entering mode. As a
side effect an intermediate dependent member -- e.g. `B` in `A<T>::B::C` -- is
speculatively resolved against the template pattern instead of being left as a
dependent name.

Such a resolved nested-name-specifier does not survive instantiation: the member
is never re-resolved against the instantiated parent, so the qualified type 
stays
spuriously dependent in otherwise non-dependent code. That dependent type then
makes CTAD / `auto` deduction silently bail out, leaving an undeduced 
placeholder
that crashes codegen:

  Assertion `!A->getDeducedType().isNull() && "cannot request the size of an
  undeduced or dependent auto type"' failed.

Once `getTypeName` knows the qualified-id names a type (the implicit-typename
path), rebuild the qualifier so that every component resolved against a 
dependent
(non current-instantiation) context becomes a `DependentNameType` -- exactly the
type produced when `typename` is written explicitly. This is the recovery the
existing FIXME in `ActOnTemplateIdType` refers to. Out-of-line definitions are
parsed as declarators and do not reach this path, so they are unaffected.

Fixes #174301
---
 clang/docs/ReleaseNotes.rst                   |  4 +
 clang/lib/Sema/SemaDecl.cpp                   | 91 +++++++++++++++++++
 .../implicit-typename-instantiation.cpp       | 52 +++++++++++
 3 files changed, 147 insertions(+)
 create mode 100644 clang/test/SemaTemplate/implicit-typename-instantiation.cpp

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index d4c286644033b..a0764ae606492 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -812,6 +812,10 @@ Bug Fixes to C++ Support
 - Fixed an issue where Clang incorrectly accepted invalid unqualified uses of 
local nested class names outside their declaring scope. (#GH184622)
 - Fixed a crash when parsing invalid friend declaration with storage-class 
specifier. (#GH186569)
 - Fixed a missing vtable for ``dynamic_cast<FinalClass *>(this)`` in a 
function template. (#GH198511)
+- Fixed a crash when a dependent qualified type whose nested-name-specifier 
contains an
+  intermediate dependent member (e.g. ``A<T>::B::C``) was written without the 
``typename``
+  keyword. The intermediate member was speculatively resolved against the 
template pattern and
+  the resulting type spuriously stayed dependent after instantiation. 
(#GH174301)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index d45c3eb35094f..71f9c9f016f8b 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -273,6 +273,82 @@ static ParsedType recoverFromTypeInKnownDependentBase(Sema 
&S,
   return S.CreateParsedType(T, Builder.getTypeSourceInfo(Context, T));
 }
 
+/// A dependent qualified type written without the 'typename' keyword (accepted
+/// as the C++20 / extension "implicit typename" recovery) has its
+/// nested-name-specifier parsed while "entering" a context. As a side effect,
+/// intermediate dependent members -- e.g. 'B' in 'A<T>::B::C' -- are
+/// speculatively resolved against the template pattern instead of being left 
as
+/// dependent names. Such a resolved nested-name-specifier does not survive
+/// template instantiation: the member is never re-resolved against the
+/// instantiated parent, so a spuriously dependent type leaks into otherwise
+/// non-dependent code (and later crashes e.g. CTAD / codegen).
+///
+/// Rebuild the qualifier so that every component resolved against a dependent
+/// (non current-instantiation) context becomes a DependentNameType, matching
+/// the type produced when 'typename' is written explicitly.
+static NestedNameSpecifierLoc
+rebuildDependentlyResolvedQualifier(Sema &S, NestedNameSpecifierLoc QLoc) {
+  if (!QLoc || QLoc.getNestedNameSpecifier().getKind() !=
+                   NestedNameSpecifier::Kind::Type)
+    return QLoc;
+
+  TypeLoc TL = QLoc.castAsTypeLoc();
+
+  // Only members that were resolved to a concrete declaration (typedef, tag, 
or
+  // using) carry a qualifier + name we could re-express as a dependent name.
+  NestedNameSpecifierLoc PrefixLoc;
+  SourceLocation KeywordLoc, NameLoc;
+  const IdentifierInfo *II = nullptr;
+  if (auto T = TL.getAs<TypedefTypeLoc>()) {
+    PrefixLoc = T.getQualifierLoc();
+    KeywordLoc = T.getElaboratedKeywordLoc();
+    NameLoc = T.getNameLoc();
+    II = T.getDecl()->getIdentifier();
+  } else if (auto T = TL.getAs<TagTypeLoc>()) {
+    PrefixLoc = T.getQualifierLoc();
+    KeywordLoc = T.getElaboratedKeywordLoc();
+    NameLoc = T.getNameLoc();
+    II = T.getDecl()->getIdentifier();
+  } else if (auto T = TL.getAs<UsingTypeLoc>()) {
+    PrefixLoc = T.getQualifierLoc();
+    KeywordLoc = T.getElaboratedKeywordLoc();
+    NameLoc = T.getNameLoc();
+    II = T.getDecl()->getIdentifier();
+  } else if (auto T = TL.getAs<UnresolvedUsingTypeLoc>()) {
+    PrefixLoc = T.getQualifierLoc();
+    KeywordLoc = T.getElaboratedKeywordLoc();
+    NameLoc = T.getNameLoc();
+    II = T.getDecl()->getIdentifier();
+  } else {
+    return QLoc;
+  }
+
+  if (!II || !PrefixLoc)
+    return QLoc;
+
+  NestedNameSpecifierLoc NewPrefixLoc =
+      rebuildDependentlyResolvedQualifier(S, PrefixLoc);
+
+  // The member can only be left unresolved (a dependent name) if its prefix is
+  // itself dependent; otherwise the original resolution is correct.
+  if (!NewPrefixLoc.getNestedNameSpecifier().isDependent())
+    return QLoc;
+
+  ASTContext &Ctx = S.Context;
+  QualType DNT = Ctx.getDependentNameType(
+      ElaboratedTypeKeyword::None, NewPrefixLoc.getNestedNameSpecifier(), II);
+
+  TypeLocBuilder TLB;
+  auto DTL = TLB.push<DependentNameTypeLoc>(DNT);
+  DTL.setElaboratedKeywordLoc(KeywordLoc);
+  DTL.setQualifierLoc(NewPrefixLoc);
+  DTL.setNameLoc(NameLoc);
+
+  CXXScopeSpec Rebuilt;
+  Rebuilt.Make(Ctx, TLB.getTypeLocInContext(Ctx, DNT), QLoc.getEndLoc());
+  return Rebuilt.getWithLocInContext(Ctx);
+}
+
 ParsedType Sema::getTypeName(const IdentifierInfo &II, SourceLocation NameLoc,
                              Scope *S, CXXScopeSpec *SS, bool isClassName,
                              bool HasTrailingDot, ParsedType ObjectTypePtr,
@@ -298,6 +374,21 @@ ParsedType Sema::getTypeName(const IdentifierInfo &II, 
SourceLocation NameLoc,
 
     if (!LookupCtx) {
       if (isDependentScopeSpecifier(*SS)) {
+        // The qualifier is dependent and we now know (from the grammar) that
+        // this names a type. If it was parsed while entering a context, an
+        // intermediate dependent member may have been speculatively resolved
+        // against the template pattern; rebuild it as a dependent name so it
+        // survives instantiation. See rebuildDependentlyResolvedQualifier.
+        if (NestedNameSpecifierLoc QLoc = SS->getWithLocInContext(Context)) {
+          NestedNameSpecifierLoc NewQLoc =
+              rebuildDependentlyResolvedQualifier(*this, QLoc);
+          if (NewQLoc.getNestedNameSpecifier() !=
+              QLoc.getNestedNameSpecifier()) {
+            SS->clear();
+            SS->Adopt(NewQLoc);
+          }
+        }
+
         // C++ [temp.res]p3:
         //   A qualified-id that refers to a type and in which the
         //   nested-name-specifier depends on a template-parameter (14.6.2)
diff --git a/clang/test/SemaTemplate/implicit-typename-instantiation.cpp 
b/clang/test/SemaTemplate/implicit-typename-instantiation.cpp
new file mode 100644
index 0000000000000..22c8e23b2d92c
--- /dev/null
+++ b/clang/test/SemaTemplate/implicit-typename-instantiation.cpp
@@ -0,0 +1,52 @@
+// RUN: %clang_cc1 -fsyntax-only -verify=cxx17 -std=c++17 %s
+// RUN: %clang_cc1 -fsyntax-only -verify=cxx20 -std=c++20 %s
+// cxx20-no-diagnostics
+
+// When 'typename' is omitted from a dependent qualified type whose
+// nested-name-specifier contains an intermediate dependent member
+// (e.g. the 'Inner' / 'Node_traits' below), the implicit-typename recovery
+// used to leave the intermediate member speculatively resolved against the
+// template pattern. That nested-name-specifier did not survive instantiation,
+// producing a spuriously dependent type that crashed CTAD and codegen.
+// Omitting and writing 'typename' must produce the same type.
+
+namespace intermediate_typedef {
+template <class T> struct Iter { T *p; };
+template <class T> struct Traits { typedef Iter<T> Const_iterator; };
+template <class T> struct ListBase { typedef Traits<T> Node_traits; };
+
+template <class T> struct List {
+  // cxx17-warning@+1 {{missing 'typename' prior to dependent type name 
'ListBase<T>::Node_traits::Const_iterator' is a C++20 extension}}
+  typedef ListBase<T>::Node_traits::Const_iterator const_iterator;
+  const_iterator begin() { return const_iterator(); }
+};
+
+struct S {};
+template <class U> void use(U) {}
+
+void test() {
+  List<S> lst;
+  Iter it = lst.begin(); // CTAD; previously crashed.
+  use(it);
+  static_assert(__is_same(decltype(it), Iter<S>));
+}
+} // namespace intermediate_typedef
+
+namespace intermediate_tag {
+// Reduced from llvm/llvm-project#174301.
+template <class T> struct Tester {
+  struct Inner { using T2 = int; };
+  static void test();
+};
+
+template <class T>
+// cxx17-warning@+1 {{missing 'typename' prior to dependent type name 
'Tester<T>::Inner::T2' is a C++20 extension}}
+Tester<T>::Inner::T2 getInnerT2() { return {}; }
+
+template <class T> void Tester<T>::test() {
+  auto x = getInnerT2<T>();
+  static_assert(__is_same(decltype(x), int));
+}
+
+template struct Tester<int>;
+} // namespace intermediate_tag

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to