diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_dunder_call.py b/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_dunder_call.py index 342f69609f865..d8971714befe8 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_dunder_call.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_dunder_call.py @@ -102,3 +102,6 @@ def use_descriptor(self, item): blah = dict[{"a": 1}.__delitem__("a")] # OK "abc".__contains__("a") + +# https://github.com/astral-sh/ruff/issues/14597 +assert "abc".__str__() == "abc" diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs index b854407bfb2de..12a4a6fa5dd1f 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs @@ -166,15 +166,21 @@ pub(crate) fn unnecessary_dunder_call(checker: &mut Checker, call: &ast::ExprCal ); if let Some(mut fixed) = fixed { - // by the looks of it, we don't need to wrap the expression in parens if - // it's the only argument to a call expression. - // being in any other kind of expression though, we *will* add parens. - // e.g. `print(a.__add__(3))` -> `print(a + 3)` instead of `print((a + 3))` - // a multiplication expression example: `x = 2 * a.__add__(3)` -> `x = 2 * (a + 3)` - let wrap_in_paren = checker - .semantic() - .current_expression_parent() - .is_some_and(|parent| !can_be_represented_without_parentheses(parent)); + let dunder = DunderReplacement::from_method(attr); + + // We never need to wrap builtin functions in extra parens + // since function calls have high precedence + let wrap_in_paren = (!matches!(dunder, Some(DunderReplacement::Builtin(_,_)))) + // By the looks of it, we don't need to wrap the expression in + // parens if it's the only argument to a call expression. + // Being in any other kind of expression though, we *will* + // add parens. e.g. `print(a.__add__(3))` -> `print(a + 3)` + // instead of `print((a + 3))` + // and `x = 2 * a.__add__(3)` -> `x = 2 * (a + 3)` + && checker + .semantic() + .current_expression_parent() + .is_some_and(|parent| !can_be_represented_without_parentheses(parent)); if wrap_in_paren { fixed = format!("({fixed})"); diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2801_unnecessary_dunder_call.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2801_unnecessary_dunder_call.py.snap index 40632facb3b4b..bc41c2974d8a4 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2801_unnecessary_dunder_call.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2801_unnecessary_dunder_call.py.snap @@ -1088,6 +1088,8 @@ unnecessary_dunder_call.py:104:1: PLC2801 [*] Unnecessary dunder call to `__cont 103 | 104 | "abc".__contains__("a") | ^^^^^^^^^^^^^^^^^^^^^^^ PLC2801 +105 | +106 | # https://github.com/astral-sh/ruff/issues/14597 | = help: Use `in` operator @@ -1097,3 +1099,21 @@ unnecessary_dunder_call.py:104:1: PLC2801 [*] Unnecessary dunder call to `__cont 103 103 | 104 |-"abc".__contains__("a") 104 |+"a" in "abc" +105 105 | +106 106 | # https://github.com/astral-sh/ruff/issues/14597 +107 107 | assert "abc".__str__() == "abc" + +unnecessary_dunder_call.py:107:8: PLC2801 [*] Unnecessary dunder call to `__str__`. Use `str()` builtin. + | +106 | # https://github.com/astral-sh/ruff/issues/14597 +107 | assert "abc".__str__() == "abc" + | ^^^^^^^^^^^^^^^ PLC2801 + | + = help: Use `str()` builtin + +ℹ Unsafe fix +104 104 | "abc".__contains__("a") +105 105 | +106 106 | # https://github.com/astral-sh/ruff/issues/14597 +107 |-assert "abc".__str__() == "abc" + 107 |+assert str("abc") == "abc"