https://github.com/hokein updated https://github.com/llvm/llvm-project/pull/190744
>From ab2692f72ed8bc47382e497c4238ecd2bedb50db Mon Sep 17 00:00:00 2001 From: Haojian Wu <[email protected]> Date: Tue, 7 Apr 2026 09:17:16 +0200 Subject: [PATCH 1/2] [clang][Parser] Improve error recovery for missing semicolons in class members. --- clang/include/clang/Parse/Parser.h | 7 +++++++ clang/lib/Parse/ParseCXXInlineMethods.cpp | 3 ++- clang/lib/Parse/ParseDeclCXX.cpp | 3 ++- clang/lib/Parse/Parser.cpp | 11 +++++++++++ clang/test/AST/ast-dump-decl-recovery.cpp | 16 ++++++++++++++++ clang/test/Parser/recovery.cpp | 2 +- 6 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 clang/test/AST/ast-dump-decl-recovery.cpp diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 08a3d88ee6a36..50e56b24e1b6a 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -815,6 +815,13 @@ class Parser : public CodeCompletionHandler { /// to the semicolon, consumes that extra token. bool ExpectAndConsumeSemi(unsigned DiagID, StringRef TokenUsed = ""); + /// Try to inject a synthetic semicolon if the next token appears to be the + /// start of a new declaration (i.e., it starts a new line and is a + /// declaration specifier). This is used for error recovery to prevent + /// aggressive skipping and preserve subsequent declarations in the AST. + /// Returns true if a semicolon was injected. + bool TryInjectSemicolon(); + /// Consume any extra semi-colons until the end of the line. void ConsumeExtraSemi(ExtraSemiKind Kind, DeclSpec::TST T = TST_unspecified); diff --git a/clang/lib/Parse/ParseCXXInlineMethods.cpp b/clang/lib/Parse/ParseCXXInlineMethods.cpp index bc18881e89110..24724a430bc13 100644 --- a/clang/lib/Parse/ParseCXXInlineMethods.cpp +++ b/clang/lib/Parse/ParseCXXInlineMethods.cpp @@ -131,7 +131,8 @@ NamedDecl *Parser::ParseCXXInlineMethodDef( << Delete; SkipUntil(tok::semi); } else if (ExpectAndConsume(tok::semi, diag::err_expected_after, - Delete ? "delete" : "default")) { + Delete ? "delete" : "default") && + !TryInjectSemicolon()) { SkipUntil(tok::semi); } diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index 5a7863506cc91..2b098168075c4 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -3244,7 +3244,8 @@ Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration( } if (ExpectSemi && - ExpectAndConsume(tok::semi, diag::err_expected_semi_decl_list)) { + ExpectAndConsume(tok::semi, diag::err_expected_semi_decl_list) && + !TryInjectSemicolon()) { // Skip to end of block or statement. SkipUntil(tok::r_brace, StopAtSemi | StopBeforeMatch); // If we stopped at a ';', eat it. diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index c4f745612e06c..c42b32b71be16 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -194,6 +194,17 @@ bool Parser::ExpectAndConsumeSemi(unsigned DiagID, StringRef TokenUsed) { return ExpectAndConsume(tok::semi, DiagID , TokenUsed); } +bool Parser::TryInjectSemicolon() { + if (Tok.isAtStartOfLine() && + (isDeclarationSpecifier(ImplicitTypenameContext::No))) { + PP.EnterToken(Tok, /*IsReinject=*/true); + Tok.setKind(tok::semi); + ConsumeToken(); + return true; + } + return false; +} + void Parser::ConsumeExtraSemi(ExtraSemiKind Kind, DeclSpec::TST TST) { if (!Tok.is(tok::semi)) return; diff --git a/clang/test/AST/ast-dump-decl-recovery.cpp b/clang/test/AST/ast-dump-decl-recovery.cpp new file mode 100644 index 0000000000000..f6ad1de015dc3 --- /dev/null +++ b/clang/test/AST/ast-dump-decl-recovery.cpp @@ -0,0 +1,16 @@ +// RUN: not %clang_cc1 -triple x86_64-unknown-unknown -std=c++20 -ast-dump %s | FileCheck -strict-whitespace %s + +namespace MissingSemicolon { +class Foo { + void f1() = delete + void g1(); + void f2() + void g2(); +}; +// CHECK: NamespaceDecl {{.*}} MissingSemicolon +// CHECK: CXXMethodDecl {{.*}} f1 'void ()' delete +// CHECK: CXXMethodDecl {{.*}} g1 'void ()' +// CHECK: CXXMethodDecl {{.*}} f2 'void ()' +// CHECK: CXXMethodDecl {{.*}} g2 'void ()' + +} // namespace MissingSemicolon diff --git a/clang/test/Parser/recovery.cpp b/clang/test/Parser/recovery.cpp index 261f5dc99bad4..0637d4cbe72ae 100644 --- a/clang/test/Parser/recovery.cpp +++ b/clang/test/Parser/recovery.cpp @@ -24,7 +24,7 @@ struct S { int a, b, c; S(); int x // expected-error {{expected ';'}} - friend void f() + friend void f() // expected-error {{expected ';' at end of declaration list}} }; 8S::S() : a{ 5 }, b{ 6 }, c{ 2 } { // expected-error {{unqualified-id}} return; >From f1cbbe92e30378c86f9e050c2a1bb619d8069269 Mon Sep 17 00:00:00 2001 From: Haojian Wu <[email protected]> Date: Sat, 11 Apr 2026 23:06:50 +0200 Subject: [PATCH 2/2] Simplify the change, no need to inject a token and consume it. --- clang/include/clang/Parse/Parser.h | 10 ++++------ clang/lib/Parse/ParseCXXInlineMethods.cpp | 2 +- clang/lib/Parse/ParseDeclCXX.cpp | 2 +- clang/lib/Parse/Parser.cpp | 12 +++--------- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 50e56b24e1b6a..6669dd8a7e889 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -815,12 +815,10 @@ class Parser : public CodeCompletionHandler { /// to the semicolon, consumes that extra token. bool ExpectAndConsumeSemi(unsigned DiagID, StringRef TokenUsed = ""); - /// Try to inject a synthetic semicolon if the next token appears to be the - /// start of a new declaration (i.e., it starts a new line and is a - /// declaration specifier). This is used for error recovery to prevent - /// aggressive skipping and preserve subsequent declarations in the AST. - /// Returns true if a semicolon was injected. - bool TryInjectSemicolon(); + /// Returns true if the current token is likely the start of a new + /// declaration (e.g., it starts a new line and is a declaration specifier). + /// This is a heuristic used for error recovery. + bool isLikelyAtStartOfNewDeclaration(); /// Consume any extra semi-colons until the end of the line. void ConsumeExtraSemi(ExtraSemiKind Kind, DeclSpec::TST T = TST_unspecified); diff --git a/clang/lib/Parse/ParseCXXInlineMethods.cpp b/clang/lib/Parse/ParseCXXInlineMethods.cpp index 24724a430bc13..114df51aa08ba 100644 --- a/clang/lib/Parse/ParseCXXInlineMethods.cpp +++ b/clang/lib/Parse/ParseCXXInlineMethods.cpp @@ -132,7 +132,7 @@ NamedDecl *Parser::ParseCXXInlineMethodDef( SkipUntil(tok::semi); } else if (ExpectAndConsume(tok::semi, diag::err_expected_after, Delete ? "delete" : "default") && - !TryInjectSemicolon()) { + !isLikelyAtStartOfNewDeclaration()) { SkipUntil(tok::semi); } diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index 2b098168075c4..683a1b4695625 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -3245,7 +3245,7 @@ Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration( if (ExpectSemi && ExpectAndConsume(tok::semi, diag::err_expected_semi_decl_list) && - !TryInjectSemicolon()) { + !isLikelyAtStartOfNewDeclaration()) { // Skip to end of block or statement. SkipUntil(tok::r_brace, StopAtSemi | StopBeforeMatch); // If we stopped at a ';', eat it. diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index c42b32b71be16..5d87453cf219e 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -194,15 +194,9 @@ bool Parser::ExpectAndConsumeSemi(unsigned DiagID, StringRef TokenUsed) { return ExpectAndConsume(tok::semi, DiagID , TokenUsed); } -bool Parser::TryInjectSemicolon() { - if (Tok.isAtStartOfLine() && - (isDeclarationSpecifier(ImplicitTypenameContext::No))) { - PP.EnterToken(Tok, /*IsReinject=*/true); - Tok.setKind(tok::semi); - ConsumeToken(); - return true; - } - return false; +bool Parser::isLikelyAtStartOfNewDeclaration() { + return Tok.isAtStartOfLine() && + isDeclarationSpecifier(ImplicitTypenameContext::No); } void Parser::ConsumeExtraSemi(ExtraSemiKind Kind, DeclSpec::TST TST) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
