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

Reply via email to