[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