diff --git a/CHANGELOG.md b/CHANGELOG.md index 6af8ede7..a13b0196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index ccf9c6ed..e02afc50 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/pyi.py b/pyi.py index e224201b..72462720 100644 --- a/pyi.py +++ b/pyi.py @@ -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 @@ -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): @@ -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: @@ -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) diff --git a/tests/union_duplicates.pyi b/tests/union_duplicates.pyi index 2c515d2b..0d9622c4 100644 --- a/tests/union_duplicates.pyi +++ b/tests/union_duplicates.pyi @@ -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 @@ -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"