[clang] eb8324a - [clang][Parser] Improve error recovery for missing semicolons in class members. (#190744)

via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 16 00:27:01 PDT 2026


Author: Haojian Wu
Date: 2026-04-16T07:26:55Z
New Revision: eb8324a472ff720c90fe4d90214c330d4daa1750

URL: https://github.com/llvm/llvm-project/commit/eb8324a472ff720c90fe4d90214c330d4daa1750
DIFF: https://github.com/llvm/llvm-project/commit/eb8324a472ff720c90fe4d90214c330d4daa1750.diff

LOG: [clang][Parser] Improve error recovery for missing semicolons in class members. (#190744)

This is something I discovered when doing the investigation for
https://github.com/llvm/llvm-project/pull/188123#issuecomment-4162665482.

This patch improves recovery when a semicolon is missing after a class
member declarations.

When the parser expects a semicolon but encounters a token that is at
the start of a line and is a valid declaration specifier, it injects a
`;` instead of skipping tokens, this allows us to preserve the
declaration after the missing ";" instead of discarding it.

Added: 
    clang/test/AST/ast-dump-decl-recovery.cpp

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Parse/Parser.h
    clang/lib/Parse/ParseCXXInlineMethods.cpp
    clang/lib/Parse/ParseDeclCXX.cpp
    clang/lib/Parse/Parser.cpp
    clang/test/Parser/recovery.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index a6b6345168ef2..7d58204553b0c 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -382,6 +382,10 @@ Improvements to Clang's diagnostics
   code can automatically be made portable to other host platforms that don't
   support backslashes.
 
+- Improved error recovery for missing semicolons after class members. Clang now avoids
+  skipping subsequent valid declarations when their previous decl is missing semicolon.
+
+
 Improvements to Clang's time-trace
 ----------------------------------
 

diff  --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index c077671cb2407..e29c43f8751ec 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -815,6 +815,11 @@ class Parser : public CodeCompletionHandler {
   /// to the semicolon, consumes that extra token.
   bool ExpectAndConsumeSemi(unsigned DiagID, StringRef TokenUsed = "");
 
+  /// 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 bc18881e89110..114df51aa08ba 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") &&
+               !isLikelyAtStartOfNewDeclaration()) {
       SkipUntil(tok::semi);
     }
 

diff  --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 5a7863506cc91..683a1b4695625 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) &&
+      !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 c4f745612e06c..5d87453cf219e 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -194,6 +194,11 @@ bool Parser::ExpectAndConsumeSemi(unsigned DiagID, StringRef TokenUsed) {
   return ExpectAndConsume(tok::semi, DiagID , TokenUsed);
 }
 
+bool Parser::isLikelyAtStartOfNewDeclaration() {
+  return Tok.isAtStartOfLine() &&
+         isDeclarationSpecifier(ImplicitTypenameContext::No);
+}
+
 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;


        


More information about the cfe-commits mailing list