Skip to content

Commit 6cbb237

Browse files
fixed infer_constraints
1 parent 5e239a3 commit 6cbb237

File tree

5 files changed

+155
-38
lines changed

5 files changed

+155
-38
lines changed

mypy/argmap.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434

3535
IterableType = NewType("IterableType", Instance)
3636
"""Represents an instance of `Iterable[T]`."""
37+
TupleInstance = NewType("TupleInstance", Instance)
38+
"""Represents an instance of `builtins.tuple[T, ...]`."""
3739

3840

3941
def map_actuals_to_formals(
@@ -218,18 +220,18 @@ def expand_actual_type(
218220
if actual_kind == nodes.ARG_STAR:
219221
# parse *args as one of the following:
220222
# IterableType | TupleType | ParamSpecType | AnyType
221-
star_args = self.parse_star_args_type(actual_type)
223+
star_args_type = self.parse_star_args_type(actual_type)
222224

223-
if self.is_iterable_instance_type(star_args):
224-
return star_args.args[0]
225-
elif isinstance(star_args, TupleType):
225+
if self.is_iterable_instance_type(star_args_type):
226+
return star_args_type.args[0]
227+
elif isinstance(star_args_type, TupleType):
226228
# Get the next tuple item of a tuple *arg.
227-
if self.tuple_index >= len(star_args.items):
229+
if self.tuple_index >= len(star_args_type.items):
228230
# Exhausted a tuple -- continue to the next *args.
229231
self.tuple_index = 1
230232
else:
231233
self.tuple_index += 1
232-
item = star_args.items[self.tuple_index - 1]
234+
item = star_args_type.items[self.tuple_index - 1]
233235
if isinstance(item, UnpackType) and not allow_unpack:
234236
# An unpack item that doesn't have special handling, use upper bound as above.
235237
unpacked = get_proper_type(item.type)
@@ -243,9 +245,9 @@ def expand_actual_type(
243245
)
244246
item = fallback.args[0]
245247
return item
246-
elif isinstance(star_args, ParamSpecType):
248+
elif isinstance(star_args_type, ParamSpecType):
247249
# ParamSpec is valid in *args but it can't be unpacked.
248-
return star_args
250+
return star_args_type
249251
else:
250252
return AnyType(TypeOfAny.from_error)
251253
elif actual_kind == nodes.ARG_STAR2:
@@ -301,6 +303,10 @@ def _make_iterable_instance_type(self, arg: Type) -> IterableType:
301303
value = Instance(self.context.iterable_type.type, [arg])
302304
return cast(IterableType, value)
303305

306+
def _make_tuple_instance_type(self, arg: Type) -> TupleInstance:
307+
value = Instance(self.context.tuple_type.type, [arg])
308+
return cast(TupleInstance, value)
309+
304310
def _solve_as_iterable(self, typ: Type) -> IterableType | AnyType:
305311
r"""Use the solver to cast a type as Iterable[T].
306312
@@ -391,9 +397,9 @@ def as_iterable_type(self, typ: Type) -> IterableType | AnyType:
391397
def parse_star_args_type(
392398
self, typ: Type
393399
) -> TupleType | IterableType | ParamSpecType | AnyType:
394-
"""Parse the type of a *args argument.
400+
"""Parse the type of a ``*args`` argument.
395401
396-
Returns one TupleType, IterableType, ParamSpecType or AnyType.
402+
Returns one of TupleType, TupleInstance or AnyType.
397403
"""
398404
p_t = get_proper_type(typ)
399405
if isinstance(p_t, (TupleType, ParamSpecType, AnyType)):

mypy/checkexpr.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2288,6 +2288,7 @@ def argument_infer_context(self) -> ArgumentInferContext:
22882288
self.chk.named_type("typing.Mapping"),
22892289
self.chk.named_type("typing.Iterable"),
22902290
self.chk.named_type("builtins.function"),
2291+
self.chk.named_type("builtins.tuple"),
22912292
)
22922293
return self._arg_infer_context_cache
22932294

@@ -5251,33 +5252,35 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
52515252
ctx = ctx_item.type
52525253
else:
52535254
ctx = None
5254-
tt = self.accept(item.expr, ctx)
5255-
tt = get_proper_type(tt)
5256-
# convert tt to one of TupleType, IterableType, AnyType or
5257-
mapper = ArgTypeExpander(self.argument_infer_context())
5258-
tt = mapper.parse_star_args_type(tt)
5259-
5260-
if isinstance(tt, TupleType):
5261-
if find_unpack_in_list(tt.items) is not None:
5255+
5256+
arg_type_expander = ArgTypeExpander(self.argument_infer_context())
5257+
original_arg_type = self.accept(item.expr, ctx)
5258+
# convert arg type to one of TupleType, IterableType, AnyType or
5259+
star_args_type = arg_type_expander.parse_star_args_type(original_arg_type)
5260+
5261+
if isinstance(star_args_type, TupleType):
5262+
if find_unpack_in_list(star_args_type.items) is not None:
52625263
if seen_unpack_in_items:
52635264
# Multiple unpack items are not allowed in tuples,
52645265
# fall back to instance type.
52655266
return self.check_lst_expr(e, "builtins.tuple", "<tuple>")
52665267
else:
52675268
seen_unpack_in_items = True
5268-
items.extend(tt.items)
5269+
items.extend(star_args_type.items)
52695270
# Note: this logic depends on full structure match in tuple_context_matches().
52705271
if unpack_in_context:
52715272
j += 1
52725273
else:
52735274
# If there is an unpack in expressions, but not in context, this will
52745275
# result in an error later, just do something predictable here.
5275-
j += len(tt.items)
5276+
j += len(star_args_type.items)
52765277
else:
52775278
if allow_precise_tuples and not seen_unpack_in_items:
52785279
# Handle (x, *y, z), where y is e.g. tuple[Y, ...].
5279-
if isinstance(tt, Instance) and self.chk.type_is_iterable(tt):
5280-
item_type = self.chk.iterable_item_type(tt, e)
5280+
if isinstance(star_args_type, Instance) and self.chk.type_is_iterable(
5281+
star_args_type
5282+
):
5283+
item_type = self.chk.iterable_item_type(star_args_type, e)
52815284
mapped = self.chk.named_generic_type("builtins.tuple", [item_type])
52825285
items.append(UnpackType(mapped))
52835286
seen_unpack_in_items = True

mypy/constraints.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,18 @@ def infer_constraints_for_callable(
167167
allow_unpack=True,
168168
)
169169

170-
if arg_kinds[actual] != ARG_STAR or isinstance(
171-
get_proper_type(actual_arg_type), TupleType
172-
):
173-
actual_types.append(expanded_actual)
170+
if arg_kinds[actual] == ARG_STAR:
171+
# Use the expanded form, one of TupleType | IterableType | ParamSpecType | AnyType
172+
star_args_type = mapper.parse_star_args_type(actual_arg_type)
173+
if isinstance(star_args_type, TupleType):
174+
actual_types.append(expanded_actual)
175+
else:
176+
# If we are expanding an iterable inside * actual, append a homogeneous item instead
177+
actual_types.append(
178+
UnpackType(tuple_instance.copy_modified(args=[expanded_actual]))
179+
)
174180
else:
175-
# If we are expanding an iterable inside * actual, append a homogeneous item instead
176-
actual_types.append(
177-
UnpackType(tuple_instance.copy_modified(args=[expanded_actual]))
178-
)
181+
actual_types.append(expanded_actual)
179182

180183
if isinstance(unpacked_type, TypeVarTupleType):
181184
constraints.append(

mypy/infer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class ArgumentInferContext(NamedTuple):
2929
mapping_type: Instance
3030
iterable_type: Instance
3131
function_type: Instance
32+
tuple_type: Instance
3233

3334

3435
def infer_function_type_arguments(

test-data/unit/check-kwargs.test

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -498,14 +498,92 @@ f(**m)
498498
g(**m) # E: Argument 1 to "g" has incompatible type "**Mapping[str, object]"; expected "int"
499499
[builtins fixtures/dict.pyi]
500500

501-
[case testTupleExpressionWithUnionStarArgs]
501+
[case testTupleConversionWithUnionStarArgs]
502502
from typing import Union, List, Set, Tuple, Unpack, TypeVarTuple
503503

504504
class A: pass
505505
class B: pass
506506
class C: pass
507507
class D: pass
508508

509+
Ts = TypeVarTuple('Ts')
510+
def as_tuple(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
511+
return (*args,)
512+
513+
def test_union_same_size_tuple(
514+
x1: Union[tuple[A, B], tuple[None, None]],
515+
x2: Union[tuple[A, B], tuple[None, Unpack[tuple[None]]]],
516+
x3: Union[tuple[A, B], tuple[None, None, Unpack[tuple[()]]]],
517+
xx: tuple[Union[A, None], Union[B, None]], # reference type
518+
) -> None:
519+
reveal_type( as_tuple(*x1) ) # N: Revealed type is "tuple[Union[__main__.A, None], Union[__main__.B, None]]"
520+
reveal_type( as_tuple(*x2) ) # N: Revealed type is "tuple[Union[__main__.A, None], Union[__main__.B, None]]"
521+
reveal_type( as_tuple(*x3) ) # N: Revealed type is "tuple[Union[__main__.A, None], Union[__main__.B, None]]"
522+
reveal_type( as_tuple(*xx) ) # N: Revealed type is "tuple[Union[__main__.A, None], Union[__main__.B, None]]"
523+
524+
def test_union_different_size_tuple(
525+
y1: Union[tuple[A, B], tuple[None, None, None]],
526+
y2: Union[tuple[A, B], tuple[None]],
527+
y3: Union[tuple[A, B], tuple[None, None, None, Unpack[tuple[()]]]],
528+
yy: tuple[Union[A, B, None], ...], # reference type
529+
) -> None:
530+
reveal_type( as_tuple(*y1) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, __main__.B, None], ...]"
531+
reveal_type( as_tuple(*y2) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, __main__.B, None], ...]"
532+
reveal_type( as_tuple(*y2) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, __main__.B, None], ...]"
533+
reveal_type( as_tuple(*yy) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, __main__.B, None], ...]"
534+
535+
def test_union_fixed_size_and_variadic_tuple(
536+
z1: Union[tuple[A, B, C], tuple[None, ...]],
537+
z2: Union[tuple[A, B, C], tuple[None, Unpack[tuple[None, ...]], None]],
538+
zz: tuple[Union[A, B, C, None], ...], # reference type
539+
) -> None:
540+
reveal_type( as_tuple(*z1) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, __main__.B, __main__.C, None], ...]"
541+
reveal_type( as_tuple(*z2) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, __main__.B, __main__.C, None], ...]"
542+
reveal_type( as_tuple(*zz) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, __main__.B, __main__.C, None], ...]"
543+
544+
def test_union_variable_size_tuples(
545+
# same + subtype
546+
tt1: Union[Tuple[Union[A, None], ...], Tuple[A, ...]],
547+
ll1: Union[List[Union[A, None]], List[A]],
548+
ss1: Union[Set[Union[A, None]], Set[A]],
549+
# mixed + subtype
550+
tl1: Union[Tuple[Union[A, None], ...], List[A]],
551+
ts1: Union[Tuple[Union[A, None], ...], Set[A]],
552+
ls1: Union[List[Union[A, None]], Set[A]],
553+
# same + not subtype
554+
tt2: Union[Tuple[A, ...], Tuple[B, ...]],
555+
ll2: Union[List[A], List[B]],
556+
ss2: Union[Set[A], Set[B]],
557+
# mixed + not subtype
558+
tl2: Union[Tuple[A, ...], List[B]],
559+
ts2: Union[Tuple[A, ...], Set[B]],
560+
ls2: Union[List[A], Set[B]],
561+
) -> None:
562+
reveal_type( as_tuple(*tt1) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, None], ...]"
563+
reveal_type( as_tuple(*ll1) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, None], ...]"
564+
reveal_type( as_tuple(*ss1) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, None], ...]"
565+
reveal_type( as_tuple(*tl1) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, None], ...]"
566+
reveal_type( as_tuple(*ts1) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, None], ...]"
567+
reveal_type( as_tuple(*ls1) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, None], ...]"
568+
reveal_type( as_tuple(*tt2) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, __main__.B], ...]"
569+
reveal_type( as_tuple(*ll2) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, __main__.B], ...]"
570+
reveal_type( as_tuple(*ss2) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, __main__.B], ...]"
571+
reveal_type( as_tuple(*tl2) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, __main__.B], ...]"
572+
reveal_type( as_tuple(*ts2) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, __main__.B], ...]"
573+
reveal_type( as_tuple(*ls2) ) # N: Revealed type is "builtins.tuple[Union[__main__.A, __main__.B], ...]"
574+
575+
NESTED = Union[str, list[NESTED]]
576+
def test_union_recursive(x: Union[list[Union[NESTED, None]], list[NESTED]]) -> None:
577+
reveal_type( as_tuple(*x) ) # N: Revealed type is "builtins.tuple[Union[builtins.str, builtins.list[Union[builtins.str, builtins.list[...]]], None], ...]"
578+
[builtins fixtures/primitives.pyi]
579+
580+
[case testTupleExpressionWithUnionStarArgs]
581+
from typing import Union, List, Set, Tuple, Unpack
582+
583+
class A: pass
584+
class B: pass
585+
class C: pass
586+
class D: pass
509587

510588
def test_union_same_size_tuple(
511589
x1: Union[tuple[A, B], tuple[None, None]],
@@ -576,7 +654,7 @@ def test_union_recursive(x: Union[list[Union[NESTED, None]], list[NESTED]]) -> N
576654

577655

578656
[case testListExpressionWithUnionStarArgs]
579-
from typing import Union, List, Set, Tuple, Unpack, TypeVarTuple
657+
from typing import Union, List, Set, Tuple, Unpack
580658

581659
class A: pass
582660
class B: pass
@@ -652,7 +730,7 @@ def test_union_recursive(x: Union[list[Union[NESTED, None]], list[NESTED]]) -> N
652730

653731

654732
[case testSetExpressionWithUnionStarArgs]
655-
from typing import Union, List, Set, Tuple, Unpack, TypeVarTuple
733+
from typing import Union, List, Set, Tuple, Unpack
656734

657735
class A: pass
658736
class B: pass
@@ -727,22 +805,48 @@ def test_union_recursive(x: Union[list[Union[NESTED, None]], list[NESTED]]) -> N
727805
[builtins fixtures/primitives.pyi]
728806

729807

730-
[case testStarArgsWithUnionTypeVarTuple-xfail]
808+
[case testStarArgsWithPaddedTypeVarTuple]
731809
# https://github.com/python/mypy/issues/19659
732810
from typing import Union, TypeVarTuple
733811

734812
class A: pass
735813
Ts = TypeVarTuple('Ts')
736814

737815
def test_union_typevartuple(
738-
tt1: Union[tuple[A, *Ts, A], tuple[None, None, None]],
816+
padded_tuple: tuple[A, *Ts, A],
817+
padded_union: Union[tuple[A, *Ts, A], tuple[None, None, None]],
739818
) -> None:
740819
# technically, this should be ``typ[A | None | Union[*Ts]]``
741-
reveal_type( (*tt1,) ) # N: Revealed type is "builtins.tuple[Any, ...]"
742-
reveal_type( [*tt1] ) # N: Revealed type is "builtins.list[Any]"
743-
reveal_type( {*tt1} ) # N: Revealed type is "builtins.set[Any]"
820+
reveal_type( (*padded_tuple,) ) # N: Revealed type is "tuple[__main__.A, Unpack[Ts`-1], __main__.A]"
821+
reveal_type( [*padded_tuple] ) # N: Revealed type is "builtins.list[builtins.object]"
822+
reveal_type( {*padded_tuple} ) # N: Revealed type is "builtins.set[builtins.object]"
823+
reveal_type( (*padded_union,) ) # N: Revealed type is "builtins.tuple[builtins.object, ...]"
824+
reveal_type( [*padded_union] ) # N: Revealed type is "builtins.list[builtins.object]"
825+
reveal_type( {*padded_union} ) # N: Revealed type is "builtins.set[builtins.object]"
744826
[builtins fixtures/primitives.pyi]
745827

828+
[case testStarArgsWithParamSpec]
829+
# https://github.com/python/mypy/issues/19663
830+
from typing import Union, ParamSpec, Callable
831+
832+
P = ParamSpec('P')
833+
834+
def test_paramspec(
835+
a: A, # E: Name "A" is not defined
836+
dummy: Callable[P, None], # ensure P is bound
837+
/,
838+
*args: P.args,
839+
**kwargs: P.kwargs,
840+
) -> None:
841+
# technically, this should be ``typ[A | None | Union[*Ts]]``
842+
reveal_type( args ) # N: Revealed type is "P.args`-1"
843+
reveal_type( (*args,) ) # N: Revealed type is "builtins.tuple[P.args`-1, ...]"
844+
reveal_type( [*args] ) # N: Revealed type is "builtins.list[P.args`-1]"
845+
reveal_type( {*args} ) # N: Revealed type is "builtins.set[P.args`-1]"
846+
847+
# test padded tuple expression
848+
reveal_type( (a, *args, a) ) # N: Revealed type is "builtins.tuple[__main__.A, Unpack[builtins.tuple[P.args`-1, ...]], __main__.A]"
849+
[builtins fixtures/primitives.pyi]
746850

747851
[case testStarArgsWithUnion]
748852
from typing import Union

0 commit comments

Comments
 (0)