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

Fix bug where methods defined using lambdas were flagged by FURB118 #14639

Merged
merged 7 commits into from
Nov 28, 2024

Conversation

AlexWaygood
Copy link
Member

Summary

Fixes #13829.

Replacing an instance method definition with an operator-module function doesn't work, because instance methods need to be callable objects with __get__ methods ("descriptors"), and while user-defined functions are descriptors, this is not true for the operator-module functions. We already avoid flagging user-defined methods that are defined using def statements, but we currently incorrectly flag user-defined functions that are defined by assigning lambdas to variables in class bodies. I.e., this is a perfectly valid definition of an instance method, and FURB118's suggestion to replace the lambda with operator.eq function would break it:

class Spam:
    foo = lambda self, other: self == other

As a demonstration in the REPL:

>>> class Spam:
...     foo = lambda self, other: self == other
...     
>>> Spam().foo(Spam())
False
>>> from operator import eq
>>> class Spam:
...     foo = eq
...     
>>> Spam().foo(Spam())
Traceback (most recent call last):
  File "<python-input-4>", line 1, in <module>
    Spam().foo(Spam())
    ~~~~~~~~~~^^^^^^^^
TypeError: eq expected 2 arguments, got 1

This PR fixes things so that lambda assignments in class bodies are considered off-limits for the rule in the same way as def statements in class bodies. It also makes the fix-safety docs more expansive, so that they're clearer that there's a wider range of reasons why the rule might be unsafe than just the keyword-argument issue.

Test Plan

I added a new fixture that confirms that all function definitions in class bodies are ignored by the rule.

@AlexWaygood AlexWaygood added bug Something isn't working preview Related to preview mode features labels Nov 27, 2024
Copy link
Contributor

github-actions bot commented Nov 27, 2024

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+0 -1 violations, +0 -0 fixes in 1 projects; 54 projects unchanged)

zulip/zulip (+0 -1 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --output-format concise --preview --select ALL

- zerver/models/custom_profile_fields.py:131:81: FURB118 Use `operator.itemgetter(1)` instead of defining a lambda

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
FURB118 1 0 1 0 0

@AlexWaygood
Copy link
Member Author

Hmm, the airflow hits in the ecosystem check are false negatives. I think I can avoid them.

Comment on lines +1653 to +1657
Expr::Lambda(lambda_expr) => {
if checker.enabled(Rule::ReimplementedOperator) {
refurb::rules::reimplemented_operator(checker, &lambda_expr.into());
}
}
Copy link
Member Author

@AlexWaygood AlexWaygood Nov 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needed to be moved from deferred_lambdas.rs to expressions.rs because otherwise the semantic model doesn't understand that the lambda it's analysing is ever inside a class scope. As far as I can tell, there's no particular advantage associated with deferring the analysis of this rule to after the semantic model has been fully built (what we currently do). The rule never analyses any bindings or uses of bindings

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for writing this comment. It saved me quiet some time guessing why it was moved.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it took me a little while to figure out why the semantic model didn't think lambdas inside class scopes were inside class scopes 😅

@AlexWaygood
Copy link
Member Author

AlexWaygood commented Nov 27, 2024

7e13565 got rid of the new false negatives this PR initially introduced on airflow. The remaining ecosystem hit on zerver is also a false negative introduced by this PR, but I think it's acceptable personally. Flagging lambdas inside assignments in class scopes is too risky.

Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you

Comment on lines +1653 to +1657
Expr::Lambda(lambda_expr) => {
if checker.enabled(Rule::ReimplementedOperator) {
refurb::rules::reimplemented_operator(checker, &lambda_expr.into());
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for writing this comment. It saved me quiet some time guessing why it was moved.

@AlexWaygood AlexWaygood enabled auto-merge (squash) November 28, 2024 12:48
@AlexWaygood AlexWaygood enabled auto-merge (squash) November 28, 2024 12:54
@AlexWaygood AlexWaygood merged commit d8bca0d into astral-sh:main Nov 28, 2024
20 checks passed
@AlexWaygood AlexWaygood deleted the alex/furb118 branch November 28, 2024 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working preview Related to preview mode features
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[preview] FURB118 can be unsafe for methods
2 participants