[Lldb-commits] [lldb] [lldb] Restrict expressions to one top-level assignment in DIL (PR #214348)
Ilia Kuklin via lldb-commits
lldb-commits at lists.llvm.org
Thu Aug 20 11:58:42 PDT 2026
https://github.com/kuilpd updated https://github.com/llvm/llvm-project/pull/214348
>From 0fedba653fb77500356ad89222eaf1a754d73756 Mon Sep 17 00:00:00 2001
From: Ilia Kuklin <ikuklin at accesssoftek.com>
Date: Mon, 3 Aug 2026 22:53:48 +0500
Subject: [PATCH 1/5] [lldb] Restrict expressions to one assignment in DIL
---
lldb/docs/dil-expr-lang.ebnf | 22 ++---
lldb/include/lldb/ValueObject/DILParser.h | 3 +-
lldb/source/ValueObject/DILParser.cpp | 89 ++++++++++---------
.../expr/Assignment/TestFrameVarDILAssign.py | 23 +++++
.../TestFrameVarDILCompositeAssign.py | 17 ++++
5 files changed, 103 insertions(+), 51 deletions(-)
diff --git a/lldb/docs/dil-expr-lang.ebnf b/lldb/docs/dil-expr-lang.ebnf
index a0d49204ceb55..9d0c35f8c8c97 100644
--- a/lldb/docs/dil-expr-lang.ebnf
+++ b/lldb/docs/dil-expr-lang.ebnf
@@ -3,18 +3,20 @@
(* This is currently a subset of the final DIL Language, matching the current
DIL implementation. *)
-expression = conditional_expression ;
+expression = assignment_expression ;
-conditional_expression = assignment_expression
- | assignment_expression "?" expression ":" expression ;
-
-assignment_expression = logical_or_expression
- | logical_or_expression assignment_operator assignment_expression ;
+assignment_expression = pure_expression
+ | pure_expression assignment_operator pure_expression ;
assignment_operator = "="
| "+="
| "-=" ;
+pure_expression = conditional_expression ;
+
+conditional_expression = logical_or_expression
+ | logical_or_expression "?" pure_expression ":" pure_expression ;
+
logical_or_expression = logical_and_expression {"||" logical_and_expression} ;
logical_and_expression = inclusive_or_expression {"&&" inclusive_or_expression} ;
@@ -52,16 +54,16 @@ unary_expression = postfix_expression
unary_operator = "*" | "&" | "+" | "-" | "~" | "!" ;
postfix_expression = primary_expression
- | postfix_expression "[" expression "]"
- | postfix_expression "[" expression ":" expression "]"
+ | postfix_expression "[" pure_expression "]"
+ | postfix_expression "[" pure_expression ":" pure_expression "]"
| postfix_expression "." id_expression
| postfix_expression "->" id_expression ;
primary_expression = numeric_literal
| boolean_literal
| id_expression
- | "(" expression ")"
- | "sizeof" "(" expression ")"
+ | "(" pure_expression ")"
+ | "sizeof" "(" pure_expression ")"
| "sizeof" "(" type_id ")" ;
id_expression = unqualified_id
diff --git a/lldb/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h
index cc495d48d93c5..0e0982acd9192 100644
--- a/lldb/include/lldb/ValueObject/DILParser.h
+++ b/lldb/include/lldb/ValueObject/DILParser.h
@@ -82,8 +82,9 @@ class DILParser {
ASTNodeUP Run();
ASTNodeUP ParseExpression();
- ASTNodeUP ParseConditionalExpression();
ASTNodeUP ParseAssignmentExpression();
+ ASTNodeUP ParsePureExpression();
+ ASTNodeUP ParseConditionalExpression();
ASTNodeUP ParseLogicalOrExpression();
ASTNodeUP ParseLogicalAndExpression();
ASTNodeUP ParseInclusiveOrExpression();
diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp
index dd6e6244461a4..8a442a5d418b8 100644
--- a/lldb/source/ValueObject/DILParser.cpp
+++ b/lldb/source/ValueObject/DILParser.cpp
@@ -129,41 +129,13 @@ ASTNodeUP DILParser::Run() {
// expression:
// assignment_expression
//
-ASTNodeUP DILParser::ParseExpression() { return ParseConditionalExpression(); }
-
-// Parse a conditional_expression.
-//
-// conditional_expression:
-// assignment_expression
-// assignment_expression "?" expression ":" expression
-//
-ASTNodeUP DILParser::ParseConditionalExpression() {
- auto lhs = ParseAssignmentExpression();
- assert(lhs && "ASTNodeUP must not contain a nullptr");
-
- // Check if it's a ternary operator.
- if (CurToken().Is(Token::question)) {
- Token token = CurToken();
- m_dil_lexer.Advance();
- auto true_op = ParseExpression();
- assert(true_op && "ASTNodeUP must not contain a nullptr");
- Expect(Token::colon);
- m_dil_lexer.Advance();
- auto false_op = ParseExpression();
- assert(false_op && "ASTNodeUP must not contain a nullptr");
- lhs = std::make_unique<ConditionalNode>(token.GetLocation(), std::move(lhs),
- std::move(true_op),
- std::move(false_op));
- }
-
- return lhs;
-}
+ASTNodeUP DILParser::ParseExpression() { return ParseAssignmentExpression(); }
// Parse an assignment_expression
//
// assignment_expression
-// logical_or_expression
-// logical_or_expression assignment_operator assignment_expression
+// pure_expression
+// pure_expression assignment_operator pure_expression
//
// assignment_operator:
// "="
@@ -171,7 +143,7 @@ ASTNodeUP DILParser::ParseConditionalExpression() {
// "-="
//
ASTNodeUP DILParser::ParseAssignmentExpression() {
- auto lhs = ParseLogicalOrExpression();
+ auto lhs = ParsePureExpression();
assert(lhs && "ASTNodeUP must not contain a nullptr");
// Check if it's an assignment expression.
@@ -179,7 +151,7 @@ ASTNodeUP DILParser::ParseAssignmentExpression() {
// That's an assignment!
Token token = CurToken();
m_dil_lexer.Advance();
- auto rhs = ParseAssignmentExpression();
+ auto rhs = ParsePureExpression();
assert(rhs && "ASTNodeUP must not contain a nullptr");
lhs = std::make_unique<BinaryOpNode>(
token.GetLocation(), GetBinaryOpKindFromToken(token.GetKind()),
@@ -188,6 +160,43 @@ ASTNodeUP DILParser::ParseAssignmentExpression() {
return lhs;
}
+// Parse a pure expression without side effects.
+//
+// pure_expression:
+// conditional_expression
+//
+ASTNodeUP DILParser::ParsePureExpression() {
+ return ParseConditionalExpression();
+}
+
+// Parse a conditional_expression.
+//
+// conditional_expression:
+// logical_or_expression
+// logical_or_expression "?" pure_expression ":" pure_expression
+//
+ASTNodeUP DILParser::ParseConditionalExpression() {
+ auto lhs = ParseLogicalOrExpression();
+ assert(lhs && "ASTNodeUP must not contain a nullptr");
+
+ // Check if it's a ternary operator.
+ if (CurToken().Is(Token::question)) {
+ Token token = CurToken();
+ m_dil_lexer.Advance();
+ auto true_op = ParsePureExpression();
+ assert(true_op && "ASTNodeUP must not contain a nullptr");
+ Expect(Token::colon);
+ m_dil_lexer.Advance();
+ auto false_op = ParsePureExpression();
+ assert(false_op && "ASTNodeUP must not contain a nullptr");
+ lhs = std::make_unique<ConditionalNode>(token.GetLocation(), std::move(lhs),
+ std::move(true_op),
+ std::move(false_op));
+ }
+
+ return lhs;
+}
+
// Parse a logical_or_expression.
//
// logical_or_expression:
@@ -526,8 +535,8 @@ ASTNodeUP DILParser::ParseUnaryExpression() {
//
// postfix_expression:
// primary_expression
-// postfix_expression "[" expression "]"
-// postfix_expression "[" expression ":" expression "]"
+// postfix_expression "[" pure_expression "]"
+// postfix_expression "[" pure_expression ":" pure_expression "]"
// postfix_expression "." id_expression
// postfix_expression "->" id_expression
//
@@ -540,11 +549,11 @@ ASTNodeUP DILParser::ParsePostfixExpression() {
switch (token.GetKind()) {
case Token::l_square: {
m_dil_lexer.Advance();
- ASTNodeUP index = ParseExpression();
+ ASTNodeUP index = ParsePureExpression();
assert(index && "ASTNodeUP must not contain a nullptr");
if (CurToken().GetKind() == Token::colon) {
m_dil_lexer.Advance();
- ASTNodeUP last_index = ParseExpression();
+ ASTNodeUP last_index = ParsePureExpression();
assert(last_index && "ASTNodeUP must not contain a nullptr");
lhs = std::make_unique<BitFieldExtractionNode>(
loc, std::move(lhs), std::move(index), std::move(last_index));
@@ -584,7 +593,7 @@ ASTNodeUP DILParser::ParsePostfixExpression() {
// numeric_literal
// boolean_literal
// id_expression
-// "(" expression ")"
+// "(" pure_expression ")"
//
ASTNodeUP DILParser::ParsePrimaryExpression() {
if (CurToken().IsOneOf({Token::integer_constant, Token::float_constant}))
@@ -608,7 +617,7 @@ ASTNodeUP DILParser::ParsePrimaryExpression() {
return std::make_unique<SizeOfNode>(loc, *type_id);
}
TentativeParsingRollback(save_token_idx);
- ASTNodeUP expr = ParseExpression();
+ ASTNodeUP expr = ParsePureExpression();
Expect(Token::r_paren);
m_dil_lexer.Advance();
return std::make_unique<SizeOfNode>(loc, std::move(expr));
@@ -619,7 +628,7 @@ ASTNodeUP DILParser::ParsePrimaryExpression() {
if (CurToken().Is(Token::l_paren)) {
m_dil_lexer.Advance();
- auto expr = ParseExpression();
+ auto expr = ParsePureExpression();
Expect(Token::r_paren);
m_dil_lexer.Advance();
return expr;
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILAssign.py b/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILAssign.py
index 5734395cf0ff0..826adc03d0d8d 100644
--- a/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILAssign.py
+++ b/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILAssign.py
@@ -206,3 +206,26 @@ def test_assignment(self):
error=True,
substrs=["new value is too big"],
)
+
+ # Check that there can be only one assignment and only at top level
+ self.expect(
+ "frame variable 'i = i = 1'",
+ error=True,
+ substrs=["expected 'eof', got: <'='"],
+ )
+ self.expect(
+ "frame variable 'i = 1 - (i = 1)'",
+ error=True,
+ substrs=["expected 'r_paren', got: <'='"],
+ )
+ self.expect(
+ "frame variable '1 + (i = 1)'",
+ error=True,
+ substrs=["expected 'r_paren', got: <'='"],
+ )
+ self.expect(
+ "frame variable '(i = 1) + 1'",
+ error=True,
+ substrs=["expected 'r_paren', got: <'='"],
+ )
+ self.expect("frame variable '*(arr + 1) = 5'", substrs=["= 5"])
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILCompositeAssign.py b/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILCompositeAssign.py
index 6f87fba87c9a5..9b58a8323ea5e 100644
--- a/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILCompositeAssign.py
+++ b/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILCompositeAssign.py
@@ -81,3 +81,20 @@ def test_assignment(self):
"frame variable 'p = (int *)10'", substrs=["p = 0x000000000000000a"]
)
self.expect("frame variable 'p -= 2'", substrs=["p = 0x0000000000000002"])
+
+ # Check that there can be only one assignment and only at top level
+ self.expect(
+ "frame variable 'i = i += 1'",
+ error=True,
+ substrs=["expected 'eof', got: <'+='"],
+ )
+ self.expect(
+ "frame variable 'i += 1 + (i -= 1)'",
+ error=True,
+ substrs=["expected 'r_paren', got: <'-='"],
+ )
+ self.expect(
+ "frame variable '*(arr + 1) = arr[1] += 1'",
+ error=True,
+ substrs=["expected 'eof', got: <'+='"],
+ )
>From 2b3f22ef679ac8f5f2a05f73dfe800fa9405a547 Mon Sep 17 00:00:00 2001
From: Ilia Kuklin <ikuklin at accesssoftek.com>
Date: Thu, 6 Aug 2026 01:03:57 +0500
Subject: [PATCH 2/5] Add a verbose error for unexpected assignment
---
lldb/source/ValueObject/DILParser.cpp | 4 ++++
.../var-dil/expr/Assignment/TestFrameVarDILAssign.py | 8 ++++----
.../expr/Assignment/TestFrameVarDILCompositeAssign.py | 6 +++---
3 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp
index 8a442a5d418b8..30cb96dd9b1a7 100644
--- a/lldb/source/ValueObject/DILParser.cpp
+++ b/lldb/source/ValueObject/DILParser.cpp
@@ -1070,6 +1070,10 @@ ASTNodeUP DILParser::ParseFloatingPointLiteral() {
}
void DILParser::Expect(Token::Kind kind) {
+ if (CurToken().IsOneOf({Token::equal, Token::plusequal, Token::minusequal})) {
+ BailOut("Assignment is allowed only at top level.",
+ CurToken().GetLocation(), CurToken().GetSpelling().length());
+ }
if (CurToken().IsNot(kind)) {
BailOut(llvm::formatv("expected {0}, got: {1}", kind, CurToken()),
CurToken().GetLocation(), CurToken().GetSpelling().length());
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILAssign.py b/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILAssign.py
index 826adc03d0d8d..d954467c2cf97 100644
--- a/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILAssign.py
+++ b/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILAssign.py
@@ -211,21 +211,21 @@ def test_assignment(self):
self.expect(
"frame variable 'i = i = 1'",
error=True,
- substrs=["expected 'eof', got: <'='"],
+ substrs=["Assignment is allowed only at top level"],
)
self.expect(
"frame variable 'i = 1 - (i = 1)'",
error=True,
- substrs=["expected 'r_paren', got: <'='"],
+ substrs=["Assignment is allowed only at top level"],
)
self.expect(
"frame variable '1 + (i = 1)'",
error=True,
- substrs=["expected 'r_paren', got: <'='"],
+ substrs=["Assignment is allowed only at top level"],
)
self.expect(
"frame variable '(i = 1) + 1'",
error=True,
- substrs=["expected 'r_paren', got: <'='"],
+ substrs=["Assignment is allowed only at top level"],
)
self.expect("frame variable '*(arr + 1) = 5'", substrs=["= 5"])
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILCompositeAssign.py b/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILCompositeAssign.py
index 9b58a8323ea5e..17e3dce781d96 100644
--- a/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILCompositeAssign.py
+++ b/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILCompositeAssign.py
@@ -86,15 +86,15 @@ def test_assignment(self):
self.expect(
"frame variable 'i = i += 1'",
error=True,
- substrs=["expected 'eof', got: <'+='"],
+ substrs=["Assignment is allowed only at top level"],
)
self.expect(
"frame variable 'i += 1 + (i -= 1)'",
error=True,
- substrs=["expected 'r_paren', got: <'-='"],
+ substrs=["Assignment is allowed only at top level"],
)
self.expect(
"frame variable '*(arr + 1) = arr[1] += 1'",
error=True,
- substrs=["expected 'eof', got: <'+='"],
+ substrs=["Assignment is allowed only at top level"],
)
>From e0f12469919611e0e8f50d64e758ceea70f051c7 Mon Sep 17 00:00:00 2001
From: Ilia Kuklin <ikuklin at accesssoftek.com>
Date: Thu, 6 Aug 2026 02:51:10 +0500
Subject: [PATCH 3/5] Add errors for -- and ++
---
lldb/include/lldb/ValueObject/DILLexer.h | 2 ++
lldb/source/ValueObject/DILLexer.cpp | 6 +++++
lldb/source/ValueObject/DILParser.cpp | 22 +++++++++++++++++--
.../TestFrameVarDILCompositeAssign.py | 22 +++++++++++++++++++
4 files changed, 50 insertions(+), 2 deletions(-)
diff --git a/lldb/include/lldb/ValueObject/DILLexer.h b/lldb/include/lldb/ValueObject/DILLexer.h
index 69d6b49800b29..446891d5ca000 100644
--- a/lldb/include/lldb/ValueObject/DILLexer.h
+++ b/lldb/include/lldb/ValueObject/DILLexer.h
@@ -51,12 +51,14 @@ class Token {
lessless,
minus,
minusequal,
+ minusminus,
percent,
period,
pipe,
pipepipe,
plus,
plusequal,
+ plusplus,
question,
r_paren,
r_square,
diff --git a/lldb/source/ValueObject/DILLexer.cpp b/lldb/source/ValueObject/DILLexer.cpp
index 0f1978e28e512..abc54f1fd2f46 100644
--- a/lldb/source/ValueObject/DILLexer.cpp
+++ b/lldb/source/ValueObject/DILLexer.cpp
@@ -72,6 +72,8 @@ llvm::StringRef Token::GetTokenName(Kind kind) {
return "minus";
case Kind::minusequal:
return "minusequal";
+ case Kind::minusminus:
+ return "minusminus";
case Token::percent:
return "percent";
case Kind::period:
@@ -84,6 +86,8 @@ llvm::StringRef Token::GetTokenName(Kind kind) {
return "plus";
case Kind::plusequal:
return "plusequal";
+ case Kind::plusplus:
+ return "plusplus";
case Kind::question:
return "question";
case Kind::r_paren:
@@ -234,8 +238,10 @@ llvm::Expected<Token> DILLexer::Lex(llvm::StringRef expr,
{Token::lessequal, "<="},
{Token::lessless, "<<"},
{Token::minusequal, "-="},
+ {Token::minusminus, "--"},
{Token::pipepipe, "||"},
{Token::plusequal, "+="},
+ {Token::plusplus, "++"},
{Token::amp, "&"},
{Token::caret, "^"},
{Token::colon, ":"},
diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp
index 30cb96dd9b1a7..08b618dceb203 100644
--- a/lldb/source/ValueObject/DILParser.cpp
+++ b/lldb/source/ValueObject/DILParser.cpp
@@ -499,7 +499,8 @@ ASTNodeUP DILParser::ParseCastExpression() {
//
ASTNodeUP DILParser::ParseUnaryExpression() {
if (CurToken().IsOneOf({Token::amp, Token::star, Token::minus, Token::plus,
- Token::tilde, Token::exclaim})) {
+ Token::tilde, Token::exclaim, Token::minusminus,
+ Token::plusplus})) {
Token token = CurToken();
uint32_t loc = token.GetLocation();
m_dil_lexer.Advance();
@@ -524,6 +525,14 @@ ASTNodeUP DILParser::ParseUnaryExpression() {
case Token::exclaim:
return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::LNot,
std::move(rhs));
+ case Token::minusminus:
+ BailOut("Decrement operator is not supported. Use `-=` instead.",
+ CurToken().GetLocation(), CurToken().GetSpelling().length());
+ return std::make_unique<ErrorNode>();
+ case Token::plusplus:
+ BailOut("Increment operator is not supported. Use `+=` instead.",
+ CurToken().GetLocation(), CurToken().GetSpelling().length());
+ return std::make_unique<ErrorNode>();
default:
llvm_unreachable("invalid token kind");
}
@@ -543,7 +552,8 @@ ASTNodeUP DILParser::ParseUnaryExpression() {
ASTNodeUP DILParser::ParsePostfixExpression() {
ASTNodeUP lhs = ParsePrimaryExpression();
assert(lhs && "ASTNodeUP must not contain a nullptr");
- while (CurToken().IsOneOf({Token::l_square, Token::period, Token::arrow})) {
+ while (CurToken().IsOneOf({Token::l_square, Token::period, Token::arrow,
+ Token::minusminus, Token::plusplus})) {
uint32_t loc = CurToken().GetLocation();
Token token = CurToken();
switch (token.GetKind()) {
@@ -579,6 +589,14 @@ ASTNodeUP DILParser::ParsePostfixExpression() {
token.GetKind() == Token::arrow, member_id);
break;
}
+ case Token::minusminus:
+ BailOut("Decrement operator is not supported. Use `-=` instead.",
+ CurToken().GetLocation(), CurToken().GetSpelling().length());
+ return std::make_unique<ErrorNode>();
+ case Token::plusplus:
+ BailOut("Increment operator is not supported. Use `+=` instead.",
+ CurToken().GetLocation(), CurToken().GetSpelling().length());
+ return std::make_unique<ErrorNode>();
default:
llvm_unreachable("invalid token");
}
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILCompositeAssign.py b/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILCompositeAssign.py
index 17e3dce781d96..dd5cd68796e70 100644
--- a/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILCompositeAssign.py
+++ b/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILCompositeAssign.py
@@ -98,3 +98,25 @@ def test_assignment(self):
error=True,
substrs=["Assignment is allowed only at top level"],
)
+
+ # Check that operators `--` and `++` return an error message.
+ self.expect(
+ "frame variable 'i++'",
+ error=True,
+ substrs=["Increment operator is not supported. Use `+=` instead."],
+ )
+ self.expect(
+ "frame variable '++i'",
+ error=True,
+ substrs=["Increment operator is not supported. Use `+=` instead."],
+ )
+ self.expect(
+ "frame variable 'i--'",
+ error=True,
+ substrs=["Decrement operator is not supported. Use `-=` instead."],
+ )
+ self.expect(
+ "frame variable -- '--i'",
+ error=True,
+ substrs=["Decrement operator is not supported. Use `-=` instead."],
+ )
>From 127d9ad5bce32d9c48af6fbd8830d161e3878892 Mon Sep 17 00:00:00 2001
From: Ilia Kuklin <ikuklin at accesssoftek.com>
Date: Thu, 6 Aug 2026 03:54:43 +0500
Subject: [PATCH 4/5] Fix the shell test
---
lldb/test/Shell/Commands/command-dil-diagnostics.test | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lldb/test/Shell/Commands/command-dil-diagnostics.test b/lldb/test/Shell/Commands/command-dil-diagnostics.test
index 5b15cff4e3cd5..b9647bf0a64e3 100644
--- a/lldb/test/Shell/Commands/command-dil-diagnostics.test
+++ b/lldb/test/Shell/Commands/command-dil-diagnostics.test
@@ -13,9 +13,9 @@ frame var a+b
# CHECK-NEXT: {{^ (╰─ )?}}error: use of undeclared identifier 'b'
## Check diagnostics when called from API.
-script lldb.frame.GetValueForVariablePath("++foo")
+script lldb.frame.GetValueForVariablePath("1+foo")
# CHECK: error: <user expression>:1:3: use of undeclared identifier 'foo'
-# CHECK-NEXT: {{^ }}1 | ++foo
+# CHECK-NEXT: {{^ }}1 | 1+foo
# CHECK-NEXT: {{^ }} | ^~~
## Check that a result that fails to retrieve data is displaying an error.
>From aec018e2cc8b0b589e4a9540583c2e55429fcba5 Mon Sep 17 00:00:00 2001
From: Ilia Kuklin <ikuklin at accesssoftek.com>
Date: Thu, 20 Aug 2026 23:56:36 +0500
Subject: [PATCH 5/5] Keep conditional operator precedence over assignment
---
lldb/docs/dil-expr-lang.ebnf | 4 +-
lldb/include/lldb/ValueObject/DILParser.h | 1 +
lldb/source/ValueObject/DILParser.cpp | 44 ++++++++++++-------
.../expr/Assignment/TestFrameVarDILAssign.py | 23 ++++++++++
4 files changed, 53 insertions(+), 19 deletions(-)
diff --git a/lldb/docs/dil-expr-lang.ebnf b/lldb/docs/dil-expr-lang.ebnf
index 9d0c35f8c8c97..3dddcbd1ef609 100644
--- a/lldb/docs/dil-expr-lang.ebnf
+++ b/lldb/docs/dil-expr-lang.ebnf
@@ -5,8 +5,8 @@
expression = assignment_expression ;
-assignment_expression = pure_expression
- | pure_expression assignment_operator pure_expression ;
+assignment_expression = conditional_expression
+ | logical_or_expression assignment_operator pure_expression ;
assignment_operator = "="
| "+="
diff --git a/lldb/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h
index 0e0982acd9192..ad7bc8ee73bba 100644
--- a/lldb/include/lldb/ValueObject/DILParser.h
+++ b/lldb/include/lldb/ValueObject/DILParser.h
@@ -85,6 +85,7 @@ class DILParser {
ASTNodeUP ParseAssignmentExpression();
ASTNodeUP ParsePureExpression();
ASTNodeUP ParseConditionalExpression();
+ ASTNodeUP ParseConditionalBranches(ASTNodeUP condition);
ASTNodeUP ParseLogicalOrExpression();
ASTNodeUP ParseLogicalAndExpression();
ASTNodeUP ParseInclusiveOrExpression();
diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp
index 08b618dceb203..97fd768545e61 100644
--- a/lldb/source/ValueObject/DILParser.cpp
+++ b/lldb/source/ValueObject/DILParser.cpp
@@ -134,8 +134,8 @@ ASTNodeUP DILParser::ParseExpression() { return ParseAssignmentExpression(); }
// Parse an assignment_expression
//
// assignment_expression
-// pure_expression
-// pure_expression assignment_operator pure_expression
+// conditional_expression
+// logical_or_expression assignment_operator pure_expression
//
// assignment_operator:
// "="
@@ -143,9 +143,15 @@ ASTNodeUP DILParser::ParseExpression() { return ParseAssignmentExpression(); }
// "-="
//
ASTNodeUP DILParser::ParseAssignmentExpression() {
- auto lhs = ParsePureExpression();
+ auto lhs = ParseLogicalOrExpression();
assert(lhs && "ASTNodeUP must not contain a nullptr");
+ // Check if it's a ternary operator.
+ // This is done to keep conditional_expression's precedence
+ // over assignment_expression.
+ if (CurToken().Is(Token::question))
+ return ParseConditionalBranches(std::move(lhs));
+
// Check if it's an assignment expression.
if (CurToken().IsOneOf({Token::equal, Token::plusequal, Token::minusequal})) {
// That's an assignment!
@@ -179,24 +185,28 @@ ASTNodeUP DILParser::ParseConditionalExpression() {
auto lhs = ParseLogicalOrExpression();
assert(lhs && "ASTNodeUP must not contain a nullptr");
- // Check if it's a ternary operator.
- if (CurToken().Is(Token::question)) {
- Token token = CurToken();
- m_dil_lexer.Advance();
- auto true_op = ParsePureExpression();
- assert(true_op && "ASTNodeUP must not contain a nullptr");
- Expect(Token::colon);
- m_dil_lexer.Advance();
- auto false_op = ParsePureExpression();
- assert(false_op && "ASTNodeUP must not contain a nullptr");
- lhs = std::make_unique<ConditionalNode>(token.GetLocation(), std::move(lhs),
- std::move(true_op),
- std::move(false_op));
- }
+ if (CurToken().Is(Token::question))
+ lhs = ParseConditionalBranches(std::move(lhs));
return lhs;
}
+ASTNodeUP DILParser::ParseConditionalBranches(ASTNodeUP condition) {
+ assert(condition && "ASTNodeUP must not contain a nullptr");
+
+ Token token = CurToken();
+ m_dil_lexer.Advance();
+ auto true_op = ParsePureExpression();
+ assert(true_op && "ASTNodeUP must not contain a nullptr");
+ Expect(Token::colon);
+ m_dil_lexer.Advance();
+ auto false_op = ParsePureExpression();
+ assert(false_op && "ASTNodeUP must not contain a nullptr");
+ return std::make_unique<ConditionalNode>(
+ token.GetLocation(), std::move(condition), std::move(true_op),
+ std::move(false_op));
+}
+
// Parse a logical_or_expression.
//
// logical_or_expression:
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILAssign.py b/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILAssign.py
index d954467c2cf97..9b3ab51422064 100644
--- a/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILAssign.py
+++ b/lldb/test/API/commands/frame/var-dil/expr/Assignment/TestFrameVarDILAssign.py
@@ -229,3 +229,26 @@ def test_assignment(self):
substrs=["Assignment is allowed only at top level"],
)
self.expect("frame variable '*(arr + 1) = 5'", substrs=["= 5"])
+
+ # Check that assignment is parsed correctly when combined
+ # with ternary conditional operator
+ self.expect("frame variable 'i = false ? 1 : 2'", substrs=["i = 2"])
+ self.expect(
+ "frame variable 'false ? i = 1 : i'",
+ error=True,
+ substrs=["Assignment is allowed only at top level"],
+ )
+ self.expect(
+ "frame variable 'false ? i : i = 1'",
+ error=True,
+ substrs=["Assignment is allowed only at top level"],
+ )
+ self.expect("frame variable '(false ? i : j) = 1'", substrs=["j = 1"])
+ self.expect(
+ "frame variable '(true ? arr[0] : arr[1]) = false ? 0 : 5'", substrs=["= 5"]
+ )
+ self.expect(
+ "frame variable 'true ? arr[0] : arr[1] = false ? 0 : 5'",
+ error=True,
+ substrs=["Assignment is allowed only at top level"],
+ )
More information about the lldb-commits
mailing list