From 4ae632b6f07b33dc5937a3b1dd28af98a080bc88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 2 Jul 2024 01:15:20 +0200 Subject: [PATCH] Fix type comments crash inside generic definitions (#16849) Closes https://github.com/python/mypy/issues/16649 It's the first time I am contributing to mypy so I am not very familiar with how it works entirely behind the scene. The issue that I had is that a crash happens when using tuple type comments inside functions/classes that depend on a *constrained* type variable. After investigation, the reason is that the type checker generates all possible definitions (since constraints are known) and expands the functions definitions and bodies accordingly. However, by doing so, a tuple type comment ('# type: (int, float)') would have a FakeInfo, so `ExpandTypeVisitor` would fail since it queries `t.type.fullname`. By the way, feel free to change where my test should lie. --- mypy/expandtype.py | 12 ++++++++++- test-data/unit/check-typevar-values.test | 26 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 5c4d6af9458e..9336be54437b 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -2,7 +2,7 @@ from typing import Final, Iterable, Mapping, Sequence, TypeVar, cast, overload -from mypy.nodes import ARG_STAR, Var +from mypy.nodes import ARG_STAR, FakeInfo, Var from mypy.state import state from mypy.types import ( ANY_STRATEGY, @@ -208,6 +208,16 @@ def visit_erased_type(self, t: ErasedType) -> Type: def visit_instance(self, t: Instance) -> Type: args = self.expand_types_with_unpack(list(t.args)) + + if isinstance(t.type, FakeInfo): + # The type checker expands function definitions and bodies + # if they depend on constrained type variables but the body + # might contain a tuple type comment (e.g., # type: (int, float)), + # in which case 't.type' is not yet available. + # + # See: https://github.com/python/mypy/issues/16649 + return t.copy_modified(args=args) + if t.type.fullname == "builtins.tuple": # Normalize Tuple[*Tuple[X, ...], ...] -> Tuple[X, ...] arg = args[0] diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index effaf620f1f0..8b961d88d23d 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -706,3 +706,29 @@ Func = Callable[[], T] class A: ... class B: ... + +[case testTypeCommentInGenericTypeWithConstrainedTypeVar] +from typing import Generic, TypeVar + +NT = TypeVar("NT", int, float) + +class Foo1(Generic[NT]): + p = 1 # type: int + +class Foo2(Generic[NT]): + p, q = 1, 2.0 # type: (int, float) + +class Foo3(Generic[NT]): + def bar(self) -> None: + p = 1 # type: int + +class Foo4(Generic[NT]): + def bar(self) -> None: + p, q = 1, 2.0 # type: (int, float) + +def foo3(x: NT) -> None: + p = 1 # type: int + +def foo4(x: NT) -> None: + p, q = 1, 2.0 # type: (int, float) +[builtins fixtures/tuple.pyi]