diff --git a/docs/source/error_code_list2.rst b/docs/source/error_code_list2.rst index 11f463f93018..5bbf06a9a5f5 100644 --- a/docs/source/error_code_list2.rst +++ b/docs/source/error_code_list2.rst @@ -416,3 +416,24 @@ Example: # The following will not generate an error on either # Python 3.8, or Python 3.9 42 + "testing..." # type: ignore + + +.. _code-str-unpacking: + +Check that ``str`` is explicitly unpacked [str-unpacking] +--------------------------------------------------------- + +It can sometimes be surprising that ``str`` is iterable, especially when unpacking. + +Example: + +.. code-block:: python + + # Use "mypy --enable-error-code str-unpacking ..." + + def print_dict(d: dict[str, str]) -> int: + # We meant to do d.items(), but instead we're unpacking the str keys of d + + # Error: Unpacking a string is disallowed + for k, v in d: + print(k, v) diff --git a/mypy/checker.py b/mypy/checker.py index 1026376cce63..69925f9becc5 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3491,7 +3491,11 @@ def check_multi_assignment( self.check_multi_assignment_from_union( lvalues, rvalue, rvalue_type, context, infer_lvalue_type ) - elif isinstance(rvalue_type, Instance) and rvalue_type.type.fullname == "builtins.str": + elif ( + self.msg.errors.is_error_code_enabled(codes.STR_UNPACKING) + and isinstance(rvalue_type, Instance) + and rvalue_type.type.fullname == "builtins.str" + ): self.msg.unpacking_strings_disallowed(context) else: self.check_multi_assignment_from_iterable( diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index e87b04b6f473..868f8f39fccb 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -203,6 +203,9 @@ def __hash__(self) -> int: "General", default_enabled=False, ) +STR_UNPACKING: Final[ErrorCode] = ErrorCode( + "str-unpacking", "Warn about expressions that unpack str", "General", default_enabled=False +) NAME_MATCH: Final = ErrorCode( "name-match", "Check that type definition has consistent naming", "General" ) diff --git a/mypy/messages.py b/mypy/messages.py index 9d703a1a974a..b1db7574fdbd 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1108,7 +1108,7 @@ def wrong_number_values_to_unpack( ) def unpacking_strings_disallowed(self, context: Context) -> None: - self.fail("Unpacking a string is disallowed", context) + self.fail("Unpacking a string is disallowed", context, code=codes.STR_UNPACKING) def type_not_iterable(self, type: Type, context: Context) -> None: self.fail(f"{format_type(type, self.options)} object is not iterable", context) diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 1fa551f6a2e4..94ed809b28b8 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -2330,3 +2330,16 @@ T = TypeVar("T") x: int x + T # E: Unsupported operand types for + ("int" and "object") T() # E: "object" not callable + +[case testStringDisallowedUnpacking] +# flags: --enable-error-code str-unpacking +from typing import Dict + +d: Dict[str, str] + +for a, b in d: # E: Unpacking a string is disallowed + pass + +s = "foo" +a, b = s # E: Unpacking a string is disallowed +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index 65d5c1abc7e8..777383e5d1d4 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -721,26 +721,11 @@ reveal_type(d) # N: Revealed type is "builtins.list[builtins.int]" from typing import Union bad: Union[int, str] -x, y = bad # E: "int" object is not iterable \ - # E: Unpacking a string is disallowed +x, y = bad # E: "int" object is not iterable reveal_type(x) # N: Revealed type is "Any" reveal_type(y) # N: Revealed type is "Any" [out] -[case testStringDisallowedUnpacking] -from typing import Dict - -d: Dict[str, str] - -for a, b in d: # E: Unpacking a string is disallowed - pass - -s = "foo" -a, b = s # E: Unpacking a string is disallowed - -[builtins fixtures/dict.pyi] -[out] - [case testUnionAlwaysTooMany] from typing import Union, Tuple bad: Union[Tuple[int, int, int], Tuple[str, str, str]]