Skip to content

Commit

Permalink
Avoid recommending Self usages in metaclasses (#8639)
Browse files Browse the repository at this point in the history
PEP 673 forbids the use of `typing(_extensions).Self` in metaclasses, so
we want to avoid flagging `PYI034` on metaclasses. This is based on an
analogous change in `flake8-pyi`:
PyCQA/flake8-pyi#436.

Closes #8353.
  • Loading branch information
charliermarsh authored Nov 13, 2023
1 parent 7fd95e1 commit 213d315
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 97 deletions.
29 changes: 28 additions & 1 deletion crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import abc
import builtins
import collections.abc
import enum
import typing
from abc import abstractmethod
from abc import ABCMeta, abstractmethod
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator
from enum import EnumMeta
from typing import Any, overload

import typing_extensions
Expand Down Expand Up @@ -199,6 +201,31 @@ def __aiter__(self) -> AsyncIterable[str]:
... # Y045 "__aiter__" methods should return an AsyncIterator, not an AsyncIterable


class MetaclassInWhichSelfCannotBeUsed(type):
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed: ...
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed: ...
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed: ...
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed) -> MetaclassInWhichSelfCannotBeUsed: ...

class MetaclassInWhichSelfCannotBeUsed2(EnumMeta):
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed2: ...
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed2: ...
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed2: ...
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed2) -> MetaclassInWhichSelfCannotBeUsed2: ...

class MetaclassInWhichSelfCannotBeUsed3(enum.EnumType):
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed3: ...
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed3: ...
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed3: ...
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed3) -> MetaclassInWhichSelfCannotBeUsed3: ...

class MetaclassInWhichSelfCannotBeUsed4(ABCMeta):
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed4: ...
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ...
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ...
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed4) -> MetaclassInWhichSelfCannotBeUsed4: ...


class Abstract(Iterator[str]):
@abstractmethod
def __iter__(self) -> Iterator[str]:
Expand Down
28 changes: 27 additions & 1 deletion crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import abc
import builtins
import collections.abc
import enum
import typing
from abc import abstractmethod
from abc import ABCMeta, abstractmethod
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator
from enum import EnumMeta
from typing import Any, overload

import typing_extensions
Expand Down Expand Up @@ -152,6 +154,30 @@ class AsyncIteratorReturningAsyncIterable:
str
]: ... # Y045 "__aiter__" methods should return an AsyncIterator, not an AsyncIterable

class MetaclassInWhichSelfCannotBeUsed(type):
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed: ...
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed: ...
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed: ...
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed) -> MetaclassInWhichSelfCannotBeUsed: ...

class MetaclassInWhichSelfCannotBeUsed2(EnumMeta):
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed2: ...
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed2: ...
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed2: ...
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed2) -> MetaclassInWhichSelfCannotBeUsed2: ...

class MetaclassInWhichSelfCannotBeUsed3(enum.EnumType):
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed3: ...
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed3: ...
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed3: ...
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed3) -> MetaclassInWhichSelfCannotBeUsed3: ...

class MetaclassInWhichSelfCannotBeUsed4(ABCMeta):
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed4: ...
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ...
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ...
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed4) -> MetaclassInWhichSelfCannotBeUsed4: ...

class Abstract(Iterator[str]):
@abstractmethod
def __iter__(self) -> Iterator[str]: ...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ pub(crate) fn non_self_return_type(
return;
};

// PEP 673 forbids the use of `typing(_extensions).Self` in metaclasses.
if is_metaclass(class_def, checker.semantic()) {
return;
}

// Skip any abstract or overloaded methods.
if is_abstract(decorator_list, checker.semantic())
|| is_overload(decorator_list, checker.semantic())
Expand Down Expand Up @@ -214,6 +219,26 @@ pub(crate) fn non_self_return_type(
}
}

/// Returns `true` if the given class is a metaclass.
fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
class_def.arguments.as_ref().is_some_and(|arguments| {
arguments
.args
.iter()
.any(|expr| is_metaclass_base(expr, semantic))
})
}

/// Returns `true` if the given expression resolves to a metaclass.
fn is_metaclass_base(base: &Expr, semantic: &SemanticModel) -> bool {
semantic.resolve_call_path(base).is_some_and(|call_path| {
matches!(
call_path.as_slice(),
["" | "builtins", "type"] | ["abc", "ABCMeta"] | ["enum", "EnumMeta" | "EnumType"]
)
})
}

/// Returns `true` if the method is an in-place binary operator.
fn is_inplace_bin_op(name: &str) -> bool {
matches!(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,91 +1,91 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI034.py:19:9: PYI034 `__new__` methods in classes like `Bad` usually return `self` at runtime
PYI034.py:21:9: PYI034 `__new__` methods in classes like `Bad` usually return `self` at runtime
|
17 | object
18 | ): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
19 | def __new__(cls, *args: Any, **kwargs: Any) -> Bad:
19 | object
20 | ): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
21 | def __new__(cls, *args: Any, **kwargs: Any) -> Bad:
| ^^^^^^^ PYI034
20 | ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
22 | ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
|
= help: Consider using `typing_extensions.Self` as return type

PYI034.py:34:9: PYI034 `__enter__` methods in classes like `Bad` usually return `self` at runtime
PYI034.py:36:9: PYI034 `__enter__` methods in classes like `Bad` usually return `self` at runtime
|
32 | ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
33 |
34 | def __enter__(self) -> Bad:
34 | ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
35 |
36 | def __enter__(self) -> Bad:
| ^^^^^^^^^ PYI034
35 | ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
37 | ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
|
= help: Consider using `typing_extensions.Self` as return type

PYI034.py:37:15: PYI034 `__aenter__` methods in classes like `Bad` usually return `self` at runtime
PYI034.py:39:15: PYI034 `__aenter__` methods in classes like `Bad` usually return `self` at runtime
|
35 | ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
36 |
37 | async def __aenter__(self) -> Bad:
37 | ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
38 |
39 | async def __aenter__(self) -> Bad:
| ^^^^^^^^^^ PYI034
38 | ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
40 | ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
= help: Consider using `typing_extensions.Self` as return type

PYI034.py:40:9: PYI034 `__iadd__` methods in classes like `Bad` usually return `self` at runtime
PYI034.py:42:9: PYI034 `__iadd__` methods in classes like `Bad` usually return `self` at runtime
|
38 | ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
39 |
40 | def __iadd__(self, other: Bad) -> Bad:
40 | ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
41 |
42 | def __iadd__(self, other: Bad) -> Bad:
| ^^^^^^^^ PYI034
41 | ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
43 | ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
|
= help: Consider using `typing_extensions.Self` as return type

PYI034.py:163:9: PYI034 `__iter__` methods in classes like `BadIterator1` usually return `self` at runtime
PYI034.py:165:9: PYI034 `__iter__` methods in classes like `BadIterator1` usually return `self` at runtime
|
162 | class BadIterator1(Iterator[int]):
163 | def __iter__(self) -> Iterator[int]:
164 | class BadIterator1(Iterator[int]):
165 | def __iter__(self) -> Iterator[int]:
| ^^^^^^^^ PYI034
164 | ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
166 | ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
= help: Consider using `typing_extensions.Self` as return type

PYI034.py:170:9: PYI034 `__iter__` methods in classes like `BadIterator2` usually return `self` at runtime
PYI034.py:172:9: PYI034 `__iter__` methods in classes like `BadIterator2` usually return `self` at runtime
|
168 | typing.Iterator[int]
169 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
170 | def __iter__(self) -> Iterator[int]:
170 | typing.Iterator[int]
171 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
172 | def __iter__(self) -> Iterator[int]:
| ^^^^^^^^ PYI034
171 | ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
173 | ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
= help: Consider using `typing_extensions.Self` as return type

PYI034.py:177:9: PYI034 `__iter__` methods in classes like `BadIterator3` usually return `self` at runtime
PYI034.py:179:9: PYI034 `__iter__` methods in classes like `BadIterator3` usually return `self` at runtime
|
175 | typing.Iterator[int]
176 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
177 | def __iter__(self) -> collections.abc.Iterator[int]:
177 | typing.Iterator[int]
178 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
179 | def __iter__(self) -> collections.abc.Iterator[int]:
| ^^^^^^^^ PYI034
178 | ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
180 | ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
= help: Consider using `typing_extensions.Self` as return type

PYI034.py:183:9: PYI034 `__iter__` methods in classes like `BadIterator4` usually return `self` at runtime
PYI034.py:185:9: PYI034 `__iter__` methods in classes like `BadIterator4` usually return `self` at runtime
|
181 | class BadIterator4(Iterator[int]):
182 | # Note: *Iterable*, not *Iterator*, returned!
183 | def __iter__(self) -> Iterable[int]:
183 | class BadIterator4(Iterator[int]):
184 | # Note: *Iterable*, not *Iterator*, returned!
185 | def __iter__(self) -> Iterable[int]:
| ^^^^^^^^ PYI034
184 | ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
186 | ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
= help: Consider using `typing_extensions.Self` as return type

PYI034.py:193:9: PYI034 `__aiter__` methods in classes like `BadAsyncIterator` usually return `self` at runtime
PYI034.py:195:9: PYI034 `__aiter__` methods in classes like `BadAsyncIterator` usually return `self` at runtime
|
192 | class BadAsyncIterator(collections.abc.AsyncIterator[str]):
193 | def __aiter__(self) -> typing.AsyncIterator[str]:
194 | class BadAsyncIterator(collections.abc.AsyncIterator[str]):
195 | def __aiter__(self) -> typing.AsyncIterator[str]:
| ^^^^^^^^^ PYI034
194 | ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
196 | ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
|
= help: Consider using `typing_extensions.Self` as return type

Expand Down
Loading

0 comments on commit 213d315

Please sign in to comment.