Skip to content

Commit 3d11d63

Browse files
authored
Check for bare Incomplete annotations (#475)
1 parent 3b260bf commit 3d11d63

File tree

4 files changed

+50
-0
lines changed

4 files changed

+50
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Change Log
22

3+
New error codes:
4+
* Y065: Don't use bare `Incomplete` in argument and return annotations.
5+
36
Bugfixes:
47
* Y090: Fix false positive for `tuple[Unpack[Ts]]`.
58

ERRORCODES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ The following warnings are currently emitted by default:
7878
| Y062 | `Literal[]` slices shouldn't contain duplicates, e.g. `Literal[True, True]` is not allowed. | Redundant code
7979
| Y063 | Use [PEP 570 syntax](https://peps.python.org/pep-0570/) (e.g. `def foo(x: int, /) -> None: ...`) to denote positional-only arguments, rather than [the older Python 3.7-compatible syntax described in PEP 484](https://peps.python.org/pep-0484/#positional-only-arguments) (`def foo(__x: int) -> None: ...`, etc.). | Style
8080
| Y064 | Use simpler syntax to define final literal types. For example, use `x: Final = 42` instead of `x: Final[Literal[42]]`. | Style
81+
| Y065 | Don't use bare `Incomplete` in argument and return annotations. Instead, leave them unannotated. Omitting an annotation entirely from a function will cause some type checkers to view the parameter or return type as "untyped"; this may result in stricter type-checking on code that makes use of the stubbed function. | Style
8182

8283
## Warnings disabled by default
8384

pyi.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ def _is_object(node: ast.AST | None, name: str, *, from_: Container[str]) -> boo
337337
_is_builtins_object = partial(_is_object, name="object", from_={"builtins"})
338338
_is_builtins_type = partial(_is_object, name="type", from_={"builtins"})
339339
_is_Unused = partial(_is_object, name="Unused", from_={"_typeshed"})
340+
_is_Incomplete = partial(_is_object, name="Incomplete", from_={"_typeshed"})
340341
_is_Iterable = partial(_is_object, name="Iterable", from_=_TYPING_OR_COLLECTIONS_ABC)
341342
_is_AsyncIterable = partial(
342343
_is_object, name="AsyncIterable", from_=_TYPING_OR_COLLECTIONS_ABC
@@ -2162,6 +2163,9 @@ def _visit_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
21622163
with self.in_function.enabled():
21632164
self.generic_visit(node)
21642165

2166+
if node.name != "__getattr__" and node.returns and _is_Incomplete(node.returns):
2167+
self.error(node.returns, Y065.format(what="return type"))
2168+
21652169
body = node.body
21662170
if len(body) > 1:
21672171
self.error(body[1], Y048)
@@ -2186,6 +2190,8 @@ def _visit_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
21862190
def visit_arg(self, node: ast.arg) -> None:
21872191
if _is_NoReturn(node.annotation):
21882192
self.error(node, Y050)
2193+
if _is_Incomplete(node.annotation):
2194+
self.error(node, Y065.format(what=f'parameter "{node.arg}"'))
21892195
with self.visiting_arg.enabled():
21902196
self.generic_visit(node)
21912197

@@ -2408,6 +2414,7 @@ def parse_options(options: argparse.Namespace) -> None:
24082414
Y062 = 'Y062 Duplicate "Literal[]" member "{}"'
24092415
Y063 = "Y063 Use PEP-570 syntax to indicate positional-only arguments"
24102416
Y064 = 'Y064 Use "{suggestion}" instead of "{original}"'
2417+
Y065 = 'Y065 Leave {what} unannotated rather than using "Incomplete"'
24112418
Y090 = (
24122419
'Y090 "{original}" means '
24132420
'"a tuple of length 1, in which the sole element is of type {typ!r}". '

tests/incomplete.pyi

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from _typeshed import Incomplete
2+
from typing_extensions import TypeAlias
3+
4+
IncompleteAlias: TypeAlias = Incomplete # ok
5+
6+
att: Incomplete # ok
7+
8+
def ok(x: Incomplete | None) -> list[Incomplete]: ...
9+
def aliased(x: IncompleteAlias) -> IncompleteAlias: ... # ok
10+
def err1(
11+
x: Incomplete, # Y065 Leave parameter "x" unannotated rather than using "Incomplete"
12+
) -> None: ...
13+
def err2() -> (
14+
Incomplete # Y065 Leave return type unannotated rather than using "Incomplete"
15+
): ...
16+
17+
class Foo:
18+
att: Incomplete
19+
def ok(self, x: Incomplete | None) -> list[Incomplete]: ...
20+
def err1(
21+
self,
22+
x: Incomplete, # Y065 Leave parameter "x" unannotated rather than using "Incomplete"
23+
) -> None: ...
24+
def err2(
25+
self,
26+
) -> (
27+
Incomplete # Y065 Leave return type unannotated rather than using "Incomplete"
28+
): ...
29+
def __getattr__(
30+
self, name: str
31+
) -> Incomplete: ... # allowed in __getattr__ return type
32+
33+
class Bar:
34+
def __getattr__(
35+
self,
36+
name: Incomplete, # Y065 Leave parameter "name" unannotated rather than using "Incomplete"
37+
) -> Incomplete: ...
38+
39+
def __getattr__(name: str) -> Incomplete: ... # allowed in __getattr__ return type

0 commit comments

Comments
 (0)