https://github.com/kuilpd updated https://github.com/llvm/llvm-project/pull/211344
>From 5680bd1e2cc3041039d1a1160733f6139ad829e3 Mon Sep 17 00:00:00 2001 From: Ilia Kuklin <[email protected]> Date: Mon, 20 Jul 2026 22:33:02 +0500 Subject: [PATCH 1/3] [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 9d7d2a9b09072..a0d49204ceb55 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 = logical_or_expression | logical_or_expression assignment_operator assignment_expression ; diff --git a/lldb/include/lldb/ValueObject/DILAST.h b/lldb/include/lldb/ValueObject/DILAST.h index 90bdea13273a4..00a50c97324a1 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, @@ -339,6 +340,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; +}; + class SizeOfNode : public ASTNode { public: SizeOfNode(uint32_t location, ASTNodeUP node) @@ -388,6 +413,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; virtual llvm::Expected<lldb::ValueObjectSP> Visit(const SizeOfNode &node) = 0; }; diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h index ce08961cc7db9..1510a1f06c7fa 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; llvm::Expected<lldb::ValueObjectSP> Visit(const SizeOfNode &node) override; /// Perform usual unary conversions on a value. At the moment this diff --git a/lldb/include/lldb/ValueObject/DILLexer.h b/lldb/include/lldb/ValueObject/DILLexer.h index 21953c60e5198..69d6b49800b29 100644 --- a/lldb/include/lldb/ValueObject/DILLexer.h +++ b/lldb/include/lldb/ValueObject/DILLexer.h @@ -57,6 +57,7 @@ class Token { pipepipe, 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 fbfe35314ff71..cc495d48d93c5 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 ParseLogicalOrExpression(); ASTNodeUP ParseLogicalAndExpression(); diff --git a/lldb/source/ValueObject/DILAST.cpp b/lldb/source/ValueObject/DILAST.cpp index 666e8708812fa..7128233d227a9 100644 --- a/lldb/source/ValueObject/DILAST.cpp +++ b/lldb/source/ValueObject/DILAST.cpp @@ -109,6 +109,10 @@ 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); +} + llvm::Expected<lldb::ValueObjectSP> SizeOfNode::Accept(Visitor *v) const { return v->Visit(*this); } diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp index 67fb7bd993ba1..296c18ed121ad 100644 --- a/lldb/source/ValueObject/DILEval.cpp +++ b/lldb/source/ValueObject/DILEval.cpp @@ -1966,6 +1966,39 @@ 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(); +} + llvm::Expected<lldb::ValueObjectSP> Interpreter::Visit(const SizeOfNode &node) { CompilerType typearg = node.GetTypeArg(); Scalar size; diff --git a/lldb/source/ValueObject/DILLexer.cpp b/lldb/source/ValueObject/DILLexer.cpp index 87ededc8e6b8a..0f1978e28e512 100644 --- a/lldb/source/ValueObject/DILLexer.cpp +++ b/lldb/source/ValueObject/DILLexer.cpp @@ -84,6 +84,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: @@ -248,6 +250,7 @@ llvm::Expected<Token> DILLexer::Lex(llvm::StringRef expr, {Token::period, "."}, {Token::pipe, "|"}, {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 64f1f8b47c965..dd6e6244461a4 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 b6a7ea5d75914cfcf789b5e45dc28ea6e16a78b9 Mon Sep 17 00:00:00 2001 From: Ilia Kuklin <[email protected]> Date: Tue, 28 Jul 2026 03:53:13 +0500 Subject: [PATCH 2/3] 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") >From e49497afda8e3fb3164de9f0e11d524e43b115a6 Mon Sep 17 00:00:00 2001 From: Ilia Kuklin <[email protected]> Date: Tue, 4 Aug 2026 19:49:41 +0500 Subject: [PATCH 3/3] Remove C++ mentions in comments --- lldb/source/ValueObject/DILEval.cpp | 2 +- .../var-dil/expr/Conditional/TestFrameVarDILConditional.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp index 296c18ed121ad..d448444b43eba 100644 --- a/lldb/source/ValueObject/DILEval.cpp +++ b/lldb/source/ValueObject/DILEval.cpp @@ -1981,7 +1981,7 @@ Interpreter::Visit(const ConditionalNode &node) { return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, node.GetLocation()); } - // Note: Unlike C++, DIL evaluates only the operand chosen by the condition, + // Note: 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) { 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 4221ff1cdd4e2..da9d703554723 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 @@ -19,7 +19,7 @@ def test_conditional(self): self.runCmd("settings set target.experimental.use-DIL true") - # Unlike C++, DIL evaluates only the operand chosen by the condition, + # 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") _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
