Spruce up docs for flake8-pyi rules (#10422)

This commit is contained in:
Alex Waygood 2024-03-18 18:03:32 +00:00 committed by GitHub
parent 162d2eb723
commit ae0ff9b029
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 173 additions and 115 deletions

View file

@ -8,28 +8,34 @@ use crate::checkers::ast::Checker;
/// ## What it does /// ## What it does
/// Checks for `__eq__` and `__ne__` implementations that use `typing.Any` as /// Checks for `__eq__` and `__ne__` implementations that use `typing.Any` as
/// the type annotation for the `obj` parameter. /// the type annotation for their second parameter.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// The Python documentation recommends the use of `object` to "indicate that a /// The Python documentation recommends the use of `object` to "indicate that a
/// value could be any type in a typesafe manner", while `Any` should be used to /// value could be any type in a typesafe manner". `Any`, on the other hand,
/// "indicate that a value is dynamically typed." /// should be seen as an "escape hatch when you need to mix dynamically and
/// statically typed code". Since using `Any` allows you to write highly unsafe
/// code, you should generally only use `Any` when the semantics of your code
/// would otherwise be inexpressible to the type checker.
/// ///
/// The semantics of `__eq__` and `__ne__` are such that the `obj` parameter /// The expectation in Python is that a comparison of two arbitrary objects
/// should be any type, as opposed to a dynamically typed value. Therefore, the /// using `==` or `!=` should never raise an exception. This contract can be
/// `object` type annotation is more appropriate. /// fully expressed in the type system and does not involve requesting unsound
/// behaviour from a type checker. As such, `object` is a more appropriate
/// annotation than `Any` for the second parameter of the methods implementing
/// these comparison operators -- `__eq__` and `__ne__`.
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
/// class Foo: /// class Foo:
/// def __eq__(self, obj: typing.Any): /// def __eq__(self, obj: typing.Any) -> bool:
/// ... /// ...
/// ``` /// ```
/// ///
/// Use instead: /// Use instead:
/// ```python /// ```python
/// class Foo: /// class Foo:
/// def __eq__(self, obj: object): /// def __eq__(self, obj: object) -> bool:
/// ... /// ...
/// ``` /// ```
/// ## References /// ## References

View file

@ -13,16 +13,17 @@ use crate::checkers::ast::Checker;
/// ## Why is this bad? /// ## Why is this bad?
/// `typing.NamedTuple` is the "typed version" of `collections.namedtuple`. /// `typing.NamedTuple` is the "typed version" of `collections.namedtuple`.
/// ///
/// The class generated by subclassing `typing.NamedTuple` is equivalent to /// Inheriting from `typing.NamedTuple` creates a custom `tuple` subclass in
/// `collections.namedtuple`, with the exception that `typing.NamedTuple` /// the same way as using the `collections.namedtuple` factory function.
/// includes an `__annotations__` attribute, which allows type checkers to /// However, using `typing.NamedTuple` allows you to provide a type annotation
/// infer the types of the fields. /// for each field in the class. This means that type checkers will have more
/// information to work with, and will be able to analyze your code more
/// precisely.
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
/// from collections import namedtuple /// from collections import namedtuple
/// ///
///
/// person = namedtuple("Person", ["name", "age"]) /// person = namedtuple("Person", ["name", "age"])
/// ``` /// ```
/// ///

View file

@ -20,18 +20,28 @@ use crate::checkers::ast::Checker;
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
/// from typing import TypeAlias
///
/// a = b = int /// a = b = int
/// a.b = int ///
///
/// class Klass:
/// ...
///
///
/// Klass.X: TypeAlias = int
/// ``` /// ```
/// ///
/// Use instead: /// Use instead:
/// ```python /// ```python
/// from typing import TypeAlias
///
/// a: TypeAlias = int /// a: TypeAlias = int
/// b: TypeAlias = int /// b: TypeAlias = int
/// ///
/// ///
/// class a: /// class Klass:
/// b: int /// X: TypeAlias = int
/// ``` /// ```
#[violation] #[violation]
pub struct ComplexAssignmentInStub; pub struct ComplexAssignmentInStub;

View file

@ -10,16 +10,16 @@ use crate::checkers::ast::Checker;
/// Checks for `if` statements with complex conditionals in stubs. /// Checks for `if` statements with complex conditionals in stubs.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// Stub files support simple conditionals to test for differences in Python /// Type checkers understand simple conditionals to express variations between
/// versions and platforms. However, type checkers only understand a limited /// different Python versions and platforms. However, complex tests may not be
/// subset of these conditionals; complex conditionals may result in false /// understood by a type checker, leading to incorrect inferences when they
/// positives or false negatives. /// analyze your code.
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
/// import sys /// import sys
/// ///
/// if (2, 7) < sys.version_info < (3, 5): /// if (3, 10) <= sys.version_info < (3, 12):
/// ... /// ...
/// ``` /// ```
/// ///
@ -27,9 +27,12 @@ use crate::checkers::ast::Checker;
/// ```python /// ```python
/// import sys /// import sys
/// ///
/// if sys.version_info < (3, 5): /// if sys.version_info >= (3, 10) and sys.version_info < (3, 12):
/// ... /// ...
/// ``` /// ```
///
/// ## References
/// The [typing documentation on stub files](https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks)
#[violation] #[violation]
pub struct ComplexIfStatementInStub; pub struct ComplexIfStatementInStub;

View file

@ -19,25 +19,32 @@ use crate::checkers::ast::Checker;
/// methods. /// methods.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// Improperly-annotated `__exit__` and `__aexit__` methods can cause /// Improperly annotated `__exit__` and `__aexit__` methods can cause
/// unexpected behavior when interacting with type checkers. /// unexpected behavior when interacting with type checkers.
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
/// from types import TracebackType
///
///
/// class Foo: /// class Foo:
/// def __exit__(self, typ, exc, tb, extra_arg) -> None: /// def __exit__(
/// self, typ: BaseException, exc: BaseException, tb: TracebackType
/// ) -> None:
/// ... /// ...
/// ``` /// ```
/// ///
/// Use instead: /// Use instead:
/// ```python /// ```python
/// from types import TracebackType
///
///
/// class Foo: /// class Foo:
/// def __exit__( /// def __exit__(
/// self, /// self,
/// typ: type[BaseException] | None, /// typ: type[BaseException] | None,
/// exc: BaseException | None, /// exc: BaseException | None,
/// tb: TracebackType | None, /// tb: TracebackType | None,
/// extra_arg: int = 0,
/// ) -> None: /// ) -> None:
/// ... /// ...
/// ``` /// ```

View file

@ -10,9 +10,10 @@ use crate::checkers::ast::Checker;
/// statement in stub files. /// statement in stub files.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// Stub files are already evaluated under `annotations` semantics. As such, /// Stub files natively support forward references in all contexts, as stubs are
/// the `from __future__ import annotations` import statement has no effect /// never executed at runtime. (They should be thought of as "data files" for
/// and should be omitted. /// type checkers.) As such, the `from __future__ import annotations` import
/// statement has no effect and should be omitted.
/// ///
/// ## References /// ## References
/// - [Static Typing with Python: Type Stubs](https://typing.readthedocs.io/en/latest/source/stubs.html) /// - [Static Typing with Python: Type Stubs](https://typing.readthedocs.io/en/latest/source/stubs.html)

View file

@ -15,24 +15,46 @@ use crate::checkers::ast::Checker;
/// `__iter__` methods should always should return an `Iterator` of some kind, /// `__iter__` methods should always should return an `Iterator` of some kind,
/// not an `Iterable`. /// not an `Iterable`.
/// ///
/// In Python, an `Iterator` is an object that has a `__next__` method, which /// In Python, an `Iterable` is an object that has an `__iter__` method; an
/// provides a consistent interface for sequentially processing elements from /// `Iterator` is an object that has `__iter__` and `__next__` methods. All
/// a sequence or other iterable object. Meanwhile, an `Iterable` is an object /// `__iter__` methods are expected to return `Iterator`s. Type checkers may
/// with an `__iter__` method, which itself returns an `Iterator`. /// not always recognize an object as being iterable if its `__iter__` method
/// does not return an `Iterator`.
/// ///
/// Every `Iterator` is an `Iterable`, but not every `Iterable` is an `Iterator`. /// Every `Iterator` is an `Iterable`, but not every `Iterable` is an `Iterator`.
/// By returning an `Iterable` from `__iter__`, you may end up returning an /// For example, `list` is an `Iterable`, but not an `Iterator`; you can obtain
/// object that doesn't implement `__next__`, which will cause a `TypeError` /// an iterator over a list's elements by passing the list to `iter()`:
/// at runtime. For example, returning a `list` from `__iter__` will cause ///
/// a `TypeError` when you call `__next__` on it, as a `list` is an `Iterable`, /// ```pycon
/// but not an `Iterator`. /// >>> import collections.abc
/// >>> x = [42]
/// >>> isinstance(x, collections.abc.Iterable)
/// True
/// >>> isinstance(x, collections.abc.Iterator)
/// False
/// >>> next(x)
/// Traceback (most recent call last):
/// File "<stdin>", line 1, in <module>
/// TypeError: 'list' object is not an iterator
/// >>> y = iter(x)
/// >>> isinstance(y, collections.abc.Iterable)
/// True
/// >>> isinstance(y, collections.abc.Iterator)
/// True
/// >>> next(y)
/// 42
/// ```
///
/// Using `Iterable` rather than `Iterator` as a return type for an `__iter__`
/// methods would imply that you would not necessarily be able to call `next()`
/// on the returned object, violating the expectations of the interface.
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
/// import collections.abc /// import collections.abc
/// ///
/// ///
/// class Class: /// class Klass:
/// def __iter__(self) -> collections.abc.Iterable[str]: /// def __iter__(self) -> collections.abc.Iterable[str]:
/// ... /// ...
/// ``` /// ```
@ -42,7 +64,7 @@ use crate::checkers::ast::Checker;
/// import collections.abc /// import collections.abc
/// ///
/// ///
/// class Class: /// class Klass:
/// def __iter__(self) -> collections.abc.Iterator[str]: /// def __iter__(self) -> collections.abc.Iterator[str]:
/// ... /// ...
/// ``` /// ```

View file

@ -9,19 +9,22 @@ use crate::checkers::ast::Checker;
use crate::settings::types::PythonVersion::Py311; use crate::settings::types::PythonVersion::Py311;
/// ## What it does /// ## What it does
/// Checks for uses of `typing.NoReturn` (and `typing_extensions.NoReturn`) in /// Checks for uses of `typing.NoReturn` (and `typing_extensions.NoReturn`) for
/// stubs. /// parameter annotations.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// Prefer `typing.Never` (or `typing_extensions.Never`) over `typing.NoReturn`, /// Prefer `Never` over `NoReturn` for parameter annotations. `Never` has a
/// as the former is more explicit about the intent of the annotation. This is /// clearer name in these contexts, since it makes little sense to talk about a
/// a purely stylistic choice, as the two are semantically equivalent. /// parameter annotation "not returning".
///
/// This is a purely stylistic lint: the two types have identical semantics for
/// type checkers. Both represent Python's "[bottom type]" (a type that has no
/// members).
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
/// from typing import NoReturn /// from typing import NoReturn
/// ///
///
/// def foo(x: NoReturn): ... /// def foo(x: NoReturn): ...
/// ``` /// ```
/// ///
@ -29,13 +32,14 @@ use crate::settings::types::PythonVersion::Py311;
/// ```python /// ```python
/// from typing import Never /// from typing import Never
/// ///
///
/// def foo(x: Never): ... /// def foo(x: Never): ...
/// ``` /// ```
/// ///
/// ## References /// ## References
/// - [Python documentation: `typing.Never`](https://docs.python.org/3/library/typing.html#typing.Never) /// - [Python documentation: `typing.Never`](https://docs.python.org/3/library/typing.html#typing.Never)
/// - [Python documentation: `typing.NoReturn`](https://docs.python.org/3/library/typing.html#typing.NoReturn) /// - [Python documentation: `typing.NoReturn`](https://docs.python.org/3/library/typing.html#typing.NoReturn)
///
/// [bottom type]: https://en.wikipedia.org/wiki/Bottom_type
#[violation] #[violation]
pub struct NoReturnArgumentAnnotationInStub { pub struct NoReturnArgumentAnnotationInStub {
module: TypingModule, module: TypingModule,

View file

@ -10,9 +10,9 @@ use crate::checkers::ast::Checker;
/// Checks for non-empty function stub bodies. /// Checks for non-empty function stub bodies.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// Stub files are meant to be used as a reference for the interface of a /// Stub files are never executed at runtime; they should be thought of as
/// module, and should not contain any implementation details. Thus, the /// "data files" for type checkers or IDEs. Function bodies are redundant
/// body of a stub function should be empty. /// for this purpose.
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
@ -26,7 +26,8 @@ use crate::checkers::ast::Checker;
/// ``` /// ```
/// ///
/// ## References /// ## References
/// - [PEP 484 Type Hints: Stub Files](https://www.python.org/dev/peps/pep-0484/#stub-files) /// - [The recommended style for stub functions and methods](https://typing.readthedocs.io/en/latest/source/stubs.html#id6)
/// in the typing docs.
#[violation] #[violation]
pub struct NonEmptyStubBody; pub struct NonEmptyStubBody;

View file

@ -10,13 +10,13 @@ use ruff_python_semantic::{ScopeKind, SemanticModel};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
/// ## What it does /// ## What it does
/// Checks for methods that are annotated with a fixed return type, which /// Checks for methods that are annotated with a fixed return type which
/// should instead be returning `self`. /// should instead be returning `Self`.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// If methods like `__new__` or `__enter__` are annotated with a fixed return /// If methods that generally return `self` at runtime are annotated with a
/// type, and the class is subclassed, type checkers will not be able to infer /// fixed return type, and the class is subclassed, type checkers will not be
/// the correct return type. /// able to infer the correct return type.
/// ///
/// For example: /// For example:
/// ```python /// ```python
@ -30,7 +30,7 @@ use crate::checkers::ast::Checker;
/// self.radius = radius /// self.radius = radius
/// return self /// return self
/// ///
/// # This returns `Shape`, not `Circle`. /// # Type checker infers return type as `Shape`, not `Circle`.
/// Circle().set_scale(0.5) /// Circle().set_scale(0.5)
/// ///
/// # Thus, this expression is invalid, as `Shape` has no attribute `set_radius`. /// # Thus, this expression is invalid, as `Shape` has no attribute `set_radius`.
@ -40,7 +40,7 @@ use crate::checkers::ast::Checker;
/// Specifically, this check enforces that the return type of the following /// Specifically, this check enforces that the return type of the following
/// methods is `Self`: /// methods is `Self`:
/// ///
/// 1. In-place binary operations, like `__iadd__`, `__imul__`, etc. /// 1. In-place binary-operation dunder methods, like `__iadd__`, `__imul__`, etc.
/// 1. `__new__`, `__enter__`, and `__aenter__`, if those methods return the /// 1. `__new__`, `__enter__`, and `__aenter__`, if those methods return the
/// class name. /// class name.
/// 1. `__iter__` methods that return `Iterator`, despite the class inheriting /// 1. `__iter__` methods that return `Iterator`, despite the class inheriting
@ -51,16 +51,16 @@ use crate::checkers::ast::Checker;
/// ## Example /// ## Example
/// ```python /// ```python
/// class Foo: /// class Foo:
/// def __new__(cls, *args: Any, **kwargs: Any) -> Bad: /// def __new__(cls, *args: Any, **kwargs: Any) -> Foo:
/// ... /// ...
/// ///
/// def __enter__(self) -> Bad: /// def __enter__(self) -> Foo:
/// ... /// ...
/// ///
/// async def __aenter__(self) -> Bad: /// async def __aenter__(self) -> Foo:
/// ... /// ...
/// ///
/// def __iadd__(self, other: Bad) -> Bad: /// def __iadd__(self, other: Foo) -> Foo:
/// ... /// ...
/// ``` /// ```
/// ///
@ -79,11 +79,11 @@ use crate::checkers::ast::Checker;
/// async def __aenter__(self) -> Self: /// async def __aenter__(self) -> Self:
/// ... /// ...
/// ///
/// def __iadd__(self, other: Bad) -> Self: /// def __iadd__(self, other: Foo) -> Self:
/// ... /// ...
/// ``` /// ```
/// ## References /// ## References
/// - [PEP 673](https://peps.python.org/pep-0673/) /// - [`typing.Self` documentation](https://docs.python.org/3/library/typing.html#typing.Self)
#[violation] #[violation]
pub struct NonSelfReturnType { pub struct NonSelfReturnType {
class_name: String, class_name: String,

View file

@ -12,14 +12,15 @@ use crate::checkers::ast::Checker;
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// If a function has a default value where the literal representation is /// If a function has a default value where the literal representation is
/// greater than 50 characters, it is likely to be an implementation detail or /// greater than 50 characters, the value is likely to be an implementation
/// a constant that varies depending on the system you're running on. /// detail or a constant that varies depending on the system you're running on.
/// ///
/// Consider replacing such constants with ellipses (`...`). /// Default values like these should generally be omitted from stubs. Use
/// ellipses (`...`) instead.
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
/// def foo(arg: int = 12345678901) -> None: /// def foo(arg: int = 693568516352839939918568862861217771399698285293568) -> None:
/// ... /// ...
/// ``` /// ```
/// ///

View file

@ -7,31 +7,25 @@ use crate::checkers::ast::Checker;
use crate::fix; use crate::fix;
/// ## What it does /// ## What it does
/// Checks for the presence of the `pass` statement within a class body /// Checks for the presence of the `pass` statement in non-empty class bodies
/// in a stub (`.pyi`) file. /// in `.pyi` files.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// In stub files, class definitions are intended to provide type hints, but /// The `pass` statement is always unnecessary in non-empty class bodies in
/// are never actually evaluated. As such, it's unnecessary to include a `pass` /// stubs.
/// statement in a class body, since it has no effect.
///
/// Instead of `pass`, prefer `...` to indicate that the class body is empty
/// and adhere to common stub file conventions.
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
/// class MyClass: /// class MyClass:
/// x: int
/// pass /// pass
/// ``` /// ```
/// ///
/// Use instead: /// Use instead:
/// ```python /// ```python
/// class MyClass: /// class MyClass:
/// ... /// x: int
/// ``` /// ```
///
/// ## References
/// - [Mypy documentation: Stub files](https://mypy.readthedocs.io/en/stable/stubs.html)
#[violation] #[violation]
pub struct PassInClassBody; pub struct PassInClassBody;

View file

@ -9,22 +9,22 @@ use crate::checkers::ast::Checker;
/// Checks for `pass` statements in empty stub bodies. /// Checks for `pass` statements in empty stub bodies.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// For consistency, empty stub bodies should contain `...` instead of `pass`. /// For stylistic consistency, `...` should always be used rather than `pass`
/// /// in stub files.
/// Additionally, an ellipsis better conveys the intent of the stub body (that
/// the body has been implemented, but has been intentionally left blank to
/// document the interface).
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
/// def foo(bar: int) -> list[int]: /// def foo(bar: int) -> list[int]: pass
/// pass
/// ``` /// ```
/// ///
/// Use instead: /// Use instead:
/// ```python /// ```python
/// def foo(bar: int) -> list[int]: ... /// def foo(bar: int) -> list[int]: ...
/// ``` /// ```
///
/// ## References
/// The [recommended style for functions and methods](https://typing.readthedocs.io/en/latest/source/stubs.html#functions-and-methods)
/// in the typing docs.
#[violation] #[violation]
pub struct PassStatementStubBody; pub struct PassStatementStubBody;

View file

@ -25,12 +25,12 @@ impl fmt::Display for VarKind {
} }
/// ## What it does /// ## What it does
/// Checks that type `TypeVar`, `ParamSpec`, and `TypeVarTuple` definitions in /// Checks that type `TypeVar`s, `ParamSpec`s, and `TypeVarTuple`s in stubs
/// stubs are prefixed with `_`. /// have names prefixed with `_`.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// By prefixing type parameters with `_`, we can avoid accidentally exposing /// Prefixing type parameters with `_` avoids accidentally exposing names
/// names internal to the stub. /// internal to the stub.
/// ///
/// ## Example /// ## Example
/// ```python /// ```python

View file

@ -9,10 +9,10 @@ use crate::checkers::ast::Checker;
/// Checks for quoted type annotations in stub (`.pyi`) files, which should be avoided. /// Checks for quoted type annotations in stub (`.pyi`) files, which should be avoided.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// Stub files are evaluated using `annotations` semantics, as if /// Stub files natively support forward references in all contexts, as stubs
/// `from __future__ import annotations` were included in the file. As such, /// are never executed at runtime. (They should be thought of as "data files"
/// quotes are never required for type annotations in stub files, and should be /// for type checkers and IDEs.) As such, quotes are never required for type
/// omitted. /// annotations in stub files, and should be omitted.
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
@ -25,6 +25,9 @@ use crate::checkers::ast::Checker;
/// def function() -> int: /// def function() -> int:
/// ... /// ...
/// ``` /// ```
///
/// ## References
/// - [Static Typing with Python: Type Stubs](https://typing.readthedocs.io/en/latest/source/stubs.html)
#[violation] #[violation]
pub struct QuotedAnnotationInStub; pub struct QuotedAnnotationInStub;

View file

@ -13,30 +13,28 @@ use crate::checkers::ast::Checker;
use crate::fix::snippet::SourceCodeSnippet; use crate::fix::snippet::SourceCodeSnippet;
/// ## What it does /// ## What it does
/// Checks for the presence of redundant `Literal` types and builtin super /// Checks for redundant unions between a `Literal` and a builtin supertype of
/// types in an union. /// that `Literal`.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// The use of `Literal` types in a union with the builtin super type of one of /// Using a `Literal` type in a union with its builtin supertype is redundant,
/// its literal members is redundant, as the super type is strictly more /// as the supertype will be strictly more general than the `Literal` type.
/// general than the `Literal` type.
///
/// For example, `Literal["A"] | str` is equivalent to `str`, and /// For example, `Literal["A"] | str` is equivalent to `str`, and
/// `Literal[1] | int` is equivalent to `int`, as `str` and `int` are the super /// `Literal[1] | int` is equivalent to `int`, as `str` and `int` are the
/// types of `"A"` and `1` respectively. /// supertypes of `"A"` and `1` respectively.
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
/// from typing import Literal /// from typing import Literal
/// ///
/// A: Literal["A"] | str /// x: Literal["A", b"B"] | str
/// ``` /// ```
/// ///
/// Use instead: /// Use instead:
/// ```python /// ```python
/// from typing import Literal /// from typing import Literal
/// ///
/// A: Literal["A"] /// x: Literal[b"B"] | str
/// ``` /// ```
#[violation] #[violation]
pub struct RedundantLiteralUnion { pub struct RedundantLiteralUnion {

View file

@ -7,34 +7,41 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
/// ## What it does /// ## What it does
/// Checks for union annotations that contain redundant numeric types (e.g., /// Checks for parameter annotations that contain redundant unions between
/// `int | float`). /// builtin numeric types (e.g., `int | float`).
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// In Python, `int` is a subtype of `float`, and `float` is a subtype of /// The [typing specification] states:
/// `complex`. As such, a union that includes both `int` and `float` is
/// redundant, as it is equivalent to a union that only includes `float`.
/// ///
/// For more, see [PEP 3141], which defines Python's "numeric tower". /// > Pythons numeric types `complex`, `float` and `int` are not subtypes of
/// > each other, but to support common use cases, the type system contains a
/// > straightforward shortcut: when an argument is annotated as having type
/// > `float`, an argument of type `int` is acceptable; similar, for an
/// > argument annotated as having type `complex`, arguments of type `float` or
/// > `int` are acceptable.
/// ///
/// Unions with redundant elements are less readable than unions without them. /// As such, a union that includes both `int` and `float` is redundant in the
/// specific context of a parameter annotation, as it is equivalent to a union
/// that only includes `float`. For readability and clarity, unions should omit
/// redundant elements.
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
/// def foo(x: float | int) -> None: /// def foo(x: float | int | str) -> None:
/// ... /// ...
/// ``` /// ```
/// ///
/// Use instead: /// Use instead:
/// ```python /// ```python
/// def foo(x: float) -> None: /// def foo(x: float | str) -> None:
/// ... /// ...
/// ``` /// ```
/// ///
/// ## References /// ## References
/// - [Python documentation: The numeric tower](https://docs.python.org/3/library/numbers.html#the-numeric-tower) /// - [The typing specification](https://docs.python.org/3/library/numbers.html#the-numeric-tower)
/// - [PEP 484: The numeric tower](https://peps.python.org/pep-0484/#the-numeric-tower)
/// ///
/// [PEP 3141]: https://peps.python.org/pep-3141/ /// [typing specification]: https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex
#[violation] #[violation]
pub struct RedundantNumericUnion { pub struct RedundantNumericUnion {
redundancy: Redundancy, redundancy: Redundancy,