diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index b5ec2bb52a0d..01dcb7c9a67a 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_required: 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 eee98d4d20fa..616d427da45a 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -235,12 +235,20 @@ 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, + allow_new_syntax=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_required=True, allow_placeholder=not self.api.is_func_scope() + type, + allow_required=True, + allow_unpack=True, + allow_placeholder=not self.api.is_func_scope(), ) if analyzed is None: return None @@ -316,6 +324,7 @@ def analyze_typeddict_classdef_fields( analyzed = self.api.anal_type( stmt.type, allow_required=True, + # TODO allow_unpack=True ? allow_placeholder=not self.api.is_func_scope(), prohibit_self_type="TypedDict item type", ) @@ -520,6 +529,7 @@ def parse_typeddict_fields_with_types( analyzed = self.api.anal_type( type, allow_required=True, + # TODO allow_unpack=True ? allow_placeholder=not self.api.is_func_scope(), prohibit_self_type="TypedDict item type", ) diff --git a/test-data/unit/check-python311.test b/test-data/unit/check-python311.test index 2d1a09ef3336..4db0222f1918 100644 --- a/test-data/unit/check-python311.test +++ b/test-data/unit/check-python311.test @@ -173,3 +173,38 @@ Alias4 = Callable[[*IntList], int] # E: "List[int]" cannot be unpacked (must be x4: Alias4[int] # E: Bad number of arguments for type alias, expected 0, given 1 reveal_type(x4) # N: Revealed type is "def (*Unpack[builtins.tuple[Any, ...]]) -> builtins.int" [builtins fixtures/tuple.pyi] + +[case testTypeVarTupleNewSyntaxTypedDict] +# flags: --python-version 3.11 +from typing import Tuple, Callable, Generic +from typing_extensions import TypeVarTuple, Unpack, TypedDict + +Ts = TypeVarTuple("Ts") +class A(TypedDict, Generic[*Ts, T]): + fn: Callable[[*Ts], None] + val: T + +class B(A[*Ts, T]): + gn: Callable[[*Ts], None] + vals: Tuple[*Ts] + +y: B[int, str] +reveal_type(y) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int), 'val': builtins.str, 'gn': def (builtins.int), 'vals': Tuple[builtins.int]})" +reveal_type(y["gn"]) # N: Revealed type is "def (builtins.int)" +reveal_type(y["vals"]) # N: Revealed type is "Tuple[builtins.int]" + +z: B[*Tuple[int, ...]] +reveal_type(z) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (*builtins.int), 'val': builtins.int, 'gn': def (*builtins.int), 'vals': builtins.tuple[builtins.int, ...]})" +reveal_type(z["gn"]) # N: Revealed type is "def (*builtins.int)" + +t: B[int, *Tuple[int, str], str] +reveal_type(t) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int, builtins.int, builtins.str), 'val': builtins.str, 'gn': def (builtins.int, builtins.int, builtins.str), 'vals': Tuple[builtins.int, builtins.int, builtins.str]})" + +def ftest(x: int, y: str) -> None: ... +def gtest(x: int, y: str) -> None: ... +td = B({"fn": ftest, "val": 42, "gn": gtest, "vals": (6, "7")}) +reveal_type(td) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int, builtins.str), 'val': builtins.int, 'gn': def (builtins.int, builtins.str), 'vals': Tuple[builtins.int, builtins.str]})" + +def gbad() -> int: ... +td2 = B({"fn": ftest, "val": 42, "gn": gbad, "vals": (6, "7")}) # E: Incompatible types (expression has type "Callable[[], int]", TypedDict item "gn" has type "Callable[[int, str], None]") +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index f704e3c5c713..201a1af9983c 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -1155,6 +1155,40 @@ def bad() -> int: ... td2 = A({"fn": bad, "val": 42}) # E: Incompatible types (expression has type "Callable[[], int]", TypedDict item "fn" has type "Callable[[], None]") [builtins fixtures/tuple.pyi] +[case testVariadicTypedDictExtending] +from typing import Tuple, Callable, Generic +from typing_extensions import TypeVarTuple, Unpack, TypedDict + +Ts = TypeVarTuple("Ts") +class A(TypedDict, Generic[Unpack[Ts], T]): + fn: Callable[[Unpack[Ts]], None] + val: T + +class B(A[Unpack[Ts], T]): + gn: Callable[[Unpack[Ts]], None] + vals: Tuple[Unpack[Ts]] + +y: B[int, str] +reveal_type(y) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int), 'val': builtins.str, 'gn': def (builtins.int), 'vals': Tuple[builtins.int]})" +reveal_type(y["gn"]) # N: Revealed type is "def (builtins.int)" +reveal_type(y["vals"]) # N: Revealed type is "Tuple[builtins.int]" + +z: B[Unpack[Tuple[int, ...]]] +reveal_type(z) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (*builtins.int), 'val': builtins.int, 'gn': def (*builtins.int), 'vals': builtins.tuple[builtins.int, ...]})" +reveal_type(z["gn"]) # N: Revealed type is "def (*builtins.int)" + +t: B[int, Unpack[Tuple[int, str]], str] +reveal_type(t) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int, builtins.int, builtins.str), 'val': builtins.str, 'gn': def (builtins.int, builtins.int, builtins.str), 'vals': Tuple[builtins.int, builtins.int, builtins.str]})" + +def ftest(x: int, y: str) -> None: ... +def gtest(x: int, y: str) -> None: ... +td = B({"fn": ftest, "val": 42, "gn": gtest, "vals": (6, "7")}) +reveal_type(td) # N: Revealed type is "TypedDict('__main__.B', {'fn': def (builtins.int, builtins.str), 'val': builtins.int, 'gn': def (builtins.int, builtins.str), 'vals': Tuple[builtins.int, builtins.str]})" + +def gbad() -> int: ... +td2 = B({"fn": ftest, "val": 42, "gn": gbad, "vals": (6, "7")}) # E: Incompatible types (expression has type "Callable[[], int]", TypedDict item "gn" has type "Callable[[int, str], None]") +[builtins fixtures/tuple.pyi] + [case testFixedUnpackWithRegularInstance] from typing import Tuple, Generic, TypeVar from typing_extensions import Unpack