[Lldb-commits] [lldb] [lldb] Add lldb.summary and lldb.synthetic decorators (PR #195351)
Dave Lee via lldb-commits
lldb-commits at lists.llvm.org
Thu May 7 15:48:35 PDT 2026
https://github.com/kastiglione updated https://github.com/llvm/llvm-project/pull/195351
>From e5d73eaaaef661f373a3959b34a5b78d9862e0c8 Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Fri, 1 May 2026 13:45:19 -0700
Subject: [PATCH 1/3] [lldb] Add lldb.summary and lldb.synthetic decorators
---
lldb/bindings/python/python-extensions.swig | 44 +++++++++++++++++++
lldb/bindings/python/python-wrapper.swig | 21 +++++++--
.../decorator-formatters/Makefile | 2 +
.../TestDecoratorFormatters.py | 31 +++++++++++++
.../decorator-formatters/formatters.py | 29 ++++++++++++
.../decorator-formatters/main.cpp | 15 +++++++
6 files changed, 138 insertions(+), 4 deletions(-)
create mode 100644 lldb/test/API/functionalities/data-formatter/decorator-formatters/Makefile
create mode 100644 lldb/test/API/functionalities/data-formatter/decorator-formatters/TestDecoratorFormatters.py
create mode 100644 lldb/test/API/functionalities/data-formatter/decorator-formatters/formatters.py
create mode 100644 lldb/test/API/functionalities/data-formatter/decorator-formatters/main.cpp
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
+}
>From 307fe8533c96fea2a2bab2c97dbee7a45a298776 Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Thu, 7 May 2026 15:46:11 -0700
Subject: [PATCH 2/3] Refactor to call HandleCommand immediately, via
CommandInterpreter
---
lldb/bindings/python/python-extensions.swig | 94 ++++++++++++---------
lldb/bindings/python/python-wrapper.swig | 21 +----
2 files changed, 58 insertions(+), 57 deletions(-)
diff --git a/lldb/bindings/python/python-extensions.swig b/lldb/bindings/python/python-extensions.swig
index 7f5cf520223f0..a1a923714a3f9 100644
--- a/lldb/bindings/python/python-extensions.swig
+++ b/lldb/bindings/python/python-extensions.swig
@@ -282,6 +282,60 @@ def command(command_name=None, doc=None):
return callable
+
+def _add_formatter(kind: str, type_name: str, options: dict):
+ import shlex
+
+ type_name = shlex.quote(type_name)
+
+ flag_list = []
+ # Convert `option=True` to "--option", all other cases convert to "--option value".
+ for key, value in options.values():
+ flag = key.replace("_", "-")
+ if value is True:
+ flag_list.append(f"--{flag}")
+ else:
+ flag_list.extend((f"--{flag}", value))
+
+ def decorator(obj):
+ qualified = f"{obj.__module__}.{obj.__qualname__}"
+ if isinstance(obj, type):
+ flag_list.extend(("--python-class", qualified))
+ elif callable(obj):
+ flag_list.extend(("--python-function", qualified))
+
+ result = SBCommandReturnObject()
+ flags = " ".join(flag_list)
+ interp = debugger.GetCommandInterpreter()
+ interp.HandleCommand(f"type {kind} add {flags} {type_name}", result)
+ if not result.Succeeded():
+ raise RuntimeError(result.GetError())
+ return obj
+
+ return decorator
+
+
+def summary(type_name, **kwargs):
+ """A decorator that registers a function as an LLDB type summary provider.
+
+ @lldb.summary("MyType")
+ def MyTypeSummary(valobj, _):
+ return "summary string"
+ """
+ return _add_formatter("summary", type_name, kwargs)
+
+
+def synthetic(type_name, **kwargs):
+ """A decorator that registers a class as an LLDB synthetic child provider.
+
+ @lldb.synthetic("MyType")
+ class MyTypeSynthetic:
+ def __init__(self, valobj, _):
+ ...
+ """
+ return _add_formatter("synthetic", type_name, kwargs)
+
+
class declaration(object):
'''A class that represents a source declaration location with file, line and column.'''
def __init__(self, file, line, col):
@@ -609,44 +663,4 @@ def is_numeric_type(basic_type):
%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 db19604ca51c4..72f90f1b23c29 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -1007,23 +1007,10 @@ 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())
- 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)));
- }
- }
+ if (!pfunc.IsAllocated())
+ return true;
+
+ pfunc(SWIGBridge::ToSWIGWrapper(std::move(debugger)), dict);
return true;
}
>From e4fdea0de86d0073bc26728aa5d53159f4c13563 Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Thu, 7 May 2026 15:48:18 -0700
Subject: [PATCH 3/3] refined comment
---
lldb/bindings/python/python-extensions.swig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/bindings/python/python-extensions.swig b/lldb/bindings/python/python-extensions.swig
index a1a923714a3f9..a8a1968e6e384 100644
--- a/lldb/bindings/python/python-extensions.swig
+++ b/lldb/bindings/python/python-extensions.swig
@@ -288,8 +288,8 @@ def _add_formatter(kind: str, type_name: str, options: dict):
type_name = shlex.quote(type_name)
+ # Convert `option=True` to "--option", all others to "--option value".
flag_list = []
- # Convert `option=True` to "--option", all other cases convert to "--option value".
for key, value in options.values():
flag = key.replace("_", "-")
if value is True:
More information about the lldb-commits
mailing list