https://github.com/medismailben created 
https://github.com/llvm/llvm-project/pull/210845

Give `type synthetic add -l` a formal `ScriptedSyntheticChildrenInterface`, 
matching the architecture used elsewhere in this series: a C++ interface 
header, a Python-backed implementation, `PluginManager` registration with 
CLI/API usages, and a generatable ABC template
(`scripted_synthetic_children.py`) wired into `scripting extension generate`.

Teach `Dispatch<T>()` to introspect the target method's arity via 
`PythonCallable::GetArgInfo()` and drop trailing args before calling, so 
providers that legitimately define an argument as optional 
(`num_children(self)` vs. `num_children(self, max_count)`) work through the 
generic dispatch path. `CalculateNumChildren` now uses `Dispatch<T>()` and the 
standalone `LLDBSwigPython_CalculateNumChildren` bridge is removed. Only 
`CreatePluginObject` takes a full `InitSession`/`TearDownSession` session; 
per-call methods just acquire the GIL.

>From c6c12414c6ee48bd6609ab55b48e04200c7959af Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <[email protected]>
Date: Mon, 20 Jul 2026 16:07:52 -0700
Subject: [PATCH] [lldb/script] Migrate synthetic children providers onto
 ScriptedPythonInterface

Give `type synthetic add -l` a formal `ScriptedSyntheticChildrenInterface`,
matching the architecture used elsewhere in this series: a C++ interface
header, a Python-backed implementation, `PluginManager` registration with
CLI/API usages, and a generatable ABC template
(`scripted_synthetic_children.py`) wired into `scripting extension
generate`.

`CreatePluginObject` goes through the generic
`ScriptedPythonInterface::CreatePluginObject` template, so
`LLDBSwigPythonCreateSyntheticProvider` is removed. A new
`SWIGBridge::ToSWIGWrapper(ValueObjectSP, bool use_synthetic)` overload
preserves the pre-migration `SetPreferSyntheticValue(false)` on the
SBValue view handed to `__init__`, so the provider does not recursively
re-enter its own synthetic children while introspecting its backing value.

Teach `Dispatch<T>()` to introspect the target method's arity via
`PythonCallable::GetArgInfo()` and drop trailing args before calling, so
providers that legitimately define an argument as optional
(`num_children(self)` vs. `num_children(self, max_count)`) work through
the generic dispatch path. `CalculateNumChildren` now uses
`Dispatch<T>()` and the standalone `LLDBSwigPython_CalculateNumChildren`
bridge is removed. Only `CreatePluginObject` takes a full
`InitSession`/`TearDownSession` session; per-call methods just acquire
the GIL.

Signed-off-by: Med Ismail Bennani <[email protected]>
---
 lldb/bindings/python/CMakeLists.txt           |   1 +
 lldb/bindings/python/python-swigsafecast.swig |   7 +
 lldb/bindings/python/python-wrapper.swig      |  64 -----
 lldb/docs/CMakeLists.txt                      |   1 +
 .../templates/scripted_synthetic_children.py  | 111 ++++++++
 .../lldb/DataFormatters/TypeSynthetic.h       |   3 +-
 .../ScriptedSyntheticChildrenInterface.h      |  44 ++++
 .../lldb/Interpreter/ScriptInterpreter.h      |  48 +---
 lldb/include/lldb/lldb-enumerations.h         |   3 +-
 lldb/include/lldb/lldb-forward.h              |   3 +
 lldb/source/DataFormatters/TypeSynthetic.cpp  |  66 ++---
 lldb/source/Interpreter/ScriptInterpreter.cpp |   4 +
 .../ScriptInterpreter/Python/CMakeLists.txt   |   1 +
 .../ScriptInterpreterPythonInterfaces.cpp     |   2 +
 .../ScriptInterpreterPythonInterfaces.h       |   1 +
 .../Interfaces/ScriptedPythonInterface.h      |  96 ++++---
 ...riptedSyntheticChildrenPythonInterface.cpp | 233 +++++++++++++++++
 ...ScriptedSyntheticChildrenPythonInterface.h |  63 +++++
 .../Python/SWIGPythonBridge.h                 |  10 +-
 .../Python/ScriptInterpreterPython.cpp        | 240 +-----------------
 .../Python/ScriptInterpreterPythonImpl.h      |  30 +--
 .../Python/PythonTestSuite.cpp                |  12 -
 22 files changed, 589 insertions(+), 454 deletions(-)
 create mode 100644 
lldb/examples/python/templates/scripted_synthetic_children.py
 create mode 100644 
lldb/include/lldb/Interpreter/Interfaces/ScriptedSyntheticChildrenInterface.h
 create mode 100644 
lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSyntheticChildrenPythonInterface.cpp
 create mode 100644 
lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSyntheticChildrenPythonInterface.h

diff --git a/lldb/bindings/python/CMakeLists.txt 
b/lldb/bindings/python/CMakeLists.txt
index d29b143c1408c..ce6201396bb4f 100644
--- a/lldb/bindings/python/CMakeLists.txt
+++ b/lldb/bindings/python/CMakeLists.txt
@@ -120,6 +120,7 @@ function(finish_swig_python swig_target 
lldb_python_bindings_dir lldb_python_tar
     "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_breakpoint.py"
     "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_hook.py"
     
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_stackframe_recognizer.py"
+    
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_synthetic_children.py"
     )
 
   if(APPLE)
diff --git a/lldb/bindings/python/python-swigsafecast.swig 
b/lldb/bindings/python/python-swigsafecast.swig
index a86dc44ce4106..c5003c019aae2 100644
--- a/lldb/bindings/python/python-swigsafecast.swig
+++ b/lldb/bindings/python/python-swigsafecast.swig
@@ -17,6 +17,13 @@ PythonObject SWIGBridge::ToSWIGWrapper(lldb::ValueObjectSP 
value_sp) {
   return ToSWIGWrapper(std::unique_ptr<lldb::SBValue>(new 
lldb::SBValue(value_sp)));
 }
 
+PythonObject SWIGBridge::ToSWIGWrapper(lldb::ValueObjectSP value_sp,
+                                       bool use_synthetic) {
+  auto sb_value = std::unique_ptr<lldb::SBValue>(new lldb::SBValue(value_sp));
+  sb_value->SetPreferSyntheticValue(use_synthetic);
+  return ToSWIGWrapper(std::move(sb_value));
+}
+
 PythonObject SWIGBridge::ToSWIGWrapper(lldb::TargetSP target_sp) {
   return ToSWIGHelper(new lldb::SBTarget(std::move(target_sp)),
                       SWIGTYPE_p_lldb__SBTarget);
diff --git a/lldb/bindings/python/python-wrapper.swig 
b/lldb/bindings/python/python-wrapper.swig
index 2392737402e20..0beac24568c84 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -181,38 +181,6 @@ bool 
lldb_private::python::SWIGBridge::LLDBSwigPythonCallTypeScript(
   return true;
 }
 
-PythonObject 
lldb_private::python::SWIGBridge::LLDBSwigPythonCreateSyntheticProvider(
-    const char *python_class_name, const char *session_dictionary_name,
-    const lldb::ValueObjectSP &valobj_sp) {
-  if (python_class_name == NULL || python_class_name[0] == '\0' ||
-      !session_dictionary_name)
-    return PythonObject();
-
-  PyErr_Cleaner py_err_cleaner(true);
-
-  auto dict = PythonModule::MainModule().ResolveName<PythonDictionary>(
-      session_dictionary_name);
-  auto pfunc = PythonObject::ResolveNameWithDictionary<PythonCallable>(
-      python_class_name, dict);
-
-  if (!pfunc.IsAllocated())
-    return PythonObject();
-
-  auto sb_value = std::unique_ptr<lldb::SBValue>(new lldb::SBValue(valobj_sp));
-  sb_value->SetPreferSyntheticValue(false);
-
-  PythonObject val_arg = SWIGBridge::ToSWIGWrapper(std::move(sb_value));
-  if (!val_arg.IsAllocated())
-    return PythonObject();
-
-  PythonObject result = pfunc(val_arg, dict);
-
-  if (result.IsAllocated())
-    return result;
-
-  return PythonObject();
-}
-
 PythonObject 
lldb_private::python::SWIGBridge::LLDBSwigPythonCreateCommandObject(
     const char *python_class_name, const char *session_dictionary_name,
     lldb::DebuggerSP debugger_sp) {
@@ -256,38 +224,6 @@ static PyObject *LLDBSwigPython_CallOptionalMember(
   return result.release();
 }
 
-size_t 
lldb_private::python::SWIGBridge::LLDBSwigPython_CalculateNumChildren(PyObject 
* implementor,
-                                                         uint32_t max) {
-  PythonObject self(PyRefType::Borrowed, implementor);
-  auto pfunc = self.ResolveName<PythonCallable>("num_children");
-
-  if (!pfunc.IsAllocated())
-    return 0;
-
-  auto arg_info = pfunc.GetArgInfo();
-  if (!arg_info) {
-    llvm::consumeError(arg_info.takeError());
-    return 0;
-  }
-
-  size_t ret_val;
-  if (arg_info.get().max_positional_args < 1)
-    ret_val = unwrapOrSetPythonException(As<long long>(pfunc.Call()));
-  else
-    ret_val = unwrapOrSetPythonException(
-        As<long long>(pfunc.Call(PythonInteger(max))));
-
-  if (PyErr_Occurred()) {
-    PyErr_Print();
-    PyErr_Clear();
-    return 0;
-  }
-
-  if (arg_info.get().max_positional_args < 1)
-    ret_val = std::min(ret_val, static_cast<size_t>(max));
-
-  return ret_val;
-}
 
 PyObject 
*lldb_private::python::SWIGBridge::LLDBSwigPython_GetChildAtIndex(PyObject * 
implementor,
                                                        uint32_t idx) {
diff --git a/lldb/docs/CMakeLists.txt b/lldb/docs/CMakeLists.txt
index dd091836dc1aa..d57415ef1e975 100644
--- a/lldb/docs/CMakeLists.txt
+++ b/lldb/docs/CMakeLists.txt
@@ -33,6 +33,7 @@ if (LLDB_ENABLE_PYTHON AND SPHINX_FOUND)
       COMMAND "${CMAKE_COMMAND}" -E copy 
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_breakpoint.py" 
"${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
       COMMAND "${CMAKE_COMMAND}" -E copy 
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_hook.py" 
"${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
       COMMAND "${CMAKE_COMMAND}" -E copy 
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_stackframe_recognizer.py"
 "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
+      COMMAND "${CMAKE_COMMAND}" -E copy 
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_synthetic_children.py" 
"${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
       COMMENT "Copying lldb.py to pretend its a Python package.")
 
     add_dependencies(lldb-python-doc-package swig_wrapper_python)
diff --git a/lldb/examples/python/templates/scripted_synthetic_children.py 
b/lldb/examples/python/templates/scripted_synthetic_children.py
new file mode 100644
index 0000000000000..61ce038b7f58b
--- /dev/null
+++ b/lldb/examples/python/templates/scripted_synthetic_children.py
@@ -0,0 +1,111 @@
+from abc import ABCMeta, abstractmethod
+from typing import Optional
+
+import lldb
+
+
+class ScriptedSyntheticChildren(metaclass=ABCMeta):
+    """
+    The base class for a scripted synthetic children provider.
+
+    A synthetic children provider allows you to customize how a value is
+    expanded into children when displayed (e.g. `frame variable`, `bt`).
+    Register it with `type synthetic add -l <ClassName> ...`.
+
+    Most of the base class methods are `@abstractmethod` that need to be
+    overwritten by the inheriting class.
+    """
+
+    valobj: lldb.SBValue
+
+    def __init__(self, valobj: lldb.SBValue, internal_dict: dict):
+        """Construct a scripted synthetic children provider.
+
+        Args:
+            valobj (lldb.SBValue): The value this provider generates children
+                for.
+            internal_dict (dict): The session dictionary for the embedded
+                interpreter, unused in most implementations.
+        """
+        self.valobj = valobj
+
+    @abstractmethod
+    def num_children(self) -> int:
+        """The number of children this value has.
+
+        This can optionally take a second `max_count` parameter (i.e.
+        `def num_children(self, max_count)`) if computing the exact count is
+        expensive; in that case return `max_count` once at least that many
+        children are known to exist.
+
+        Returns:
+            int: The number of children.
+        """
+        pass
+
+    @abstractmethod
+    def get_child_at_index(self, index: int) -> Optional[lldb.SBValue]:
+        """Get the child at the given index.
+
+        Args:
+            index (int): The index of the child to return.
+
+        Returns:
+            lldb.SBValue: The value for the child at this index, or `None` if
+            there is no child at this index.
+        """
+        pass
+
+    def get_child_index(self, name: str) -> Optional[int]:
+        """Get the index of the child with the given name.
+
+        Args:
+            name (str): The name of the child to look up.
+
+        Returns:
+            int: The index of the child with this name, or `None`/a negative
+            value if no such child exists. Defaults to a linear search over
+            `get_child_at_index`/`num_children`.
+        """
+        pass
+
+    def update(self) -> bool:
+        """Called when the value backing this provider may have changed
+        (e.g. after a `continue`), giving the provider a chance to refresh
+        any cached state.
+
+        Returns:
+            bool: `True` if the previously computed children can be reused,
+            `False` if they should be recomputed. Defaults to `False`.
+        """
+        return False
+
+    def has_children(self) -> bool:
+        """Whether this value might have children, without necessarily
+        computing them. Used as a cheap check to decide whether to show an
+        expansion arrow in graphical frontends, for example.
+
+        Returns:
+            bool: `True` if this value might have children, `False`
+            otherwise. Defaults to `True`.
+        """
+        return True
+
+    def get_value(self) -> Optional[lldb.SBValue]:
+        """Override the value shown for this synthetic value itself,
+        alongside its children.
+
+        Returns:
+            lldb.SBValue: The value to display, or `None` to keep the
+            default. Defaults to `None`.
+        """
+        return None
+
+    def get_type_name(self) -> Optional[str]:
+        """Override the type name shown for this synthetic value.
+
+        Returns:
+            str: The type name to display, or `None`/empty to keep the
+            default. Defaults to `None`.
+        """
+        pass
diff --git a/lldb/include/lldb/DataFormatters/TypeSynthetic.h 
b/lldb/include/lldb/DataFormatters/TypeSynthetic.h
index 9c17adbde7465..6eb06ba8dda7f 100644
--- a/lldb/include/lldb/DataFormatters/TypeSynthetic.h
+++ b/lldb/include/lldb/DataFormatters/TypeSynthetic.h
@@ -477,8 +477,7 @@ class ScriptedSyntheticChildren : public SyntheticChildren {
 
   private:
     std::string m_python_class;
-    StructuredData::ObjectSP m_wrapper_sp;
-    ScriptInterpreter *m_interpreter;
+    lldb::ScriptedSyntheticChildrenInterfaceSP m_interface_sp;
 
     FrontEnd(const FrontEnd &) = delete;
     const FrontEnd &operator=(const FrontEnd &) = delete;
diff --git 
a/lldb/include/lldb/Interpreter/Interfaces/ScriptedSyntheticChildrenInterface.h 
b/lldb/include/lldb/Interpreter/Interfaces/ScriptedSyntheticChildrenInterface.h
new file mode 100644
index 0000000000000..c73018c779e1e
--- /dev/null
+++ 
b/lldb/include/lldb/Interpreter/Interfaces/ScriptedSyntheticChildrenInterface.h
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_INTERPRETER_INTERFACES_SCRIPTEDSYNTHETICCHILDRENINTERFACE_H
+#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDSYNTHETICCHILDRENINTERFACE_H
+
+#include "ScriptedInterface.h"
+#include "lldb/lldb-private.h"
+#include "llvm/Support/ErrorExtras.h"
+
+namespace lldb_private {
+class ScriptedSyntheticChildrenInterface : virtual public ScriptedInterface {
+public:
+  virtual llvm::Expected<StructuredData::GenericSP>
+  CreatePluginObject(llvm::StringRef class_name, ValueObject &backend) = 0;
+
+  virtual llvm::Expected<uint32_t> CalculateNumChildren(uint32_t max) {
+    return 0;
+  }
+
+  virtual lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) {
+    return lldb::ValueObjectSP();
+  }
+
+  virtual llvm::Expected<uint32_t> GetIndexOfChildWithName(ConstString name) {
+    return llvm::createStringErrorV("type has no child named '{0}'", name);
+  }
+
+  virtual lldb::ChildCacheState Update() { return lldb::eRefetch; }
+
+  virtual bool MightHaveChildren() { return true; }
+
+  virtual lldb::ValueObjectSP GetSyntheticValue() { return nullptr; }
+
+  virtual ConstString GetSyntheticTypeName() { return ConstString(); }
+};
+} // namespace lldb_private
+
+#endif // LLDB_INTERPRETER_INTERFACES_SCRIPTEDSYNTHETICCHILDRENINTERFACE_H
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h 
b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 0e65cb4b8ac4a..015c042004e63 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -257,12 +257,6 @@ class ScriptInterpreter : public PluginInterface {
     return false;
   }
 
-  virtual StructuredData::ObjectSP
-  CreateSyntheticScriptedProvider(const char *class_name,
-                                  lldb::ValueObjectSP valobj) {
-    return StructuredData::ObjectSP();
-  }
-
   virtual StructuredData::GenericSP
   CreateScriptCommandObject(const char *class_name) {
     return StructuredData::GenericSP();
@@ -348,43 +342,6 @@ class ScriptInterpreter : public PluginInterface {
     // Clean up any ref counts to SBObjects that might be in global variables
   }
 
-  virtual size_t
-  CalculateNumChildren(const StructuredData::ObjectSP &implementor,
-                       uint32_t max) {
-    return 0;
-  }
-
-  virtual lldb::ValueObjectSP
-  GetChildAtIndex(const StructuredData::ObjectSP &implementor, uint32_t idx) {
-    return lldb::ValueObjectSP();
-  }
-
-  virtual llvm::Expected<uint32_t>
-  GetIndexOfChildWithName(const StructuredData::ObjectSP &implementor,
-                          const char *child_name) {
-    return llvm::createStringError("Type has no child named '%s'", child_name);
-  }
-
-  virtual bool
-  UpdateSynthProviderInstance(const StructuredData::ObjectSP &implementor) {
-    return false;
-  }
-
-  virtual bool MightHaveChildrenSynthProviderInstance(
-      const StructuredData::ObjectSP &implementor) {
-    return true;
-  }
-
-  virtual lldb::ValueObjectSP
-  GetSyntheticValue(const StructuredData::ObjectSP &implementor) {
-    return nullptr;
-  }
-
-  virtual ConstString
-  GetSyntheticTypeName(const StructuredData::ObjectSP &implementor) {
-    return ConstString();
-  }
-
   virtual bool
   RunScriptBasedCommand(const char *impl_function, llvm::StringRef args,
                         ScriptedCommandSynchronicity synchronicity,
@@ -586,6 +543,11 @@ class ScriptInterpreter : public PluginInterface {
     return {};
   }
 
+  virtual lldb::ScriptedSyntheticChildrenInterfaceSP
+  CreateScriptedSyntheticChildrenInterface() {
+    return {};
+  }
+
   virtual StructuredData::ObjectSP
   CreateStructuredDataFromScriptObject(ScriptObject obj) {
     return {};
diff --git a/lldb/include/lldb/lldb-enumerations.h 
b/lldb/include/lldb/lldb-enumerations.h
index 93c252b55de99..9351ed14c0524 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -268,7 +268,8 @@ enum ScriptedExtension {
   eScriptedExtensionScriptedThread,
   eScriptedExtensionScriptedFrame,
   eScriptedExtensionScriptedStackFrameRecognizer,
-  kLastScriptedExtension = eScriptedExtensionScriptedStackFrameRecognizer
+  eScriptedExtensionScriptedSyntheticChildren,
+  kLastScriptedExtension = eScriptedExtensionScriptedSyntheticChildren
 };
 
 /// Register numbering types.
diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index 157aa5743f016..a1292ed538145 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -200,6 +200,7 @@ class ScriptedThreadInterface;
 class ScriptedThreadPlanInterface;
 class ScriptedStackFrameRecognizerInterface;
 class ScriptedSyntheticChildren;
+class ScriptedSyntheticChildrenInterface;
 class SearchFilter;
 class Section;
 class SectionList;
@@ -438,6 +439,8 @@ typedef 
std::shared_ptr<lldb_private::ScriptedBreakpointInterface>
     ScriptedBreakpointInterfaceSP;
 typedef std::shared_ptr<lldb_private::ScriptedStackFrameRecognizerInterface>
     ScriptedStackFrameRecognizerInterfaceSP;
+typedef std::shared_ptr<lldb_private::ScriptedSyntheticChildrenInterface>
+    ScriptedSyntheticChildrenInterfaceSP;
 typedef std::shared_ptr<lldb_private::Section> SectionSP;
 typedef std::unique_ptr<lldb_private::SectionList> SectionListUP;
 typedef std::weak_ptr<lldb_private::Section> SectionWP;
diff --git a/lldb/source/DataFormatters/TypeSynthetic.cpp 
b/lldb/source/DataFormatters/TypeSynthetic.cpp
index 2200ccf3b092d..66bcd310ef770 100644
--- a/lldb/source/DataFormatters/TypeSynthetic.cpp
+++ b/lldb/source/DataFormatters/TypeSynthetic.cpp
@@ -16,6 +16,7 @@
 #include "lldb/DataFormatters/FormatterBytecode.h"
 #include "lldb/DataFormatters/TypeSynthetic.h"
 #include "lldb/Interpreter/CommandInterpreter.h"
+#include "lldb/Interpreter/Interfaces/ScriptedSyntheticChildrenInterface.h"
 #include "lldb/Interpreter/ScriptInterpreter.h"
 #include "lldb/Symbol/CompilerType.h"
 #include "lldb/Target/Target.h"
@@ -164,8 +165,7 @@ lldb::ValueObjectSP 
SyntheticChildrenFrontEnd::CreateChildValueObjectFromData(
 
 ScriptedSyntheticChildren::FrontEnd::FrontEnd(std::string pclass,
                                               ValueObject &backend)
-    : SyntheticChildrenFrontEnd(backend), m_python_class(pclass),
-      m_wrapper_sp(), m_interpreter(nullptr) {
+    : SyntheticChildrenFrontEnd(backend), m_python_class(pclass) {
   if (backend.GetID() == LLDB_INVALID_UID)
     return;
 
@@ -174,87 +174,95 @@ ScriptedSyntheticChildren::FrontEnd::FrontEnd(std::string 
pclass,
   if (!target_sp)
     return;
 
-  m_interpreter = target_sp->GetDebugger().GetScriptInterpreter();
+  ScriptInterpreter *interpreter =
+      target_sp->GetDebugger().GetScriptInterpreter();
 
-  if (m_interpreter != nullptr)
-    m_wrapper_sp = m_interpreter->CreateSyntheticScriptedProvider(
-        m_python_class.c_str(), backend.GetSP());
+  if (!interpreter)
+    return;
+
+  m_interface_sp = interpreter->CreateScriptedSyntheticChildrenInterface();
+  if (!m_interface_sp)
+    return;
+
+  auto obj_or_err = m_interface_sp->CreatePluginObject(m_python_class, 
backend);
+  if (!obj_or_err) {
+    llvm::consumeError(obj_or_err.takeError());
+    m_interface_sp.reset();
+  }
 }
 
 ScriptedSyntheticChildren::FrontEnd::~FrontEnd() = default;
 
 lldb::ValueObjectSP
 ScriptedSyntheticChildren::FrontEnd::GetChildAtIndex(uint32_t idx) {
-  if (!m_wrapper_sp || !m_interpreter)
+  if (!m_interface_sp)
     return lldb::ValueObjectSP();
 
-  return m_interpreter->GetChildAtIndex(m_wrapper_sp, idx);
+  return m_interface_sp->GetChildAtIndex(idx);
 }
 
 bool ScriptedSyntheticChildren::FrontEnd::IsValid() {
-  return (m_wrapper_sp && m_wrapper_sp->IsValid() && m_interpreter);
+  return m_interface_sp != nullptr;
 }
 
 llvm::Expected<uint32_t>
 ScriptedSyntheticChildren::FrontEnd::CalculateNumChildren() {
-  if (!m_wrapper_sp || m_interpreter == nullptr)
+  if (!m_interface_sp)
     return 0;
-  return m_interpreter->CalculateNumChildren(m_wrapper_sp, UINT32_MAX);
+  return m_interface_sp->CalculateNumChildren(UINT32_MAX);
 }
 
 llvm::Expected<uint32_t>
 ScriptedSyntheticChildren::FrontEnd::CalculateNumChildren(uint32_t max) {
-  if (!m_wrapper_sp || m_interpreter == nullptr)
+  if (!m_interface_sp)
     return 0;
-  return m_interpreter->CalculateNumChildren(m_wrapper_sp, max);
+  return m_interface_sp->CalculateNumChildren(max);
 }
 
 lldb::ChildCacheState ScriptedSyntheticChildren::FrontEnd::Update() {
-  if (!m_wrapper_sp || m_interpreter == nullptr)
+  if (!m_interface_sp)
     return lldb::ChildCacheState::eRefetch;
 
-  return m_interpreter->UpdateSynthProviderInstance(m_wrapper_sp)
-             ? lldb::ChildCacheState::eReuse
-             : lldb::ChildCacheState::eRefetch;
+  return m_interface_sp->Update();
 }
 
 bool ScriptedSyntheticChildren::FrontEnd::MightHaveChildren() {
-  if (!m_wrapper_sp || m_interpreter == nullptr)
+  if (!m_interface_sp)
     return false;
 
-  return m_interpreter->MightHaveChildrenSynthProviderInstance(m_wrapper_sp);
+  return m_interface_sp->MightHaveChildren();
 }
 
 llvm::Expected<size_t>
 ScriptedSyntheticChildren::FrontEnd::GetIndexOfChildWithName(ConstString name) 
{
-  if (!m_wrapper_sp || m_interpreter == nullptr)
+  if (!m_interface_sp)
     return llvm::createStringErrorV("type has no child named '{0}'", name);
-  return m_interpreter->GetIndexOfChildWithName(m_wrapper_sp,
-                                                name.GetCString());
+  return m_interface_sp->GetIndexOfChildWithName(name);
 }
 
 lldb::ValueObjectSP ScriptedSyntheticChildren::FrontEnd::GetSyntheticValue() {
-  if (!m_wrapper_sp || m_interpreter == nullptr)
+  if (!m_interface_sp)
     return nullptr;
 
-  return m_interpreter->GetSyntheticValue(m_wrapper_sp);
+  return m_interface_sp->GetSyntheticValue();
 }
 
 ConstString ScriptedSyntheticChildren::FrontEnd::GetSyntheticTypeName() {
-  if (!m_wrapper_sp || m_interpreter == nullptr)
+  if (!m_interface_sp)
     return ConstString();
 
-  return m_interpreter->GetSyntheticTypeName(m_wrapper_sp);
+  return m_interface_sp->GetSyntheticTypeName();
 }
 
 void *ScriptedSyntheticChildren::FrontEnd::GetImplementation() {
-  if (!m_wrapper_sp || m_interpreter == nullptr)
+  if (!m_interface_sp)
     return nullptr;
 
-  if (m_wrapper_sp->GetType() != eStructuredDataTypeGeneric)
+  StructuredData::GenericSP obj = m_interface_sp->GetScriptObjectInstance();
+  if (!obj)
     return nullptr;
 
-  return m_wrapper_sp->GetAsGeneric()->GetValue();
+  return obj->GetValue();
 }
 
 std::string ScriptedSyntheticChildren::GetDescription() {
diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp 
b/lldb/source/Interpreter/ScriptInterpreter.cpp
index 4f6095d097d10..873bcb2410055 100644
--- a/lldb/source/Interpreter/ScriptInterpreter.cpp
+++ b/lldb/source/Interpreter/ScriptInterpreter.cpp
@@ -221,6 +221,8 @@ 
ScriptInterpreter::ExtensionToString(lldb::ScriptedExtension extension) {
     return "ScriptedFrame";
   case eScriptedExtensionScriptedStackFrameRecognizer:
     return "ScriptedStackFrameRecognizer";
+  case eScriptedExtensionScriptedSyntheticChildren:
+    return "ScriptedSyntheticChildren";
   }
   llvm_unreachable("unhandled ScriptedExtension");
 }
@@ -241,6 +243,8 @@ ScriptInterpreter::StringToExtension(llvm::StringRef 
string) {
       .CaseLower("ScriptedFrame", eScriptedExtensionScriptedFrame)
       .CaseLower("ScriptedStackFrameRecognizer",
                  eScriptedExtensionScriptedStackFrameRecognizer)
+      .CaseLower("ScriptedSyntheticChildren",
+                 eScriptedExtensionScriptedSyntheticChildren)
       .Default(eScriptedExtensionInvalid);
 }
 
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt 
b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
index 201574da72a19..314e092d5fb47 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
@@ -33,6 +33,7 @@ set(python_plugin_sources
   Interfaces/ScriptedHookPythonInterface.cpp
   Interfaces/ScriptedBreakpointPythonInterface.cpp
   Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
+  Interfaces/ScriptedSyntheticChildrenPythonInterface.cpp
   Interfaces/ScriptedThreadPlanPythonInterface.cpp
   Interfaces/ScriptedThreadPythonInterface.cpp
 )
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
index 8914f6b239023..cff975d99c0fb 100644
--- 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
@@ -30,6 +30,7 @@ void ScriptInterpreterPythonInterfaces::Initialize() {
   ScriptedThreadPythonInterface::Initialize();
   ScriptedFramePythonInterface::Initialize();
   ScriptedStackFrameRecognizerPythonInterface::Initialize();
+  ScriptedSyntheticChildrenPythonInterface::Initialize();
 }
 
 void ScriptInterpreterPythonInterfaces::Terminate() {
@@ -43,4 +44,5 @@ void ScriptInterpreterPythonInterfaces::Terminate() {
   ScriptedThreadPythonInterface::Terminate();
   ScriptedFramePythonInterface::Terminate();
   ScriptedStackFrameRecognizerPythonInterface::Terminate();
+  ScriptedSyntheticChildrenPythonInterface::Terminate();
 }
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
index 03d747e63a592..5267cffbadbc9 100644
--- 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
@@ -20,6 +20,7 @@
 #include "ScriptedPlatformPythonInterface.h"
 #include "ScriptedProcessPythonInterface.h"
 #include "ScriptedStackFrameRecognizerPythonInterface.h"
+#include "ScriptedSyntheticChildrenPythonInterface.h"
 #include "ScriptedThreadPlanPythonInterface.h"
 #include "ScriptedThreadPythonInterface.h"
 
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index aaa0b6a0f7a59..0e2b874b95e91 100644
--- 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -110,7 +110,7 @@ class ScriptedPythonInterface : virtual public 
ScriptedInterface {
 
   llvm::Expected<std::map<llvm::StringLiteral, AbstractMethodCheckerPayload>>
   CheckAbstractMethodImplementation(
-      const python::PythonDictionary &class_dict) const {
+      const python::PythonObject &obj_class) const {
 
     using namespace python;
 
@@ -124,18 +124,17 @@ class ScriptedPythonInterface : virtual public 
ScriptedInterface {
     for (const AbstractMethodRequirement &requirement :
          GetAbstractMethodRequirements()) {
       llvm::StringLiteral method_name = requirement.name;
-      if (!class_dict.HasKey(method_name))
+      // Look up via attribute access so inherited methods are found; the
+      // class's own __dict__ omits anything defined on a base class.
+      if (!obj_class.HasAttribute(method_name))
         SET_CASE_AND_CONTINUE(method_name,
                               AbstractMethodCheckerCases::eNotImplemented)
-      llvm::Expected<PythonObject> callable_or_err =
-          class_dict.GetItem(method_name);
-      if (!callable_or_err) {
-        llvm::consumeError(callable_or_err.takeError());
+      PythonObject attr = obj_class.GetAttributeValue(method_name);
+      if (!attr.IsAllocated())
         SET_CASE_AND_CONTINUE(method_name,
                               AbstractMethodCheckerCases::eNotAllocated)
-      }
 
-      PythonCallable callable = callable_or_err->AsType<PythonCallable>();
+      PythonCallable callable = attr.AsType<PythonCallable>();
       if (!callable)
         SET_CASE_AND_CONTINUE(method_name,
                               AbstractMethodCheckerCases::eNotCallable)
@@ -295,26 +294,7 @@ class ScriptedPythonInterface : virtual public 
ScriptedInterface {
     PythonString obj_class_name =
         obj_class.GetAttributeValue("__name__").AsType<PythonString>();
 
-    PythonObject object_class_mapping_proxy =
-        obj_class.GetAttributeValue("__dict__");
-    if (!obj_class.HasAttribute("__dict__"))
-      return create_error(
-          "Resulting object class doesn't have '__dict__' member.");
-
-    PythonCallable dict_converter = PythonModule::BuiltinsModule()
-                                        .ResolveName("dict")
-                                        .AsType<PythonCallable>();
-    if (!dict_converter.IsAllocated())
-      return create_error(
-          "Python 'builtins' module doesn't have 'dict' class.");
-
-    PythonDictionary object_class_dict =
-        dict_converter(object_class_mapping_proxy).AsType<PythonDictionary>();
-    if (!object_class_dict.IsAllocated())
-      return create_error("Coudn't create dictionary from resulting object "
-                          "class mapping proxy object.");
-
-    auto checker_or_err = CheckAbstractMethodImplementation(object_class_dict);
+    auto checker_or_err = CheckAbstractMethodImplementation(obj_class);
     if (!checker_or_err)
       return checker_or_err.takeError();
 
@@ -531,15 +511,36 @@ class ScriptedPythonInterface : virtual public 
ScriptedInterface {
     std::tuple<Args...> original_args = std::forward_as_tuple(args...);
     auto transformed_args = TransformArgs(original_args);
 
+    // Trim trailing args if the Python method accepts fewer positional
+    // parameters than we're passing (e.g. `num_children(self)` vs.
+    // `num_children(self, max_count)`).
+    size_t call_arity = sizeof...(Args);
+    if (PythonObject py_method = implementor.GetAttributeValue(method_name);
+        py_method.IsAllocated()) {
+      PythonCallable callable = py_method.AsType<PythonCallable>();
+      if (callable.IsAllocated()) {
+        if (llvm::Expected<PythonCallable::ArgInfo> arg_info =
+                callable.GetArgInfo()) {
+          if (arg_info->max_positional_args !=
+                  PythonCallable::ArgInfo::UNBOUNDED &&
+              arg_info->max_positional_args < call_arity)
+            call_arity = arg_info->max_positional_args;
+        } else {
+          llvm::consumeError(arg_info.takeError());
+        }
+      }
+    }
+
     llvm::Expected<PythonObject> expected_return_object =
         llvm::createStringError("not initialized");
-    std::apply(
-        [&implementor, &method_name, &expected_return_object](auto &&...args) {
-          llvm::consumeError(expected_return_object.takeError());
-          expected_return_object =
-              implementor.CallMethod(method_name.data(), args...);
-        },
-        transformed_args);
+    CallWithArity(call_arity, transformed_args,
+                  std::make_index_sequence<sizeof...(Args) + 1>{},
+                  [&implementor, &method_name,
+                   &expected_return_object](auto &&...call_args) {
+                    llvm::consumeError(expected_return_object.takeError());
+                    expected_return_object = implementor.CallMethod(
+                        method_name.data(), call_args...);
+                  });
 
     if (llvm::Error e = expected_return_object.takeError()) {
       error = Status::FromError(std::move(e));
@@ -703,6 +704,31 @@ class ScriptedPythonInterface : virtual public 
ScriptedInterface {
     return TransformTuple(args, std::make_index_sequence<sizeof...(Args)>());
   }
 
+  // Apply `fn` with the first `N` elements of `t`, for compile-time `N`.
+  template <std::size_t N, typename Tuple, typename Fn, std::size_t... I>
+  static void ApplyPrefixImpl(Tuple &&t, Fn &&fn, std::index_sequence<I...>) {
+    std::forward<Fn>(fn)(std::get<I>(std::forward<Tuple>(t))...);
+  }
+
+  template <std::size_t N, typename Tuple, typename Fn>
+  static void ApplyPrefix(Tuple &&t, Fn &&fn) {
+    ApplyPrefixImpl<N>(std::forward<Tuple>(t), std::forward<Fn>(fn),
+                       std::make_index_sequence<N>{});
+  }
+
+  // Call `fn` with a runtime-selected prefix of `t`: exactly `call_arity`
+  // leading elements. `Is...` enumerates every compile-time count in
+  // `[0, sizeof...(Args)]`; the runtime check picks the matching one.
+  template <typename Tuple, std::size_t... Is, typename Fn>
+  static void CallWithArity(size_t call_arity, Tuple &&t,
+                            std::index_sequence<Is...>, Fn &&fn) {
+    (void)std::initializer_list<int>{
+        (Is == call_arity
+             ? (ApplyPrefix<Is>(std::forward<Tuple>(t), std::forward<Fn>(fn)),
+                0)
+             : 0)...};
+  }
+
   template <typename T, typename U>
   void TransformBack(T &original_arg, U transformed_arg, Status &error) {
     ReverseTransform(original_arg, transformed_arg, error);
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSyntheticChildrenPythonInterface.cpp
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSyntheticChildrenPythonInterface.cpp
new file mode 100644
index 0000000000000..46f7afd72748f
--- /dev/null
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSyntheticChildrenPythonInterface.cpp
@@ -0,0 +1,233 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "../lldb-python.h"
+
+#include "lldb/Core/PluginManager.h"
+#include "lldb/Utility/ScriptedMetadata.h"
+#include "lldb/ValueObject/ValueObject.h"
+#include "lldb/lldb-enumerations.h"
+
+#include "../SWIGPythonBridge.h"
+#include "../ScriptInterpreterPythonImpl.h"
+#include "ScriptedSyntheticChildrenPythonInterface.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::python;
+using Locker = ScriptInterpreterPythonImpl::Locker;
+
+ScriptedSyntheticChildrenPythonInterface::
+    ScriptedSyntheticChildrenPythonInterface(
+        ScriptInterpreterPythonImpl &interpreter)
+    : ScriptedSyntheticChildrenInterface(),
+      ScriptedPythonInterface(interpreter) {}
+
+llvm::Expected<StructuredData::GenericSP>
+ScriptedSyntheticChildrenPythonInterface::CreatePluginObject(
+    llvm::StringRef class_name, ValueObject &backend) {
+  if (class_name.empty())
+    return llvm::createStringError("empty class name");
+
+  ValueObjectSP valobj_sp = backend.GetSP();
+  if (!valobj_sp)
+    return llvm::createStringError("invalid backing value");
+
+  Locker py_lock(&m_interpreter,
+                 Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN,
+                 Locker::FreeLock | Locker::TearDownSession);
+
+  // Hand the provider's __init__ a fresh SBValue view of the backing value
+  // with synthetic children disabled, so introspecting it doesn't recursively
+  // re-enter this provider. `SetPreferSyntheticValue` lives on the SBValue's
+  // ValueImpl, so this override doesn't affect the caller's original view.
+  PythonObject val_arg =
+      SWIGBridge::ToSWIGWrapper(valobj_sp, /*use_synthetic=*/false);
+
+  ScriptedMetadata scripted_metadata(class_name,
+                                     StructuredData::DictionarySP());
+  return ScriptedPythonInterface::CreatePluginObject(
+      scripted_metadata, /*script_obj=*/nullptr, std::move(val_arg));
+}
+
+llvm::Expected<uint32_t>
+ScriptedSyntheticChildrenPythonInterface::CalculateNumChildren(uint32_t max) {
+  Status error;
+  StructuredData::ObjectSP obj = Dispatch("num_children", error, max);
+  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
+                                                    error))
+    return 0;
+  // Cap at max in case the provider ignores the argument (e.g. defines
+  // `num_children(self)`) and returns an unbounded count.
+  return std::min<uint32_t>(obj->GetUnsignedIntegerValue(), max);
+}
+
+lldb::ValueObjectSP
+ScriptedSyntheticChildrenPythonInterface::GetChildAtIndex(uint32_t idx) {
+  if (!m_object_instance_sp)
+    return lldb::ValueObjectSP();
+
+  Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
+                 Locker::FreeLock);
+
+  PythonObject implementor(PyRefType::Borrowed,
+                           (PyObject *)m_object_instance_sp->GetValue());
+  if (!implementor.IsAllocated())
+    return lldb::ValueObjectSP();
+
+  PyObject *child_ptr =
+      SWIGBridge::LLDBSwigPython_GetChildAtIndex(implementor.get(), idx);
+  if (child_ptr == nullptr || child_ptr == Py_None) {
+    Py_XDECREF(child_ptr);
+    return lldb::ValueObjectSP();
+  }
+
+  lldb::SBValue *sb_value_ptr =
+      (lldb::SBValue *)LLDBSWIGPython_CastPyObjectToSBValue(child_ptr);
+  if (sb_value_ptr == nullptr) {
+    Py_XDECREF(child_ptr);
+    return lldb::ValueObjectSP();
+  }
+
+  return SWIGBridge::LLDBSWIGPython_GetValueObjectSPFromSBValue(sb_value_ptr);
+}
+
+llvm::Expected<uint32_t>
+ScriptedSyntheticChildrenPythonInterface::GetIndexOfChildWithName(
+    ConstString name) {
+  if (!m_object_instance_sp)
+    return llvm::createStringErrorV("type has no child named '{0}'", name);
+
+  Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
+                 Locker::FreeLock);
+
+  PythonObject implementor(PyRefType::Borrowed,
+                           (PyObject *)m_object_instance_sp->GetValue());
+  if (!implementor.IsAllocated())
+    return llvm::createStringErrorV("type has no child named '{0}'", name);
+
+  uint32_t ret_val = SWIGBridge::LLDBSwigPython_GetIndexOfChildWithName(
+      implementor.get(), name.GetCString());
+
+  if (ret_val == UINT32_MAX)
+    return llvm::createStringErrorV("type has no child named '{0}'", name);
+  return ret_val;
+}
+
+lldb::ChildCacheState ScriptedSyntheticChildrenPythonInterface::Update() {
+  if (!m_object_instance_sp)
+    return lldb::eRefetch;
+
+  Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
+                 Locker::FreeLock);
+
+  PythonObject implementor(PyRefType::Borrowed,
+                           (PyObject *)m_object_instance_sp->GetValue());
+  if (!implementor.IsAllocated())
+    return lldb::eRefetch;
+
+  // update() is optional; a missing method means "always refetch", matching
+  // LLDBSwigPython_UpdateSynthProviderInstance's behavior.
+  return SWIGBridge::LLDBSwigPython_UpdateSynthProviderInstance(
+             implementor.get())
+             ? lldb::eReuse
+             : lldb::eRefetch;
+}
+
+bool ScriptedSyntheticChildrenPythonInterface::MightHaveChildren() {
+  if (!m_object_instance_sp)
+    return true;
+
+  Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
+                 Locker::FreeLock);
+
+  PythonObject implementor(PyRefType::Borrowed,
+                           (PyObject *)m_object_instance_sp->GetValue());
+  if (!implementor.IsAllocated())
+    return true;
+
+  // has_children() is optional and defaults to True when missing, matching
+  // LLDBSwigPython_MightHaveChildrenSynthProviderInstance's behavior.
+  return SWIGBridge::LLDBSwigPython_MightHaveChildrenSynthProviderInstance(
+      implementor.get());
+}
+
+lldb::ValueObjectSP
+ScriptedSyntheticChildrenPythonInterface::GetSyntheticValue() {
+  if (!m_object_instance_sp)
+    return nullptr;
+
+  Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
+                 Locker::FreeLock);
+
+  PythonObject implementor(PyRefType::Borrowed,
+                           (PyObject *)m_object_instance_sp->GetValue());
+  if (!implementor.IsAllocated())
+    return nullptr;
+
+  PyObject *child_ptr =
+      SWIGBridge::LLDBSwigPython_GetValueSynthProviderInstance(
+          implementor.get());
+  if (child_ptr == nullptr || child_ptr == Py_None) {
+    Py_XDECREF(child_ptr);
+    return nullptr;
+  }
+
+  lldb::SBValue *sb_value_ptr =
+      (lldb::SBValue *)LLDBSWIGPython_CastPyObjectToSBValue(child_ptr);
+  if (sb_value_ptr == nullptr) {
+    Py_XDECREF(child_ptr);
+    return nullptr;
+  }
+
+  return SWIGBridge::LLDBSWIGPython_GetValueObjectSPFromSBValue(sb_value_ptr);
+}
+
+ConstString ScriptedSyntheticChildrenPythonInterface::GetSyntheticTypeName() {
+  if (!m_object_instance_sp)
+    return {};
+
+  Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
+                 Locker::FreeLock);
+
+  PythonObject implementor(PyRefType::Borrowed,
+                           (PyObject *)m_object_instance_sp->GetValue());
+  if (!implementor.IsAllocated())
+    return {};
+
+  llvm::Expected<PythonObject> expected_py_return =
+      implementor.CallMethod("get_type_name");
+
+  if (!expected_py_return) {
+    llvm::consumeError(expected_py_return.takeError());
+    return {};
+  }
+
+  PythonObject py_return = std::move(expected_py_return.get());
+  if (!py_return.IsAllocated() || !PythonString::Check(py_return.get()))
+    return {};
+
+  PythonString type_name(PyRefType::Borrowed, py_return.get());
+  return ConstString(type_name.GetString());
+}
+
+void ScriptedSyntheticChildrenPythonInterface::Initialize() {
+  const std::vector<llvm::StringRef> ci_usages = {
+      "type synthetic add -l <ClassName> <TypeName>"};
+  const std::vector<llvm::StringRef> api_usages = {
+      "SBTypeSynthetic.CreateWithClassName"};
+  PluginManager::RegisterPlugin(
+      GetPluginNameStatic(),
+      "Provide synthetic children for a type, used by 'type synthetic add -l'",
+      CreateInstance, eScriptedExtensionScriptedSyntheticChildren,
+      eScriptLanguagePython, {ci_usages, api_usages});
+}
+
+void ScriptedSyntheticChildrenPythonInterface::Terminate() {
+  PluginManager::UnregisterPlugin(CreateInstance);
+}
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSyntheticChildrenPythonInterface.h
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSyntheticChildrenPythonInterface.h
new file mode 100644
index 0000000000000..0cb2ffc13415f
--- /dev/null
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSyntheticChildrenPythonInterface.h
@@ -0,0 +1,63 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef 
LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSYNTHETICCHILDRENPYTHONINTERFACE_H
+#define 
LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSYNTHETICCHILDRENPYTHONINTERFACE_H
+
+#include "lldb/Interpreter/Interfaces/ScriptedSyntheticChildrenInterface.h"
+
+#include "ScriptedPythonInterface.h"
+namespace lldb_private {
+
+class ScriptedSyntheticChildrenPythonInterface
+    : public ScriptedSyntheticChildrenInterface,
+      public ScriptedPythonInterface,
+      public PluginInterface {
+public:
+  ScriptedSyntheticChildrenPythonInterface(
+      ScriptInterpreterPythonImpl &interpreter);
+
+  llvm::Expected<StructuredData::GenericSP>
+  CreatePluginObject(llvm::StringRef class_name, ValueObject &backend) 
override;
+
+  llvm::SmallVector<AbstractMethodRequirement>
+  GetAbstractMethodRequirements() const override {
+    // Providers that never expose children (num_children == 0 / has_children 
==
+    // False) legitimately don't implement get_child_at_index; LLDB simply
+    // won't call it. Treating any single method as required here is stricter
+    // than the pre-migration behavior and would reject those providers.
+    return {};
+  }
+
+  llvm::Expected<uint32_t> CalculateNumChildren(uint32_t max) override;
+
+  lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override;
+
+  llvm::Expected<uint32_t> GetIndexOfChildWithName(ConstString name) override;
+
+  lldb::ChildCacheState Update() override;
+
+  bool MightHaveChildren() override;
+
+  lldb::ValueObjectSP GetSyntheticValue() override;
+
+  ConstString GetSyntheticTypeName() override;
+
+  static void Initialize();
+
+  static void Terminate();
+
+  static llvm::StringRef GetPluginNameStatic() {
+    return "ScriptedSyntheticChildrenPythonInterface";
+  }
+
+  llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
+};
+} // namespace lldb_private
+
+#endif // 
LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSYNTHETICCHILDRENPYTHONINTERFACE_H
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h 
b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
index 07e0da1dcf70d..97e06d06ed52c 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -79,6 +79,8 @@ class SWIGBridge {
   static PythonObject
   ToSWIGWrapper(std::unique_ptr<lldb::SBCommandReturnObject> result_up);
   static PythonObject ToSWIGWrapper(lldb::ValueObjectSP value_sp);
+  static PythonObject ToSWIGWrapper(lldb::ValueObjectSP value_sp,
+                                    bool use_synthetic);
   static PythonObject ToSWIGWrapper(lldb::TargetSP target_sp);
   static PythonObject ToSWIGWrapper(lldb::ProcessSP process_sp);
   static PythonObject ToSWIGWrapper(lldb::ModuleSP module_sp);
@@ -138,19 +140,11 @@ class SWIGBridge {
       const lldb::ValueObjectSP &valobj_sp, void **pyfunct_wrapper,
       const lldb::TypeSummaryOptionsSP &options_sp, std::string &retval);
 
-  static python::PythonObject
-  LLDBSwigPythonCreateSyntheticProvider(const char *python_class_name,
-                                        const char *session_dictionary_name,
-                                        const lldb::ValueObjectSP &valobj_sp);
-
   static python::PythonObject
   LLDBSwigPythonCreateCommandObject(const char *python_class_name,
                                     const char *session_dictionary_name,
                                     lldb::DebuggerSP debugger_sp);
 
-  static size_t LLDBSwigPython_CalculateNumChildren(PyObject *implementor,
-                                                    uint32_t max);
-
   static PyObject *LLDBSwigPython_GetChildAtIndex(PyObject *implementor,
                                                   uint32_t idx);
 
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp 
b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index 7b1bd9d8411c5..6df6d5a071bf1 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -289,6 +289,8 @@ llvm::Expected<std::string> 
ScriptInterpreterPython::ExtensionToImportPath(
     return "lldb.plugins.scripted_process";
   case eScriptedExtensionScriptedStackFrameRecognizer:
     return "lldb.plugins.scripted_stackframe_recognizer";
+  case eScriptedExtensionScriptedSyntheticChildren:
+    return "lldb.plugins.scripted_synthetic_children";
   case eScriptedExtensionInvalid:
     return llvm::createStringError("invalid extension name");
   }
@@ -2003,6 +2005,11 @@ 
ScriptInterpreterPythonImpl::CreateScriptedStackFrameRecognizerInterface() {
   return std::make_shared<ScriptedStackFrameRecognizerPythonInterface>(*this);
 }
 
+ScriptedSyntheticChildrenInterfaceSP
+ScriptInterpreterPythonImpl::CreateScriptedSyntheticChildrenInterface() {
+  return std::make_shared<ScriptedSyntheticChildrenPythonInterface>(*this);
+}
+
 ScriptedThreadInterfaceSP
 ScriptInterpreterPythonImpl::CreateScriptedThreadInterface() {
   return std::make_shared<ScriptedThreadPythonInterface>(*this);
@@ -2086,37 +2093,6 @@ StructuredData::DictionarySP 
ScriptInterpreterPythonImpl::GetDynamicSettings(
   return py_dict.CreateStructuredDictionary();
 }
 
-StructuredData::ObjectSP
-ScriptInterpreterPythonImpl::CreateSyntheticScriptedProvider(
-    const char *class_name, lldb::ValueObjectSP valobj) {
-  if (class_name == nullptr || class_name[0] == '\0')
-    return StructuredData::ObjectSP();
-
-  if (!valobj.get())
-    return StructuredData::ObjectSP();
-
-  ExecutionContext exe_ctx(valobj->GetExecutionContextRef());
-  Target *target = exe_ctx.GetTargetPtr();
-
-  if (!target)
-    return StructuredData::ObjectSP();
-
-  Debugger &debugger = target->GetDebugger();
-  ScriptInterpreterPythonImpl *python_interpreter =
-      GetPythonInterpreter(debugger);
-
-  if (!python_interpreter)
-    return StructuredData::ObjectSP();
-
-  Locker py_lock(this,
-                 Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN);
-  PythonObject ret_val = SWIGBridge::LLDBSwigPythonCreateSyntheticProvider(
-      class_name, python_interpreter->m_dictionary_name.c_str(), valobj);
-
-  return StructuredData::ObjectSP(
-      new StructuredPythonObject(std::move(ret_val)));
-}
-
 StructuredData::GenericSP
 ScriptInterpreterPythonImpl::CreateScriptCommandObject(const char *class_name) 
{
   DebuggerSP debugger_sp(m_debugger.shared_from_this());
@@ -2375,208 +2351,6 @@ bool 
ScriptInterpreterPythonImpl::WatchpointCallbackFunction(
   return true;
 }
 
-size_t ScriptInterpreterPythonImpl::CalculateNumChildren(
-    const StructuredData::ObjectSP &implementor_sp, uint32_t max) {
-  if (!implementor_sp)
-    return 0;
-  StructuredData::Generic *generic = implementor_sp->GetAsGeneric();
-  if (!generic)
-    return 0;
-  auto *implementor = static_cast<PyObject *>(generic->GetValue());
-  if (!implementor)
-    return 0;
-
-  size_t ret_val = 0;
-
-  {
-    Locker py_lock(this,
-                   Locker::AcquireLock | Locker::InitSession | 
Locker::NoSTDIN);
-    ret_val = SWIGBridge::LLDBSwigPython_CalculateNumChildren(implementor, 
max);
-  }
-
-  return ret_val;
-}
-
-lldb::ValueObjectSP ScriptInterpreterPythonImpl::GetChildAtIndex(
-    const StructuredData::ObjectSP &implementor_sp, uint32_t idx) {
-  if (!implementor_sp)
-    return lldb::ValueObjectSP();
-
-  StructuredData::Generic *generic = implementor_sp->GetAsGeneric();
-  if (!generic)
-    return lldb::ValueObjectSP();
-  auto *implementor = static_cast<PyObject *>(generic->GetValue());
-  if (!implementor)
-    return lldb::ValueObjectSP();
-
-  lldb::ValueObjectSP ret_val;
-  {
-    Locker py_lock(this,
-                   Locker::AcquireLock | Locker::InitSession | 
Locker::NoSTDIN);
-    PyObject *child_ptr =
-        SWIGBridge::LLDBSwigPython_GetChildAtIndex(implementor, idx);
-    if (child_ptr != nullptr && child_ptr != Py_None) {
-      lldb::SBValue *sb_value_ptr =
-          (lldb::SBValue *)LLDBSWIGPython_CastPyObjectToSBValue(child_ptr);
-      if (sb_value_ptr == nullptr)
-        Py_XDECREF(child_ptr);
-      else
-        ret_val = SWIGBridge::LLDBSWIGPython_GetValueObjectSPFromSBValue(
-            sb_value_ptr);
-    } else {
-      Py_XDECREF(child_ptr);
-    }
-  }
-
-  return ret_val;
-}
-
-llvm::Expected<uint32_t> ScriptInterpreterPythonImpl::GetIndexOfChildWithName(
-    const StructuredData::ObjectSP &implementor_sp, const char *child_name) {
-  if (!implementor_sp)
-    return llvm::createStringErrorV("type has no child named '{0}'",
-                                    child_name);
-
-  StructuredData::Generic *generic = implementor_sp->GetAsGeneric();
-  if (!generic)
-    return llvm::createStringErrorV("type has no child named '{0}'",
-                                    child_name);
-  auto *implementor = static_cast<PyObject *>(generic->GetValue());
-  if (!implementor)
-    return llvm::createStringErrorV("type has no child named '{0}'",
-                                    child_name);
-
-  uint32_t ret_val = UINT32_MAX;
-
-  {
-    Locker py_lock(this,
-                   Locker::AcquireLock | Locker::InitSession | 
Locker::NoSTDIN);
-    ret_val = SWIGBridge::LLDBSwigPython_GetIndexOfChildWithName(implementor,
-                                                                 child_name);
-  }
-
-  if (ret_val == UINT32_MAX)
-    return llvm::createStringErrorV("type has no child named '{0}'",
-                                    child_name);
-  return ret_val;
-}
-
-bool ScriptInterpreterPythonImpl::UpdateSynthProviderInstance(
-    const StructuredData::ObjectSP &implementor_sp) {
-  bool ret_val = false;
-
-  if (!implementor_sp)
-    return ret_val;
-
-  StructuredData::Generic *generic = implementor_sp->GetAsGeneric();
-  if (!generic)
-    return ret_val;
-  auto *implementor = static_cast<PyObject *>(generic->GetValue());
-  if (!implementor)
-    return ret_val;
-
-  {
-    Locker py_lock(this,
-                   Locker::AcquireLock | Locker::InitSession | 
Locker::NoSTDIN);
-    ret_val =
-        SWIGBridge::LLDBSwigPython_UpdateSynthProviderInstance(implementor);
-  }
-
-  return ret_val;
-}
-
-bool ScriptInterpreterPythonImpl::MightHaveChildrenSynthProviderInstance(
-    const StructuredData::ObjectSP &implementor_sp) {
-  bool ret_val = false;
-
-  if (!implementor_sp)
-    return ret_val;
-
-  StructuredData::Generic *generic = implementor_sp->GetAsGeneric();
-  if (!generic)
-    return ret_val;
-  auto *implementor = static_cast<PyObject *>(generic->GetValue());
-  if (!implementor)
-    return ret_val;
-
-  {
-    Locker py_lock(this,
-                   Locker::AcquireLock | Locker::InitSession | 
Locker::NoSTDIN);
-    ret_val = 
SWIGBridge::LLDBSwigPython_MightHaveChildrenSynthProviderInstance(
-        implementor);
-  }
-
-  return ret_val;
-}
-
-lldb::ValueObjectSP ScriptInterpreterPythonImpl::GetSyntheticValue(
-    const StructuredData::ObjectSP &implementor_sp) {
-  lldb::ValueObjectSP ret_val(nullptr);
-
-  if (!implementor_sp)
-    return ret_val;
-
-  StructuredData::Generic *generic = implementor_sp->GetAsGeneric();
-  if (!generic)
-    return ret_val;
-  auto *implementor = static_cast<PyObject *>(generic->GetValue());
-  if (!implementor)
-    return ret_val;
-
-  {
-    Locker py_lock(this,
-                   Locker::AcquireLock | Locker::InitSession | 
Locker::NoSTDIN);
-    PyObject *child_ptr =
-        SWIGBridge::LLDBSwigPython_GetValueSynthProviderInstance(implementor);
-    if (child_ptr != nullptr && child_ptr != Py_None) {
-      lldb::SBValue *sb_value_ptr =
-          (lldb::SBValue *)LLDBSWIGPython_CastPyObjectToSBValue(child_ptr);
-      if (sb_value_ptr == nullptr)
-        Py_XDECREF(child_ptr);
-      else
-        ret_val = SWIGBridge::LLDBSWIGPython_GetValueObjectSPFromSBValue(
-            sb_value_ptr);
-    } else {
-      Py_XDECREF(child_ptr);
-    }
-  }
-
-  return ret_val;
-}
-
-ConstString ScriptInterpreterPythonImpl::GetSyntheticTypeName(
-    const StructuredData::ObjectSP &implementor_sp) {
-  Locker py_lock(this,
-                 Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN);
-
-  if (!implementor_sp)
-    return {};
-
-  StructuredData::Generic *generic = implementor_sp->GetAsGeneric();
-  if (!generic)
-    return {};
-
-  PythonObject implementor(PyRefType::Borrowed,
-                           (PyObject *)generic->GetValue());
-  if (!implementor.IsAllocated())
-    return {};
-
-  llvm::Expected<PythonObject> expected_py_return =
-      implementor.CallMethod("get_type_name");
-
-  if (!expected_py_return) {
-    llvm::consumeError(expected_py_return.takeError());
-    return {};
-  }
-
-  PythonObject py_return = std::move(expected_py_return.get());
-  if (!py_return.IsAllocated() || !PythonString::Check(py_return.get()))
-    return {};
-
-  PythonString type_name(PyRefType::Borrowed, py_return.get());
-  return ConstString(type_name.GetString());
-}
-
 bool ScriptInterpreterPythonImpl::RunScriptFormatKeyword(
     const char *impl_function, Process *process, std::string &output,
     Status &error) {
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h 
b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
index d8a817198253f..d32e9dde4c6f1 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
@@ -66,10 +66,6 @@ class ScriptInterpreterPythonImpl : public 
ScriptInterpreterPython {
   bool GenerateScriptAliasFunction(StringList &input,
                                    std::string &output) override;
 
-  StructuredData::ObjectSP
-  CreateSyntheticScriptedProvider(const char *class_name,
-                                  lldb::ValueObjectSP valobj) override;
-
   StructuredData::GenericSP
   CreateScriptCommandObject(const char *class_name) override;
 
@@ -86,6 +82,9 @@ class ScriptInterpreterPythonImpl : public 
ScriptInterpreterPython {
   lldb::ScriptedStackFrameRecognizerInterfaceSP
   CreateScriptedStackFrameRecognizerInterface() override;
 
+  lldb::ScriptedSyntheticChildrenInterfaceSP
+  CreateScriptedSyntheticChildrenInterface() override;
+
   lldb::ScriptedThreadInterfaceSP CreateScriptedThreadInterface() override;
 
   lldb::ScriptedFrameInterfaceSP CreateScriptedFrameInterface() override;
@@ -107,29 +106,6 @@ class ScriptInterpreterPythonImpl : public 
ScriptInterpreterPython {
                      const char *setting_name,
                      lldb_private::Status &error) override;
 
-  size_t CalculateNumChildren(const StructuredData::ObjectSP &implementor,
-                              uint32_t max) override;
-
-  lldb::ValueObjectSP
-  GetChildAtIndex(const StructuredData::ObjectSP &implementor,
-                  uint32_t idx) override;
-
-  llvm::Expected<uint32_t>
-  GetIndexOfChildWithName(const StructuredData::ObjectSP &implementor,
-                          const char *child_name) override;
-
-  bool UpdateSynthProviderInstance(
-      const StructuredData::ObjectSP &implementor) override;
-
-  bool MightHaveChildrenSynthProviderInstance(
-      const StructuredData::ObjectSP &implementor) override;
-
-  lldb::ValueObjectSP
-  GetSyntheticValue(const StructuredData::ObjectSP &implementor) override;
-
-  ConstString
-  GetSyntheticTypeName(const StructuredData::ObjectSP &implementor) override;
-
   bool
   RunScriptBasedCommand(const char *impl_function, llvm::StringRef args,
                         ScriptedCommandSynchronicity synchronicity,
diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp 
b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
index c9298191ec3c1..4aa8992b6aed6 100644
--- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
+++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
@@ -66,13 +66,6 @@ bool 
lldb_private::python::SWIGBridge::LLDBSwigPythonCallTypeScript(
   return false;
 }
 
-python::PythonObject
-lldb_private::python::SWIGBridge::LLDBSwigPythonCreateSyntheticProvider(
-    const char *python_class_name, const char *session_dictionary_name,
-    const lldb::ValueObjectSP &valobj_sp) {
-  return python::PythonObject();
-}
-
 python::PythonObject
 lldb_private::python::SWIGBridge::LLDBSwigPythonCreateCommandObject(
     const char *python_class_name, const char *session_dictionary_name,
@@ -80,11 +73,6 @@ 
lldb_private::python::SWIGBridge::LLDBSwigPythonCreateCommandObject(
   return python::PythonObject();
 }
 
-size_t lldb_private::python::SWIGBridge::LLDBSwigPython_CalculateNumChildren(
-    PyObject *implementor, uint32_t max) {
-  return 0;
-}
-
 PyObject *lldb_private::python::SWIGBridge::LLDBSwigPython_GetChildAtIndex(
     PyObject *implementor, uint32_t idx) {
   return nullptr;

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to