Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeVarTuple as type arg for subclasses of generic TypedDict (fixes #16975) #16977

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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,
# TODO 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,
# TODO 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