[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