-
-
Notifications
You must be signed in to change notification settings - Fork 3k
[conflict] Fix #1855: Multiassign from Union #2154
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
Changes from 1 commit
7e043d0
9a50d73
5c4d86e
60cfbbb
5e9e0f2
71f8475
42b6e73
0560bd8
7683ee2
8f0316f
fde83d5
2fbf387
da3a516
ab60317
6bb5519
0d2c594
2c73db5
d2c6e8b
5c03ac9
c3e1b35
87f7d90
c752b42
5f0d02c
bec9454
a9fac0b
ea23ac0
4a34623
2db6a11
6151d20
4a457fc
c8a9b52
af0b24a
d5c5c02
640921b
20aea69
a3b6546
91a2275
bc4a390
2e6cb95
d6a7069
eb16d1d
dd71ddc
38651c4
9000099
f20f3d6
61be4e9
9830cb4
59dc8b7
7f304e4
ff1ca80
168087e
3f198cf
4fa059f
ab35a4c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,7 +48,7 @@ | |
from mypy.erasetype import erase_typevars | ||
from mypy.expandtype import expand_type | ||
from mypy.visitor import NodeVisitor | ||
from mypy.join import join_types | ||
from mypy.join import join_types, join_type_list | ||
from mypy.treetransform import TransformVisitor | ||
from mypy.meet import meet_simple, nearest_builtin_ancestor, is_overlapping_types | ||
from mypy.binder import ConditionalTypeBinder | ||
|
@@ -1037,8 +1037,13 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type | |
new_syntax: bool = False) -> None: | ||
"""Type check a single assignment: lvalue = rvalue.""" | ||
if isinstance(lvalue, TupleExpr) or isinstance(lvalue, ListExpr): | ||
self.check_assignment_to_multiple_lvalues(lvalue.items, rvalue, lvalue, | ||
infer_lvalue_type) | ||
if isinstance(rvalue, TupleExpr) or isinstance(rvalue, ListExpr): | ||
self.check_multi_assign_literal(lvalue.items, rvalue, lvalue, infer_lvalue_type) | ||
return | ||
# Infer the type of an ordinary rvalue expression. | ||
# TODO maybe elsewhere; redundant | ||
rvalue_type = self.accept(rvalue) | ||
self.check_multi_assign(lvalue.items, rvalue, rvalue_type, lvalue, infer_lvalue_type) | ||
else: | ||
lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) | ||
if lvalue_type: | ||
|
@@ -1089,44 +1094,6 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type | |
if inferred: | ||
self.infer_variable_type(inferred, lvalue, self.accept(rvalue), | ||
rvalue) | ||
|
||
def check_assignment_to_multiple_lvalues(self, lvalues: List[Lvalue], rvalue: Expression, | ||
context: Context, | ||
infer_lvalue_type: bool = True) -> None: | ||
if isinstance(rvalue, TupleExpr) or isinstance(rvalue, ListExpr): | ||
# Recursively go into Tuple or List expression rhs instead of | ||
# using the type of rhs, because this allowed more fine grained | ||
# control in cases like: a, b = [int, str] where rhs would get | ||
# type List[object] | ||
# Tuple is also special cased to handle mutually nested lists and tuples | ||
|
||
rvalues = rvalue.items | ||
|
||
if self.check_rvalue_count_in_assignment(lvalues, len(rvalues), context): | ||
star_index = next((i for i, lv in enumerate(lvalues) if | ||
isinstance(lv, StarExpr)), len(lvalues)) | ||
|
||
left_lvs = lvalues[:star_index] | ||
star_lv = cast(StarExpr, | ||
lvalues[star_index]) if star_index != len(lvalues) else None | ||
right_lvs = lvalues[star_index + 1:] | ||
|
||
left_rvs, star_rvs, right_rvs = self.split_around_star( | ||
rvalues, star_index, len(lvalues)) | ||
|
||
lr_pairs = list(zip(left_lvs, left_rvs)) | ||
if star_lv: | ||
rv_list = ListExpr(star_rvs) | ||
rv_list.set_line(rvalue.get_line()) | ||
lr_pairs.append((star_lv.expr, rv_list)) | ||
lr_pairs.extend(zip(right_lvs, right_rvs)) | ||
|
||
for lv, rv in lr_pairs: | ||
self.check_assignment(lv, rv, infer_lvalue_type) | ||
else: | ||
rvalue_type = self.accept(rvalue) # TODO maybe elsewhere; redundant | ||
self.check_multi_assignment(lvalues, rvalue, rvalue_type, context, infer_lvalue_type) | ||
|
||
def check_rvalue_count_in_assignment(self, lvalues: List[Lvalue], rvalue_count: int, | ||
context: Context) -> bool: | ||
if any(isinstance(lvalue, StarExpr) for lvalue in lvalues): | ||
|
@@ -1140,39 +1107,77 @@ def check_rvalue_count_in_assignment(self, lvalues: List[Lvalue], rvalue_count: | |
return False | ||
return True | ||
|
||
def check_multi_assignment(self, lvalues: List[Lvalue], | ||
rvalue: Expression, | ||
rvalue_type: Type, | ||
context: Context, | ||
infer_lvalue_type: bool = True) -> None: | ||
"""Check the assignment of one rvalue to a number of lvalues.""" | ||
# Infer the type of an ordinary rvalue expression. | ||
undefined_rvalue = False | ||
|
||
def check_multi_assign_literal(self, lvalues: List[Lvalue], | ||
rvalue: Union[ListExpr, TupleExpr], | ||
context: Context, infer_lvalue_type: bool = True) -> None: | ||
# Recursively go into Tuple or List expression rhs instead of | ||
# using the type of rhs, because this allowed more fine grained | ||
# control in cases like: a, b = [int, str] where rhs would get | ||
# type List[object] | ||
# Tuple is also special cased to handle mutually nested lists and tuples | ||
rvalues = rvalue.items | ||
if self.check_rvalue_count_in_assignment(lvalues, len(rvalues), context): | ||
star_index = next((i for (i, lv) in enumerate(lvalues) if isinstance(lv, StarExpr)), | ||
len(lvalues)) | ||
left_lvs = lvalues[:star_index] | ||
star_lv = cast(StarExpr, lvalues[star_index]) if star_index != len(lvalues) else None | ||
right_lvs = lvalues[star_index + 1:] | ||
left_rvs, star_rvs, right_rvs = self.split_around_star( | ||
rvalues, star_index, len(lvalues)) | ||
lr_pairs = list(zip(left_lvs, left_rvs)) | ||
if star_lv: | ||
rv_list = ListExpr(star_rvs) | ||
rv_list.set_line(rvalue.get_line()) | ||
lr_pairs.append((star_lv.expr, rv_list)) | ||
lr_pairs.extend(zip(right_lvs, right_rvs)) | ||
for lv, rv in lr_pairs: | ||
self.check_assignment(lv, rv, infer_lvalue_type) | ||
|
||
def check_multi_assign(self, lvalues: List[Lvalue], | ||
rvalue: Expression, rvalue_type: Type, | ||
context: Context, infer_lvalue_type: bool = True, | ||
undefined_rvalue = False) -> None: | ||
if isinstance(rvalue_type, AnyType): | ||
for lv in lvalues: | ||
if isinstance(lv, StarExpr): | ||
lv = lv.expr | ||
self.check_assignment(lv, self.temp_node(AnyType(), context), infer_lvalue_type) | ||
elif isinstance(rvalue_type, TupleType): | ||
self.check_multi_assignment_from_tuple(lvalues, rvalue, rvalue_type, | ||
context, undefined_rvalue, infer_lvalue_type) | ||
self.check_multi_assign_from_tuple(lvalues, rvalue, rvalue_type, | ||
context, undefined_rvalue, infer_lvalue_type) | ||
elif isinstance(rvalue_type, UnionType): | ||
for item in rvalue_type.items: | ||
self.check_multi_assignment(lvalues, rvalue, item, context, infer_lvalue_type) | ||
self.check_multi_assign_from_union(lvalues, rvalue, rvalue_type, | ||
context, infer_lvalue_type) | ||
elif isinstance(rvalue_type, Instance) and self.type_is_iterable(rvalue_type): | ||
self.check_multi_assignment_from_iterable(lvalues, rvalue_type, | ||
context, infer_lvalue_type) | ||
self.check_multi_assign_from_iterable(lvalues, rvalue_type, | ||
context, infer_lvalue_type) | ||
else: | ||
self.msg.type_not_iterable(rvalue_type, context) | ||
|
||
def type_is_iterable(self, rvalue_type: Type) -> bool: | ||
return is_subtype(rvalue_type, self.named_generic_type('typing.Iterable', | ||
[AnyType()])) | ||
|
||
def check_multi_assignment_from_iterable(self, lvalues: List[Node], rvalue_type: Instance, | ||
context: Context, | ||
infer_lvalue_type: bool = True) -> None: | ||
def check_multi_assign_from_union(self, lvalues: List[Expression], rvalue: Expression, | ||
rvalue_type: UnionType, context: Context, | ||
infer_lvalue_type: bool) -> None: | ||
union_types = tuple([] for _ in lvalues) # type: Tuple[List[Type], ...] | ||
for item in rvalue_type.items: | ||
self.check_multi_assign(lvalues, rvalue, item, context, infer_lvalue_type, | ||
undefined_rvalue=True) | ||
for t, lv in zip(union_types, lvalues): | ||
t.append(self.type_map[lv]) | ||
for ut, lv in zip(union_types, lvalues): | ||
_1, _2, inferred = self.check_lvalue(lv) | ||
union = join_type_list(ut) | ||
if inferred: | ||
self.set_inferred_type(inferred, lv, union) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You'll probably need to put the new type to the binder (maybe
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The assignment to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simple |
||
else: | ||
self.store_type(lv, union) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The binding of the joined type is "retrofitted". I am not sure this is the best way to do it - maybe inferred vars should simply accumulate through joins? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Retrofitting like this may not be a problem, but I suspect it might be. Accumulating through joins might not be any better. The most correct approach would be to infer the entire type for an lvalue before setting it, so that intermediate values can't leak. I don't know, but it may be possible that the intermediate lvalue types leak to the rvalue in some weird corner cases. I'd recommend not worrying about this too much, but I'd suggest adding a TODO comment about the potential issues. |
||
|
||
def check_multi_assign_from_iterable(self, lvalues: List[Expression], rvalue_type: Instance, | ||
context: Context, | ||
infer_lvalue_type: bool = True) -> None: | ||
item_type = self.iterable_item_type(rvalue_type) | ||
for lv in lvalues: | ||
if isinstance(lv, StarExpr): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -130,7 +130,7 @@ a = C() # type: C[int, int] | |
b = a.f('a') | ||
a.f(b) # E: Argument 1 to "f" of "C" has incompatible type "int"; expected "str" | ||
|
||
[case testUnionMultissign] | ||
[case testUnionMultiassign1] | ||
from typing import Union, Tuple, Any | ||
|
||
a = None # type: Tuple[int] | ||
|
@@ -139,8 +139,17 @@ a = None # type: Tuple[int] | |
b = None # type: Union[Tuple[int], Tuple[float]] | ||
(b1,) = b | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reveal type of |
||
|
||
c = None # type: Union[Tuple[int, int], Tuple[float, float]] | ||
[case testUnionMultiassign2] | ||
from typing import Union, Tuple | ||
|
||
c = None # type: Union[Tuple[int, int], Tuple[int, str]] | ||
q = None # type: Tuple[int, float] | ||
(c1, c2) = c | ||
reveal_type(c1) # E: Revealed type is 'builtins.int' | ||
reveal_type(c2) # E: Revealed type is 'builtins.object' | ||
|
||
[case testUnionMultiassign3] | ||
from typing import Union, Tuple, Any | ||
|
||
d = None # type: Union[Any, Tuple[float, float]] | ||
(d1, d2) = d | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reveal types of |
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No annotation for
undefined_rvalue
.