Author: Nerixyz Date: 2026-08-07T12:18:02+02:00 New Revision: c783125adce180e3eed4732118b216c66b5f0093
URL: https://github.com/llvm/llvm-project/commit/c783125adce180e3eed4732118b216c66b5f0093 DIFF: https://github.com/llvm/llvm-project/commit/c783125adce180e3eed4732118b216c66b5f0093.diff LOG: [lldb][Python] Enable type annotations for most types (#213463) Swig 4.5.0 released today and adds support for customizable automatic type annotations. We have to configure the `pytyping` typemap for all cases where we change the default Swig behavior (i.e. for all `in`/`out`/`argout` typemaps). This is what I did in this PR. In two cases, we can't properly annotate: - For all `argout` typemaps, Swig includes the return type of the function. So if we'd return a `typing.List[int]`, Swig assumes we're using `Swig_AppendOutput`, so it annotates the return type with `typing.List[typing.Union[ReturnType, typing.List[int]]]`. - Related to that, the annotations for `INPUT`/`OUTPUT`/`INOUT` are wrong, because the `pytyping` typemap doesn't know about them. There, I disabled the annotations. Furthermore, overloads are not shown. The issues are known, and I have fixes for them, but they will only land in Swig 4.6. It's a bit unfortunate that this happens after 23.x, but better late than never. In the future, we could use https://github.com/tox-dev/sphinx-autodoc-typehints to show the type hints in the documentation. One downside is that without support for overloads, some methods only show `*args`. You can see the current `__init__.py` in https://gist.github.com/Nerixyz/2bf230147f933e3943a87457bf036bb0. Towards #79043. Added: Modified: lldb/bindings/python/python-typemaps.swig lldb/bindings/python/python.swig Removed: ################################################################################ diff --git a/lldb/bindings/python/python-typemaps.swig b/lldb/bindings/python/python-typemaps.swig index 072e688c4bde1..d148bc27ad413 100644 --- a/lldb/bindings/python/python-typemaps.swig +++ b/lldb/bindings/python/python-typemaps.swig @@ -53,6 +53,8 @@ AND call SWIG_fail at the same time, because it will result in a double free. free((char *) $1); } +%typemap(pytyping, out="typing.List[str]") char** "typing.Optional[typing.List[str]]" + %typecheck(SWIG_TYPECHECK_POINTER) lldb::ScriptObjectPtr { PythonObject obj(PyRefType::Borrowed, $input); if (!obj.IsValid()) { @@ -122,6 +124,8 @@ AND call SWIG_fail at the same time, because it will result in a double free. Py_INCREF($result); } +%typemap(pytyping) lldb::ScriptObjectPtr "typing.Any" + %typemap(out) lldb::SBScriptObject { $result = nullptr; if (const void* impl = $1.GetPointer()) @@ -134,6 +138,8 @@ AND call SWIG_fail at the same time, because it will result in a double free. } } +%typemap(pytyping) lldb::SBScriptObject "typing.Any" + %typemap(out) char** { int len; int i; @@ -204,6 +210,16 @@ AND call SWIG_fail at the same time, because it will result in a double free. // as char data instead of byte data. %typemap(argout) (void *char_buf, size_t size) = (char *dst, size_t dst_len); +// Uses (char *dst, size_t dst_len) argout typemap. +// Should return `str`, but annotated as `typing.List[typing.Union[int, typing.Optional[str]]]`. +%feature("python:annotations", "0") lldb::SBProcess::GetSTDOUT; +%feature("python:annotations", "0") lldb::SBProcess::GetSTDERR; +%feature("python:annotations", "0") lldb::SBProcess::GetAsyncProfileData; +%feature("python:annotations", "0") lldb::SBStructuredData::GetStringValue; + +// Uses (void *char_buf, size_t size) argout typemap. +// Should return `str`, but annotated as `typing.List[typing.Union[int, typing.Optional[SWIGTYPE_p_void]]]`. +%feature("python:annotations", "0") lldb::SBProcess::ReadCStringFromMemory; // typemap for handling an snprintf-like API like SBThread::GetStopDescription. %typemap(in) (char *dst_or_null, size_t dst_len) { @@ -244,11 +260,20 @@ AND call SWIG_fail at the same time, because it will result in a double free. free($1); } +// Uses (char *dst_or_null, size_t dst_len) argout typemap. +// Should return `str`, but annotated as `typing.List[typing.Union[int, typing.Optional[str]]]`. +%feature("python:annotations", "0") lldb::SBThread::GetStopDescription; + // For lldb::SBFileSpec::GetPath %typemap(in) (char *dst_path, size_t dst_len) = (char *dst_or_null, size_t dst_len); %typemap(argout) (char *dst_path, size_t dst_len) = (char *dst_or_null, size_t dst_len); +// Uses (char *dst_path, size_t dst_len) argout typemap. +// Should return `str`, but annotated as `typing.List[typing.Union[int, typing.Optional[str]]]`. +%feature("python:annotations", "0") lldb::SBFileSpec::GetPath; +%feature("python:annotations", "0") lldb::SBFileSpec::ResolvePath; + // typemap for an outgoing buffer // See also SBEvent::SBEvent(uint32_t event, const char *cstr, uint32_t cstr_len). @@ -272,6 +297,10 @@ AND call SWIG_fail at the same time, because it will result in a double free. SWIG_fail; } } + +%typemap(pytyping) (const char *cstr, uint32_t cstr_len), + (const char *src, size_t src_len) "typing.Union[str, bytes, bytearray]" + // For SBProcess::WriteMemory, SBTarget::GetInstructions and SBDebugger::DispatchInput. %typemap(in) (const void *buf, size_t size), (const void *data, size_t data_len), @@ -294,6 +323,10 @@ AND call SWIG_fail at the same time, because it will result in a double free. } } +%typemap(pytyping) (const void *buf, size_t size), + (const void *data, size_t data_len), + (const void *buf, uint64_t size) "typing.Union[str, bytes, bytearray]" + // typemap for an incoming buffer // See also SBProcess::ReadMemory. %typemap(in) (void *buf, size_t size) { @@ -324,6 +357,12 @@ AND call SWIG_fail at the same time, because it will result in a double free. free($1); } +// Uses (void *buf, size_t size) argout typemap. +// Should return `typing.Optional[bytes]`, but annotated as `typing.List[typing.Union[int, typing.Optional[SWIGTYPE_p_void]]]`. +%feature("python:annotations", "0") lldb::SBProcess::ReadMemory; +%feature("python:annotations", "0") lldb::SBTarget::ReadMemory; +%feature("python:annotations", "0") lldb::SBData::ReadRawData; + %{ namespace { template <class T> @@ -410,6 +449,12 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) { free($1); } +%typemap(pytyping) (uint64_t* array, size_t array_len), + (uint32_t* array, size_t array_len), + (int64_t* array, size_t array_len), + (int32_t* array, size_t array_len) "typing.Optional[typing.List[int]]" +%typemap(pytyping) (double* array, size_t array_len) "typing.Optional[typing.List[float]]" + // these typemaps wrap SBModule::GetVersion() from requiring a memory buffer // to the more Pythonic style where a list is returned and no previous allocation // is necessary - this will break if more than 50 versions are ever returned @@ -445,6 +490,10 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) { free($1); } +// Uses (uint32_t *versions, uint32_t num_versions) argout typemap. +// Should return `typing.List[int]`, but annotated as `typing.List[typing.Union[int, typing.Optional[SWIGTYPE_p_unsigned_int]]]`. +%feature("python:annotations", "0") lldb::SBModule::GetVersion; + // For Log::LogOutputCallback %typemap(in) (lldb::LogOutputCallback log_callback, void *baton) { if (!($input == Py_None || @@ -469,6 +518,8 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) { $1 = $1 || PyCallable_Check(reinterpret_cast<PyObject *>($input)); } +%typemap(pytyping) (lldb::LogOutputCallback log_callback, void *baton) "typing.Optional[typing.Callable[[str], None]]" + // For lldb::SBDebuggerDestroyCallback %typemap(in) (lldb::SBDebuggerDestroyCallback destroy_callback, void *baton) { if (!($input == Py_None || @@ -493,6 +544,8 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) { $1 = $1 || PyCallable_Check(reinterpret_cast<PyObject *>($input)); } +%typemap(pytyping) (lldb::SBDebuggerDestroyCallback destroy_callback, void *baton) "typing.Optional[typing.Callable[[int], None]]" + // For lldb::SBCommandPrintCallback %typemap(in) (lldb::SBCommandPrintCallback callback, void *baton) { if (!($input == Py_None || @@ -512,6 +565,8 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) { $1 = $1 || PyCallable_Check(reinterpret_cast<PyObject *>($input)); } +%typemap(pytyping) (lldb::SBCommandPrintCallback callback, void *baton) "typing.Optional[typing.Callable[[SBCommandReturnObject], int]]" + %typemap(in) (lldb::CommandOverrideCallback callback, void *baton) { if (!($input == Py_None || PyCallable_Check(reinterpret_cast<PyObject *>($input)))) { @@ -529,6 +584,8 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) { $1 = $1 || PyCallable_Check(reinterpret_cast<PyObject *>($input)); } +%typemap(pytyping) (lldb::CommandOverrideCallback callback, void *baton) "typing.Optional[typing.Callable[[typing.List[str]], bool]]" + %typemap(in) lldb::FileSP { PythonFile py_file(PyRefType::Borrowed, $input); if (!py_file) { @@ -604,6 +661,8 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) { } } +%typemap(pytyping) lldb::FileSP "typing.IO" + %typemap(in) (const char* string, int len) { if ($input == Py_None) { $1 = NULL; @@ -628,6 +687,7 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) { } } +%typemap(pytyping) (const char* string, int len) "typing.Optional[typing.List[str]]" // Typemap for SBFile::Write. %typemap(in) (const uint8_t *buf, size_t num_bytes) { @@ -645,6 +705,8 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) { } } +%typemap(pytyping) (const uint8_t *buf, size_t num_bytes) "typing.Union[bytes, bytearray]" + // Typemap for SBFile::Read. %typemap(in) (uint8_t *buf, size_t num_bytes) { if (PythonByteArray::Check($input)) { @@ -657,6 +719,8 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) { } } +%typemap(pytyping) (uint8_t *buf, size_t num_bytes) "bytearray" + %typemap(in) (const char **symbol_name, uint32_t num_names) { using namespace lldb_private; /* Check if is a list */ @@ -684,6 +748,8 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) { } } +%typemap(pytyping) (const char **symbol_name, uint32_t num_names) "typing.Optional[typing.List[str]]" + // For lldb::SBPlatformLocateModuleCallback %typemap(in) (lldb::SBPlatformLocateModuleCallback callback, void *callback_baton) { @@ -734,3 +800,7 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) { $1 = $input == Py_None; $1 = $1 || PyCallable_Check(reinterpret_cast<PyObject *>($input)); } + +%typemap(pytyping) + (lldb::SBPlatformLocateModuleCallback callback, void *callback_baton) + "typing.Optional[typing.Callable[[SBModuleSpec, SBFileSpec, SBFileSpec], SBError]]" diff --git a/lldb/bindings/python/python.swig b/lldb/bindings/python/python.swig index 1c9e7a24e5287..a44f5746e5a54 100644 --- a/lldb/bindings/python/python.swig +++ b/lldb/bindings/python/python.swig @@ -64,6 +64,35 @@ except ImportError: // Parameter types will be used in the autodoc string. %feature("autodoc", "1"); +#if SWIG_VERSION >= 0x040500 +%feature("python:annotations", "typing"); + +// The default pytyping entry maps to `typing.Any`. Use the proxy names here. +// They might generate incorrect annotations (`SWIGTYPE_*`) in more complex scenarios. +%typemap(pytyping) SWIGTYPE "$&pytypename"; +%typemap(pytyping) SWIGTYPE [] "typing.Optional[$pytypename]"; +%typemap(pytyping) SWIGTYPE * "typing.Optional[$pytypename]"; +%typemap(pytyping) SWIGTYPE & "$pytypename"; +%typemap(pytyping) SWIGTYPE && "$pytypename"; +%typemap(pytyping) enum SWIGTYPE "int"; + +// Exclude operator==/!=. They generate `__eq__(self, rhs: T) -> bool`. +// However, `rhs` should be `object`. +%feature("python:annotations", "0") operator==; +%feature("python:annotations", "0") operator!=; + +// OUTPUT/INOUT is not supported by the typing annotations right now. +%feature("python:annotations", "0") lldb::SBDebugger::GetProgressFromEvent; +%feature("python:annotations", "0") lldb::SBDebugger::RunCommandInterpreter; +%feature("python:annotations", "0") lldb::SBFile::Read; +%feature("python:annotations", "0") lldb::SBFile::Write; +#else +// Make sure `typing` is imported in all cases (Swig < 4.5.0). +%pythoncode %{ +import typing +%} +#endif // SWIG_VERSION >= 0x040500 + // Include lldb-python first as it sets Py_LIMITED_API. %begin %{ #include "../source/Plugins/ScriptInterpreter/Python/lldb-python.h" _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
