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
