From 221edcba5c3d78fc1a5cd7758e05b2b10dc162a5 Mon Sep 17 00:00:00 2001 From: Dan Parizher <105245560+danparizher@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:13:22 -0400 Subject: [PATCH] [`pyupgrade`] Keyword arguments in `super` should suppress the `UP008` fix (#19131) ## Summary Fixes #19096 --- .../test/fixtures/pyupgrade/UP008.py | 16 ++++++ .../rules/super_call_with_parameters.rs | 44 +++++++++------- ...er__rules__pyupgrade__tests__UP008.py.snap | 50 +++++++++++++++++++ ...__pyupgrade__tests__UP008.py__preview.snap | 50 +++++++++++++++++++ 4 files changed, 141 insertions(+), 19 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py index 174906993d..7a77087002 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py @@ -125,3 +125,19 @@ class ClassForCommentEnthusiasts(BaseClass): self # also a comment ).f() + + +# Issue #19096: super calls with keyword arguments should emit diagnostic but not be fixed +class Ord(int): + def __len__(self): + return super(Ord, self, uhoh=True, **{"error": True}).bit_length() + +class ExampleWithKeywords: + def method1(self): + super(ExampleWithKeywords, self, invalid=True).some_method() # Should emit diagnostic but NOT be fixed + + def method2(self): + super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed + + def method3(self): + super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs index d75602914e..ddb5ee034e 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs @@ -5,7 +5,7 @@ use ruff_text_size::{Ranged, TextSize}; use crate::checkers::ast::Checker; use crate::preview::is_safe_super_call_with_parameters_fix_enabled; -use crate::{AlwaysFixableViolation, Edit, Fix}; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `super` calls that pass redundant arguments. @@ -57,14 +57,16 @@ use crate::{AlwaysFixableViolation, Edit, Fix}; #[derive(ViolationMetadata)] pub(crate) struct SuperCallWithParameters; -impl AlwaysFixableViolation for SuperCallWithParameters { +impl Violation for SuperCallWithParameters { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + #[derive_message_formats] fn message(&self) -> String { "Use `super()` instead of `super(__class__, self)`".to_string() } - fn fix_title(&self) -> String { - "Remove `super()` parameters".to_string() + fn fix_title(&self) -> Option { + Some("Remove `super()` parameters".to_string()) } } @@ -165,22 +167,26 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall return; } - let applicability = if !checker.comment_ranges().intersects(call.arguments.range()) - && is_safe_super_call_with_parameters_fix_enabled(checker.settings()) - { - Applicability::Safe - } else { - Applicability::Unsafe - }; - let mut diagnostic = checker.report_diagnostic(SuperCallWithParameters, call.arguments.range()); - diagnostic.set_fix(Fix::applicable_edit( - Edit::deletion( - call.arguments.start() + TextSize::new(1), - call.arguments.end() - TextSize::new(1), - ), - applicability, - )); + + // Only provide a fix if there are no keyword arguments, since super() doesn't accept keyword arguments + if call.arguments.keywords.is_empty() { + let applicability = if !checker.comment_ranges().intersects(call.arguments.range()) + && is_safe_super_call_with_parameters_fix_enabled(checker.settings()) + { + Applicability::Safe + } else { + Applicability::Unsafe + }; + + diagnostic.set_fix(Fix::applicable_edit( + Edit::deletion( + call.arguments.start() + TextSize::new(1), + call.arguments.end() - TextSize::new(1), + ), + applicability, + )); + } } /// Returns `true` if a call is an argumented `super` invocation. diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap index 0e742cce05..f6070b8ded 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap @@ -249,3 +249,53 @@ UP008.py:123:14: UP008 [*] Use `super()` instead of `super(__class__, self)` 126 |- # also a comment 127 |- ).f() 123 |+ super().f() +128 124 | +129 125 | +130 126 | # Issue #19096: super calls with keyword arguments should emit diagnostic but not be fixed + +UP008.py:133:21: UP008 Use `super()` instead of `super(__class__, self)` + | +131 | class Ord(int): +132 | def __len__(self): +133 | return super(Ord, self, uhoh=True, **{"error": True}).bit_length() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008 +134 | +135 | class ExampleWithKeywords: + | + = help: Remove `super()` parameters + +UP008.py:137:14: UP008 Use `super()` instead of `super(__class__, self)` + | +135 | class ExampleWithKeywords: +136 | def method1(self): +137 | super(ExampleWithKeywords, self, invalid=True).some_method() # Should emit diagnostic but NOT be fixed + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008 +138 | +139 | def method2(self): + | + = help: Remove `super()` parameters + +UP008.py:140:14: UP008 Use `super()` instead of `super(__class__, self)` + | +139 | def method2(self): +140 | super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008 +141 | +142 | def method3(self): + | + = help: Remove `super()` parameters + +UP008.py:143:14: UP008 [*] Use `super()` instead of `super(__class__, self)` + | +142 | def method3(self): +143 | super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008 + | + = help: Remove `super()` parameters + +ℹ Unsafe fix +140 140 | super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed +141 141 | +142 142 | def method3(self): +143 |- super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords + 143 |+ super().some_method() # Should be fixed - no keywords diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py__preview.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py__preview.snap index 2f52d1313a..c6c4bf37f1 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py__preview.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py__preview.snap @@ -249,3 +249,53 @@ UP008.py:123:14: UP008 [*] Use `super()` instead of `super(__class__, self)` 126 |- # also a comment 127 |- ).f() 123 |+ super().f() +128 124 | +129 125 | +130 126 | # Issue #19096: super calls with keyword arguments should emit diagnostic but not be fixed + +UP008.py:133:21: UP008 Use `super()` instead of `super(__class__, self)` + | +131 | class Ord(int): +132 | def __len__(self): +133 | return super(Ord, self, uhoh=True, **{"error": True}).bit_length() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008 +134 | +135 | class ExampleWithKeywords: + | + = help: Remove `super()` parameters + +UP008.py:137:14: UP008 Use `super()` instead of `super(__class__, self)` + | +135 | class ExampleWithKeywords: +136 | def method1(self): +137 | super(ExampleWithKeywords, self, invalid=True).some_method() # Should emit diagnostic but NOT be fixed + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008 +138 | +139 | def method2(self): + | + = help: Remove `super()` parameters + +UP008.py:140:14: UP008 Use `super()` instead of `super(__class__, self)` + | +139 | def method2(self): +140 | super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008 +141 | +142 | def method3(self): + | + = help: Remove `super()` parameters + +UP008.py:143:14: UP008 [*] Use `super()` instead of `super(__class__, self)` + | +142 | def method3(self): +143 | super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008 + | + = help: Remove `super()` parameters + +ℹ Safe fix +140 140 | super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed +141 141 | +142 142 | def method3(self): +143 |- super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords + 143 |+ super().some_method() # Should be fixed - no keywords