[clang] [clang][Parser] Preserve LHS in RecoveryExpr for binary operations statement boundaries. (PR #192535)

Haojian Wu via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 16 14:05:26 PDT 2026


https://github.com/hokein created https://github.com/llvm/llvm-project/pull/192535

Fixes https://github.com/clangd/clangd/issues/2640

>From 495cec9f313acb74817a1cd50515480fdc6fc639 Mon Sep 17 00:00:00 2001
From: Haojian Wu <hokein.wu at gmail.com>
Date: Thu, 16 Apr 2026 22:59:53 +0200
Subject: [PATCH] [clang][Parser] Preserve LHS in RecoveryExpr for binary
 operations at statement boundaries

---
 clang/docs/ReleaseNotes.rst          |  5 +++++
 clang/lib/Parse/ParseExpr.cpp        | 12 +++++++++---
 clang/test/AST/ast-dump-recovery.cpp | 13 +++++++++++++
 3 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 5edec5f04e976..846f46be26ae2 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -275,6 +275,7 @@ Attribute Changes in Clang
 
 Improvements to Clang's diagnostics
 -----------------------------------
+
 - ``-Wunused-but-set-variable`` now diagnoses file-scope variables with
   internal linkage (``static`` storage class) that are assigned but never used.
   This new coverage is added under the subgroup ``-Wunused-but-set-global``,
@@ -665,6 +666,10 @@ Improvements
 ^^^^^^^^^^^^
 - Improved substitution performance in concept checking. (#GH172266)
 
+- Clang now preserves the left-hand side of a binary expression (such as an
+  assignment or comparison) in a ``RecoveryExpr`` when the right-hand side fails
+  to parse. This improves IDE features like go-to-definition in ``clangd``. (#GH2640)
+
 Additional Information
 ======================
 
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 11d06b73d27ea..aacefb1a5d828 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -481,7 +481,9 @@ Parser::ParseRHSOfBinaryExpression(ExprResult LHS, prec::Level MinPrec) {
     else
       RHS = ParseCastExpression(CastParseKind::AnyCastExpr);
 
-    if (RHS.isInvalid()) {
+    // We preserve the LHS only if we hit a clear statement boundary (tok::semi)
+    // to avoid additional bogus diagnostics.
+    if (RHS.isInvalid() && Tok.isNot(tok::semi)) {
       LHS = ExprError();
     }
 
@@ -513,7 +515,7 @@ Parser::ParseRHSOfBinaryExpression(ExprResult LHS, prec::Level MinPrec) {
                             static_cast<prec::Level>(ThisPrec + !isRightAssoc));
       RHSIsInitList = false;
 
-      if (RHS.isInvalid()) {
+      if (RHS.isInvalid() && Tok.isNot(tok::semi)) {
         LHS = ExprError();
       }
 
@@ -540,7 +542,11 @@ Parser::ParseRHSOfBinaryExpression(ExprResult LHS, prec::Level MinPrec) {
 
     if (!LHS.isInvalid()) {
       // Combine the LHS and RHS into the LHS (e.g. build AST).
-      if (TernaryMiddle.isInvalid()) {
+      if (RHS.isInvalid()) {
+        LHS = Actions.CreateRecoveryExpr(LHS.get()->getBeginLoc(),
+                                         PrevTokLocation,
+                                         {LHS.get()});
+      } else if (TernaryMiddle.isInvalid()) {
         // If we're using '>>' as an operator within a template
         // argument list (in C++98), suggest the addition of
         // parentheses so that the code remains well-formed in C++0x.
diff --git a/clang/test/AST/ast-dump-recovery.cpp b/clang/test/AST/ast-dump-recovery.cpp
index 80ec0a449d556..3c1811b2c8d02 100644
--- a/clang/test/AST/ast-dump-recovery.cpp
+++ b/clang/test/AST/ast-dump-recovery.cpp
@@ -309,3 +309,16 @@ void foo() {
 void brokenDeducedVarDecl() {
    auto* x = some_func(nullptr);
 }
+
+// CHECK:     FunctionDecl {{.*}} test_stmt_recovery
+// CHECK-NEXT:|-ParmVarDecl {{.*}} a
+// CHECK-NEXT:`-CompoundStmt
+// CHECK-NEXT:  |-RecoveryExpr {{.*}} contains-errors
+// CHECK-NEXT:  | `-DeclRefExpr {{.*}} 'a'
+// CHECK-NEXT:  `-RecoveryExpr {{.*}} contains-errors
+// CHECK-NEXT:    `-DeclRefExpr {{.*}} 'a'
+// DISABLED-NOT: -RecoveryExpr {{.*}} contains-errors
+void test_stmt_recovery(int a) {
+  a = unresolved;
+  a < unresolved;
+}



More information about the cfe-commits mailing list