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
19 changes: 13 additions & 6 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4116,8 +4116,19 @@ def check_multi_assignment(
lvalues, rvalue, rvalue_type, context, infer_lvalue_type
)
else:
rvalue_literal = rvalue_type
if isinstance(rvalue_type, Instance) and rvalue_type.type.fullname == "builtins.str":
if rvalue_type.last_known_value is None:
self.msg.unpacking_strings_disallowed(context)
else:
rvalue_literal = rvalue_type.last_known_value
if (
isinstance(rvalue_literal, LiteralType)
and isinstance(rvalue_literal.value, str)
and len(lvalues) != len(rvalue_literal.value)
):
self.msg.unpacking_strings_disallowed(context)

self.check_multi_assignment_from_iterable(
lvalues, rvalue_type, context, infer_lvalue_type
)
Expand Down Expand Up @@ -4363,9 +4374,7 @@ def check_multi_assignment_from_iterable(
infer_lvalue_type: bool = True,
) -> None:
rvalue_type = get_proper_type(rvalue_type)
if self.type_is_iterable(rvalue_type) and isinstance(
rvalue_type, (Instance, CallableType, TypeType, Overloaded)
):
if self.type_is_iterable(rvalue_type):
item_type = self.iterable_item_type(rvalue_type, context)
for lv in lvalues:
if isinstance(lv, StarExpr):
Expand Down Expand Up @@ -7803,9 +7812,7 @@ def note(
return
self.msg.note(msg, context, offset=offset, code=code)

def iterable_item_type(
self, it: Instance | CallableType | TypeType | Overloaded, context: Context
) -> Type:
def iterable_item_type(self, it: ProperType, context: Context) -> Type:
if isinstance(it, Instance):
iterable = map_instance_to_supertype(it, self.lookup_typeinfo("typing.Iterable"))
item_type = iterable.args[0]
Expand Down
21 changes: 21 additions & 0 deletions test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -2505,3 +2505,24 @@ a2, b2 = s # E: Unpacking a string is disallowed
reveal_type(a2) # N: Revealed type is "builtins.str"
reveal_type(b2) # N: Revealed type is "builtins.str"
[builtins fixtures/primitives.pyi]

[case testStringLiteralUnpacking]
from typing import Literal, Final

def takes_literal(xy: Literal["xy"]):
x, y = xy
reveal_type(x) # N: Revealed type is "builtins.str"
reveal_type(y) # N: Revealed type is "builtins.str"

x, y, z = xy # E: Unpacking a string is disallowed
reveal_type(z) # N: Revealed type is "builtins.str"

def last_known_value() -> None:
xy: Final = "xy"
x, y = xy
reveal_type(x) # N: Revealed type is "builtins.str"
reveal_type(y) # N: Revealed type is "builtins.str"

x, y, z = xy # E: Unpacking a string is disallowed
reveal_type(z) # N: Revealed type is "builtins.str"
[builtins fixtures/primitives.pyi]
2 changes: 1 addition & 1 deletion test-data/unit/check-tuples.test
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ a, b = None, None # type: (A, B)
a1, b1 = a, a # type: (A, B) # E: Incompatible types in assignment (expression has type "A", variable has type "B")
a2, b2 = b, b # type: (A, B) # E: Incompatible types in assignment (expression has type "B", variable has type "A")
a3, b3 = a # type: (A, B) # E: "A" object is not iterable
a4, b4 = None # type: (A, B) # E: "None" object is not iterable
a4, b4 = None # type: (A, B) # E: "None" has no attribute "__iter__" (not iterable)
a5, b5 = a, b, a # type: (A, B) # E: Too many values to unpack (2 expected, 3 provided)

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