Author: Akash Manna Date: 2026-10-03T15:26:53Z New Revision: 9a9daa97acbd8a5e31daea241bb5e5ee0a5de171
URL: https://github.com/llvm/llvm-project/commit/9a9daa97acbd8a5e31daea241bb5e5ee0a5de171 DIFF: https://github.com/llvm/llvm-project/commit/9a9daa97acbd8a5e31daea241bb5e5ee0a5de171.diff LOG: [Clang] Keep scope annotations covering typo-corrected qualifiers (#223978) Fixes #221890 While tentatively deciding whether `(void(foo::S<int>))` is a type-id, the parser annotates `foo::S<int>`. Lookup of `S` in `foo` fails, typo correction suggests plain `S`, and Sema rebuilds the `CXXScopeSpec` without the qualifier. The annotation token takes its start from that scope specifier, so it only covers `S<int>` and the already-consumed `foo ::` tokens are never replaced in the token cache. They resurface after the tentative parse backtracks, every later parse sees `foo :: <type>`, and the second `isTypeIdInParens()` in `ParseCXXAmbiguousParenExpression` contradicts the first. The parser owns the invariant that an annotation replaces every token consumed to produce it, so the fix lives in `ParseOptionalCXXScopeSpecifier`. After each Sema call that can rewrite the qualifier through typo correction, the scope specifier is re-ranged over the tokens actually consumed. When the qualifier was dropped entirely, the template-id annotation is extended back over those tokens instead. Sema and the typo-correction recovery are untouched. Added: Modified: clang/docs/ReleaseNotes.md clang/lib/Parse/ParseExprCXX.cpp clang/test/Parser/cxx-ambig-paren-expr.cpp Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index c99b871cc1e20..1c041d4bf54e1 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -663,6 +663,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 diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp index c3ec0f03abd62..bfdad0d501282 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(); @@ -365,20 +376,14 @@ bool Parser::ParseOptionalCXXScopeSpecifier( TemplateId->NumArgs); if (TemplateId->isInvalid() || - Actions.ActOnCXXNestedNameSpecifier(getCurScope(), - SS, - TemplateId->TemplateKWLoc, - TemplateId->Template, - TemplateId->TemplateNameLoc, - TemplateId->LAngleLoc, - TemplateArgsPtr, - TemplateId->RAngleLoc, - CCLoc, - EnteringContext)) { - SourceLocation StartLoc - = SS.getBeginLoc().isValid()? SS.getBeginLoc() - : TemplateId->TemplateNameLoc; + Actions.ActOnCXXNestedNameSpecifier( + getCurScope(), SS, TemplateId->TemplateKWLoc, + TemplateId->Template, TemplateId->TemplateNameLoc, + TemplateId->LAngleLoc, TemplateArgsPtr, TemplateId->RAngleLoc, + CCLoc, EnteringContext)) { SS.SetInvalid(SourceRange(StartLoc, CCLoc)); + } else { + RestoreScopeSpecRange(SourceRange(StartLoc, CCLoc)); } continue; @@ -473,6 +478,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; @@ -489,11 +495,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: @@ -517,6 +526,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, @@ -528,6 +540,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'?}} +} + _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
