mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-07 21:25:08 +00:00
Consider __new__
methods as special function type for enforcing class method or static method rules (#13305)
## Summary `__new__` methods are technically static methods, with `cls` as their first argument. However, Ruff currently classifies them as classmethod, which causes two issues: - It conveys incorrect information, leading to confusion. For example, in cases like ARG003, `__new__` is explicitly treated as a classmethod. - Future rules that should apply to staticmethod may not be applied correctly due to this misclassification. Motivated by this, the current PR makes the following adjustments: 1. Introduces `FunctionType::NewMethod` as an enum variant, since, for the purposes of lint rules, `__new__` sometimes behaves like a static method and other times like a class method. This is an internal change. 2. The following rule behaviors and messages are totally unchanged: - [too-many-arguments (PLR0913)](https://docs.astral.sh/ruff/rules/too-many-arguments/#too-many-arguments-plr0913) - [too-many-positional-arguments (PLR0917)](https://docs.astral.sh/ruff/rules/too-many-positional-arguments/#too-many-positional-arguments-plr0917) 3. The following rule behaviors are unchanged, but the messages have been changed for correctness to use "`__new__` method" instead of "class method": - [self-or-cls-assignment (PLW0642)](https://docs.astral.sh/ruff/rules/self-or-cls-assignment/#self-or-cls-assignment-plw0642) 4. The following rules are changed _unconditionally_ (not gated behind preview) because their current behavior is an honest bug: it just isn't true that `__new__` is a class method, and it _is_ true that `__new__` is a static method: - [unused-class-method-argument (ARG003)](https://docs.astral.sh/ruff/rules/unused-class-method-argument/#unused-class-method-argument-arg003) no longer applies to `__new__` - [unused-static-method-argument (ARG004)](https://docs.astral.sh/ruff/rules/unused-static-method-argument/#unused-static-method-argument-arg004) now applies to `__new__` 5. The only changes which differ based on `preview` are the following: - [invalid-first-argument-name-for-class-method (N804)](https://docs.astral.sh/ruff/rules/invalid-first-argument-name-for-class-method/#invalid-first-argument-name-for-class-method-n804): This is _skipped_ when `preview` is _enabled_. When `preview` is _disabled_, the rule is the same but the _message_ has been modified to say "`__new__` method" instead of "class method". - [bad-staticmethod-argument (PLW0211)](https://docs.astral.sh/ruff/rules/bad-staticmethod-argument/#bad-staticmethod-argument-plw0211): When `preview` is enabled, this now applies to `__new__`. Closes #13154 --------- Co-authored-by: dylwil3 <dylwil3@gmail.com> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
f29c7b03ec
commit
96dd1b1587
22 changed files with 184 additions and 24 deletions
|
@ -11,6 +11,9 @@ pub enum FunctionType {
|
|||
Method,
|
||||
ClassMethod,
|
||||
StaticMethod,
|
||||
/// `__new__` is an implicit static method but
|
||||
/// is treated similarly to class methods for several lint rules
|
||||
NewMethod,
|
||||
}
|
||||
|
||||
/// Classify a function based on its scope, name, and decorators.
|
||||
|
@ -30,17 +33,22 @@ pub fn classify(
|
|||
.any(|decorator| is_static_method(decorator, semantic, staticmethod_decorators))
|
||||
{
|
||||
FunctionType::StaticMethod
|
||||
} else if matches!(name, "__new__" | "__init_subclass__" | "__class_getitem__") // Special-case class method, like `__new__`.
|
||||
|| decorator_list.iter().any(|decorator| is_class_method(decorator, semantic, classmethod_decorators))
|
||||
} else if decorator_list
|
||||
.iter()
|
||||
.any(|decorator| is_class_method(decorator, semantic, classmethod_decorators))
|
||||
{
|
||||
FunctionType::ClassMethod
|
||||
} else {
|
||||
// It's an instance method.
|
||||
FunctionType::Method
|
||||
match name {
|
||||
"__new__" => FunctionType::NewMethod, // Implicit static method.
|
||||
"__init_subclass__" | "__class_getitem__" => FunctionType::ClassMethod, // Implicit class methods.
|
||||
_ => FunctionType::Method, // Default to instance method.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if a [`Decorator`] is indicative of a static method.
|
||||
/// Note: Implicit static methods like `__new__` are not considered.
|
||||
fn is_static_method(
|
||||
decorator: &Decorator,
|
||||
semantic: &SemanticModel,
|
||||
|
@ -81,6 +89,7 @@ fn is_static_method(
|
|||
}
|
||||
|
||||
/// Return `true` if a [`Decorator`] is indicative of a class method.
|
||||
/// Note: Implicit class methods like `__init_subclass__` and `__class_getitem__` are not considered.
|
||||
fn is_class_method(
|
||||
decorator: &Decorator,
|
||||
semantic: &SemanticModel,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue