Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,6 @@ def f(self):


class LambdaMethod(BaseClass):
# TODO(charlie): class-body lambda rewrite is still missed.
f = lambda self: super(LambdaMethod, self).f() # can use super()


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::visitor::{Visitor, walk_expr, walk_stmt};
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_semantic::{ScopeKind, SemanticModel};
use ruff_python_semantic::{Scope, ScopeKind, SemanticModel};
use ruff_text_size::{Ranged, TextSize};

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -75,12 +75,18 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall
if !is_super_call_with_arguments(call, checker) {
return;
}
let scope = checker.semantic().current_scope();

// Check: are we in a Function scope?
if !scope.kind.is_function() {
// Check: are we in a callable scope (function or lambda)?
let callable_scope = match &checker.semantic().current_scope().kind {
ScopeKind::Function(_) | ScopeKind::Lambda(_) => Some(checker.semantic().current_scope()),
ScopeKind::DunderClassCell => checker
.semantic()
.first_non_type_parent_scope(checker.semantic().current_scope())
.filter(|scope| matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Lambda(_))),
_ => None,
};
let Some(callable_scope) = callable_scope else {
return;
}
};

let mut parents = checker.semantic().current_statements();
// For a `super` invocation to be unnecessary, the first argument needs to match
Expand All @@ -90,28 +96,53 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall
return;
};

// Find the enclosing function definition (if any).
let Some(
func_stmt @ Stmt::FunctionDef(ast::StmtFunctionDef {
parameters: parent_parameters,
// Find the enclosing callable and extract the name of its first parameter.
let (parent_arg_name, has_local_dunder_class_var_ref) = match &callable_scope.kind {
ScopeKind::Function(_) => {
let Some(
func_stmt @ Stmt::FunctionDef(ast::StmtFunctionDef {
parameters: parent_parameters,
..
}),
) = parents.find(|stmt| stmt.is_function_def_stmt())
else {
return;
};

let Some(parent_arg) = parent_parameters.args.first() else {
return;
};

(
parent_arg.name().as_str(),
has_local_dunder_class_var_ref(callable_scope, |finder| {
finder.visit_stmt(func_stmt);
}),
)
}
ScopeKind::Lambda(ast::ExprLambda {
parameters: Some(parent_parameters),
body,
..
}),
) = parents.find(|stmt| stmt.is_function_def_stmt())
else {
return;
}) => {
let Some(parent_arg) = parent_parameters.args.first() else {
return;
};

(
parent_arg.name().as_str(),
has_local_dunder_class_var_ref(callable_scope, |finder| {
finder.visit_expr(body);
}),
)
}
_ => return,
};

if is_builtins_super(checker.semantic(), call)
&& !has_local_dunder_class_var_ref(checker.semantic(), func_stmt)
{
if is_builtins_super(checker.semantic(), call) && !has_local_dunder_class_var_ref {
return;
}

// Extract the name of the first argument to the enclosing function.
let Some(parent_arg) = parent_parameters.args.first() else {
return;
};

let mut enclosing_classes = checker.semantic().current_scopes().filter_map(|scope| {
let ScopeKind::Class(class_def) = &scope.kind else {
return None;
Expand All @@ -136,7 +167,7 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall
return;
};

if second_arg_id != parent_arg.name().as_str() {
if second_arg_id != parent_arg_name {
return;
}

Expand All @@ -145,7 +176,7 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall
// For `super(Outer.Inner, self)`, verify each segment matches the enclosing class nesting.
match first_arg {
Expr::Name(ast::ExprName { id, .. }) => {
if checker.semantic().current_scope().has(id) {
if callable_scope.has(id) {
return;
}

Expand Down Expand Up @@ -263,18 +294,22 @@ fn is_super_call_with_arguments(call: &ast::ExprCall, checker: &Checker) -> bool
checker.semantic().match_builtin_expr(&call.func, "super") && !call.arguments.is_empty()
}

/// Returns `true` if the function contains load references to `__class__` or `super` without
/// local binding.
/// Returns `true` if the callable body contains load references to `__class__` or `super` without
/// a local binding.
///
/// This indicates that the function relies on the implicit `__class__` cell variable created by
/// Python when `super()` is called without arguments, making it unsafe to remove `super()` parameters.
fn has_local_dunder_class_var_ref(semantic: &SemanticModel, func_stmt: &Stmt) -> bool {
if semantic.current_scope().has("__class__") {
/// This indicates that the callable relies on the implicit `__class__` cell variable created by
/// Python when `super()` is called without arguments, making it unsafe to remove `super()`
/// parameters.
fn has_local_dunder_class_var_ref(
callable_scope: &Scope,
visit: impl FnOnce(&mut ClassCellReferenceFinder),
) -> bool {
if callable_scope.has("__class__") {
return false;
}

let mut finder = ClassCellReferenceFinder::new();
finder.visit_stmt(func_stmt);
visit(&mut finder);

finder.found()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,52 +552,69 @@ help: Remove `super()` parameters
398 | class LambdaMethod(BaseClass):

UP008 [*] Use `super()` instead of `super(__class__, self)`
--> UP008.py:406:14
--> UP008.py:399:27
|
404 | @classmethod
405 | def f(cls):
406 | super(ClassMethod, cls).f() # can use super()
398 | class LambdaMethod(BaseClass):
399 | f = lambda self: super(LambdaMethod, self).f() # can use super()
| ^^^^^^^^^^^^^^^^^^^^
|
help: Remove `super()` parameters
396 |
397 |
398 | class LambdaMethod(BaseClass):
- f = lambda self: super(LambdaMethod, self).f() # can use super()
399 + f = lambda self: super().f() # can use super()
400 |
401 |
402 | class ClassMethod(BaseClass):

UP008 [*] Use `super()` instead of `super(__class__, self)`
--> UP008.py:405:14
|
403 | @classmethod
404 | def f(cls):
405 | super(ClassMethod, cls).f() # can use super()
| ^^^^^^^^^^^^^^^^^^
|
help: Remove `super()` parameters
403 | class ClassMethod(BaseClass):
404 | @classmethod
405 | def f(cls):
402 | class ClassMethod(BaseClass):
403 | @classmethod
404 | def f(cls):
- super(ClassMethod, cls).f() # can use super()
406 + super().f() # can use super()
405 + super().f() # can use super()
406 |
407 |
408 |
409 | class AsyncMethod(BaseClass):
408 | class AsyncMethod(BaseClass):

UP008 [*] Use `super()` instead of `super(__class__, self)`
--> UP008.py:411:14
--> UP008.py:410:14
|
409 | class AsyncMethod(BaseClass):
410 | async def f(self):
411 | super(AsyncMethod, self).f() # can use super()
408 | class AsyncMethod(BaseClass):
409 | async def f(self):
410 | super(AsyncMethod, self).f() # can use super()
| ^^^^^^^^^^^^^^^^^^^
|
help: Remove `super()` parameters
408 |
409 | class AsyncMethod(BaseClass):
410 | async def f(self):
407 |
408 | class AsyncMethod(BaseClass):
409 | async def f(self):
- super(AsyncMethod, self).f() # can use super()
411 + super().f() # can use super()
410 + super().f() # can use super()
411 |
412 |
413 |
414 | class OuterWithWhitespace:
413 | class OuterWithWhitespace:

UP008 [*] Use `super()` instead of `super(__class__, self)`
--> UP008.py:417:19
--> UP008.py:416:19
|
415 | class Inner(BaseClass):
416 | def f(self):
417 | super (OuterWithWhitespace.Inner, self).f() # can use super()
414 | class Inner(BaseClass):
415 | def f(self):
416 | super (OuterWithWhitespace.Inner, self).f() # can use super()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Remove `super()` parameters
414 | class OuterWithWhitespace:
415 | class Inner(BaseClass):
416 | def f(self):
413 | class OuterWithWhitespace:
414 | class Inner(BaseClass):
415 | def f(self):
- super (OuterWithWhitespace.Inner, self).f() # can use super()
417 + super ().f() # can use super()
416 + super ().f() # can use super()
Loading