[Lldb-commits] [lldb] [lldb] Add ternary conditional operator to DIL (PR #211344)
Ilia Kuklin via lldb-commits
lldb-commits at lists.llvm.org
Mon Jul 27 15:53:42 PDT 2026
https://github.com/kuilpd updated https://github.com/llvm/llvm-project/pull/211344
>From ab7cb383949091729f9b5f3281f22eb8d76609b3 Mon Sep 17 00:00:00 2001
From: Ilia Kuklin <ikuklin at accesssoftek.com>
Date: Mon, 20 Jul 2026 22:33:02 +0500
Subject: [PATCH 1/2] [lldb] Add ternary conditional operator to DIL
---
lldb/docs/dil-expr-lang.ebnf | 5 +-
lldb/include/lldb/ValueObject/DILAST.h | 27 +++++++
lldb/include/lldb/ValueObject/DILEval.h | 2 +
lldb/include/lldb/ValueObject/DILLexer.h | 1 +
lldb/include/lldb/ValueObject/DILParser.h | 2 +-
lldb/source/ValueObject/DILAST.cpp | 4 +
lldb/source/ValueObject/DILEval.cpp | 33 ++++++++
lldb/source/ValueObject/DILLexer.cpp | 3 +
lldb/source/ValueObject/DILParser.cpp | 30 +++++++-
.../frame/var-dil/expr/Conditional/Makefile | 3 +
.../Conditional/TestFrameVarDILConditional.py | 76 +++++++++++++++++++
.../frame/var-dil/expr/Conditional/main.cpp | 20 +++++
12 files changed, 203 insertions(+), 3 deletions(-)
create mode 100644 lldb/test/API/commands/frame/var-dil/expr/Conditional/Makefile
create mode 100644 lldb/test/API/commands/frame/var-dil/expr/Conditional/TestFrameVarDILConditional.py
create mode 100644 lldb/test/API/commands/frame/var-dil/expr/Conditional/main.cpp
diff --git a/lldb/docs/dil-expr-lang.ebnf b/lldb/docs/dil-expr-lang.ebnf
index f3c465711e956..94e5859e5f8eb 100644
--- a/lldb/docs/dil-expr-lang.ebnf
+++ b/lldb/docs/dil-expr-lang.ebnf
@@ -3,7 +3,10 @@
(* This is currently a subset of the final DIL Language, matching the current
DIL implementation. *)
-expression = assignment_expression ;
+expression = conditional_expression ;
+
+conditional_expression = assignment_expression
+ | assignment_expression "?" expression ":" expression ;
assignment_expression = shift_expression
| shift_expression assignment_operator assignment_expression ;
diff --git a/lldb/include/lldb/ValueObject/DILAST.h b/lldb/include/lldb/ValueObject/DILAST.h
index 93310a91a15bb..fc95215e896fe 100644
--- a/lldb/include/lldb/ValueObject/DILAST.h
+++ b/lldb/include/lldb/ValueObject/DILAST.h
@@ -24,6 +24,7 @@ enum class NodeKind {
eBitExtractionNode,
eBooleanLiteralNode,
eCastNode,
+ eConditionalNode,
eErrorNode,
eFloatLiteralNode,
eIdentifierNode,
@@ -320,6 +321,30 @@ class CastNode : public ASTNode {
CastKind m_cast_kind;
};
+class ConditionalNode : public ASTNode {
+public:
+ ConditionalNode(uint32_t location, ASTNodeUP condition, ASTNodeUP true_op,
+ ASTNodeUP false_op)
+ : ASTNode(location, NodeKind::eConditionalNode),
+ m_condition(std::move(condition)), m_true_op(std::move(true_op)),
+ m_false_op(std::move(false_op)) {}
+
+ llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const override;
+
+ ASTNode &GetCondition() const { return *m_condition; }
+ ASTNode &GetTrueOperand() const { return *m_true_op; }
+ ASTNode &GetFalseOperand() const { return *m_false_op; }
+
+ static bool classof(const ASTNode &node) {
+ return node.GetKind() == NodeKind::eConditionalNode;
+ }
+
+private:
+ ASTNodeUP m_condition;
+ ASTNodeUP m_true_op;
+ ASTNodeUP m_false_op;
+};
+
/// This class contains one Visit method for each specialized type of
/// DIL AST node. The Visit methods are used to dispatch a DIL AST node to
/// the correct function in the DIL expression evaluator for evaluating that
@@ -346,6 +371,8 @@ class Visitor {
virtual llvm::Expected<lldb::ValueObjectSP>
Visit(const BooleanLiteralNode &node) = 0;
virtual llvm::Expected<lldb::ValueObjectSP> Visit(const CastNode &node) = 0;
+ virtual llvm::Expected<lldb::ValueObjectSP>
+ Visit(const ConditionalNode &node) = 0;
};
} // namespace lldb_private::dil
diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h
index 35784ea9987f9..05b99618d2f13 100644
--- a/lldb/include/lldb/ValueObject/DILEval.h
+++ b/lldb/include/lldb/ValueObject/DILEval.h
@@ -73,6 +73,8 @@ class Interpreter : Visitor {
llvm::Expected<lldb::ValueObjectSP>
Visit(const BooleanLiteralNode &node) override;
llvm::Expected<lldb::ValueObjectSP> Visit(const CastNode &node) override;
+ llvm::Expected<lldb::ValueObjectSP>
+ Visit(const ConditionalNode &node) override;
/// Perform usual unary conversions on a value. At the moment this
/// includes array-to-pointer and integral promotion for eligible types.
diff --git a/lldb/include/lldb/ValueObject/DILLexer.h b/lldb/include/lldb/ValueObject/DILLexer.h
index f9f42dc59b311..52a9701b2e2a9 100644
--- a/lldb/include/lldb/ValueObject/DILLexer.h
+++ b/lldb/include/lldb/ValueObject/DILLexer.h
@@ -46,6 +46,7 @@ class Token {
period,
plus,
plusequal,
+ question,
r_paren,
r_square,
slash,
diff --git a/lldb/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h
index 9e2bbff4b6614..4ad58f36f736c 100644
--- a/lldb/include/lldb/ValueObject/DILParser.h
+++ b/lldb/include/lldb/ValueObject/DILParser.h
@@ -82,7 +82,7 @@ class DILParser {
ASTNodeUP Run();
ASTNodeUP ParseExpression();
-
+ ASTNodeUP ParseConditionalExpression();
ASTNodeUP ParseAssignmentExpression();
ASTNodeUP ParseShiftExpression();
ASTNodeUP ParseAdditiveExpression();
diff --git a/lldb/source/ValueObject/DILAST.cpp b/lldb/source/ValueObject/DILAST.cpp
index 40bf07bdd5aab..86fa2031207a1 100644
--- a/lldb/source/ValueObject/DILAST.cpp
+++ b/lldb/source/ValueObject/DILAST.cpp
@@ -87,4 +87,8 @@ llvm::Expected<lldb::ValueObjectSP> CastNode::Accept(Visitor *v) const {
return v->Visit(*this);
}
+llvm::Expected<lldb::ValueObjectSP> ConditionalNode::Accept(Visitor *v) const {
+ return v->Visit(*this);
+}
+
} // namespace lldb_private::dil
diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp
index 4c5ac96dccf74..a63e03de05bf2 100644
--- a/lldb/source/ValueObject/DILEval.cpp
+++ b/lldb/source/ValueObject/DILEval.cpp
@@ -1578,4 +1578,37 @@ llvm::Expected<lldb::ValueObjectSP> Interpreter::Visit(const CastNode &node) {
node.GetLocation());
}
+llvm::Expected<lldb::ValueObjectSP>
+Interpreter::Visit(const ConditionalNode &node) {
+ auto cond_or_err = EvaluateAndDereference(node.GetCondition());
+ if (!cond_or_err)
+ return cond_or_err;
+ lldb::ValueObjectSP condition = *cond_or_err;
+
+ CompilerType cond_type = condition->GetCompilerType();
+ if (!cond_type.IsContextuallyConvertibleToBool()) {
+ std::string errMsg = llvm::formatv(
+ "value of type {0} is not contextually convertible to 'bool'",
+ cond_type.TypeDescription());
+ return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
+ node.GetLocation());
+ }
+ // Note: Unlike C++, DIL evaluates only the operand chosen by the condition,
+ // and doesn't check the type or evaluate the other operand.
+ auto value_or_err = condition->GetValueAsBool();
+ if (value_or_err) {
+ if (*value_or_err) {
+ auto true_or_err = EvaluateAndDereference(node.GetTrueOperand());
+ if (!true_or_err)
+ return true_or_err;
+ return *true_or_err;
+ }
+ auto false_or_err = EvaluateAndDereference(node.GetFalseOperand());
+ if (!false_or_err)
+ return false_or_err;
+ return *false_or_err;
+ }
+ return value_or_err.takeError();
+}
+
} // namespace lldb_private::dil
diff --git a/lldb/source/ValueObject/DILLexer.cpp b/lldb/source/ValueObject/DILLexer.cpp
index 997ba6b09f872..fa8b960908f4b 100644
--- a/lldb/source/ValueObject/DILLexer.cpp
+++ b/lldb/source/ValueObject/DILLexer.cpp
@@ -62,6 +62,8 @@ llvm::StringRef Token::GetTokenName(Kind kind) {
return "plus";
case Kind::plusequal:
return "plusequal";
+ case Kind::question:
+ return "question";
case Kind::r_paren:
return "r_paren";
case Kind::r_square:
@@ -213,6 +215,7 @@ llvm::Expected<Token> DILLexer::Lex(llvm::StringRef expr,
{Token::percent, "%"},
{Token::period, "."},
{Token::plus, "+"},
+ {Token::question, "?"},
{Token::r_paren, ")"},
{Token::r_square, "]"},
{Token::slash, "/"},
diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp
index b55b12a2bc42a..467d766ebd1ef 100644
--- a/lldb/source/ValueObject/DILParser.cpp
+++ b/lldb/source/ValueObject/DILParser.cpp
@@ -129,7 +129,35 @@ ASTNodeUP DILParser::Run() {
// expression:
// assignment_expression
//
-ASTNodeUP DILParser::ParseExpression() { return ParseAssignmentExpression(); }
+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;
+}
// Parse an assignment_expression
//
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Conditional/Makefile b/lldb/test/API/commands/frame/var-dil/expr/Conditional/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/expr/Conditional/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Conditional/TestFrameVarDILConditional.py b/lldb/test/API/commands/frame/var-dil/expr/Conditional/TestFrameVarDILConditional.py
new file mode 100644
index 0000000000000..20bc6464b7779
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/expr/Conditional/TestFrameVarDILConditional.py
@@ -0,0 +1,76 @@
+"""
+Test DIL ternary conditional operator.
+"""
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test import lldbutil
+
+
+class TestFrameVarDILConditional(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ def test_conditional(self):
+ self.build()
+ lldbutil.run_to_source_breakpoint(
+ self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp")
+ )
+
+ self.runCmd("settings set target.experimental.use-DIL true")
+
+ # Unlike C++, DIL evaluates only the operand chosen by the condition,
+ # and doesn't check the type or evaluate the other operand.
+ # Check integer values.
+ self.expect_var_path("true ? sh : sh", value="2", type="short")
+ self.expect_var_path("true ? sh : 1", value="2", type="short")
+ self.expect_var_path("true ? sh : 1.0f", value="2", type="short")
+ self.expect_var_path("false ? 1 : sh", value="2", type="short")
+ self.expect_var_path("1 + false ? 10 : 20", value="10")
+ self.expect_var_path("1 + (false ? 10 : 20)", value="21")
+
+ # Check enums.
+ self.expect_var_path("false ? b_enum : a_enum", value="kTwoA")
+ self.expect_var_path("false ? sh : b_enum", value="kOneB")
+ self.expect_var_path("false ? b_enum : sh", value="2")
+
+ # Check references
+ self.expect_var_path("iref ? 1 : 2", value="1", type="int")
+ self.expect_var_path("true ? iref : 2", value="1", type="int")
+ self.expect_var_path("false ? 1 : iref", value="1", type="int")
+
+ # Check pointers and arrays.
+ nullptr = "0x" + "00" * self.target().GetAddressByteSize()
+ self.expect_var_path("true ? 0 : nullptr", value="0")
+ self.expect_var_path("true ? nullptr : 0", value=nullptr)
+ self.expect_var_path("true ? arr2 : arr3", type="int[2]")
+ self.expect_var_path("true ? arr2 : 0", type="int[2]")
+ self.expect_var_path("true ? 0 : arr2", value="0")
+ self.expect_var_path("true ? nullptr : arr2", value=nullptr)
+ self.expect_var_path("*(true ? arr2 : arr3)", value="1")
+
+ # Check result with incompatible type operands.
+ # (these would return an error in C++)
+ self.expect_var_path("true ? s : 1", type="S")
+ self.expect_var_path("true ? 1 : t", value="1")
+ self.expect_var_path("true ? nullptr : 1", value=nullptr)
+ self.expect_var_path("true ? 1.25 : arr", value="1.25")
+ self.expect_var_path("*(true ? iptr : 2.0)", value="1")
+ self.expect_var_path("&(true ? i : arr)", type="int *")
+ self.expect_var_path("true ? iptr : nullptr", type="int *")
+
+ # Check non-existent values
+ self.expect_var_path("true ? 1 : __doesnt_exist", value="1")
+ self.expect_var_path("false ? __doesnt_exist : 2", value="2")
+
+ # Use different types in bool context.
+ self.expect_var_path("iptr ? 1 : 2", value="1")
+ self.expect_var_path("nptr ? 1 : 2", value="2")
+ self.expect_var_path("arr2 ? 1 : 2", value="1")
+ self.expect_var_path("1.0f ? 1 : 2", value="1")
+ self.expect_var_path("a_enum ? 1 : 2", value="1")
+ self.expect(
+ "frame var -- 's ? 1 : 2'",
+ error=True,
+ substrs=["value of type 'S' is not contextually convertible to 'bool'"],
+ )
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Conditional/main.cpp b/lldb/test/API/commands/frame/var-dil/expr/Conditional/main.cpp
new file mode 100644
index 0000000000000..83820c53a7070
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/expr/Conditional/main.cpp
@@ -0,0 +1,20 @@
+void stop() {}
+
+int main(int argc, char **argv) {
+ int i = 1;
+ int &iref = i;
+ int *iptr = &i;
+ short sh = 2;
+ int arr2[2] = {1, 2};
+ int arr3[3] = {0, 1, 2};
+ double dbl_arr[2] = {1.0, 2.0};
+ void *nptr = nullptr;
+
+ struct S {
+ } s;
+
+ enum EnumA { kOneA = 1, kTwoA } a_enum = kTwoA;
+ enum EnumB { kOneB = 1 } b_enum = kOneB;
+
+ stop(); // Set a breakpoint here
+}
>From 89229322f5888b4ff0d9e1d0747f659ab5d76b30 Mon Sep 17 00:00:00 2001
From: Ilia Kuklin <ikuklin at accesssoftek.com>
Date: Tue, 28 Jul 2026 03:53:13 +0500
Subject: [PATCH 2/2] Add nested operator tests
---
.../var-dil/expr/Conditional/TestFrameVarDILConditional.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Conditional/TestFrameVarDILConditional.py b/lldb/test/API/commands/frame/var-dil/expr/Conditional/TestFrameVarDILConditional.py
index 20bc6464b7779..4221ff1cdd4e2 100644
--- a/lldb/test/API/commands/frame/var-dil/expr/Conditional/TestFrameVarDILConditional.py
+++ b/lldb/test/API/commands/frame/var-dil/expr/Conditional/TestFrameVarDILConditional.py
@@ -28,6 +28,9 @@ def test_conditional(self):
self.expect_var_path("false ? 1 : sh", value="2", type="short")
self.expect_var_path("1 + false ? 10 : 20", value="10")
self.expect_var_path("1 + (false ? 10 : 20)", value="21")
+ self.expect_var_path("0 ? 1 ? 2 : 3 : 4 ? 5 : 6", value="5")
+ self.expect_var_path("0 ? 1 : 2 ? 3 : 4 ? 5 : 6", value="3")
+ self.expect_var_path("6 ? iref ? arr3[0] ? 5 : sh : 2 : i", value="2")
# Check enums.
self.expect_var_path("false ? b_enum : a_enum", value="kTwoA")
More information about the lldb-commits
mailing list