From 7e13565d1feb8af7a2b8c6fe0e1b8066195c0cc9 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 27 Nov 2024 18:18:46 +0000 Subject: [PATCH] Fix false negative on lambdas inside decorators --- .../resources/test/fixtures/refurb/FURB118.py | 18 ++++++ .../refurb/rules/reimplemented_operator.rs | 12 +++- ...es__refurb__tests__FURB118_FURB118.py.snap | 58 +++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB118.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB118.py index 693cb2b2d0f937..f49b068cf5511d 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB118.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB118.py @@ -102,3 +102,21 @@ def x(self, other): class Bar: y = lambda self, other: self == other + +from typing import Callable +class Baz: + z: Callable = lambda self, other: self == other + + +# lambdas used in decorators do not constitute method definitions, +# so these *should* be flagged: +class TheLambdasHereAreNotMethods: + @pytest.mark.parametrize( + "slicer, expected", + [ + (lambda x: x[-2:], "foo"), + (lambda x: x[-5:-3], "bar"), + ], + ) + def test_inlet_asset_alias_extra_slice(self, slicer, expected): + assert slice("whatever") == expected diff --git a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs index cd5896825629a5..ad68ed8509fa36 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs @@ -92,7 +92,13 @@ impl Violation for ReimplementedOperator { /// FURB118 pub(crate) fn reimplemented_operator(checker: &mut Checker, target: &FunctionLike) { // Ignore methods, whether defined using the `def` keyword or via a `lambda` assignment. - if checker.semantic().current_scope().kind.is_class() { + if checker.semantic().current_scope().kind.is_class() + && (target.is_function_def() + || checker + .semantic() + .current_statements() + .any(|stmt| matches!(stmt, Stmt::AnnAssign(_) | Stmt::Assign(_)))) + { return; } @@ -152,6 +158,10 @@ impl FunctionLike<'_> { } } + const fn is_function_def(&self) -> bool { + matches!(self, Self::Function(_)) + } + /// Return the body of the function-like node. /// /// If the node is a function definition that consists of more than a single return statement, diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB118_FURB118.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB118_FURB118.py.snap index 1bc82ac000cf23..d245b7c98bc57b 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB118_FURB118.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB118_FURB118.py.snap @@ -949,3 +949,61 @@ FURB118.py:95:17: FURB118 [*] Use `operator.itemgetter((1, 2))` instead 96 97 | 97 98 | 98 99 | # All methods in classes are ignored, even those defined using lambdas: + +FURB118.py:117:14: FURB118 [*] Use `operator.itemgetter(slice(-2, None))` instead of defining a lambda + | +115 | "slicer, expected", +116 | [ +117 | (lambda x: x[-2:], "foo"), + | ^^^^^^^^^^^^^^^^ FURB118 +118 | (lambda x: x[-5:-3], "bar"), +119 | ], + | + = help: Replace with `operator.itemgetter(slice(-2, None))` + +ℹ Unsafe fix +104 104 | y = lambda self, other: self == other +105 105 | +106 106 | from typing import Callable + 107 |+import operator +107 108 | class Baz: +108 109 | z: Callable = lambda self, other: self == other +109 110 | +-------------------------------------------------------------------------------- +114 115 | @pytest.mark.parametrize( +115 116 | "slicer, expected", +116 117 | [ +117 |- (lambda x: x[-2:], "foo"), + 118 |+ (operator.itemgetter(slice(-2, None)), "foo"), +118 119 | (lambda x: x[-5:-3], "bar"), +119 120 | ], +120 121 | ) + +FURB118.py:118:14: FURB118 [*] Use `operator.itemgetter(slice(-5, -3))` instead of defining a lambda + | +116 | [ +117 | (lambda x: x[-2:], "foo"), +118 | (lambda x: x[-5:-3], "bar"), + | ^^^^^^^^^^^^^^^^^^ FURB118 +119 | ], +120 | ) + | + = help: Replace with `operator.itemgetter(slice(-5, -3))` + +ℹ Unsafe fix +104 104 | y = lambda self, other: self == other +105 105 | +106 106 | from typing import Callable + 107 |+import operator +107 108 | class Baz: +108 109 | z: Callable = lambda self, other: self == other +109 110 | +-------------------------------------------------------------------------------- +115 116 | "slicer, expected", +116 117 | [ +117 118 | (lambda x: x[-2:], "foo"), +118 |- (lambda x: x[-5:-3], "bar"), + 119 |+ (operator.itemgetter(slice(-5, -3)), "bar"), +119 120 | ], +120 121 | ) +121 122 | def test_inlet_asset_alias_extra_slice(self, slicer, expected):