Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 22 additions & 21 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -5192,19 +5192,6 @@ class C(Generic[T, Unpack[Ts]]): ...
vars = list(t.variables)
args = flatten_nested_tuples(args)

# TODO: this logic is duplicated with semanal_typeargs.
for tv, arg in zip(t.variables, args):
if isinstance(tv, ParamSpecType):
if not isinstance(
get_proper_type(arg), (Parameters, ParamSpecType, AnyType, UnboundType)
):
self.chk.fail(
"Can only replace ParamSpec with a parameter types list or"
f" another ParamSpec, got {format_type(arg, self.chk.options)}",
ctx,
)
return [AnyType(TypeOfAny.from_error)] * len(vars)

# TODO: in future we may want to support type application to variadic functions.
if not vars or not t.is_type_obj() or t.type_object().fullname == "builtins.tuple":
return list(args)
Expand All @@ -5221,14 +5208,28 @@ class C(Generic[T, Unpack[Ts]]): ...
args = list(fake.args)

if not any(isinstance(v, TypeVarTupleType) for v in vars):
return args

prefix = next(i for (i, v) in enumerate(vars) if isinstance(v, TypeVarTupleType))
suffix = len(vars) - prefix - 1
tvt = vars[prefix]
assert isinstance(tvt, TypeVarTupleType)
start, middle, end = split_with_prefix_and_suffix(tuple(args), prefix, suffix)
return list(start) + [TupleType(list(middle), tvt.tuple_fallback)] + list(end)
result = list(args)
else:
prefix = next(i for (i, v) in enumerate(vars) if isinstance(v, TypeVarTupleType))
suffix = len(vars) - prefix - 1
tvt = vars[prefix]
assert isinstance(tvt, TypeVarTupleType)
start, middle, end = split_with_prefix_and_suffix(tuple(args), prefix, suffix)
result = list(start) + [TupleType(list(middle), tvt.tuple_fallback)] + list(end)

for tv, arg in zip(vars, result):
if isinstance(tv, ParamSpecType):
if not isinstance(
get_proper_type(arg), (Parameters, ParamSpecType, AnyType, UnboundType)
):
self.chk.fail(
"Can only replace ParamSpec with a parameter types list or"
f" another ParamSpec, got {format_type(arg, self.chk.options)}",
ctx,
)
return [AnyType(TypeOfAny.from_error)] * len(vars)

return result

def apply_type_arguments_to_callable(
self, tp: Type, args: Sequence[Type], ctx: Context
Expand Down
118 changes: 114 additions & 4 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
)
from mypy.types_utils import get_bad_type_type_item
from mypy.typevars import fill_typevars
from mypy.typevartuples import split_variadic_args_with_defaults

T = TypeVar("T")

Expand Down Expand Up @@ -2165,16 +2166,73 @@ def fix_instance(
for tv in t.type.defn.type_vars
)
max_tv_count = len(t.type.type_vars)
if arg_count < min_tv_count or arg_count > max_tv_count:
if arg_count < min_tv_count or (
not t.type.has_type_var_tuple_type and arg_count > max_tv_count
):
# Don't use existing args if arg_count doesn't match
if arg_count > max_tv_count:
if not t.type.has_type_var_tuple_type and arg_count > max_tv_count:
# Already wrong arg count error, don't emit missing type parameters error as well.
disallow_any = False
t.args = ()

env: dict[TypeVarId, Type] = {}
if t.type.has_type_var_tuple_type and t.args:
prefix = t.type.type_var_tuple_prefix
assert prefix is not None
prefix_args, middle_args, suffix_args, omitted_suffix_vars = (
split_variadic_args_with_defaults(t.type.defn.type_vars, prefix, t.args)
)
if omitted_suffix_vars:
for tv, arg in zip(t.type.defn.type_vars[:prefix], prefix_args):
env[tv.id] = arg
tvt = t.type.defn.type_vars[prefix]
assert isinstance(tvt, TypeVarTupleType)
env[tvt.id] = TupleType(list(middle_args), tvt.tuple_fallback)
suffix_vars = t.type.defn.type_vars[prefix + 1 :]
for tv, arg in zip(suffix_vars[: len(suffix_args)], suffix_args):
env[tv.id] = arg
default_args: list[Type] = []
for tv in omitted_suffix_vars:
if tv.has_default():
arg = tv.default
if analyzing_tvar_def:
used_default = True
if is_typevar_default_recursive(tv.fullname, t.type):
fullname = None if use_generic_error else t.type.fullname
arg = get_omitted_any(
disallow_any,
fail,
note,
t,
options,
fullname,
unexpanded_type,
used_default,
)
with state.strict_optional_set(options.strict_optional):
arg = expand_type(arg, env)
env[tv.id] = arg
default_args.append(arg)
used_default = True
else:
fullname = None if use_generic_error else t.type.fullname
arg = get_omitted_any(
disallow_any,
fail,
note,
t,
options,
fullname,
unexpanded_type,
used_default,
)
env[tv.id] = arg
default_args.append(arg)
t.args = prefix_args + middle_args + suffix_args + tuple(default_args)
return used_default

args: list[Type] = list(t.args)
any_type: AnyType | None = None
env: dict[TypeVarId, Type] = {}
tvt_no_default = False

for tv, arg in itertools.zip_longest(t.type.defn.type_vars, t.args, fillvalue=None):
Expand Down Expand Up @@ -2340,6 +2398,15 @@ def instantiate_type_alias(
# Variadic tuple is always correct.
correct = True
fill_typevars = not correct
if correct and node.tvar_tuple_index is not None:
prefix = node.tvar_tuple_index
suffix_vars = node.alias_tvars[prefix + 1 :]
if any(v.has_default() for v in suffix_vars):
_, _, _, omitted_suffix_vars = split_variadic_args_with_defaults(
node.alias_tvars, prefix, args
)
if omitted_suffix_vars:
fill_typevars = True
if fill_typevars:
if not correct:
if use_standard_error:
Expand Down Expand Up @@ -2429,8 +2496,42 @@ def set_any_tvars(
else:
type_of_any = TypeOfAny.from_omitted_generics
any_type = AnyType(type_of_any, line=newline, column=newcolumn)

env: dict[TypeVarId, Type] = {}

if node.tvar_tuple_index is not None and args:
prefix = node.tvar_tuple_index
prefix_args, middle_args, suffix_args, omitted_suffix_vars = (
split_variadic_args_with_defaults(node.alias_tvars, prefix, args)
)
if omitted_suffix_vars:
for tv, arg in zip(node.alias_tvars[:prefix], prefix_args):
env[tv.id] = arg
tvt = node.alias_tvars[prefix]
assert isinstance(tvt, TypeVarTupleType)
env[tvt.id] = TupleType(list(middle_args), tvt.tuple_fallback)
suffix_vars = node.alias_tvars[prefix + 1 :]
for tv, arg in zip(suffix_vars[: len(suffix_args)], suffix_args):
env[tv.id] = arg
default_args: list[Type] = []
for tv in omitted_suffix_vars:
if tv.has_default():
arg = tv.default
if analyzing_tvar_def:
used_default = True
if is_typevar_default_recursive(tv.fullname, node):
arg = any_type
with state.strict_optional_set(options.strict_optional):
arg = expand_type(arg, env)
env[tv.id] = arg
default_args.append(arg)
used_default = True
else:
arg = any_type
env[tv.id] = arg
default_args.append(arg)
args = list(prefix_args + middle_args + suffix_args + tuple(default_args))
return TypeAliasType(node, args, newline, newcolumn), used_default

used_any_type = False
for tv, arg in itertools.zip_longest(node.alias_tvars, args, fillvalue=None):
if tv is None:
Expand Down Expand Up @@ -2704,6 +2805,15 @@ def validate_instance(t: Instance, fail: MsgCallback, indexed: bool) -> bool:
if act_prefix < exp_prefix or act_suffix < exp_suffix:
fail("TypeVarTuple cannot be split", t, code=codes.TYPE_ARG)
return False
prefix = t.type.type_var_tuple_prefix
assert prefix is not None
suffix_vars = t.type.defn.type_vars[prefix + 1 :]
if any(v.has_default() for v in suffix_vars):
_, _, _, omitted_suffix_vars = split_variadic_args_with_defaults(
t.type.defn.type_vars, prefix, t.args
)
if omitted_suffix_vars:
return False
elif any(isinstance(a, UnpackType) for a in t.args):
# A variadic unpack in fixed size instance (fixed unpacks must be flattened by the caller)
fail(message_registry.INVALID_UNPACK_POSITION, t, code=codes.VALID_TYPE)
Expand Down
45 changes: 45 additions & 0 deletions mypy/typevartuples.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
from mypy.types import (
AnyType,
Instance,
Parameters,
ParamSpecType,
Type,
TypeVarLikeType,
TypeVarTupleType,
UnboundType,
UnpackType,
get_proper_type,
split_with_prefix_and_suffix,
)

Expand All @@ -34,3 +38,44 @@ def erased_vars(type_vars: Sequence[TypeVarLikeType], type_of_any: int) -> list[
else:
args.append(AnyType(type_of_any))
return args


def is_valid_paramspec_arg(arg: Type) -> bool:
proper = get_proper_type(arg)
return isinstance(proper, (Parameters, ParamSpecType, AnyType, UnboundType))


def split_variadic_args_with_defaults(
type_vars: Sequence[TypeVarLikeType], prefix: int, args: Sequence[Type]
) -> tuple[tuple[Type, ...], tuple[Type, ...], tuple[Type, ...], list[TypeVarLikeType]]:
"""Partition arguments for a variadic generic with possible defaulted suffix variables.

Returns: (prefix_args, middle_args, suffix_args, omitted_suffix_vars)
"""
suffix_vars = type_vars[prefix + 1 :]
num_req_suffix = sum(not v.has_default() for v in suffix_vars)
num_opt_suffix = len(suffix_vars) - num_req_suffix

if len(args) < prefix + num_req_suffix:
return tuple(args[:prefix]), (), tuple(args[prefix:]), list(suffix_vars)

opt_args_count = 0
if num_opt_suffix > 0 and len(args) - prefix > num_req_suffix:
for _ in range(num_opt_suffix):
idx = len(args) - 1 - opt_args_count
if idx < prefix + num_req_suffix:
break
candidate = args[idx]
tv = suffix_vars[len(suffix_vars) - 1 - opt_args_count]
if isinstance(tv, ParamSpecType) and is_valid_paramspec_arg(candidate):
opt_args_count += 1
else:
break

prov_suffix_count = num_req_suffix + opt_args_count
omitted_suffix_vars = list(suffix_vars[prov_suffix_count:])

prefix_args = tuple(args[:prefix])
middle_args = tuple(args[prefix : len(args) - prov_suffix_count])
suffix_args = tuple(args[len(args) - prov_suffix_count :])
return prefix_args, middle_args, suffix_args, omitted_suffix_vars
113 changes: 113 additions & 0 deletions test-data/unit/check-python313.test
Original file line number Diff line number Diff line change
Expand Up @@ -503,3 +503,116 @@ def test_vec_concat(left: Vec[A], right: Vec[B]) -> None:
_0: Vec[A | B] = concat_vecs(left, right) # OK
_1: Vec[A | B | C] = concat_vecs(left, right) # OK
_2: Vec[object] = concat_vecs(left, right) # OK

[case testPEP695TypeVarTupleWithDefaultedParamSpec]
from typing import Callable

class Foo[*Ts, **P = [float, bool]]:
def args(self) -> tuple[*Ts]: ...
def fn(self) -> Callable[P, None]: ...

foo = Foo[int, str]()
reveal_type(foo.args()) # N: Revealed type is "tuple[builtins.int, builtins.str]"
reveal_type(foo.fn()) # N: Revealed type is "def (builtins.float, builtins.bool)"

bar = Foo[int, str, [bytes]]()
reveal_type(bar.args()) # N: Revealed type is "tuple[builtins.int, builtins.str]"
reveal_type(bar.fn()) # N: Revealed type is "def (builtins.bytes)"

baz = Foo[int, str, ...]()
reveal_type(baz.args()) # N: Revealed type is "tuple[builtins.int, builtins.str]"
reveal_type(baz.fn()) # N: Revealed type is "def (*Any, **Any)"

qux = Foo[[bytes]]()
reveal_type(qux.args()) # N: Revealed type is "tuple[()]"
reveal_type(qux.fn()) # N: Revealed type is "def (builtins.bytes)"

single = Foo[int]()
reveal_type(single.args()) # N: Revealed type is "tuple[builtins.int]"
reveal_type(single.fn()) # N: Revealed type is "def (builtins.float, builtins.bool)"

def test_ann(
a: Foo[int, str],
b: Foo[int, str, [bytes]],
c: Foo[[bytes]],
d: Foo[int],
) -> None:
reveal_type(a.args()) # N: Revealed type is "tuple[builtins.int, builtins.str]"
reveal_type(a.fn()) # N: Revealed type is "def (builtins.float, builtins.bool)"
reveal_type(b.args()) # N: Revealed type is "tuple[builtins.int, builtins.str]"
reveal_type(b.fn()) # N: Revealed type is "def (builtins.bytes)"
reveal_type(c.args()) # N: Revealed type is "tuple[()]"
reveal_type(c.fn()) # N: Revealed type is "def (builtins.bytes)"
reveal_type(d.args()) # N: Revealed type is "tuple[builtins.int]"
reveal_type(d.fn()) # N: Revealed type is "def (builtins.float, builtins.bool)"

type Alias[*Ts, **P = [float, bool]] = Foo[*Ts, P]
al1: Alias[int, str]
al2: Alias[int, str, [bytes]]
reveal_type(al1.args()) # N: Revealed type is "tuple[builtins.int, builtins.str]"
reveal_type(al1.fn()) # N: Revealed type is "def (builtins.float, builtins.bool)"
reveal_type(al2.args()) # N: Revealed type is "tuple[builtins.int, builtins.str]"
reveal_type(al2.fn()) # N: Revealed type is "def (builtins.bytes)"

class Sub1(Foo[int, str]): ...
class Sub2(Foo[int, str, [bytes]]): ...
s1 = Sub1()
s2 = Sub2()
reveal_type(s1.args()) # N: Revealed type is "tuple[builtins.int, builtins.str]"
reveal_type(s1.fn()) # N: Revealed type is "def (builtins.float, builtins.bool)"
reveal_type(s2.args()) # N: Revealed type is "tuple[builtins.int, builtins.str]"
reveal_type(s2.fn()) # N: Revealed type is "def (builtins.bytes)"
[builtins fixtures/tuple.pyi]

[case testPEP695TypeVarTupleWithDefaultedParamSpecPrefix]
from typing import Callable

class PrefFoo[T, *Ts, **P = [float, bool]]:
def first(self) -> T: ...
def args(self) -> tuple[*Ts]: ...
def fn(self) -> Callable[P, None]: ...

p1 = PrefFoo[int, str, bool]()
reveal_type(p1.first()) # N: Revealed type is "builtins.int"
reveal_type(p1.args()) # N: Revealed type is "tuple[builtins.str, builtins.bool]"
reveal_type(p1.fn()) # N: Revealed type is "def (builtins.float, builtins.bool)"

p2 = PrefFoo[int, str, [bytes]]()
reveal_type(p2.first()) # N: Revealed type is "builtins.int"
reveal_type(p2.args()) # N: Revealed type is "tuple[builtins.str]"
reveal_type(p2.fn()) # N: Revealed type is "def (builtins.bytes)"

p3 = PrefFoo[int, [bytes]]()
reveal_type(p3.first()) # N: Revealed type is "builtins.int"
reveal_type(p3.args()) # N: Revealed type is "tuple[()]"
reveal_type(p3.fn()) # N: Revealed type is "def (builtins.bytes)"

p4 = PrefFoo[int]()
reveal_type(p4.first()) # N: Revealed type is "builtins.int"
reveal_type(p4.args()) # N: Revealed type is "tuple[()]"
reveal_type(p4.fn()) # N: Revealed type is "def (builtins.float, builtins.bool)"
[builtins fixtures/tuple.pyi]

[case testPEP695TypeVarTupleWithDefaultedParamSpecBothDefaults]
from typing import Callable

class BothFoo[*Ts = *tuple[int, str], **P = [float, bool]]:
def args(self) -> tuple[*Ts]: ...
def fn(self) -> Callable[P, None]: ...

b1 = BothFoo()
reveal_type(b1.args()) # N: Revealed type is "tuple[builtins.int, builtins.str]"
reveal_type(b1.fn()) # N: Revealed type is "def (builtins.float, builtins.bool)"

b2 = BothFoo[bool]()
reveal_type(b2.args()) # N: Revealed type is "tuple[builtins.bool]"
reveal_type(b2.fn()) # N: Revealed type is "def (builtins.float, builtins.bool)"

b3 = BothFoo[[bytes]]()
reveal_type(b3.args()) # N: Revealed type is "tuple[()]"
reveal_type(b3.fn()) # N: Revealed type is "def (builtins.bytes)"

b4 = BothFoo[bool, [bytes]]()
reveal_type(b4.args()) # N: Revealed type is "tuple[builtins.bool]"
reveal_type(b4.fn()) # N: Revealed type is "def (builtins.bytes)"
[builtins fixtures/tuple.pyi]
Loading
Loading