Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
allow_unpack=True, # Fixed length unpacks can be used for non-variadic aliases.
)
if node.has_param_spec_type and len(node.alias_tvars) == 1:
an_args = self.pack_paramspec_args(an_args)
an_args = self.pack_paramspec_args(an_args, t.empty_tuple_index)

disallow_any = self.options.disallow_any_generics and not self.is_typeshed_stub
res = instantiate_type_alias(
Expand Down Expand Up @@ -521,13 +521,16 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
else: # sym is None
return AnyType(TypeOfAny.special_form)

def pack_paramspec_args(self, an_args: Sequence[Type]) -> list[Type]:
def pack_paramspec_args(self, an_args: Sequence[Type], empty_tuple_index: bool) -> list[Type]:
# "Aesthetic" ParamSpec literals for single ParamSpec: C[int, str] -> C[[int, str]].
# These do not support mypy_extensions VarArgs, etc. as they were already analyzed
# TODO: should these be re-analyzed to get rid of this inconsistency?
count = len(an_args)
if count == 0:
if count == 0 and empty_tuple_index:
return [Parameters([], [], [])]
elif count == 0:
return []

if count == 1 and isinstance(get_proper_type(an_args[0]), AnyType):
# Single Any is interpreted as ..., rather that a single argument with Any type.
# I didn't find this in the PEP, but it sounds reasonable.
Expand Down Expand Up @@ -872,7 +875,7 @@ def analyze_type_with_type_info(
instance.end_line = ctx.end_line
instance.end_column = ctx.end_column
if len(info.type_vars) == 1 and info.has_param_spec_type:
instance.args = tuple(self.pack_paramspec_args(instance.args))
instance.args = tuple(self.pack_paramspec_args(instance.args, empty_tuple_index))

# Check type argument count.
instance.args = tuple(flatten_nested_tuples(instance.args))
Expand Down
31 changes: 23 additions & 8 deletions test-data/unit/check-generics.test
Original file line number Diff line number Diff line change
Expand Up @@ -3625,30 +3625,45 @@ t2.foo = [1] # E: Value of type variable "T" of "foo" of "Test" cannot be "int"

[case testMissingTypeArgsInApplication]
from typing import Generic
from typing_extensions import Unpack, TypeVarTuple
from typing_extensions import Unpack, TypeVarTuple, ParamSpec

Ts = TypeVarTuple("Ts")
P = ParamSpec("P")

class CanHaveZero(Generic[Unpack[Ts]]): ...
class CanHaveZero1(Generic[Unpack[Ts]]): ...
class CanHaveZero2(Generic[P]): ...

ok1: CanHaveZero[()]
ok2: tuple[()]
ok1: CanHaveZero1[()]
ok2: CanHaveZero2[()]
ok3: tuple[()]

reveal_type(ok1) # N: Revealed type is "__main__.CanHaveZero1[()]"
reveal_type(ok2) # N: Revealed type is "__main__.CanHaveZero2[[]]"
reveal_type(ok3) # N: Revealed type is "tuple[()]"

bad1: list[()] = [] # E: "list" expects 1 type argument, but none given
[builtins fixtures/tuple.pyi]

[case testMissingTypeArgsInApplicationStrict]
# flags: --strict
from typing import Generic
from typing_extensions import Unpack, TypeVarTuple
from typing_extensions import Unpack, TypeVarTuple, ParamSpec

Ts = TypeVarTuple("Ts")
P = ParamSpec("P")

class CanHaveZero1(Generic[Unpack[Ts]]): ...
class CanHaveZero2(Generic[P]): ...

class CanHaveZero(Generic[Unpack[Ts]]): ...
ok1: CanHaveZero1[()]
ok2: CanHaveZero2[()]
ok3: tuple[()]

ok1: CanHaveZero[()]
ok2: tuple[()]
reveal_type(ok1) # N: Revealed type is "__main__.CanHaveZero1[()]"
reveal_type(ok2) # N: Revealed type is "__main__.CanHaveZero2[[]]"
reveal_type(ok3) # N: Revealed type is "tuple[()]"

bad1: list[()] = [] # E: "list" expects 1 type argument, but none given \
# E: Missing type arguments for generic type "list"

[builtins fixtures/tuple.pyi]