Skip to content

Commit 115c1b7

Browse files
address feedback
1 parent 9844f81 commit 115c1b7

6 files changed

Lines changed: 159 additions & 13 deletions

File tree

mypy/checker.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5378,11 +5378,10 @@ def analyze_iterable_item_type_without_expression(
53785378
echk = self.expr_checker
53795379
iterable: Type
53805380
iterable = get_proper_type(type)
5381+
53815382
if self.options.disallow_str_iteration and self.is_str_iteration_type(iterable):
53825383
self.msg.str_iteration_disallowed(context)
5383-
item_type = self.named_type("builtins.str")
5384-
iterator_type = self.named_generic_type("typing.Iterator", [item_type])
5385-
return iterator_type, item_type
5384+
53865385
iterator = echk.check_method_call_by_name("__iter__", iterable, [], [], context)[0]
53875386

53885387
if (

mypy/subtypes.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,18 @@ def visit_instance(self, left: Instance) -> bool:
484484
and self.options.disallow_str_iteration
485485
and left.type.fullname == "builtins.str"
486486
and isinstance(right, Instance)
487-
and right.type.fullname in ("typing.Sequence", "collections.abc.Sequence")
487+
and right.type.fullname
488+
in (
489+
"collections.abc.Collection",
490+
"collections.abc.Iterable",
491+
"collections.abc.Reversible",
492+
"collections.abc.Sequence",
493+
"typing.Collection",
494+
"typing.Iterable",
495+
"typing.Reversible",
496+
"typing.Sequence",
497+
"_typeshed.SupportsLenAndGetItem",
498+
)
488499
):
489500
return False
490501
if isinstance(right, TupleType) and right.partial_fallback.type.is_enum:

test-data/unit/check-flags.test

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2452,33 +2452,50 @@ f(memoryview(b"asdf")) # E: Argument 1 to "f" has incompatible type "memoryview
24522452
[builtins fixtures/primitives.pyi]
24532453

24542454
[case testDisallowStrIteration]
2455-
# flags: --disallow-str-iteration --python-version 3.12
2456-
from typing import Iterable, Sequence
2455+
# flags: --disallow-str-iteration
2456+
from typing import Collection, Iterable, Reversible, Sequence, TypeVar
2457+
2458+
def takes_str(x: str):
2459+
for ch in x: # E: Iterating over "str" is disallowed # N: This is because --disallow-str-iteration is enabled
2460+
reveal_type(ch) # N: Revealed type is "builtins.str"
2461+
[ch for ch in x] # E: Iterating over "str" is disallowed # N: This is because --disallow-str-iteration is enabled
24572462

24582463
s = "hello"
2459-
for ch in s: # E: Iterating over "str" is disallowed # N: This is because --disallow-str-iteration is enabled
2460-
reveal_type(ch) # N: Revealed type is "builtins.str"
2461-
[x for x in s] # E: Iterating over "str" is disallowed # N: This is because --disallow-str-iteration is enabled
24622464

2463-
def takes_seq(x: Sequence[int]) -> None: ...
24642465
def takes_seq_str(x: Sequence[str]) -> None: ...
24652466
takes_seq_str(s) # E: Argument 1 to "takes_seq_str" has incompatible type "str"; expected "Sequence[str]"
24662467

24672468
def takes_iter_str(x: Iterable[str]) -> None: ...
24682469
takes_iter_str(s) # E: Argument 1 to "takes_iter_str" has incompatible type "str"; expected "Iterable[str]"
24692470

2471+
def takes_collection_str(x: Collection[str]) -> None: ...
2472+
takes_collection_str(s) # E: Argument 1 to "takes_collection_str" has incompatible type "str"; expected "Collection[str]"
2473+
2474+
def takes_reversible_str(x: Reversible[str]) -> None: ...
2475+
takes_reversible_str(s) # E: Argument 1 to "takes_reversible_str" has incompatible type "str"; expected "Reversible[str]"
2476+
24702477
seq: Sequence[str] = s # E: Incompatible types in assignment (expression has type "str", variable has type "Sequence[str]")
24712478
iterable: Iterable[str] = s # E: Incompatible types in assignment (expression has type "str", variable has type "Iterable[str]")
2479+
collection: Collection[str] = s # E: Incompatible types in assignment (expression has type "str", variable has type "Collection[str]")
2480+
reversible: Reversible[str] = s # E: Incompatible types in assignment (expression has type "str", variable has type "Reversible[str]")
24722481

2473-
def takes_maybe_seq(x: str | Sequence[str]) -> None:
2482+
def takes_maybe_seq(x: "str | Sequence[int]") -> None:
24742483
for ch in x: # E: Iterating over "str" is disallowed # N: This is because --disallow-str-iteration is enabled
2475-
reveal_type(ch) # N: Revealed type is "builtins.str"
2484+
reveal_type(ch) # N: Revealed type is "builtins.str | builtins.int"
2485+
2486+
T = TypeVar('T', bound=str)
24762487

2477-
def takes_bound[T: str](x: T) -> None:
2488+
def takes_str_upper_bound(x: T) -> None:
24782489
for ch in x: # E: Iterating over "str" is disallowed # N: This is because --disallow-str-iteration is enabled
24792490
reveal_type(ch) # N: Revealed type is "builtins.str"
24802491

2492+
reveal_type(reversed(s)) # N: Revealed type is "builtins.reversed[builtins.str]" # E: Argument 1 to "reversed" has incompatible type "str"; expected "Reversible[str]"
2493+
2494+
[builtins fixtures/str-iter.pyi]
2495+
[typing fixtures/typing-str-iter.pyi]
2496+
24812497
[case testIterStrOverload]
2498+
# flags: --disallow-str-iteration
24822499
reveal_type(iter("foo")) # N: Revealed type is "typing.Iterable[builtins.str]"
24832500
[builtins fixtures/dict.pyi]
24842501

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Builtins stub used in disallow-str-iteration tests.
2+
3+
4+
from _typeshed import SupportsLenAndGetItem
5+
from typing import Generic, Iterator, Sequence, Reversible, TypeVar, overload
6+
7+
_T = TypeVar("_T")
8+
9+
class object:
10+
def __init__(self) -> None: pass
11+
12+
class type: pass
13+
class int: pass
14+
class bool(int): pass
15+
class ellipsis: pass
16+
class slice: pass
17+
18+
class str:
19+
def __iter__(self) -> Iterator[str]: pass
20+
def __len__(self) -> int: pass
21+
def __contains__(self, item: object) -> bool: pass
22+
def __reversed__(self) -> Iterator[str]: pass
23+
def __getitem__(self, i: int) -> str: pass
24+
25+
class list(Sequence[_T], Generic[_T]):
26+
def __iter__(self) -> Iterator[_T]: pass
27+
def __len__(self) -> int: pass
28+
def __contains__(self, item: object) -> bool: pass
29+
def __reversed__(self) -> Iterator[_T]: pass
30+
@overload
31+
def __getitem__(self, i: int, /) -> _T: ...
32+
@overload
33+
def __getitem__(self, s: slice, /) -> list[_T]: ...
34+
35+
class tuple(Sequence[_T], Generic[_T]):
36+
def __iter__(self) -> Iterator[_T]: pass
37+
def __len__(self) -> int: pass
38+
def __contains__(self, item: object) -> bool: pass
39+
def __reversed__(self) -> Iterator[_T]: pass
40+
@overload
41+
def __getitem__(self, i: int, /) -> _T: ...
42+
@overload
43+
def __getitem__(self, s: slice, /) -> list[_T]: ...
44+
45+
class dict: pass
46+
47+
class reversed(Iterator[_T]):
48+
@overload
49+
def __new__(cls, sequence: Reversible[_T], /) -> Iterator[_T]: ... # type: ignore[misc]
50+
@overload
51+
def __new__(cls, sequence: SupportsLenAndGetItem[_T], /) -> Iterator[_T]: ... # type: ignore[misc]
52+
def __next__(self) -> _T: ...
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Minimal typing fixture for disallow-str-iteration tests.
2+
3+
from abc import ABCMeta, abstractmethod
4+
5+
Any = object()
6+
TypeVar = 0
7+
Generic = 0
8+
Protocol = 0
9+
overload = 0
10+
11+
_T = TypeVar("_T")
12+
_KT = TypeVar("_KT")
13+
_T_co = TypeVar("_T_co", covariant=True)
14+
_VT_co = TypeVar("_VT_co", covariant=True) # Value type covariant containers.
15+
_TC = TypeVar("_TC", bound=type[object])
16+
17+
@runtime_checkable
18+
class Iterable(Protocol[_T_co]):
19+
@abstractmethod
20+
def __iter__(self) -> Iterator[_T_co]: ...
21+
22+
@runtime_checkable
23+
class Iterator(Iterable[_T_co], Protocol[_T_co]):
24+
@abstractmethod
25+
def __next__(self) -> _T_co: ...
26+
def __iter__(self) -> Iterator[_T_co]: ...
27+
28+
@runtime_checkable
29+
class Reversible(Iterable[_T_co], Protocol[_T_co]):
30+
@abstractmethod
31+
def __reversed__(self) -> Iterator[_T_co]: ...
32+
33+
@runtime_checkable
34+
class Container(Protocol[_T_co]):
35+
# This is generic more on vibes than anything else
36+
@abstractmethod
37+
def __contains__(self, x: object, /) -> bool: ...
38+
39+
@runtime_checkable
40+
class Collection(Iterable[_T_co], Container[_T_co], Protocol[_T_co]):
41+
# Implement Sized (but don't have it as a base class).
42+
@abstractmethod
43+
def __len__(self) -> int: ...
44+
45+
class Sequence(Reversible[_T_co], Collection[_T_co]):
46+
@overload
47+
@abstractmethod
48+
def __getitem__(self, index: int) -> _T_co: ...
49+
@overload
50+
@abstractmethod
51+
def __getitem__(self, index: slice) -> Sequence[_T_co]: ...
52+
def __contains__(self, value: object) -> bool: ...
53+
def __iter__(self) -> Iterator[_T_co]: ...
54+
def __reversed__(self) -> Iterator[_T_co]: ...
55+
56+
class Mapping(Collection[_KT], Generic[_KT, _VT_co]):
57+
@abstractmethod
58+
def __getitem__(self, key: _KT, /) -> _VT_co: ...
59+
def __contains__(self, key: object, /) -> bool: ...
60+
61+
def runtime_checkable(cls: _TC) -> _TC:
62+
return cls

test-data/unit/lib-stub/_typeshed.pyi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ from typing import Protocol, TypeVar, Iterable
22

33
_KT = TypeVar("_KT")
44
_VT_co = TypeVar("_VT_co", covariant=True)
5+
_T_co = TypeVar("_T_co", covariant=True)
56

67
class SupportsKeysAndGetItem(Protocol[_KT, _VT_co]):
78
def keys(self) -> Iterable[_KT]: pass
89
def __getitem__(self, __key: _KT) -> _VT_co: pass
10+
11+
class SupportsLenAndGetItem(Protocol[_T_co]):
12+
def __len__(self) -> int: ...
13+
def __getitem__(self, k: int, /) -> _T_co: ...

0 commit comments

Comments
 (0)