[Lldb-commits] [lldb] [lldb] Add lldb.summary and lldb.synthetic decorators (PR #195351)

via lldb-commits lldb-commits at lists.llvm.org
Fri May 1 13:46:23 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Dave Lee (kastiglione)

<details>
<summary>Changes</summary>



---
Full diff: https://github.com/llvm/llvm-project/pull/195351.diff


6 Files Affected:

- (modified) lldb/bindings/python/python-extensions.swig (+44) 
- (modified) lldb/bindings/python/python-wrapper.swig (+17-4) 
- (added) lldb/test/API/functionalities/data-formatter/decorator-formatters/Makefile (+2) 
- (added) lldb/test/API/functionalities/data-formatter/decorator-formatters/TestDecoratorFormatters.py (+31) 
- (added) lldb/test/API/functionalities/data-formatter/decorator-formatters/formatters.py (+29) 
- (added) lldb/test/API/functionalities/data-formatter/decorator-formatters/main.cpp (+15) 


``````````diff
diff --git a/lldb/bindings/python/python-extensions.swig b/lldb/bindings/python/python-extensions.swig
index 40fa76872ee96..7f5cf520223f0 100644
--- a/lldb/bindings/python/python-extensions.swig
+++ b/lldb/bindings/python/python-extensions.swig
@@ -606,3 +606,47 @@ def is_numeric_type(basic_type):
     return (False,False)
 
 %}
+
+%pythoncode %{
+
+def _type_spec(type_name, regex):
+    type_spec = f"'{type_name}'"
+    if regex:
+        type_spec = f"-x {type_spec}"
+    return type_spec
+
+def summary(type_name, regex=False):
+    """A decorator that registers a function as an LLDB type summary provider.
+
+    @lldb.summary("MyType")
+    def MyTypeSummary(valobj, internal_dict):
+        return "summary string"
+    """
+    type_spec = _type_spec(type_name, regex)
+    def decorator(func):
+        qualified = f"{func.__module__}.{func.__qualname__}"
+        func._lldb_register_command = f"type summary add -F {qualified} {type_spec}"
+        return func
+    return decorator
+
+def synthetic(type_name, regex=False):
+    """A decorator that registers a class as an LLDB synthetic child provider.
+
+    @lldb.synthetic("MyType")
+    class MyTypeSynthetic:
+        def __init__(self, valobj, internal_dict):
+            ...
+    """
+    type_spec = _type_spec(type_name, regex)
+    def decorator(cls):
+        qualified = f"{cls.__module__}.{cls.__qualname__}"
+        cls._lldb_register_command = f"type synthetic add -l {qualified} {type_spec}"
+        return cls
+    return decorator
+
+def _register_lldb_decorators(module, debugger):
+    for obj in vars(module).values():
+        if cmd := getattr(obj, '_lldb_register_command', None):
+            debugger.HandleCommand(cmd)
+
+%}
diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
index 72f90f1b23c29..db19604ca51c4 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -1007,10 +1007,23 @@ bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallModuleInit(
 
   // This method is optional and need not exist.  So if we don't find it,
   // it's actually a success, not a failure.
-  if (!pfunc.IsAllocated())
-    return true;
-
-  pfunc(SWIGBridge::ToSWIGWrapper(std::move(debugger)), dict);
+  if (pfunc.IsAllocated())
+    pfunc(SWIGBridge::ToSWIGWrapper(debugger), dict);
+
+  // Scan the module for @lldb.summary and @lldb.synthetic decorators.
+  auto lldb_module =
+      unwrapIgnoringErrors(PythonModule::Import("lldb"));
+  if (lldb_module.IsAllocated()) {
+    auto register_func =
+        lldb_module.ResolveName<PythonCallable>("_register_lldb_decorators");
+    if (register_func.IsAllocated()) {
+      auto user_module =
+          unwrapIgnoringErrors(PythonModule::Import(python_module_name));
+      if (user_module.IsAllocated())
+        register_func(std::move(user_module),
+                       SWIGBridge::ToSWIGWrapper(std::move(debugger)));
+    }
+  }
 
   return true;
 }
diff --git a/lldb/test/API/functionalities/data-formatter/decorator-formatters/Makefile b/lldb/test/API/functionalities/data-formatter/decorator-formatters/Makefile
new file mode 100644
index 0000000000000..3d0b98f13f3d7
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/decorator-formatters/Makefile
@@ -0,0 +1,2 @@
+CXX_SOURCES := main.cpp
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/data-formatter/decorator-formatters/TestDecoratorFormatters.py b/lldb/test/API/functionalities/data-formatter/decorator-formatters/TestDecoratorFormatters.py
new file mode 100644
index 0000000000000..14c5584d02f81
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/decorator-formatters/TestDecoratorFormatters.py
@@ -0,0 +1,31 @@
+"""
+Test @lldb.summary and @lldb.synthetic decorators lead to automatic formatter
+registration, when using `command script import`.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_summary(self):
+        self.build()
+        self.runCmd("command script import formatters.py")
+        _, _, thread, _ = lldbutil.run_to_source_breakpoint(
+            self, "break here", lldb.SBFileSpec("main.cpp")
+        )
+        frame = thread.selected_frame
+        p = frame.var("p")
+        self.assertEqual(p.summary, "(1, 2)")
+
+    def test_synthetic(self):
+        self.build()
+        self.runCmd("command script import formatters.py")
+        lldbutil.run_to_source_breakpoint(
+            self, "break here", lldb.SBFileSpec("main.cpp")
+        )
+        self.expect("v c", substrs=["[0] = 10", "[1] = 20", "[2] = 30"])
diff --git a/lldb/test/API/functionalities/data-formatter/decorator-formatters/formatters.py b/lldb/test/API/functionalities/data-formatter/decorator-formatters/formatters.py
new file mode 100644
index 0000000000000..f45f96a27f3ae
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/decorator-formatters/formatters.py
@@ -0,0 +1,29 @@
+import lldb
+
+
+ at lldb.summary("Pair")
+def pair_summary(valobj, _):
+    first = valobj.GetChildMemberWithName("first").GetValueAsSigned()
+    second = valobj.GetChildMemberWithName("second").GetValueAsSigned()
+    return f"({first}, {second})"
+
+
+ at lldb.synthetic("Container")
+class ContainerSyntheticProvider:
+    valobj: lldb.SBValue
+    count: int
+    items: lldb.SBValue
+
+    def __init__(self, valobj: lldb.SBValue, _) -> None:
+        self.valobj = valobj
+
+    def update(self) -> bool:
+        self.count = self.valobj.GetChildMemberWithName("size").GetValueAsSigned()
+        self.items = self.valobj.GetChildMemberWithName("items")
+        return True
+
+    def num_children(self) -> int:
+        return self.count
+
+    def get_child_at_index(self, index: int) -> lldb.SBValue:
+        return self.items.GetChildAtIndex(index)
diff --git a/lldb/test/API/functionalities/data-formatter/decorator-formatters/main.cpp b/lldb/test/API/functionalities/data-formatter/decorator-formatters/main.cpp
new file mode 100644
index 0000000000000..ccd43df0aafaa
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/decorator-formatters/main.cpp
@@ -0,0 +1,15 @@
+struct Pair {
+  int first;
+  int second;
+};
+
+struct Container {
+  int items[3];
+  int size;
+};
+
+int main() {
+  Pair p = {1, 2};
+  Container c = {{10, 20, 30}, 3};
+  return 0; // break here
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/195351


More information about the lldb-commits mailing list