Skip to content

Commit 3debb73

Browse files
authored
Minor improvements to Y029 and Y036 checks (#216)
1 parent 266d22a commit 3debb73

4 files changed

Lines changed: 20 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
Features:
66
* Introduce Y039: Use `str` instead of `typing.Text` for Python 3 stubs.
7+
* Teach the Y036 check that `builtins.object` (as well as the unqualified `object`) is
8+
acceptable as an annotation for an `__(a)exit__` method argument.
9+
* Teach the Y029 check to emit errors for `__repr__` and `__str__` methods that return
10+
`builtins.str` (as opposed to the unqualified `str`).
711

812
## 22.4.1
913

pyi.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ def _is_name(node: ast.expr | None, name: str) -> bool:
296296
_TYPING_MODULES = frozenset({"typing", "typing_extensions"})
297297

298298

299-
def _is_object(node: ast.expr, name: str, *, from_: Container[str]) -> bool:
299+
def _is_object(node: ast.expr | None, name: str, *, from_: Container[str]) -> bool:
300300
"""Determine whether `node` is an ast representation of `name`.
301301
302302
Return True if `node` is either:
@@ -334,6 +334,7 @@ def _is_object(node: ast.expr, name: str, *, from_: Container[str]) -> bool:
334334
_is_Final = partial(_is_object, name="Final", from_=_TYPING_MODULES)
335335
_is_Self = partial(_is_object, name="Self", from_=({"_typeshed"} | _TYPING_MODULES))
336336
_is_TracebackType = partial(_is_object, name="TracebackType", from_={"types"})
337+
_is_builtins_object = partial(_is_object, name="object", from_={"builtins"})
337338

338339

339340
def _is_type_or_Type(node: ast.expr) -> bool:
@@ -1165,7 +1166,8 @@ def error_for_bad_exit_method(details: str) -> None:
11651166
if varargs:
11661167
varargs_annotation = varargs.annotation
11671168
if not (
1168-
varargs_annotation is None or _is_name(varargs_annotation, "object")
1169+
varargs_annotation is None
1170+
or _is_builtins_object(varargs_annotation)
11691171
):
11701172
error_for_bad_exit_method(
11711173
f'Star-args in an {method_name} method should be annotated with "object", '
@@ -1211,7 +1213,7 @@ def error_for_bad_annotation(
12111213

12121214
if num_args >= 2:
12131215
arg1_annotation = non_kw_only_args[1].annotation
1214-
if arg1_annotation is None or _is_name(arg1_annotation, "object"):
1216+
if arg1_annotation is None or _is_builtins_object(arg1_annotation):
12151217
pass
12161218
elif _is_PEP_604_union(arg1_annotation):
12171219
is_union_with_None, non_None_part = _analyse_exit_method_arg(
@@ -1229,7 +1231,7 @@ def error_for_bad_annotation(
12291231

12301232
if num_args >= 3:
12311233
arg2_annotation = non_kw_only_args[2].annotation
1232-
if arg2_annotation is None or _is_name(arg2_annotation, "object"):
1234+
if arg2_annotation is None or _is_builtins_object(arg2_annotation):
12331235
pass
12341236
elif _is_PEP_604_union(arg2_annotation):
12351237
is_union_with_None, non_None_part = _analyse_exit_method_arg(
@@ -1242,7 +1244,7 @@ def error_for_bad_annotation(
12421244

12431245
if num_args >= 4:
12441246
arg3_annotation = non_kw_only_args[3].annotation
1245-
if arg3_annotation is None or _is_name(arg3_annotation, "object"):
1247+
if arg3_annotation is None or _is_builtins_object(arg3_annotation):
12461248
pass
12471249
elif _is_PEP_604_union(arg3_annotation):
12481250
is_union_with_None, non_None_part = _analyse_exit_method_arg(
@@ -1299,7 +1301,7 @@ def _visit_synchronous_method(self, node: ast.FunctionDef) -> None:
12991301
if method_name in {"__repr__", "__str__"}:
13001302
if (
13011303
len(non_kw_only_args) == 1
1302-
and _is_name(node.returns, "str")
1304+
and _is_object(node.returns, "str", from_={"builtins"})
13031305
and not any(_is_abstractmethod(deco) for deco in node.decorator_list)
13041306
):
13051307
self.error(node, Y029)
@@ -1374,6 +1376,8 @@ def _check_class_method_for_bad_typevars(
13741376
else:
13751377
return
13761378

1379+
# Don't error if the first argument is annotated with `builtins.type[T]` or `typing.Type[T]`
1380+
# These are edge cases, and it's hard to give good error messages for them.
13771381
if not _is_name(first_arg_annotation.value, "type"):
13781382
return
13791383

tests/classdefs.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import abc
2+
import builtins
23
import collections.abc
34
import typing
45
from abc import abstractmethod
@@ -11,7 +12,7 @@ from typing_extensions import final
1112
class Bad:
1213
def __new__(cls, *args: Any, **kwargs: Any) -> Bad: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "_typeshed.Self" in "Bad.__new__", e.g. "def __new__(cls: type[Self], *args: Any, **kwargs: Any) -> Self: ..."
1314
def __repr__(self) -> str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
14-
def __str__(self) -> str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
15+
def __str__(self) -> builtins.str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
1516
def __eq__(self, other: Any) -> bool: ... # Y032 Prefer "object" to "Any" for the second parameter in "__eq__" methods
1617
def __ne__(self, other: typing.Any) -> typing.Any: ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
1718
def __enter__(self) -> Bad: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "_typeshed.Self" in "Bad.__enter__", e.g. "def __enter__(self: Self) -> Self: ..."

tests/exit_methods.pyi

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import builtins
12
import types
23
import typing
34
from collections.abc import Awaitable
@@ -15,7 +16,7 @@ class One:
1516
async def __aexit__(self, *args) -> str: ...
1617

1718
class Two:
18-
def __exit__(self, typ: type[BaseException] | None, *args: object) -> bool | None: ...
19+
def __exit__(self, typ: type[BaseException] | None, *args: builtins.object) -> bool | None: ...
1920
async def __aexit__(self, typ: Type[BaseException] | None, *args: object) -> bool: ...
2021

2122
class Three:
@@ -31,8 +32,8 @@ class Five:
3132
def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> Awaitable[None]: ...
3233

3334
class FiveAndAHalf:
34-
def __exit__(self, typ: object, exc: object, tb: object) -> None: ...
35-
async def __aexit__(self, typ: object, exc: object, tb: object) -> None: ...
35+
def __exit__(self, typ: object, exc: builtins.object, tb: object) -> None: ...
36+
async def __aexit__(self, typ: object, exc: object, tb: builtins.object) -> None: ...
3637

3738
def __exit__(foo: str, bar: int) -> list[str]: ...
3839
async def __aexit__(spam: bytes, eggs: dict[str, Any]) -> set[int]: ...

0 commit comments

Comments
 (0)