You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
🐛 fix(stubs): resolve stub imports for type hint evaluation (#654)
When stub annotations reference types only imported in the `.pyi` file
(e.g., `Sequence`, `Optional`, `Callable`), `get_type_hints()` raises
`NameError` because those names aren't in the runtime module's
namespace. The fallback returns raw strings, and `format_annotation()`
can't create cross-reference links — so rendered docs show plain text
with no hyperlinks for any non-builtin type from a stub. 🐛 This is the
issue @agronholm identified in #652.
The fix extracts the stub's top-level import statements from the AST,
resolves them via `importlib.import_module`, and passes the resulting
namespace as `localns` to `get_type_hints()`. This complements the
runtime module's `__dict__` (which already provides self-references and
cross-class refs defined in the module) with the stub-specific imports
needed for evaluation.
### Why not `eval` the AST annotation nodes directly?
We investigated using `eval(compile(ast.Expression(body=annotation),
...))` instead of `ast.unparse` + `get_type_hints()`. This fails on the
most common cases because class definitions in the stub aren't imports —
they're AST nodes with no corresponding object in the eval namespace:
| Scenario | `eval` | string + `get_type_hints()` |
|---|---|---|
| Self-reference (`Foo.method() -> Foo`) | `NameError` | Works via
runtime `__dict__` |
| Cross-class ref (`Bar.get_foo() -> Foo`) | `NameError` | Works |
| Compound (`Optional[Foo]`) | `NameError` | Works |
| Forward ref (class defined later in stub) | `NameError` | Works |
### How other tools handle this
- **pdoc** imports the entire `.pyi` as a Python module via
[`SourceFileLoader`](https://github.com/mitmproxy/pdoc/blob/main/pdoc/doc_pyi.py)
— creates real objects but shadows runtime classes, breaking Sphinx's
ability to link to the documented types
- **griffe** builds a custom [expression
tree](https://github.com/mkdocstrings/griffe/blob/main/src/_griffe/expressions.py)
from AST nodes, reimplementing all name resolution independently of
Python's runtime — effective but massive complexity for our use case
- **sphinx autodoc** (built-in) had no native stub support for C
extensions until
[v8.2.0](sphinx-doc/sphinx#13253) (see
[#7630](sphinx-doc/sphinx#7630))
No standalone library exists for "resolve stub imports into a namespace"
— the operation is ~20 lines of `importlib.import_module` + `getattr`,
and every tool that needs it has different requirements. Our
implementation keeps it minimal and focused.
0 commit comments