Skip to content

Commit ab63d64

Browse files
authored
Treat Literal["xyz"] as iterable (#20347)
The extra condition that excluded LiteralType was introduced in #14827. I see no particular reason to have an instance check at all I was looking at this because of this comment from Emma #15511 (comment) Previously we errored with `"Literal['xy']" object is not iterable` which is of course totally false Now I issue the same error as in the str case, but restrict to cases where the unpack length does not match Related to #20325
1 parent f1bb818 commit ab63d64

File tree

3 files changed

+35
-7
lines changed

3 files changed

+35
-7
lines changed

mypy/checker.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4116,8 +4116,19 @@ def check_multi_assignment(
41164116
lvalues, rvalue, rvalue_type, context, infer_lvalue_type
41174117
)
41184118
else:
4119+
rvalue_literal = rvalue_type
41194120
if isinstance(rvalue_type, Instance) and rvalue_type.type.fullname == "builtins.str":
4121+
if rvalue_type.last_known_value is None:
4122+
self.msg.unpacking_strings_disallowed(context)
4123+
else:
4124+
rvalue_literal = rvalue_type.last_known_value
4125+
if (
4126+
isinstance(rvalue_literal, LiteralType)
4127+
and isinstance(rvalue_literal.value, str)
4128+
and len(lvalues) != len(rvalue_literal.value)
4129+
):
41204130
self.msg.unpacking_strings_disallowed(context)
4131+
41214132
self.check_multi_assignment_from_iterable(
41224133
lvalues, rvalue_type, context, infer_lvalue_type
41234134
)
@@ -4363,9 +4374,7 @@ def check_multi_assignment_from_iterable(
43634374
infer_lvalue_type: bool = True,
43644375
) -> None:
43654376
rvalue_type = get_proper_type(rvalue_type)
4366-
if self.type_is_iterable(rvalue_type) and isinstance(
4367-
rvalue_type, (Instance, CallableType, TypeType, Overloaded)
4368-
):
4377+
if self.type_is_iterable(rvalue_type):
43694378
item_type = self.iterable_item_type(rvalue_type, context)
43704379
for lv in lvalues:
43714380
if isinstance(lv, StarExpr):
@@ -7803,9 +7812,7 @@ def note(
78037812
return
78047813
self.msg.note(msg, context, offset=offset, code=code)
78057814

7806-
def iterable_item_type(
7807-
self, it: Instance | CallableType | TypeType | Overloaded, context: Context
7808-
) -> Type:
7815+
def iterable_item_type(self, it: ProperType, context: Context) -> Type:
78097816
if isinstance(it, Instance):
78107817
iterable = map_instance_to_supertype(it, self.lookup_typeinfo("typing.Iterable"))
78117818
item_type = iterable.args[0]

test-data/unit/check-expressions.test

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2517,3 +2517,24 @@ a2, b2 = s # E: Unpacking a string is disallowed
25172517
reveal_type(a2) # N: Revealed type is "builtins.str"
25182518
reveal_type(b2) # N: Revealed type is "builtins.str"
25192519
[builtins fixtures/primitives.pyi]
2520+
2521+
[case testStringLiteralUnpacking]
2522+
from typing import Literal, Final
2523+
2524+
def takes_literal(xy: Literal["xy"]):
2525+
x, y = xy
2526+
reveal_type(x) # N: Revealed type is "builtins.str"
2527+
reveal_type(y) # N: Revealed type is "builtins.str"
2528+
2529+
x, y, z = xy # E: Unpacking a string is disallowed
2530+
reveal_type(z) # N: Revealed type is "builtins.str"
2531+
2532+
def last_known_value() -> None:
2533+
xy: Final = "xy"
2534+
x, y = xy
2535+
reveal_type(x) # N: Revealed type is "builtins.str"
2536+
reveal_type(y) # N: Revealed type is "builtins.str"
2537+
2538+
x, y, z = xy # E: Unpacking a string is disallowed
2539+
reveal_type(z) # N: Revealed type is "builtins.str"
2540+
[builtins fixtures/primitives.pyi]

test-data/unit/check-tuples.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ a, b = None, None # type: (A, B)
515515
a1, b1 = a, a # type: (A, B) # E: Incompatible types in assignment (expression has type "A", variable has type "B")
516516
a2, b2 = b, b # type: (A, B) # E: Incompatible types in assignment (expression has type "B", variable has type "A")
517517
a3, b3 = a # type: (A, B) # E: "A" object is not iterable
518-
a4, b4 = None # type: (A, B) # E: "None" object is not iterable
518+
a4, b4 = None # type: (A, B) # E: "None" has no attribute "__iter__" (not iterable)
519519
a5, b5 = a, b, a # type: (A, B) # E: Too many values to unpack (2 expected, 3 provided)
520520

521521
ax, bx = a, b # type: (A, B)

0 commit comments

Comments
 (0)