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

Revise Y041 so that it only applies to parameter annotations #305

Merged
merged 1 commit into from Nov 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,13 @@ Bugfixes:
* Specify encoding when opening files. Prevents `UnicodeDecodeError` on Windows
when the file contains non-CP1252 characters.
Contributed by [Avasam](https://github.com/Avasam).
* Significant changes have been made to the Y041 check. Previously, Y041 flagged
"redundant numeric unions" (e.g. `float | int`, `complex | float` or `complex | int`)
in all contexts outside of type aliases. This was incorrect. PEP 484 only
specifies that type checkers should treat `int` as an implicit subtype of
`float` in the specific context of parameter annotations for functions and
methods. Y041 has therefore been revised to only emit errors on "redundant
numeric unions" in the context of parameter annotations.

## 22.10.0

Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -74,7 +74,7 @@ currently emitted:
| Y038 | Use `from collections.abc import Set as AbstractSet` instead of `from typing import AbstractSet`. Like Y027, this error code should be switched off in your config file if your stubs support Python 2.
| Y039 | Use `str` instead of `typing.Text`. This error code is incompatible with stubs supporting Python 2.
| Y040 | Never explicitly inherit from `object`, as all classes implicitly inherit from `object` in Python 3. This error code is incompatible with stubs supporting Python 2.
| Y041 | Y041 detects redundant numeric unions. For example, PEP 484 specifies that type checkers should treat `int` as an implicit subtype of `float`, so `int` is redundant in the union `int \| float`. In the same way, `int` is redundant in the union `int \| complex`, and `float` is redundant in the union `float \| complex`.
| Y041 | Y041 detects redundant numeric unions in the context of parameter annotations. For example, PEP 484 specifies that type checkers should allow `int` objects to be passed to a function, even if the function states that it accepts a `float`. As such, `int` is redundant in the union `int \| float` in the context of a parameter annotation. In the same way, `int` is sometimes redundant in the union `int \| complex`, and `float` is sometimes redundant in the union `float \| complex`.
| Y042 | Type alias names should use CamelCase rather than snake_case
| Y043 | Do not use names ending in "T" for private type aliases. (The "T" suffix implies that an object is a `TypeVar`.)
| Y044 | `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics.
Expand Down
10 changes: 5 additions & 5 deletions pyi.py
Expand Up @@ -730,7 +730,7 @@ def __init__(self, filename: Path | None = None) -> None:
self.string_literals_allowed = NestingCounter()
self.in_function = NestingCounter()
self.in_class = NestingCounter()
self.visiting_TypeAlias = NestingCounter()
self.visiting_arg = NestingCounter()
# This is only relevant for visiting classes
self.current_class_node: ast.ClassDef | None = None

Expand Down Expand Up @@ -1063,8 +1063,7 @@ def visit_Expr(self, node: ast.Expr) -> None:
_Y043_REGEX = re.compile(r"^_.*[a-z]T\d?$")

def _check_typealias(self, node: ast.AnnAssign, alias_name: str) -> None:
with self.visiting_TypeAlias.enabled():
self.generic_visit(node)
self.generic_visit(node)
if alias_name.startswith("_"):
self.typealias_decls[alias_name] = node
if self._Y042_REGEX.match(alias_name):
Expand Down Expand Up @@ -1110,7 +1109,7 @@ def _check_union_members(self, members: Sequence[ast.expr]) -> None:
self._check_for_Y051_violations(analysis)
if analysis.multiple_literals_in_union:
self._error_for_multiple_literals_in_union(first_union_member, analysis)
if not self.visiting_TypeAlias.active:
if self.visiting_arg.active:
self._check_for_redundant_numeric_unions(first_union_member, analysis)

def _check_for_Y051_violations(self, analysis: UnionAnalysis) -> None:
Expand Down Expand Up @@ -1715,7 +1714,8 @@ def _visit_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
def visit_arg(self, node: ast.arg) -> None:
if _is_NoReturn(node.annotation):
self.error(node, Y050)
self.generic_visit(node)
with self.visiting_arg.enabled():
self.generic_visit(node)

def visit_arguments(self, node: ast.arguments) -> None:
self.generic_visit(node)
Expand Down
12 changes: 6 additions & 6 deletions tests/union_duplicates.pyi
Expand Up @@ -25,19 +25,19 @@ just_literals_pipe_union: TypeAlias = Literal[True] | Literal['idk'] # Y042 Typ
_mixed_pipe_union: TypeAlias = Union[Literal[966], bytes, Literal['baz']] # Y042 Type aliases should use the CamelCase naming convention # Y047 Type alias "_mixed_pipe_union" is not used # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal[966, 'baz']".
ManyLiteralMembersButNeedsCombining: TypeAlias = int | Literal['a', 'b'] | Literal['baz'] # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal['a', 'b', 'baz']".

a: int | float # Y041 Use "float" instead of "int | float" (see "The numeric tower" in PEP 484)
b: Union[builtins.float, str, bytes, builtins.int] # Y041 Use "float" instead of "int | float" (see "The numeric tower" in PEP 484)
a: int | float # No error here, Y041 only applies to argument annotations
b: Union[builtins.float, str, bytes, builtins.int] # No error here, Y041 only applies to argument annotations
def func(arg: float | list[str] | type[bool] | complex) -> None: ... # Y041 Use "complex" instead of "float | complex" (see "The numeric tower" in PEP 484)

class Foo:
def method(self, arg: int | builtins.float | complex) -> None: ... # Y041 Use "complex" instead of "float | complex" (see "The numeric tower" in PEP 484) # Y041 Use "complex" instead of "int | complex" (see "The numeric tower" in PEP 484)

c: Union[builtins.complex, memoryview, slice, int] # Y041 Use "complex" instead of "int | complex" (see "The numeric tower" in PEP 484)
c: Union[builtins.complex, memoryview, slice, int] # No error here, Y041 only applies to argument annotations

# Don't error with Y041 here, the two error messages combined are quite confusing
d: int | int | float # Y016 Duplicate union member "int"
def foo(d: int | int | float) -> None: ... # Y016 Duplicate union member "int"

f: Literal["foo"] | Literal["bar"] | int | float | builtins.bool # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal['foo', 'bar']". # Y041 Use "float" instead of "int | float" (see "The numeric tower" in PEP 484)
def bar(f: Literal["foo"] | Literal["bar"] | int | float | builtins.bool) -> None: ... # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal['foo', 'bar']". # Y041 Use "float" instead of "int | float" (see "The numeric tower" in PEP 484)

# Type aliases are special-cased to be excluded from Y041
MyTypeAlias: TypeAlias = int | float | bool
Expand All @@ -52,5 +52,5 @@ class Four:
var: builtins.bool | Literal[True] # Y051 "Literal[True]" is redundant in a union with "bool"

DupesHereSoNoY051: TypeAlias = int | int | Literal[42] # Y016 Duplicate union member "int"
NightmareAlias1 = int | float | Literal[4, b"bar"] | Literal["foo"] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "NightmareAlias1: TypeAlias = int | float | Literal[4, b'bar'] | Literal['foo']" # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal[4, b'bar', 'foo']". # Y041 Use "float" instead of "int | float" (see "The numeric tower" in PEP 484) # Y051 "Literal[4]" is redundant in a union with "int"
NightmareAlias1 = int | float | Literal[4, b"bar"] | Literal["foo"] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "NightmareAlias1: TypeAlias = int | float | Literal[4, b'bar'] | Literal['foo']" # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal[4, b'bar', 'foo']". # Y051 "Literal[4]" is redundant in a union with "int"
nightmare_alias2: TypeAlias = int | float | Literal[True, 4] | Literal["foo"] # Y042 Type aliases should use the CamelCase naming convention # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal[True, 4, 'foo']". # Y051 "Literal[4]" is redundant in a union with "int"