Skip to content

Commit 5bc395d

Browse files
authored
Make Y060 more aggressive (#432)
1 parent 7840da6 commit 5bc395d

File tree

3 files changed

+53
-25
lines changed

3 files changed

+53
-25
lines changed

ERRORCODES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ The following warnings are currently emitted by default:
6262
| Y057 | Do not use `typing.ByteString` or `collections.abc.ByteString`. These types have unclear semantics, and are deprecated; use `typing_extensions.Buffer` or a union such as `bytes \| bytearray \| memoryview` instead. See [PEP 688](https://peps.python.org/pep-0688/) for more details.
6363
| Y058 | Use `Iterator` rather than `Generator` as the return value for simple `__iter__` methods, and `AsyncIterator` rather than `AsyncGenerator` as the return value for simple `__aiter__` methods. Using `(Async)Iterator` for these methods is simpler and more elegant, and reflects the fact that the precise kind of iterator returned from an `__iter__` method is usually an implementation detail that could change at any time, and should not be relied upon.
6464
| Y059 | `Generic[]` should always be the last base class, if it is present in a class's bases tuple. At runtime, if `Generic[]` is not the final class in a the bases tuple, this [can cause the class creation to fail](https://github.com/python/cpython/issues/106102). In a stub file, however, this rule is enforced purely for stylistic consistency.
65-
| Y060 | Redundant inheritance from `Generic[]`. For example, `class Foo(Iterable[_T], Generic[_T]): ...` can be written more simply as `class Foo(Iterable[_T]): ...`.<br><br>To avoid false-positive errors, and to avoid complexity in the implementation, this check is deliberately conservative: it only looks at classes that have exactly two bases.
65+
| Y060 | Redundant inheritance from `Generic[]`. For example, `class Foo(Iterable[_T], Generic[_T]): ...` can be written more simply as `class Foo(Iterable[_T]): ...`.<br><br>To avoid false-positive errors, and to avoid complexity in the implementation, this check is deliberately conservative: it only flags classes where all subscripted bases have identical code inside their subscript slices.
6666
| Y061 | Do not use `None` inside a `Literal[]` slice. For example, use `Literal["foo"] \| None` instead of `Literal["foo", None]`. While both are legal according to [PEP 586](https://peps.python.org/pep-0586/), the former is preferred for stylistic consistency.
6767

6868
## Warnings disabled by default

pyi.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from copy import deepcopy
1313
from dataclasses import dataclass
1414
from functools import partial
15-
from itertools import chain, zip_longest
15+
from itertools import chain, groupby, zip_longest
1616
from keyword import iskeyword
1717
from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, Union
1818

@@ -62,6 +62,15 @@ class TypeVarInfo(NamedTuple):
6262
name: str
6363

6464

65+
def all_equal(iterable: Iterable[object]) -> bool:
66+
"""Returns True if all the elements are equal to each other.
67+
68+
Adapted from the CPython itertools documentation."""
69+
g = groupby(iterable)
70+
next(g, True)
71+
return not next(g, False)
72+
73+
6574
_MAPPING_SLICE = "KeyType, ValueType"
6675

6776
# Y022: Use stdlib imports instead of aliases from typing/typing_extensions
@@ -1570,26 +1579,27 @@ def _check_class_bases(self, bases: list[ast.expr]) -> None:
15701579
Y040_encountered = False
15711580
Y059_encountered = False
15721581
Generic_basenode: ast.Subscript | None = None
1582+
subscript_bases: list[ast.Subscript] = []
15731583
num_bases = len(bases)
15741584

15751585
for i, base_node in enumerate(bases, start=1):
15761586
if not Y040_encountered and _is_builtins_object(base_node):
15771587
self.error(base_node, Y040)
15781588
Y040_encountered = True
1579-
if isinstance(base_node, ast.Subscript) and _is_Generic(base_node.value):
1580-
Generic_basenode = base_node
1581-
if i < num_bases and not Y059_encountered:
1582-
Y059_encountered = True
1583-
self.error(base_node, Y059)
1584-
1585-
if (
1586-
Generic_basenode is not None
1587-
and num_bases == 2
1588-
and isinstance(bases[0], ast.Subscript)
1589-
and isinstance(bases[1], ast.Subscript)
1590-
and ast.dump(bases[0].slice) == ast.dump(bases[1].slice)
1591-
):
1592-
self.error(Generic_basenode, Y060)
1589+
if isinstance(base_node, ast.Subscript):
1590+
subscript_bases.append(base_node)
1591+
if _is_Generic(base_node.value):
1592+
Generic_basenode = base_node
1593+
if i < num_bases and not Y059_encountered:
1594+
Y059_encountered = True
1595+
self.error(base_node, Y059)
1596+
1597+
if Generic_basenode is not None:
1598+
assert subscript_bases
1599+
if len(subscript_bases) > 1 and all_equal(
1600+
ast.dump(subscript_base.slice) for subscript_base in subscript_bases
1601+
):
1602+
self.error(Generic_basenode, Y060)
15931603

15941604
def visit_ClassDef(self, node: ast.ClassDef) -> None:
15951605
if node.name.startswith("_") and not self.in_class.active:

tests/classdefs.pyi

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ from collections.abc import (
1414
Generator,
1515
Iterable,
1616
Iterator,
17+
Mapping,
1718
)
1819
from enum import EnumMeta
1920
from typing import Any, Generic, TypeVar, overload
@@ -190,22 +191,39 @@ def __imul__(self, other: Any) -> list[str]: ...
190191
_S = TypeVar("_S")
191192
_T = TypeVar("_T")
192193

194+
class BadGeneric(Generic[_T], int): ... # Y059 "Generic[]" should always be the last base class
193195
class GoodGeneric(Generic[_T]): ...
194-
class GoodGeneric2(int, typing.Generic[_T]): ...
195-
class BreaksAtRuntimeButWhatever(int, str, typing_extensions.Generic[_T]): ...
196-
class DoesntTypeCheckButWhatever(Iterable[int], str, Generic[_T]): ...
197196

198-
class BadGeneric(Generic[_T], int): ... # Y059 "Generic[]" should always be the last base class
199197
class BadGeneric2(int, typing.Generic[_T], str): ... # Y059 "Generic[]" should always be the last base class
198+
class GoodGeneric2(int, typing.Generic[_T]): ...
199+
200200
class BadGeneric3(typing_extensions.Generic[_T], int, str): ... # Y059 "Generic[]" should always be the last base class
201+
class GoodGeneric3(int, str, typing_extensions.Generic[_T]): ...
202+
201203
class BadGeneric4(Generic[_T], Iterable[int], str): ... # Y059 "Generic[]" should always be the last base class
204+
class GoodGeneric4(Iterable[int], str, Generic[_T]): ...
202205

203206
class RedundantGeneric1(Iterable[_T], Generic[_T]): ... # Y060 Redundant inheritance from "Generic[]"; class would be inferred as generic anyway
207+
class Corrected1(Iterable[_T]): ...
208+
204209
class RedundantGeneric2(Generic[_S], GoodGeneric[_S]): ... # Y059 "Generic[]" should always be the last base class # Y060 Redundant inheritance from "Generic[]"; class would be inferred as generic anyway
210+
class Corrected2(GoodGeneric[_S]): ...
211+
212+
class RedundantGeneric3(int, Iterator[_T], str, float, memoryview, bytes, Generic[_T]): ... # Y060 Redundant inheritance from "Generic[]"; class would be inferred as generic anyway
213+
class Corrected3(int, Iterator[_T], str, float, memoryview, bytes): ...
214+
215+
class RedundantGeneric4(Iterable[_T], Iterator[_T], Generic[_T]): ... # Y060 Redundant inheritance from "Generic[]"; class would be inferred as generic anyway
216+
class Corrected4(Iterable[_T], Iterator[_T]): ...
205217

206-
# Strictly speaking these inheritances from Generic are "redundant",
218+
class BadAndRedundantGeneric(object, Generic[_T], Container[_T]): ... # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3 # Y059 "Generic[]" should always be the last base class # Y060 Redundant inheritance from "Generic[]"; class would be inferred as generic anyway
219+
class Corrected5(Container[_T]): ...
220+
221+
# Strictly speaking this inheritance from Generic is "redundant",
207222
# but people may consider it more readable to explicitly inherit from Generic,
208-
# so we deliberately don't flag them with Y060
209-
class GoodGeneric3(Container[_S], Iterator[_T], Generic[_S, _T]): ...
210-
class GoodGeneric4(int, Iterator[_T], str, Generic[_T]): ...
211-
class BadGeneric5(object, Generic[_T], Container[_T]): ... # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3 # Y059 "Generic[]" should always be the last base class
223+
# so we deliberately don't flag it with Y060
224+
class GoodGeneric5(Container[_S], Iterator[_T], Generic[_S, _T]): ...
225+
226+
# And these definitely arent't redundant,
227+
# since the order of the type variables is changed via the inheritance from Generic:
228+
class GoodGeneric6(Container[_S], Iterator[_T], Generic[_T, _S]): ...
229+
class GoodGeneric7(Mapping[_S, _T], Generic[_T, _S]): ...

0 commit comments

Comments
 (0)