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 <[email protected]> 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 <[email protected]> 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") _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
