Skip to content

Commit

Permalink
Allow TypeVarTuple as type argument for subclasses of generic TypedDi…
Browse files Browse the repository at this point in the history
…ct (fixes python#16975)
  • Loading branch information
erikkemperman committed May 8, 2024
1 parent fb31409 commit 591f377
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 2 deletions.
1 change: 1 addition & 0 deletions mypy/semanal_shared.py
Expand Up @@ -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,
Expand Down
14 changes: 12 additions & 2 deletions mypy/semanal_typeddict.py
Expand Up @@ -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
Expand Down Expand Up @@ -316,6 +324,7 @@ def analyze_typeddict_classdef_fields(
analyzed = self.api.anal_type(
stmt.type,
allow_required=True,
allow_unpack=True,
allow_placeholder=not self.api.is_func_scope(),
prohibit_self_type="TypedDict item type",
)
Expand Down Expand Up @@ -520,6 +529,7 @@ def parse_typeddict_fields_with_types(
analyzed = self.api.anal_type(
type,
allow_required=True,
allow_unpack=True,
allow_placeholder=not self.api.is_func_scope(),
prohibit_self_type="TypedDict item type",
)
Expand Down
35 changes: 35 additions & 0 deletions test-data/unit/check-python311.test
Expand Up @@ -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]
34 changes: 34 additions & 0 deletions test-data/unit/check-typevar-tuple.test
Expand Up @@ -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
Expand Down

0 comments on commit 591f377

Please sign in to comment.