diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_boolean_trap/FBT.py b/crates/ruff_linter/resources/test/fixtures/flake8_boolean_trap/FBT.py index 32074fb467..1c872e44ac 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_boolean_trap/FBT.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_boolean_trap/FBT.py @@ -94,7 +94,7 @@ class Registry: object.__setattr__(self, "flag", True) -from typing import Optional, Union +from typing import Optional, Union, Self def func(x: Union[list, Optional[int | str | float | bool]]): @@ -154,3 +154,23 @@ from pydantic_settings import BaseSettings class Settings(BaseSettings): foo: bool = Field(True, exclude=True) + + +# https://github.com/astral-sh/ruff/issues/14202 +class SupportsXorBool: + def __xor__(self, other: bool) -> Self: ... + +# check overload +class CustomFloat: + @overload + def __mul__(self, other: bool) -> Self: ... + @overload + def __mul__(self, other: float) -> Self: ... + @overload + def __mul__(self, other: Self) -> Self: ... + +# check union +class BooleanArray: + def __or__(self, other: Self | bool) -> Self: ... + def __ror__(self, other: Self | bool) -> Self: ... + def __ior__(self, other: Self | bool) -> Self: ... diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/helpers.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/helpers.rs index a931241001..f11b2b12fa 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/helpers.rs @@ -66,9 +66,88 @@ pub(super) fn is_user_allowed_func_call( }) } +/// Returns `true` if a function defines a binary operator. +/// +/// This only includes operators, i.e., functions that are usually not called directly. +/// +/// See: +pub(super) fn is_operator_method(name: &str) -> bool { + matches!( + name, + "__contains__" // in + // item access ([]) + | "__getitem__" // [] + | "__setitem__" // []= + | "__delitem__" // del [] + // addition (+) + | "__add__" // + + | "__radd__" // + + | "__iadd__" // += + // subtraction (-) + | "__sub__" // - + | "__rsub__" // - + | "__isub__" // -= + // multiplication (*) + | "__mul__" // * + | "__rmul__" // * + | "__imul__" // *= + // division (/) + | "__truediv__" // / + | "__rtruediv__" // / + | "__itruediv__" // /= + // floor division (//) + | "__floordiv__" // // + | "__rfloordiv__" // // + | "__ifloordiv__" // //= + // remainder (%) + | "__mod__" // % + | "__rmod__" // % + | "__imod__" // %= + // exponentiation (**) + | "__pow__" // ** + | "__rpow__" // ** + | "__ipow__" // **= + // left shift (<<) + | "__lshift__" // << + | "__rlshift__" // << + | "__ilshift__" // <<= + // right shift (>>) + | "__rshift__" // >> + | "__rrshift__" // >> + | "__irshift__" // >>= + // matrix multiplication (@) + | "__matmul__" // @ + | "__rmatmul__" // @ + | "__imatmul__" // @= + // meet (&) + | "__and__" // & + | "__rand__" // & + | "__iand__" // &= + // join (|) + | "__or__" // | + | "__ror__" // | + | "__ior__" // |= + // xor (^) + | "__xor__" // ^ + | "__rxor__" // ^ + | "__ixor__" // ^= + // comparison (>, <, >=, <=, ==, !=) + | "__gt__" // > + | "__lt__" // < + | "__ge__" // >= + | "__le__" // <= + | "__eq__" // == + | "__ne__" // != + // unary operators (included for completeness) + | "__pos__" // + + | "__neg__" // - + | "__invert__" // ~ + ) +} + /// Returns `true` if a function definition is allowed to use a boolean trap. pub(super) fn is_allowed_func_def(name: &str) -> bool { - matches!(name, "__setitem__" | "__post_init__") + matches!(name, "__post_init__") || is_operator_method(name) } /// Returns `true` if an argument is allowed to use a boolean trap. To return diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs index daeb58b0d0..1f7ec1b852 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs @@ -27,6 +27,9 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def; /// keyword-only argument, to force callers to be explicit when providing /// the argument. /// +/// Dunder methods that define operators are exempt from this rule, as are +/// setters and `@override` definitions. +/// /// In [preview], this rule will also flag annotations that include boolean /// variants, like `bool | int`. ///