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

Allow iterable class objects to be unpacked #14803

Closed
wants to merge 11 commits into from
15 changes: 15 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3385,6 +3385,9 @@ def check_multi_assignment(
undefined_rvalue: bool = False,
) -> None:
"""Check the assignment of one rvalue to a number of lvalues."""
# TODO: Overloaded `__iter__` methods
# lead to unpacked variables being silently inferred as `Any`.
# See #14811
AlexWaygood marked this conversation as resolved.
Show resolved Hide resolved

# Infer the type of an ordinary rvalue expression.
# TODO: maybe elsewhere; redundant.
Expand All @@ -3399,6 +3402,18 @@ def check_multi_assignment(
if len(relevant_items) == 1:
rvalue_type = get_proper_type(relevant_items[0])

if isinstance(rvalue_type, FunctionLike) and rvalue_type.is_type_obj():
ret_type = get_proper_type(rvalue_type.items[0].ret_type)
AlexWaygood marked this conversation as resolved.
Show resolved Hide resolved
if isinstance(ret_type, Instance):
metaclass = ret_type.type.metaclass_type
if metaclass and metaclass.type.get_method("__iter__"):
rvalue_type = metaclass

if isinstance(rvalue_type, TypeType) and isinstance(rvalue_type.item, Instance):
metaclass = rvalue_type.item.type.metaclass_type
if metaclass and metaclass.type.get_method("__iter__"):
rvalue_type = metaclass

if isinstance(rvalue_type, AnyType):
for lv in lvalues:
if isinstance(lv, StarExpr):
Expand Down
88 changes: 88 additions & 0 deletions test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,94 @@ def f() -> None:
class A: pass
[out]

[case testClassObjectsNotUnpackableWithoutIterableMetaclass]
from typing import Type

class Foo: ...
A: Type[Foo] = Foo
a, b = Foo # E: "Type[Foo]" object is not iterable
c, d = A # E: "Type[Foo]" object is not iterable

class Meta(type): ...
class Bar(metaclass=Meta): ...
B: Type[Bar] = Bar
e, f = Bar # E: "Type[Bar]" object is not iterable
g, h = B # E: "Type[Bar]" object is not iterable

reveal_type(a) # E: Cannot determine type of "a" # N: Revealed type is "Any"
reveal_type(b) # E: Cannot determine type of "b" # N: Revealed type is "Any"
reveal_type(c) # E: Cannot determine type of "c" # N: Revealed type is "Any"
reveal_type(d) # E: Cannot determine type of "d" # N: Revealed type is "Any"
reveal_type(e) # E: Cannot determine type of "e" # N: Revealed type is "Any"
reveal_type(f) # E: Cannot determine type of "f" # N: Revealed type is "Any"
reveal_type(g) # E: Cannot determine type of "g" # N: Revealed type is "Any"
reveal_type(h) # E: Cannot determine type of "h" # N: Revealed type is "Any"
[out]

[case testInferringLvarTypesUnpackedFromIterableClassObject]
from typing import Iterator, Type, TypeVar, Union
class Meta(type):
def __iter__(cls) -> Iterator[int]:
yield from [1, 2, 3]

class Meta2(type):
def __iter__(cls) -> Iterator[str]:
yield from ["foo", "bar", "baz"]

class Meta3(type): ...

class Foo(metaclass=Meta): ...
class Bar(metaclass=Meta2): ...
class Baz(metaclass=Meta3): ...
class Spam: ...

A: Type[Foo] = Foo
B: Type[Union[Foo, Bar]] = Foo
C: Union[Type[Foo], Type[Bar]] = Foo
D: Type[Union[Foo, Baz]] = Foo
E: Type[Union[Foo, Spam]] = Foo

a, b, c = Foo
d, e, f = A
g, h, i = B
j, k, l = C
m, n, o = D # E: "Type[Baz]" object is not iterable
p, q, r = E # E: "Type[Spam]" object is not iterable

for var in [a, b, c, d, e, f]:
reveal_type(var) # N: Revealed type is "builtins.int"

for var2 in [g, h, i, j, k, l]:
reveal_type(var2) # N: Revealed type is "Union[builtins.int, builtins.str]"

for var3 in [m, n, o, p, q, r]:
reveal_type(var3) # N: Revealed type is "Union[builtins.int, Any]"

T = TypeVar("T", bound=Type[Foo])

def check(x: T) -> T:
a, b, c = x
for var in [a, b, c]:
reveal_type(var) # N: Revealed type is "builtins.int"
return x

T2 = TypeVar("T2", bound=Type[Union[Foo, Bar]])

def check2(x: T2) -> T2:
a, b, c = x
for var in [a, b, c]:
reveal_type(var) # N: Revealed type is "Union[builtins.int, builtins.str]"
return x

T3 = TypeVar("T3", bound=Union[Type[Foo], Type[Bar]])

def check3(x: T3) -> T3:
a, b, c = x
for var in [a, b, c]:
reveal_type(var) # N: Revealed type is "Union[builtins.int, builtins.str]"
return x
[out]

[case testInferringLvarTypesInMultiDefWithInvalidTuple]
from typing import Tuple
t = None # type: Tuple[object, object, object]
Expand Down