A generated stub class with no wrapped members had an empty suite, which
is invalid Python syntax. Stubs could also refer to base classes from
%imported modules without importing the corresponding Python proxy
module, leaving those names unresolved.
Track whether each stub class emits a member and use an ellipsis when
its body would otherwise be empty. Collect modules referenced through
%import and emit them before declarations, using the same package and
relative import rules as the generated Python proxy.
Keep annotation tests active with -pyi by parsing annotations from the
generated stub instead of expecting them on runtime objects. Include
each generated .pyi file in multi-module Pyrefly checks.
Add Python 3.14 Linux CI configurations for -pyi -typehints with and
without -builtin.
See #3473.
Assisted-by: Codex (GPT-5.6 Sol)
These are the .pyi stub file equivalents of %pythoncode and %pythonbegin,
for adding Python code to the stub file generated by the -pyi and -pyifile
options. They do nothing unless a stub file is being generated.
%pythonstubcode inserts the code at the point the directive appears,
indenting it into the class body when used inside a class. %pythonstubbegin
inserts the code just after the SWIG banner, before any real code.
The stub file is generated independently of the .py file, so code added with
%pythoncode or %pythonbegin does not appear in it. Classes configured with
%pythonabc use names from collections.abc as generated base classes and
pyabc.i imports this module into the .py file, but a separately generated
stub had no import, leaving its base class unresolved. Add the equivalent
%pythonstubcode block to pyabc.i.
Extend the python_pyi test with a collections.abc base class and blocks
exercising both new directives.
See #3473.
Assisted-by: Claude Code (Opus 5)
With -builtin, alternate constructor aliases such as pair = new_pair
were emitted as pure Python code. Pyrefly could not resolve the internal
new_ entry points imported from the native module, so these assignments
caused type checking to fail.
Register alternate constructor wrappers under their public names in the
low-level module. Do not expose internal new_ names or generate Python
assignments to provide the public names.
This covers explicitly renamed constructors, named constructor template
instantiations and implicit constructors for renamed C structs.
Add a Python 3.14 CI build with -builtin -typehints to exercise the
generated type hints with Pyrefly.
Assisted-by: Codex (GPT-5.6 Sol)
Add -typehints to enable PEP 484 annotations for a whole interface.
Run Pyrefly on generated wrappers in the Python examples and test suite
with SWIG_FEATURES=-typehints.
Keep C/C++ annotation tests in their original mode, and add a Python
3.14 Linux CI build that runs with -typehints.
See #735.
Assisted-by: Codex (GPT-5.6 Sol)
Build PEP 484 annotations from every Python output value, including a
native return that is not void. Use typing.List[typing.Union[...]] for
multiple results.
See #3469.
Assisted-by: Codex (GPT-5.6 Sol)
sprintf has no bounds checking and macOS deprecates it, which showed up as 30
deprecation warnings when building swig with cmake on macOS. Convert all 76
uses in Source to snprintf.
Most are mechanical as the destination is an in scope array. The others:
DOH/fio.c stemp points at either obuffer or a DohMalloc buffer, so
the size is now tracked alongside it
Swig/error.c the destination is malloc'd with a size known at runtime
Swig/typemap.c varname points into the middle of var, so the space left
is computed once where varname is set up
Modules/overload.cxx print_typecheck() formatted with a runtime format
string, so it now uses NewStringf as the rest of the file
already does
Modules/python.cxx one call formatted nothing, so use strcpy
sprintf is then added to the #pragma GCC poison list in doh.h to stop it
coming back. This is only active with DOH_POISON, which the Linux CI already
builds with, and can be used locally with make CPPFLAGS=-DDOH_POISON. doh.h
now includes <cstdio> for C++ so that the "using ::sprintf" it performs
happens before the poisoning rather than after.
Assisted-by: Claude Code (Opus 5)
Building SWIG with -std=c++98 -pedantic warned:
python.cxx:111: warning: comma at end of enumerator list [-Wpedantic]
Removing the trailing comma lets clang-format put the enum on one line, which
also matches the neighbouring autodoc_t enum.
Assisted-by: Claude Code (Opus 5)
A .pyi always takes precedence over its .py companion for type
checking, so once -pyi/-pyifile is active, annotations left in the .py
file are never consulted by any type checker - dead weight. Suppress
them there; the .pyi keeps the full annotations.
- returnTypeAnnotation()/variableAnnotation() now return empty once
pyi_stub is set. The previous always-full versions are renamed to
returnTypeAnnotationForStubFile()/variableAnnotationForStubFile()
and used only for the .pyi output.
- make_pyParmList() gains a for_stub flag to suppress per-parameter
annotations in a def's parameter list the same way.
- emitTypeWrapperClasses()/emitTypeWrapperClass() take a
guard_with_type_checking flag: the opaque SWIGTYPE_* wrapper classes
stay guarded by 'if typing.TYPE_CHECKING:' in the .py file (meaningful
there - they must not exist at runtime), but are emitted unconditionally
in the .pyi (a stub file has no runtime, so the guard is vacuous there).
Documentation: 33.12.1.2 (PEP 484 annotation types) now includes a
worked example of the SWIGTYPE_* opaque type wrapper class fallback
(a single extra function plus its generated .py output), and
33.12.1.3 (Generating .pyi stub files) reuses that same
OptionalInt/Unwrapped example instead of a separate Shape class,
showing the type wrapper class in the .pyi output too - which
visibly lacks the 'if typing.TYPE_CHECKING:' guard the .py version
has. All shown output verified against actual swig output (mypy
clean on the .pyi, ast-parsed the .py).
Assisted-by: Claude Code (Opus 4.8)
-pyi always wrote to the hardcoded <module>.pyi. Add -pyifile <file>
as a mandatory-argument option (matching -interface/-outfile) that
both implies -pyi and overrides the filename.
The python_pyi test-suite case continues to use plain -pyi.
Assisted-by: Claude Code (Sonnet 5)
Fixes and finishes off the -pyi-stub feature (generates a .pyi PEP 484
stub file alongside the wrapped module):
- printClassHeader() called _swig_add_metaclass, a runtime helper that
no longer exists, which would raise a NameError for any class using
%feature("python:nondynamic"). Switched to the current metaclass=
keyword-argument approach used elsewhere in this file.
- classHandler() called addSymbol() unconditionally, so plain -noproxy
builds (unrelated to -pyi) could fail with spurious "multiply
defined" errors. Gated back to (shadow || pyi_stub).
- The opaque SWIGTYPE_* wrapper classes are now emitted into the .pyi
stub too, not just the .py file, so $pytypename annotations falling
back to an opaque type resolve to a name actually defined in the stub.
- Unannotated variables/constants were emitted into the .pyi as a bare
name with nothing else on the line, which is not a valid attribute
declaration. Falls back to ": typing.Any".
- Regular instance methods and static methods were both silently
missing from the .pyi under -builtin (the option's primary intended
use case), because two separate code paths never reached the shared
pyi_stub emission logic.
- Renamed the command line option from -pyi-stub to -pyi, and improved
the -help text with the PYI ("Python Interface") acronym.
- Added header comment blocks to the new helper methods, matching the
file's existing convention.
- Documented -pyi in the manual with a worked, verified example, and
marked the pytyping/-pyi work as experimental/still evolving in
CHANGES.current.
- Added a python test-suite case (python_pyi.i) built with -builtin
-pyi, covering a constructor, regular method, static method, member
variable and an opaque-type fallback in one go. Clean up generated
.pyi files in the Makefiles the same way .py files already are.
Assisted-by: Claude Code (Sonnet 5)
Finalises the PR keeping the $pytypename mechanism but leaving the default
pytyping SWIGTYPE typemaps as typing.Any. The special variables are exercised
only through explicit typemaps; the default annotation change and the
library-wide pytyping sweep are left for a follow-up.
python.cxx:
- getProxyClassLocalName() uses import_name_string() so a class from an
imported module gets its fully qualified, package-aware name, as in
classDeclaration().
- Guard substitutePytypingVars() against a null typemap and skip work when
there is no special variable to substitute.
- Error when $*pytypename is applied to a non-pointer type instead of
emitting the literal.
- Remove the _swig_python_version_info >= (3, 5) guards; SWIG supports Python
3.5 and later, so import typing unconditionally and use a bare
if typing.TYPE_CHECKING.
- Fix two leaks (SwigType_manglestr, SwigType_typedef_resolve_all).
Doc/Manual/Python.html: correct the feature value pytyping to typing and
document the new special variables with valid HTML.
Tests: python_annotations_typing opts in to proxy-name annotations via explicit
typemaps and covers $*pytypename, class-typed member variables, a class-typed
%constant, and the opaque fallback for a forward-declared class; add
python_annotations_import for the cross-module module-qualified name.
Assisted-by: Claude Code (Opus 4.8)
- HTML doc cleanup/edits.
- Drop a stray Printf argument in emitIncompleteClass; the format string
has no conversion for it.
- Take ownership of the getProxyClassLocalName() result directly instead
of copying it, fixing a small string leak.
- Use SwigType_lstr instead of SwigType_str for the opaque class docstring
so the shown type matches the mangled SWIGTYPE_ name - short &, short *
and short[] all resolve to "short *" for SWIGTYPE_p_short.
- Emit the generated SWIGTYPE_ type wrapper classes inside an
'if typing.TYPE_CHECKING' block instead of as real runtime classes. They
exist only to give the PEP 484 annotations a named type to refer to, so
declaring them for static type checkers only keeps them out of the runtime
module namespace. A preceding comment replaces the previous per-class
"only used for type annotations ..." docstring line.
- Quote the type in the type wrapper docstrings with single quotes rather
than reStructuredText double backticks as this is the SWIG convention
and the tools that would actually render RST docstrings — Sphinx autodoc,
help()/pydoc (to inspect runtime objects) wouldn't even see them as
they inspect runtime objects.
- Replace the "is this always correct?" FIXME in the enum branch of
substituteTypenameSpecialVariable with a comment describing when it is
reached and why int is correct. The default 'enum SWIGTYPE' typemap maps
straight to "int" without substituting, so enums only reach this branch
when a pytyping typemap uses $pytypename on an enum type; int is right
because Python wraps enums as ints. Add a test that exercises this via a
custom typemap (without it the type would wrongly resolve to an opaque
SWIGTYPE_ class).
- Assert in the python_annotations_typing_runme test that the SWIGTYPE_
type wrapper classes are not present at runtime, confirming they are
declared for type checkers only.
Assisted-by: Claude Code (Opus 4.8)
Remove the Python 2 C API compatibility macros from pyhead.swg (PyClass_Check,
PyInt_*, PyString_*, Py_TPFLAGS_HAVE_CLASS, _PyLong_FromSsize_t) and the SWIG
string helper macros SWIG_Python_str_FromFormat and SWIG_Python_str_FromChar.
These mapped Python 2 names onto the Python 3 C API and were kept only for user
typemaps, which should now call the Python 3 C API directly.
SWIG_Python_str_FromChar was still used internally, so its call sites now use
PyUnicode_FromString directly.
Also remove the dead SWIG_PYTHON_SLOW_GETSET_THIS "fast getset" code in
pyrun.swg. That macro was unconditionally defined for Python 3, so the guarded
Python 2 paths (using PyInstance_Check) were never compiled; only the slow
getset path is kept.
Update the python_annotations_typing test typemap, which used PyString_Check
and PyString_AsString, to use PyBytes_Check and PyBytes_AsString.
Assisted-by: Claude Code (Opus 4.8)
Follow-up work on top of the initial Python 2.x removal: it finishes the
removal and fixes several issues found while reviewing the change.
Correctness fixes:
- pyrun.swg: SwigPyPacked_str passed the type name straight to
PyUnicode_FromFormat as its format string, so a type name containing a
'%' would be misinterpreted. Use PyUnicode_FromString instead.
- Doc/Manual/Varargs.html: the (...) varargs freearg typemap example lost
its free() loop when the surrounding Python 2 guard was removed, leaking
the memory the in typemap allocates. Restore the loop, now unconditional.
- Doc/Manual/Typemaps.html: the PyInt_Check to PyLong_Check substitution
left two typecheck excerpts reading PyLong_Check || PyLong_Check; collapse
each back to a single check.
Code generator (Source/Modules/python.cxx):
- Emit the native class X(..., metaclass=_SwigNonDynamicMeta) form for
nondynamic classes in all three base-list branches (object, Exception and
explicit bases), and drop the Python 2 _swig_add_metaclass helper.
- Emit a plain import builtins as __builtin__ instead of the Python 2
try/except import fallback.
- Update a stale Python 2.x comment.
Remove the deprecated embed.i library (it only ever worked with Python 2):
- Delete Lib/python/embed.i and the Lib/python/Makefile.in reference to it.
- Remove the python_static and python_static_cpp targets from
Examples/Makefile.in and the now-orphaned static: targets that used them
from the Examples/python example Makefiles.
- Remove the embed.i section from the manual.
Python test suite (Examples/test-suite/python):
- profiletest_runme.py: convert the Python 2 print statements to print().
- doxygen_constructors_runme.py: drop the dead sys.version_info < (3, 0)
branch, keeping the Python 3 super().__init__() form.
- li_cdata_bytes_runme.py and li_cdata_bytes_cpp_runme.py: drop the dead
exit-on-Python-2 version guard.
- file_test_runme.py and python_abstractbase_runme.py: drop the now-unused
import sys left behind by guard removal.
Documentation (Doc/Manual/Python.html):
- Drop the embed.i and SWIG_PYTHON_STRICT_UNICODE_WCHAR sections; the latter
macro was Python 2 only and no longer exists, wide strings are unicode-only
by default.
- De-duplicate the %pythonabc example and drop a stale collections.abc
compatibility note.
- Update the version support statement and other stale Python 2 mentions.
Other cleanups:
- Tools/mkdist.py: raise the minimum Python version check to Python 3.
- Reword stale Python 2 comments in pyrun.swg, pyiterators.swg and
pycontainer.swg, and fix a PyString_FromFormat left in a pyclasses.swg
doc comment.
- CHANGES.current: record that Python 2 support has been dropped.
Assisted-by: Claude Code (Opus 4.8)
For some target languages (Octave, Python and Ruby), SWIG has previously
treated nullptr or NULL as an integer 0 if used in a situation where
the type wasn't known to be a pointer.
For nullptr this is never helpful, because it has type nullptr_t which
does not implicitly convert to 0, so we no longer do this.
For NULL it's rather dubious - C and C++ allow NULL to be defined as
integer 0, so `int i = NULL` may work and is occassionally seen in real
code, but it is semantically wrong. Also GCC and clang define NULL to a
magic value and by default will warn about such misuse, so it's likely
to be less common than before they did this. So now SWIG only converts
NULL to 0 if used in a context where we know the underlying type is an
arithmetic type.
Using an integer zero (or equivalent value such as 0L) for a NULL
pointer is valid, and SWIG will still treat it as a NULL pointer if used
in a context where know the type is a pointer. This is now done based
on the value of the integer constant so also applies to 0L (previously
it was only done if the value was written in the code as literally `0`).
Fixes: #3472
Following on from supporting multi-argument pytyping typemaps,
lookupPytyping could return either an owned string or a borrowed
reference. Fix the memory handling.
Add a changes file entry for the multi-argument pytyping fix.
The autodoc-class fallback in make_autodoc() and the no-docstring
fallback in classHandler() both emitted "::ClassName" under -builtin
where the non-builtin proxy module emits "Proxy of C++ ClassName class."
(or "" when no docstring source is in scope).
Collapse the AUTODOC_CLASS builtin branch so the autodoc-derived text
is identical in both modes, drop the classHandler -builtin block that
seeded feature:python:tp_doc with "::ClassName" when have_docstring is
false, and initialise quoted_tp_doc_str to an empty string rather than
the literal "0" so the tp_doc slot becomes "" instead of inheriting
SwigPyObject's docstring via the MRO lookup that inspect.getdoc() does
on a NULL tp_doc.
Drop the is_python_builtin() gates that this divergence had forced into
autodoc_runme.py, doxygen_autodoc_docstring_runme.py and
doxygen_misc_constructs_runme.py, plus the matching #ifdef
SWIGPYTHON_BUILTIN helpers in doxygen_autodoc_docstring.i and
doxygen_misc_constructs.i. The autodoc.i helper stays - autodoc_runme.py
still needs it to gate the _autodoc.* low-level checks that genuinely
do not exist under -builtin.
The autodoc-driven __init__ checks in autodoc_runme.py also drop their
skip=True guards: 2f8cdc412 already made cdocstring(node, AUTODOC_CTOR)
flow into python:tp_init_doc for autodoc-only constructors as well as
doxygen ones, so the descriptor adapter installs the expected
"__init__(self, ...) -> Foo" string in -builtin too.
Assisted-by: Claude Opus 4.7 (1M context)
In -builtin mode the tp_init slot creates a wrapper_descriptor for
__init__ whose docstring is fixed to the generic "Initialize self. See
help(type(self)) for accurate signature.", so doxygen comments on C++
constructors were lost. Class-level doxygen comments (tp_doc) were
already handled.
Capture the constructor docstring on the class node, emit a
PyObject*-returning adapter and a PyMethodDef for it, and after the type
is created replace tp_dict["__init__"] with a method descriptor built
from that PyMethodDef. type.__call__ invokes the tp_init slot directly,
so instantiation is unaffected; only attribute access and introspection
(inspect.getdoc(Cls.__init__)) see the new descriptor.
Remove the now unused SWIGPYTHON_BUILTIN-guarded is_python_builtin()
helpers from doxygen_overloads.i and doxygen_parsing.i and drop the
runme branches that called them, so the __init__ checks run in both
wrapping modes.
Assisted-by: Claude Code (Opus 4.7)
%nokwargs (and %feature("kwargs", "0")) on an individual function was
silently ignored when the -keyword command line option was set, because
check_kwargs() used GetFlag() which cannot distinguish "feature unset"
from feature value "0". Honour the explicit feature value when set
instead of OR-ing it with use_kw.
Visible under -builtin where the affected function's wrapper now uses
METH_VARARGS instead of METH_VARARGS|METH_KEYWORDS, so calling with
kwargs raises TypeError as expected. Without -builtin, the generated
Python proxy accepts named arguments and forwards them positionally,
masking the C wrapper level rejection - the runmes therefore gate the
TypeError assertions on is_python_builtin().
Add regression test python_nokwargs_keyword (built with -keyword) that
fails on the unfixed code, plus a parallel python_nokwargs_feature that
exercises the same opt-out behaviour via a module-wide %feature("kwargs").
Both cover constructors, instance methods, static methods, and global
functions.
Assisted-by: Claude Opus 4.7
- Add protection after calling `Swig_director_emit_dynamic_cast()`
to: python, ruby, java, go, perl5, lua, php, ocaml, octave
- Improve or add `try-catch` around director code
of: python, ruby, perl5, lua, php
- Remove `Swig_director_emit_dynamic_cast()` from scilab,
as the language do not support the director feature.
Signed-off-by: Erez Geva <ErezGeva2@gmail.com>
Use 'clang-format off' comment to keep if statements alignment as there is
clang-format option to keep this better alignment.
Improved getClosure implementation to use an array of structs instead of
a single array of strings - makes for friendlier clang-formatting for
the intended data layout.
Minor tidy ups for a better output for future switch to clang-format.
These are:
- DohObjInfo comment corrections.
- Add a few trailing commas in initializers.
- Remove ;;
- return style in r.cxx
- Prefer constructor initializers in c.cxx
Fix handling of "default" typemap applied to method which takes a
single argument when "-builtin" option is used.
Fixes#2786Closes#2790Closes#3241Closes#3243
Co-authored by @ojwb who also suggested it in discussion of #2790.
For Python < 3.9 the tp_as_buffer member is set explicitly if the
interface has a bf_getbuffer slot defined. This fixes#3211.
Enabled buffer interface for non-builtin test.
This only works with Python >= 3.12, where methods __buffer__ and
__release_buffer__ were added. Unfortunately it's not practical for
these methods to reuse the slot methods (or vice versa).
Disable Py_LIMITED_API if below 3.11. The Py_buffer struct and
associated functions are not defined in earlier stable API versions.
Closes#3211
Correct SwigPyObject_richcompare and SwigPyObject_compare signatures
and avoid potential read beyond object memory.
Squashed commit of #3216 plus changes file entry and whitespace fixes.
Closes#3217
Implements https://peps.python.org/pep-0489/
Bumps minimal python3 version from 3.4 to 3.5.
The idea is to move the initialization of the module into a new SWIG_mod_exec function.
Closes#3168
Add bool output parameter to Swig_overload_dispatch to say when it has
generated code that will use the typecheck typemap code to help Lua and
Octave to not emit unused argv[] arrays.
Alas, resorted to warning suppression for deficient cpp11_initializer_list
typecheck typemap in cpp11_std_initializer_list testcase.
Only generate slots if provided by SWIG or if they are provided by users via a %feature.
So stop generating large tables of slots containing "0".
Issue #3196