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)
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)
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)