https://github.com/akash-manna-sky updated 
https://github.com/llvm/llvm-project/pull/223978

>From 9ad28c96f8fde0a66112bc9273377b5ffc81b0af Mon Sep 17 00:00:00 2001
From: Akash Manna <[email protected]>
Date: Wed, 16 Sep 2026 16:29:10 +0530
Subject: [PATCH 1/2] [Clang] Keep scope annotations covering typo-corrected
 qualifiers

When typo correction replaces or drops a qualifier during
nested-name-specifier annotation, Sema hands back a CXXScopeSpec whose
range no longer starts at the tokens the parser already consumed. The
annotation token built from it then fails to replace those tokens in
the preprocessor's cache, and they resurface after a tentative parse
backtracks, e.g. as `foo :: annot_typename`. In
ParseCXXAmbiguousParenExpression this makes the second
isTypeIdInParens() disagree with the first and trips its assertion.

Restore the scope specifier's range over the consumed tokens after
each Sema call that can rewrite it, and when the qualifier was dropped
entirely, extend the template-id annotation over those tokens instead.

Fixes #221890
---
 clang/docs/ReleaseNotes.md                 |  5 ++++
 clang/lib/Parse/ParseExprCXX.cpp           | 29 +++++++++++++++++++---
 clang/test/Parser/cxx-ambig-paren-expr.cpp | 29 ++++++++++++++++++++++
 3 files changed, 60 insertions(+), 3 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 3cca316a91d4d..ac39f24fd3f68 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -676,6 +676,11 @@ features cannot lower the translation-unit ABI level;
   class with an invalid non-static data member, such as one qualified with an
   address space. (#GH194605)
 
+- Fixed an assertion when typo correction replaced or dropped the qualifier of
+  a name such as `foo::S<int>` while the parser was deciding whether a
+  parenthesized construct like `(void(foo::S<int>))` is a type-id or an
+  expression. (#GH221890)
+
 #### Bug Fixes to AST Handling
 
 - Fixed a non-deterministic ordering of unused local typedefs that made
diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp
index 860c069e18fca..ab92837f8761a 100644
--- a/clang/lib/Parse/ParseExprCXX.cpp
+++ b/clang/lib/Parse/ParseExprCXX.cpp
@@ -238,6 +238,14 @@ bool Parser::ParseOptionalCXXScopeSpecifier(
     }
   }
 
+  // Typo correction may replace a qualifier we have already consumed the 
tokens
+  // for. The scope specifier must still cover those tokens, or the annotation
+  // built from it won't replace them and they reappear after backtracking.
+  auto RestoreScopeSpecRange = [&](SourceRange Range) {
+    if (Range.isValid() && SS.isValid() && SS.getRange() != Range)
+      SS.MakeTrivial(Actions.getASTContext(), SS.getScopeRep(), Range);
+  };
+
   // Preferred type might change when parsing qualifiers, we need the original.
   auto SavedType = PreferredType;
   while (true) {
@@ -353,6 +361,9 @@ bool Parser::ParseOptionalCXXScopeSpecifier(
       if (LastII)
         *LastII = TemplateId->Name;
 
+      SourceLocation StartLoc =
+          SS.getBeginLoc().isValid() ? SS.getBeginLoc() : Tok.getLocation();
+
       // Consume the template-id token.
       ConsumeAnnotationToken();
 
@@ -375,10 +386,9 @@ bool Parser::ParseOptionalCXXScopeSpecifier(
                                               TemplateId->RAngleLoc,
                                               CCLoc,
                                               EnteringContext)) {
-        SourceLocation StartLoc
-          = SS.getBeginLoc().isValid()? SS.getBeginLoc()
-                                      : TemplateId->TemplateNameLoc;
         SS.SetInvalid(SourceRange(StartLoc, CCLoc));
+      } else {
+        RestoreScopeSpecRange(SourceRange(StartLoc, CCLoc));
       }
 
       continue;
@@ -472,6 +482,7 @@ bool Parser::ParseOptionalCXXScopeSpecifier(
              "NextToken() not working properly!");
       Token ColonColon = Tok;
       SourceLocation CCLoc = ConsumeToken();
+      SourceLocation ScopeBeginLoc = SS.getBeginLoc();
 
       bool IsCorrectedToColon = false;
       bool *CorrectionFlagPtr = ColonIsSacred ? &IsCorrectedToColon : nullptr;
@@ -488,11 +499,14 @@ bool Parser::ParseOptionalCXXScopeSpecifier(
           break;
         }
         SS.SetInvalid(SourceRange(IdLoc, CCLoc));
+      } else {
+        RestoreScopeSpecRange(SourceRange(ScopeBeginLoc, CCLoc));
       }
       HasScopeSpecifier = true;
       continue;
     }
 
+    SourceRange ScopeRange = SS.getRange();
     CheckForTemplateAndDigraph(Next, ObjectType, EnteringContext, II, SS);
 
     // nested-name-specifier:
@@ -516,6 +530,9 @@ bool Parser::ParseOptionalCXXScopeSpecifier(
             isTemplateArgumentList(1) == TPResult::False)
           break;
 
+        RestoreScopeSpecRange(ScopeRange);
+        bool DroppedScope = ScopeRange.isValid() && SS.isEmpty();
+
         // We have found a template name, so annotate this token
         // with a template-id annotation. We do not permit the
         // template-id to be translated into a type annotation,
@@ -527,6 +544,12 @@ bool Parser::ParseOptionalCXXScopeSpecifier(
         if (AnnotateTemplateIdToken(Template, TNK, SS, SourceLocation(),
                                     TemplateName, false))
           return true;
+        if (DroppedScope) {
+          // No scope specifier is left to cover the dropped qualifier's
+          // tokens, so extend the template-id annotation over them.
+          Tok.setLocation(ScopeRange.getBegin());
+          PP.AnnotateCachedTokens(Tok);
+        }
         continue;
       }
 
diff --git a/clang/test/Parser/cxx-ambig-paren-expr.cpp 
b/clang/test/Parser/cxx-ambig-paren-expr.cpp
index cc509f7b059f3..160ce4c3bb72f 100644
--- a/clang/test/Parser/cxx-ambig-paren-expr.cpp
+++ b/clang/test/Parser/cxx-ambig-paren-expr.cpp
@@ -70,3 +70,32 @@ void test(int i) {
     return;
 }
 
+namespace GH221890 {
+template <class T> struct S {}; // expected-note 2 {{'S' declared here}}
+struct Plain {}; // expected-note {{'Plain' declared here}}
+namespace foo {}
+namespace ns1 { template <class T> struct Q {}; } // expected-note {{'ns1::Q' 
declared here}}
+namespace ba { template <class T> struct T2 {}; struct P {}; } // 
expected-note 2 {{'ba' declared here}}
+namespace bar {}
+
+// Typo correction dropped or replaced the qualifier while the parser was
+// tentatively deciding whether the parenthesized construct is a type-id, and
+// the tokens of the original qualifier resurfaced after backtracking.
+int a = (void(foo::S<int>)); // expected-error {{no template named 'S' in 
namespace 'GH221890::foo'; did you mean simply 'S'?}} \
+                             // expected-error {{expected '(' for 
function-style cast or type construction}}
+int b = (void(foo::Q<int>)); // expected-error {{no template named 'Q' in 
namespace 'GH221890::foo'; did you mean 'ns1::Q'?}} \
+                             // expected-error {{expected '(' for 
function-style cast or type construction}}
+int c = (void(bar::ba::T2<int>)); // expected-error {{no member named 'ba' in 
namespace 'GH221890::bar'; did you mean simply 'ba'?}} \
+                                  // expected-error {{expected '(' for 
function-style cast or type construction}}
+int d = (void(bar::ba::P)); // expected-error {{no member named 'ba' in 
namespace 'GH221890::bar'; did you mean simply 'ba'?}} \
+                            // expected-error {{expected '(' for 
function-style cast or type construction}}
+void f() {
+  void(foo::S<int>); // expected-error {{no template named 'S' in namespace 
'GH221890::foo'; did you mean simply 'S'?}} \
+                     // expected-error {{expected '(' for function-style cast 
or type construction}}
+}
+
+// The same constructs without a typo, and with a non-template name.
+int e = (void(S<int>)); // expected-error {{expected '(' for function-style 
cast or type construction}}
+int g = (void(foo::Plain)); // expected-error {{no member named 'Plain' in 
namespace 'GH221890::foo'; did you mean simply 'Plain'?}}
+}
+

>From 9f0fd5813488ad6574e0052cb594f08cbcaf3880 Mon Sep 17 00:00:00 2001
From: Akash Manna <[email protected]>
Date: Wed, 16 Sep 2026 16:35:25 +0530
Subject: [PATCH 2/2] Reposition the release notes to avoid the conflicts

---
 clang/docs/ReleaseNotes.md | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index ac39f24fd3f68..295bfcfdc9081 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -574,6 +574,10 @@ features cannot lower the translation-unit ABI level;
 - Fixed a bug where top-level CV qualifiers (such as ``const``) were dropped 
from pointers modified by Microsoft pointer attributes (like ``__ptr32`` and 
``__ptr64``) and WebAssembly's ``__funcref``.
 
 - Fixed a bug where we accepted ``__super`` being qualified by a scope 
specifier, causing codegen to assertion fail elsewhere. (#GH212988)
+- Fixed an assertion when typo correction replaced or dropped the qualifier of
+  a name such as `foo::S<int>` while the parser was deciding whether a
+  parenthesized construct like `(void(foo::S<int>))` is a type-id or an
+  expression. (#GH221890)
 - Fixed an issue where we tried to compare invalid NTTPs for variable 
declarations, which ended up in hitting an assertion with a constrained 
non-plain-auto NTTP, which we don't quite implement yet. (#GH208658)
 
 - Fixed a crash when a using-declaration naming an unresolvable member of a
@@ -676,11 +680,6 @@ features cannot lower the translation-unit ABI level;
   class with an invalid non-static data member, such as one qualified with an
   address space. (#GH194605)
 
-- Fixed an assertion when typo correction replaced or dropped the qualifier of
-  a name such as `foo::S<int>` while the parser was deciding whether a
-  parenthesized construct like `(void(foo::S<int>))` is a type-id or an
-  expression. (#GH221890)
-
 #### Bug Fixes to AST Handling
 
 - Fixed a non-deterministic ordering of unused local typedefs that made

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

Reply via email to