From df3881fafd363aec18f64a5cd2f8fc41263b3a84 Mon Sep 17 00:00:00 2001 From: Erik Kemperman Date: Wed, 4 Jun 2025 15:19:12 +0200 Subject: [PATCH 1/3] Allow TypeVarTuple arguments to subclasses of generic TypedDict --- mypy/semanal_shared.py | 1 + mypy/semanal_typeddict.py | 5 +- test-data/unit/check-typevar-tuple.test | 110 ++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index bdd01ef6a6f3..0585685abf27 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -182,6 +182,7 @@ def anal_type( allow_tuple_literal: bool = False, allow_unbound_tvars: bool = False, allow_typed_dict_special_forms: bool = False, + allow_unpack: bool = False, allow_placeholder: bool = False, report_invalid_types: bool = True, prohibit_self_type: str | None = None, diff --git a/mypy/semanal_typeddict.py b/mypy/semanal_typeddict.py index 8bf073d30f71..4a4cd156f844 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -253,13 +253,16 @@ def analyze_base_args(self, base: IndexExpr, ctx: Context) -> list[Type] | None: for arg_expr in args: try: - type = expr_to_unanalyzed_type(arg_expr, self.options, self.api.is_stub_file) + type = expr_to_unanalyzed_type( + arg_expr, self.options, self.api.is_stub_file, allow_unpack=True + ) except TypeTranslationError: self.fail("Invalid TypedDict type argument", ctx) return None analyzed = self.api.anal_type( type, allow_typed_dict_special_forms=True, + allow_unpack=True, allow_placeholder=not self.api.is_func_scope(), ) if analyzed is None: diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 41e90c3f8506..aebd27f09178 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -1159,6 +1159,116 @@ td2 = A({"fn": bad, "val": 42}) # E: Incompatible types (expression has type "C [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] +[case testVariadicTypedDictDeriveUnpack] +from typing import Tuple, Callable, Generic, TypedDict, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") +class A(TypedDict, Generic[Unpack[Ts]]): + fn: Callable[[Unpack[Ts]], None] + +class B(A[Unpack[Ts]]): + val: bool + +class C(A[Unpack[Ts]], Generic[Unpack[Ts], T]): + val: T + + +y: B[int, str] +reveal_type(y) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int, builtins.str), 'val': builtins.bool})" +reveal_type(y["fn"]) # N: Revealed type is "def (builtins.int, builtins.str)" +reveal_type(y["val"]) # N: Revealed type is "builtins.bool" + +y2: C[int, str, bool] +reveal_type(y2) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (builtins.int, builtins.str), 'val': builtins.bool})" +reveal_type(y2["fn"]) # N: Revealed type is "def (builtins.int, builtins.str)" +reveal_type(y2["val"]) # N: Revealed type is "builtins.bool" + +z: B[Unpack[Tuple[int, ...]]] +reveal_type(z) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (*builtins.int), 'val': builtins.bool})" +reveal_type(z["fn"]) # N: Revealed type is "def (*builtins.int)" +reveal_type(y["val"]) # N: Revealed type is "builtins.bool" + +z2: C[Unpack[Tuple[int, ...]]] +reveal_type(z2) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (*builtins.int), 'val': builtins.int})" +reveal_type(z2["fn"]) # N: Revealed type is "def (*builtins.int)" +reveal_type(z2["val"]) # N: Revealed type is "builtins.int" + +z3: C[Unpack[Tuple[int, ...]], bool] +reveal_type(z3) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (*builtins.int), 'val': builtins.bool})" +reveal_type(z3["fn"]) # N: Revealed type is "def (*builtins.int)" +reveal_type(z3["val"]) # N: Revealed type is "builtins.bool" + +t: B[int, Unpack[Tuple[int, str]]] +reveal_type(t) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int, builtins.int, builtins.str), 'val': builtins.bool})" + +t2: C[int, Unpack[Tuple[int, str]]] +reveal_type(t2) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (builtins.int, builtins.int), 'val': builtins.str})" + +def test(x: int, y: str) -> None: ... +td = B({"fn": test, "val": False}) +reveal_type(td) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int, builtins.str), 'val': builtins.bool})" +td2 = C({"fn": test, "val": False}) +reveal_type(td2) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (builtins.int, builtins.str), 'val': builtins.bool})" + +def bad() -> int: ... +td3 = B({"fn": bad, "val": False}) # E: Incompatible types (expression has type "Callable[[], int]", TypedDict item "fn" has type "Callable[[], None]") +td4 = C({"fn": bad, "val": False}) # E: Incompatible types (expression has type "Callable[[], int]", TypedDict item "fn" has type "Callable[[], None]") + +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testVariadicTypedDictDeriveStar] +from typing import Tuple, Callable, Generic, TypedDict, TypeVar +from typing_extensions import TypeVarTuple + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") +class A(TypedDict, Generic[*Ts]): + fn: Callable[[*Ts], None] + +class B(A[*Ts]): + val: bool + +class C(A[*Ts], Generic[*Ts, T]): + val: T + + +y: B[int, str] +reveal_type(y) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int, builtins.str), 'val': builtins.bool})" +reveal_type(y["fn"]) # N: Revealed type is "def (builtins.int, builtins.str)" +reveal_type(y["val"]) # N: Revealed type is "builtins.bool" + +y2: C[int, str, bool] +reveal_type(y2) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (builtins.int, builtins.str), 'val': builtins.bool})" +reveal_type(y2["fn"]) # N: Revealed type is "def (builtins.int, builtins.str)" +reveal_type(y2["val"]) # N: Revealed type is "builtins.bool" + +z: B[*Tuple[int, ...]] +reveal_type(z) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (*builtins.int), 'val': builtins.bool})" +reveal_type(z["fn"]) # N: Revealed type is "def (*builtins.int)" +reveal_type(y["val"]) # N: Revealed type is "builtins.bool" + +z2: C[*Tuple[int, ...]] +reveal_type(z2) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (*builtins.int), 'val': builtins.int})" +reveal_type(z2["fn"]) # N: Revealed type is "def (*builtins.int)" +reveal_type(z2["val"]) # N: Revealed type is "builtins.int" + +z3: C[*Tuple[int, ...], bool] +reveal_type(z3) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (*builtins.int), 'val': builtins.bool})" +reveal_type(z3["fn"]) # N: Revealed type is "def (*builtins.int)" +reveal_type(z3["val"]) # N: Revealed type is "builtins.bool" + +t: B[int, *Tuple[int, str]] +reveal_type(t) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int, builtins.int, builtins.str), 'val': builtins.bool})" + +t2: C[int, *Tuple[int, str]] +reveal_type(t2) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (builtins.int, builtins.int), 'val': builtins.str})" + +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + [case testFixedUnpackWithRegularInstance] from typing import Tuple, Generic, TypeVar from typing_extensions import Unpack From f3a1f1318fd541b461f3df6a6ce0ac9ed2eb3352 Mon Sep 17 00:00:00 2001 From: Erik Kemperman Date: Wed, 4 Jun 2025 16:34:21 +0200 Subject: [PATCH 2/3] Remove star-syntax unit test --- test-data/unit/check-typevar-tuple.test | 50 ------------------------- 1 file changed, 50 deletions(-) diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index aebd27f09178..e20846761bda 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -1219,56 +1219,6 @@ td4 = C({"fn": bad, "val": False}) # E: Incompatible types (expression has type [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] -[case testVariadicTypedDictDeriveStar] -from typing import Tuple, Callable, Generic, TypedDict, TypeVar -from typing_extensions import TypeVarTuple - -T = TypeVar("T") -Ts = TypeVarTuple("Ts") -class A(TypedDict, Generic[*Ts]): - fn: Callable[[*Ts], None] - -class B(A[*Ts]): - val: bool - -class C(A[*Ts], Generic[*Ts, T]): - val: T - - -y: B[int, str] -reveal_type(y) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int, builtins.str), 'val': builtins.bool})" -reveal_type(y["fn"]) # N: Revealed type is "def (builtins.int, builtins.str)" -reveal_type(y["val"]) # N: Revealed type is "builtins.bool" - -y2: C[int, str, bool] -reveal_type(y2) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (builtins.int, builtins.str), 'val': builtins.bool})" -reveal_type(y2["fn"]) # N: Revealed type is "def (builtins.int, builtins.str)" -reveal_type(y2["val"]) # N: Revealed type is "builtins.bool" - -z: B[*Tuple[int, ...]] -reveal_type(z) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (*builtins.int), 'val': builtins.bool})" -reveal_type(z["fn"]) # N: Revealed type is "def (*builtins.int)" -reveal_type(y["val"]) # N: Revealed type is "builtins.bool" - -z2: C[*Tuple[int, ...]] -reveal_type(z2) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (*builtins.int), 'val': builtins.int})" -reveal_type(z2["fn"]) # N: Revealed type is "def (*builtins.int)" -reveal_type(z2["val"]) # N: Revealed type is "builtins.int" - -z3: C[*Tuple[int, ...], bool] -reveal_type(z3) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (*builtins.int), 'val': builtins.bool})" -reveal_type(z3["fn"]) # N: Revealed type is "def (*builtins.int)" -reveal_type(z3["val"]) # N: Revealed type is "builtins.bool" - -t: B[int, *Tuple[int, str]] -reveal_type(t) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int, builtins.int, builtins.str), 'val': builtins.bool})" - -t2: C[int, *Tuple[int, str]] -reveal_type(t2) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (builtins.int, builtins.int), 'val': builtins.str})" - -[builtins fixtures/dict.pyi] -[typing fixtures/typing-typeddict.pyi] - [case testFixedUnpackWithRegularInstance] from typing import Tuple, Generic, TypeVar from typing_extensions import Unpack From 2632de887760abaab19702fc1064b5836249f5f3 Mon Sep 17 00:00:00 2001 From: Erik Kemperman Date: Thu, 5 Jun 2025 11:41:26 +0200 Subject: [PATCH 3/3] Add *Ts syntax to check-python311.test --- test-data/unit/check-python311.test | 49 +++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test-data/unit/check-python311.test b/test-data/unit/check-python311.test index 09c8d6082365..dcea98c2324c 100644 --- a/test-data/unit/check-python311.test +++ b/test-data/unit/check-python311.test @@ -174,6 +174,55 @@ x4: Alias4[int] # E: Bad number of arguments for type alias, expected 0, given reveal_type(x4) # N: Revealed type is "def (*Any) -> builtins.int" [builtins fixtures/tuple.pyi] +[case testVariadicTypedDictDeriveStar] +from typing import Tuple, Callable, Generic, TypedDict, TypeVar +from typing_extensions import TypeVarTuple + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") +class A(TypedDict, Generic[*Ts]): + fn: Callable[[*Ts], None] + +class B(A[*Ts]): + val: bool + +class C(A[*Ts], Generic[*Ts, T]): + val: T + +y: B[int, str] +reveal_type(y) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int, builtins.str), 'val': builtins.bool})" +reveal_type(y["fn"]) # N: Revealed type is "def (builtins.int, builtins.str)" +reveal_type(y["val"]) # N: Revealed type is "builtins.bool" + +y2: C[int, str, bool] +reveal_type(y2) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (builtins.int, builtins.str), 'val': builtins.bool})" +reveal_type(y2["fn"]) # N: Revealed type is "def (builtins.int, builtins.str)" +reveal_type(y2["val"]) # N: Revealed type is "builtins.bool" + +z: B[*Tuple[int, ...]] +reveal_type(z) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (*builtins.int), 'val': builtins.bool})" +reveal_type(z["fn"]) # N: Revealed type is "def (*builtins.int)" +reveal_type(y["val"]) # N: Revealed type is "builtins.bool" + +z2: C[*Tuple[int, ...]] +reveal_type(z2) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (*builtins.int), 'val': builtins.int})" +reveal_type(z2["fn"]) # N: Revealed type is "def (*builtins.int)" +reveal_type(z2["val"]) # N: Revealed type is "builtins.int" + +z3: C[*Tuple[int, ...], bool] +reveal_type(z3) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (*builtins.int), 'val': builtins.bool})" +reveal_type(z3["fn"]) # N: Revealed type is "def (*builtins.int)" +reveal_type(z3["val"]) # N: Revealed type is "builtins.bool" + +t: B[int, *Tuple[int, str]] +reveal_type(t) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int, builtins.int, builtins.str), 'val': builtins.bool})" + +t2: C[int, *Tuple[int, str]] +reveal_type(t2) # N: Revealed type is "TypedDict('__main__.C', {'fn': def (builtins.int, builtins.int), 'val': builtins.str})" + +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + [case testReturnInExceptStarBlock1] # flags: --python-version 3.11 def foo() -> None: