Skip to content

Commit 2485793

Browse files
committed
Warn on Callable[<parameter_list>, None] and Callable[<parameter_list>, Any]
1 parent 2a86db8 commit 2485793

File tree

4 files changed

+57
-10
lines changed

4 files changed

+57
-10
lines changed

CHANGELOG.md

+13-8
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22

33
## Unreleased
44

5-
* Introduce Y090, which warns if you have an annotation such as `tuple[int]` or
6-
`Tuple[int]`. These mean "a tuple of length 1, in which the sole element is
7-
of type `int`". This is sometimes what you want, but more usually you'll want
8-
`tuple[int, ...]`, which means "a tuple of arbitrary (possibly 0) length, in
9-
which all elements are of type `int`".
10-
11-
This error code is disabled by default due to the risk of false-positive
12-
errors. To enable it, use the `--extend-select=Y090` option.
5+
* Introduce several codes which are disabled by default due to the risk of
6+
false-positive errors. To enable these lints, use the
7+
`--extend-select={code1,code2,...}` option.
8+
* Y090, which warns if you have an annotation such as `tuple[int]` or
9+
`Tuple[int]`. These mean "a tuple of length 1, in which the sole element is
10+
of type `int`". This is sometimes what you want, but more usually you'll want
11+
`tuple[int, ...]`, which means "a tuple of arbitrary (possibly 0) length, in
12+
which all elements are of type `int`".
13+
* Y091, which warns if `Callable[<parameter_list>, Any]` or
14+
`Callable[<parameter_list>, None]` is used as an argument annotation in a
15+
function or method. Most of the time, the returned object from callbacks like
16+
these is ignored at runtime, so the annotation you're probably looking for is
17+
`Callable[<parameter_list>, object]`.
1318

1419
## 23.6.0
1520

ERRORCODES.md

+1
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,4 @@ recommend only using `--extend-select`, never `--select`.
7575
| Code | Description
7676
|------|------------
7777
| Y090 | `tuple[int]` means "a tuple of length 1, in which the sole element is of type `int`". Consider using `tuple[int, ...]` instead, which means "a tuple of arbitrary (possibly 0) length, in which all elements are of type `int`".
78+
| Y091 | `Callable[<parameter_list>, None]` or `Callable[<parameter_list>, Any]` is generally a mistake in the context of a parameter annotation for a function or method. The returned value from a callback is generally ignored at runtime, so `Callable[<parameter_list>, object]` is usually a better annotation in this kind of situation.

pyi.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -1369,8 +1369,9 @@ def _Y090_error(self, node: ast.Subscript) -> None:
13691369

13701370
def visit_Subscript(self, node: ast.Subscript) -> None:
13711371
subscripted_object = node.value
1372+
interesting_modules = _TYPING_MODULES | {"builtins", "collections.abc"}
13721373
subscripted_object_name = _get_name_of_class_if_from_modules(
1373-
subscripted_object, modules=_TYPING_MODULES | {"builtins"}
1374+
subscripted_object, modules=interesting_modules
13741375
)
13751376
self.visit(subscripted_object)
13761377
if subscripted_object_name == "Literal":
@@ -1398,6 +1399,14 @@ def _visit_slice_tuple(self, node: ast.Tuple, parent: str | None) -> None:
13981399
self.visit(elt)
13991400
else:
14001401
self.visit(node)
1402+
elif parent == "Callable":
1403+
self.visit(node)
1404+
if self.visiting_arg.active and len(node.elts) == 2:
1405+
return_annotation = node.elts[1]
1406+
if _is_None(return_annotation):
1407+
self.error(node, Y091.format(bad_return="None"))
1408+
elif _is_Any(return_annotation):
1409+
self.error(node, Y091.format(bad_return="Any"))
14011410
else:
14021411
self.visit(node)
14031412

@@ -2129,5 +2138,9 @@ def parse_options(options: argparse.Namespace) -> None:
21292138
'"a tuple of length 1, in which the sole element is of type {typ!r}". '
21302139
'Perhaps you meant "{new}"?'
21312140
)
2141+
Y091 = (
2142+
'Y091 "Callable" in argument annotations '
2143+
'should generally have "object" for the second parameter, not "{bad_return}"'
2144+
)
21322145

2133-
DISABLED_BY_DEFAULT = ["Y090"]
2146+
DISABLED_BY_DEFAULT = ["Y090", "Y091"]

tests/bad_callbacks.pyi

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# flags: --extend-select=Y091
2+
3+
import collections.abc
4+
import typing
5+
from collections.abc import Callable
6+
from typing import Any
7+
from typing_extensions import TypeAlias
8+
9+
def bad(
10+
a: Callable[[], None], # Y091 "Callable" in argument annotations should generally have "object" for the second parameter, not "None"
11+
b: Callable[..., Any], # Y091 "Callable" in argument annotations should generally have "object" for the second parameter, not "Any"
12+
c: typing.Callable[[str, int], typing.Any], # Y022 Use "collections.abc.Callable" instead of "typing.Callable" (PEP 585 syntax) # Y091 "Callable" in argument annotations should generally have "object" for the second parameter, not "Any"
13+
d: collections.abc.Callable[[None], None], # Y091 "Callable" in argument annotations should generally have "object" for the second parameter, not "None"
14+
e: int | str | tuple[Callable[[], None], int, str], # Y091 "Callable" in argument annotations should generally have "object" for the second parameter, not "None"
15+
) -> None: ...
16+
17+
def good(
18+
a: Callable[[], object],
19+
b: Callable[..., object],
20+
c: typing.Callable, # Y022 Use "collections.abc.Callable" instead of "typing.Callable" (PEP 585 syntax)
21+
d: collections.abc.Callable[[None], object],
22+
) -> None: ...
23+
24+
def also_good() -> Callable[[], None]: ...
25+
26+
_TypeAlias: TypeAlias = collections.abc.Callable[[int, str], Any]
27+
x: _TypeAlias
28+
y: typing.Callable[[], None] # Y022 Use "collections.abc.Callable" instead of "typing.Callable" (PEP 585 syntax)

0 commit comments

Comments
 (0)