Skip to content

stubgen: preserve type alias definitions when TypeAlias is imported from another module - #22383

Open
Shanmukh-l33t wants to merge 1 commit into
python:masterfrom
Shanmukh-l33t:stubgen-22294-reexported-typealias
Open

Shanmukh-l33t wants to merge 1 commit into
python:masterfrom
Shanmukh-l33t:stubgen-22294-reexported-typealias

Conversation

@Shanmukh-l33t

Copy link
Copy Markdown

Fixes #22294

Problem

stubgen loses the right-hand side of an explicit (PEP 613) type alias when
TypeAlias is imported from another module rather than directly from
typing/typing_extensions — e.g. a version-compatibility shim:

# types_compat.py
import sys
from typing import TYPE_CHECKING

if sys.version_info >= (3, 10):
    from typing import TypeAlias
elif TYPE_CHECKING:
    from typing_extensions import TypeAlias
else:
    TypeAlias = object

# example.py
from typing import Union
from types_compat import TypeAlias

LDAPTokenDictValue: TypeAlias = Union[tuple[()], tuple[str, ...]]

stubgen example.py produced:

LDAPTokenDictValue: TypeAlias

instead of preserving the alias definition:

LDAPTokenDictValue: TypeAlias = tuple[()] | tuple[str, ...]

Cause

mypy_options() runs mypy with follow_imports="skip", so the re-exported
TypeAlias name is never resolved back to typing.TypeAlias.
SemanticAnalyzer.is_pep_613() therefore fails, the statement is analysed as
a plain variable, and AssignmentStmt.type degrades to Any (instead of
staying as the unanalysed UnboundType("TypeAlias"), which is what happens
when the annotation resolves normally and the statement returns early from
check_and_set_up_type_alias()).

ASTStubGenerator.visit_assignment_stmt() only recognised the explicit alias
via o.type.name == "TypeAlias", which is now Any, so the assignment fell
through to the plain-variable path and the value was dropped from the stub.

Solution

Keep the existing check on the analysed annotation and, when it doesn't carry
the name, fall back to the annotation name as written in the source
(o.unanalyzed_type). No other code paths are touched: implicit aliases
(X = ...) and ordinary annotated assignments behave exactly as before.

Impact

  • Stubs generated for compatibility shims keep their type alias definitions.
  • No behaviour change when TypeAlias is imported from typing or
    typing_extensions, or for any non-TypeAlias annotation.

Testing

Regression test added: StubgenTypeAliasSuite .test_explicit_type_alias_imported_from_other_module in
mypy/test/teststubgen.py (reproduces the issue exactly — the compatibility
module is not a stubgen target, so follow_imports=skip applies to it).
It uses a Python test rather than a stubgen.test data case because the data
harness passes -m for every [file ...], which would make the shim a stubgen
target and hide the bug.

RED (before the fix):

$ pytest mypy/test/teststubgen.py -k test_explicit_type_alias_imported_from_other_module
Expected:
  from types_compat import TypeAlias as TypeAlias
  LDAPTokenDictValue: TypeAlias = tuple[()] | tuple[str, ...] (diff)
Actual:
  from types_compat import TypeAlias as TypeAlias
  LDAPTokenDictValue: TypeAlias (diff)
FAILED StubgenTypeAliasSuite.test_explicit_type_alias_imported_from_other_module

GREEN (after the fix):

$ pytest mypy/test/teststubgen.py -n 2
379 passed, 1 skipped, 2 xfailed in 56.09s

$ python -m mypy --config-file mypy_self_check.ini -p mypy
Success: no issues found in 196 source files

$ black --check mypy/stubgen.py mypy/test/teststubgen.py   # clean
$ ruff check mypy/stubgen.py mypy/test/teststubgen.py      # All checks passed!

The full test suite was not run locally (mypy's suite is large; CI covers it).

Manual end-to-end check with the exact reproducer from the issue now yields:

from types_compat import TypeAlias as TypeAlias

LDAPTokenDictValue: TypeAlias = tuple[()] | tuple[str, ...]

stubgen runs mypy with follow_imports=skip, so a TypeAlias name that is
imported from another module (e.g. a version compatibility shim) is never
resolved back to typing.TypeAlias. The analyzed annotation then degrades
to Any, visit_assignment_stmt() no longer treats the assignment as an
explicit (PEP 613) type alias, and the right-hand side is dropped from
the generated stub.

Fall back to the annotation name as written in the source when deciding
whether the assignment is an explicit type alias.

Fixes python#22294
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

stubgen: preserve type alias definitions when TypeAlias is imported from another module

1 participant