Repository navigation
[ruff] Add rule for default values on method receivers (RUF077) - #26700
Conversation
|
Signed-off-by: John Kagunda <rafaeljohb@gmail.com>
Signed-off-by: John Kagunda <rafaeljohb@gmail.com>
ntBre
left a comment
There was a problem hiding this comment.
Thanks! This looks good overall, but we need to make sure to add the rule in preview, and I think we can reuse some existing code for the static/class/instance method checks. I also had a request about the tests.
|
Looking back at the issue, I also saw in Micha's comment:
I think we may want to conservatively bail out in the presence of any other decorators, but especially |
β¦t.rs Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
β¦\n\nAdded more logic to ensure override and other custom decorators are handled conservatively.\n Signed-off-by: John Kagunda <rafaeljohb@gmail.com>
ntBre
left a comment
There was a problem hiding this comment.
This relates to my inline comments, but the one ecosystem hit appears to be a false positive:
- airflow-core/src/airflow/api_fastapi/execution_api/versions/v2025_05_20.py:35:65: method-receiver-default Instance receiver parameter should not have a default value
The function has a decorator that we can't resolve and ends up producing a static method:
β¦dated md tests Signed-off-by: John Kagunda <rafaeljohb@gmail.com>
Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
|
hello @ntBre , We good to go? :) |
ntBre
left a comment
There was a problem hiding this comment.
Thank you! This is looking good, just a couple more edge cases to consider.
ruff] Add rule for default values on method receivers (RUF077)
β¦t.rs Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
β¦ it's the sole decorator Previously receiver_kind treated any function classified as FunctionType::ClassMethod as having a class receiver, even when it carried unrelated or stacked decorators alongside @classmethod, which could change how the receiver is actually bound and produce false positives. Now the check only trusts the classification unconditionally for implicit classmethods (e.g. __init_subclass__), and for explicitly decorated ones only when @classmethod (or a configured equivalent) is the sole decorator. This required exposing is_class_method from ruff_python_semantic::analyze::function_type as pub so it can be called directly with the single decorator, and adds a snapshot test covering the new mdtest case.
β¦method-receiver-default
ntBre
left a comment
There was a problem hiding this comment.
I repeat this in the inline comment, but sorry for not noticing this earlier! I didn't realize we were doing all of this classification work just to customize the error message. I think we can just make the message suitably generic and then replace all of the receiver_kind stuff with something like:
let has_receiver = match decorator_list {
[] => true,
[decorator] => is_class_method(
decorator,
semantic,
&checker.settings().pep8_naming.classmethod_decorators,
),
_ => false,
};
if !has_receiver {
return;
}The custom messages were a nice touch, but it's not really clear what to do with a "maybe" metaclass, which drew my attention to this, and I think the overall simplification is a better tradeoff.
`fix_title` promised removing the default value, but no `Fix` was ever attached to the diagnostic, so `--fix` was a no-op. Attach an unsafe `Edit::deletion` that removes the default (and any surrounding parentheses) while leaving an annotation on the receiver untouched, and add mdtest coverage for the fix, including the parenthesized-default and annotated-receiver cases.
β¦ check Per review feedback, replace the classify()/FunctionType dispatch with a direct match on decorator_list: no decorators means it's always a receiver-bearing method (including implicit classmethods like __init_subclass__), and a single decorator only counts if it's the one that makes the function a classmethod. This drops now-unnecessary FunctionType/classify plumbing without changing behavior; the mdtest suite (including the previously separate is_metaclass handling that message-generification already made unnecessary) is unaffected.
β¦thod-receiver-default # Conflicts: # crates/ruff_linter/src/codes.rs
RUF077 registering in the merged rule table changes the preview rule listing this inline snapshot asserts on.
ntBre
left a comment
There was a problem hiding this comment.
Thank you! I pushed one small change documenting the options used, but this otherwise looks good to me!
Thanks for staying on top of all the merges and even picking a category! I think suspicious is perfect.
Summary
Adds a new Ruff rule that flags method receiver parameters (
self/cls) when they are given a default value.The rule targets functions defined directly inside a class body and uses semantic context to ensure it only applies to methods, not standalone functions. It determines the receiver kind, inspects the first positional parameter, and reports a diagnostic when that receiver has a default value.
Closes #25820
Test Plan
Tested by adding a Ruff fixture and snapshot for RUF077, and verifying the rule is triggered for methods whose receiver parameter has a default value.
Files added/updated for testing include:
crates/ruff_linter/resources/test/fixtures/ruff/RUF077.pycrates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF077_RUF077.py.snapI also validated the rule integration by building Ruff locally after registering the rule and hooking it into AST statement analysis.