python / python/mypy

`stubgen --inspect-mode` crashes with `AttributeError on PEP 604 union syntax (X | Y)` *(mypy 2.1.0)*

Ouverte Adaptée aux débutants
#21,689 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

bug
Langage dominant
Python
Étoiles
20.6k
Forks
3.3k
Métriques de merge des PR
Métriques de PR en attente

Description

Environment

  • mypy version: 2.1.0 (compiled: yes)
  • Python: 3.11
  • OS: Debian/Linux
  • Install method: pip, user install (~/.local/lib/python3.11/site-packages)

Steps to Reproduce

  1. Create a module using PEP 604 union syntax:
# test_union_pep604.py
def greet(name: str) -> str:
    return f"Hello, {name}"

def process(value: int | str) -> float | None:
    if isinstance(value, int):
        return float(value)
    return None

class Container:
    def __init__(self, data: list[str] | dict[str, int]) -> None:
        self.data = data

    def get(self, key: str) -> int | None:
        if isinstance(self.data, dict):
            return self.data.get(key)
        return None
  1. Run:
stubgen --inspect-mode -o /tmp/stubs_test test_union_pep604.py

Expected Behavior

stubgen generates a .pyi stub file preserving the X | Y annotations, the same way it succeeds on the equivalent Union[X, Y] syntax:

stubgen --inspect-mode -o /tmp/stubs_test test_union_old.py
# → Generated /tmp/stubs_test/test_union_old.pyi  (exit 0)

Actual Behavior

stubgen crashes with an unhandled AttributeError and exits with code 1:

Traceback (most recent call last):
  File "/home/<user>/.local/bin/stubgen", line 6, in <module>
    sys.exit(main())
  File "mypy/stubgen.py", line 2057, in main
  File "mypy/stubgen.py", line 1860, in generate_stubs
  File "mypy/stubgen.py", line 1818, in generate_stub_for_py_module
  File "/home/<user>/.local/lib/python3.11/site-packages/mypy/stubgenc.py", line 452, in generate_module
    self.generate_function_stub(name, obj, output=functions)
  File "/home/<user>/.local/lib/python3.11/site-packages/mypy/stubgenc.py", line 631, in generate_function_stub
    default_sig = self.get_default_function_sig(obj, ctx)
  File "/home/<user>/.local/lib/python3.11/site-packages/mypy/stubgenc.py", line 344, in get_default_function_sig
    add_args(args, get_pos_default)
  File "/home/<user>/.local/lib/python3.11/site-packages/mypy/stubgenc.py", line 336, in add_args
    arglist.append(ArgSig(arg, get_annotation(arg), default=False))
  File "/home/<user>/.local/lib/python3.11/site-packages/mypy/stubgenc.py", line 311, in get_annotation
    return self.get_type_fullname(argtype)
  File "/home/<user>/.local/lib/python3.11/site-packages/mypy/stubgenc.py", line 775, in get_type_fullname
    typename = getattr(typ, "__qualname__", typ.__name__)
AttributeError: 'types.UnionType' object has no attribute '__name__'

Root Cause

--inspect-mode inspects live runtime objects rather than parsing source. When Python evaluates a X | Y annotation, the resulting object is an instance of types.UnionType (introduced in Python 3.10, PEP 604). stubgenc.py's get_type_fullname assumes any annotation object falls back to having a __name__, which types.UnionType does not define — hence the crash.

Proposed Fix

File: mypy/stubgenc.py

1. Import UnionType (near line 25):

- from types import FunctionType, ModuleType
+ from types import FunctionType, ModuleType, UnionType

2. Special-case it in get_type_fullname (near line 775):

  if typ is Any:
      return "Any"
+ if isinstance(typ, UnionType):
+     return " | ".join(self.get_type_fullname(a) for a in typ.__args__)
  typename = getattr(typ, "__qualname__", typ.__name__)

This recursively resolves each member of the union and rejoins them with |, matching the original annotation's syntax. types.UnionType is available in all Python versions mypy 2.1.0 targets (3.10+), so no version gating is needed.

Verification

With the patch applied, test_union_pep604.py generates a stub with unions preserved:

$ stubgen --inspect-mode -o /tmp/stubs_test test_union_pep604.py
Processed 1 modules
Generated /tmp/stubs_test/test_union_pep604.pyi

$ cat /tmp/stubs_test/test_union_pep604.pyi
def greet(name: str) -> str: ...
def process(value: int | str) -> float | None: ...

class Container:
    def __init__(self, data: list | dict) -> None: ...
    def get(self, key: str) -> int | None: ...

test_union_old.py (using typing.Union) no longer crashes either, but it does not preserve the union — it falls back to Incomplete for those annotations, which is a pre-existing, separate limitation of runtime inspection unrelated to this patch:

Existing Issue

test_union_old.py
"""Module using classic Union[X, Y] syntax — should work with --inspect-mode."""
from typing import Union, Optional


def greet(name: str) -> str:
    return f"Hello, {name}"


def process(value: Union[int, str]) -> Optional[float]:
    if isinstance(value, int):
        return float(value)
    return None


class Container:
    def __init__(self, data: Union[list[str], dict[str, int]]) -> None:
        self.data = data

    def get(self, key: str) -> Optional[int]:
        if isinstance(self.data, dict):
            return self.data.get(key)
        return None

produces

$ stubgen --inspect-mode -o /tmp/stubs_test test_union_old.py
Processed 1 modules
Generated /tmp/stubs_test/test_union_old.pyi

$ cat /tmp/stubs_test/test_union_old.pyi
from _typeshed import Incomplete

def greet(name: str) -> str: ...
def process(value) -> Incomplete: ...

class Container:
    def __init__(self, data) -> None: ...
    def get(self, key: str) -> Incomplete: ...

So: the patch fixes the crash for PEP 604 syntax and, as a bonus, correctly resolves those unions in the stub. It does not fix typing.Union resolution — that gap already existed before this patch and is out of scope here.


Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez dans mypy/stubgenc.py, en particulier avec get_type_fullname et le chemin d’inspect-mode montré dans le traceback. Exécutez stubgen --inspect-mode avec l’exemple fourni test_union_pep604.py et comparez le stub généré à la sortie attendue. C’est terminé lorsque la commande se termine correctement et préserve les annotations d’union PEP 604 sans provoquer de régression du comportement existant décrit dans l’issue.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
python
Domaine
tooling
Type d'issue
Bug
Difficulté
2/5
Temps estimé
1-3 heures
Activité
Calme
Clarté
Clairement spécifiée
Accessibilité débutants
76/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.