Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Y037 code to check for possible usage of PEP 604 new syntax #202

Merged
merged 11 commits into from
Apr 7, 2022
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

Features:
* Introduce Y036 (check for badly defined `__exit__` and `__aexit__` methods).
* Introduce Y037 (Use PEP 604 syntax instead of `typing.Union` and `typing.Optional`).
Contributed by Oleg Höfling.

## 22.3.0

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ currently emitted:
| Y034 | Y034 detects common errors where certain methods are annotated as having a fixed return type, despite returning `self` at runtime. Such methods should be annotated with `_typeshed.Self`. This check looks for:<br><br>&nbsp;&nbsp;**1.**&nbsp;&nbsp;Any in-place BinOp dunder methods (`__iadd__`, `__ior__`, etc.) that do not return `Self`.<br>&nbsp;&nbsp;**2.**&nbsp;&nbsp;`__new__`, `__enter__` and `__aenter__` methods that return the class's name unparameterised.<br>&nbsp;&nbsp;**3.**&nbsp;&nbsp;`__iter__` methods that return `Iterator`, even if the class inherits directly from `Iterator`.<br>&nbsp;&nbsp;**4.**&nbsp;&nbsp;`__aiter__` methods that return `AsyncIterator`, even if the class inherits directly from `AsyncIterator`.<br><br>This check excludes methods decorated with `@overload` or `@abstractmethod`.
| Y035 | `__all__` in a stub file should always have a value, as `__all__` in a `.pyi` file has identical semantics to `__all__` in a `.py` file. E.g. write `__all__ = ["foo", "bar"]` instead of `__all__: list[str]`.
| Y036 | Y036 detects common errors in `__exit__` and `__aexit__` methods. For example, the first argument in an `__exit__` method should always be annotated with `type[Exception] | None`.
| Y037 | Use PEP 604 syntax instead of `typing.Union` and `typing.Optional`. E.g. use `str \| int` instead of `Union[str, int]`, and use `str \| None` instead of `Optional[str]`.

Many error codes enforce modern conventions, and some cannot yet be used in
all cases:
Expand Down
12 changes: 12 additions & 0 deletions pyi.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,17 @@ def _check_import_or_attribute(
elif fullname == "collections.namedtuple":
error_message = Y024

# Y037 errors
elif fullname == "typing.Optional":
error_message = Y037.format(
old_syntax=fullname, example='"int | None" instead of "Optional[int]"'
)

elif fullname == "typing.Union":
error_message = Y037.format(
old_syntax=fullname, example='"int | str" instead of "Union[int, str]"'
)

else:
return

Expand Down Expand Up @@ -1480,3 +1491,4 @@ def parse_options(
Y034 = 'Y034 {methods} usually return "self" at runtime. Consider using "_typeshed.Self" in "{method_name}", e.g. "{suggested_syntax}"'
Y035 = 'Y035 "__all__" in a stub file must have a value, as it has the same semantics as "__all__" at runtime.'
Y036 = "Y036 Badly defined {method_name} method: {details}"
Y037 = "Y037 Use PEP 604 union types instead of {old_syntax} (e.g. {example})."
1 change: 1 addition & 0 deletions tests/del.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flags: --ignore=Y037
from typing import TypeAlias, Union

ManyStr: TypeAlias = list[EitherStr]
Expand Down
1 change: 1 addition & 0 deletions tests/forward_refs.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flags: --ignore=Y037
from typing import Optional, TypeAlias, Union

MaybeCStr: TypeAlias = Optional[CStr]
Expand Down
1 change: 1 addition & 0 deletions tests/forward_refs_annassign.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flags: --ignore=Y037
from typing import Optional, TypeAlias, Union

MaybeCStr: TypeAlias = Optional[CStr]
Expand Down
1 change: 1 addition & 0 deletions tests/imports.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flags: --extend-ignore=Y037
# NOTE: F401 & F811 are ignored in this file in the .flake8 config file

# GOOD IMPORTS
Expand Down
32 changes: 32 additions & 0 deletions tests/pep604_union_types.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import typing
from typing import ( # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]"). # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]").
Optional,
Union,
)

x1: Optional[str]
x2: Optional
x3: Union[str, int]
x4: Union


y1: typing.Optional[str] # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]").
y2: typing.Optional # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]").
y3: typing.Union[str, int] # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]").
y4: typing.Union # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]").


def f1(x: Optional[str] = ...) -> None: ...
def f2() -> Optional[str]: ...
def f3() -> Union[str, int]: ...
def f4(x: Union[str, int] = ...) -> None: ...
def f5(x: Optional) -> None: ...
def f6(x: Union) -> None: ...


def g1(x: typing.Optional[str] = ...) -> None: ... # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]").
def g2() -> typing.Optional[str]: ... # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]").
def g3() -> typing.Union[str, int]: ... # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]").
def g4(x: typing.Union[str, int] = ...) -> None: ... # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]").
def g5(x: typing.Optional) -> None: ... # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]").
def g6(x: typing.Union) -> None: ... # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]").
1 change: 1 addition & 0 deletions tests/typevar.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flags: --extend-ignore=Y037
import typing
from typing import Annotated, ParamSpec, TypeVar, TypeVarTuple, Union

Expand Down
1 change: 1 addition & 0 deletions tests/union_duplicates.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flags: --extend-ignore=Y037
import typing
from typing import Union

Expand Down
2 changes: 1 addition & 1 deletion tests/vanilla_flake8_not_clean_forward_refs.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# flags: --no-pyi-aware-file-checker
# flags: --no-pyi-aware-file-checker --ignore=Y037
from typing import TypeAlias, Union

ManyStr: TypeAlias = list[EitherStr] # F821 undefined name 'EitherStr'
Expand Down