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

オープン 初心者向け
#21,689 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
76/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
静か
技術スタック
python
領域
tooling

調査の方向性

mypy/stubgenc.py から始め、特に get_type_fullname と traceback に示されている inspect-mode のパスを確認してください。提供されている test_union_pep604.py の例を使って stubgen --inspect-mode を実行し、生成されたスタブを期待される出力と比較してください。コマンドが正常終了し、Issue に記載されている既存の動作を後退させることなく PEP 604 の union アノテーションが維持されれば完了です。

索引モデルが issue の本文から書いたものです。

説明

bug

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.


主要言語
Python
スター
20.6k
フォーク
3.3k
平均マージ
1日 9時間
マージ済み PR(30日)
56

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

python/mypy のほかの issue

python/mypy の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。