[ruff] Fix false positives and negatives in RUF010#18690
[ruff] Fix false positives and negatives in RUF010#18690MichaReiser merged 10 commits intoastral-sh:mainfrom
ruff] Fix false positives and negatives in RUF010#18690Conversation
|
I don't get why the rule is not being trigged for the tests I added when ran with f"{str({})}"
f"{ascii({} | {})}"
import builtins
f"{builtins.repr(1)}"
f"{ascii(1)=}"
f"{ascii(lambda: 1)}"
f"{ascii(x := 2)}"Running with But in the tests it only flags for the |
i think you forgot to add a new snapshot |
|
crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs
Outdated
Show resolved
Hide resolved
crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs
Outdated
Show resolved
Hide resolved
crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs
Outdated
Show resolved
Hide resolved
crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs
Outdated
Show resolved
Hide resolved
| let contains_curly_brace = checker | ||
| .tokens() | ||
| .in_range(arg.range()) |
There was a problem hiding this comment.
I don't think this is correct. E.g we don't want to add a space for an expression like:
1 if b({ "key": "test" }) else 10
crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs
Outdated
Show resolved
Hide resolved
crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs
Outdated
Show resolved
Hide resolved
crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs
Outdated
Show resolved
Hide resolved
crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs
Outdated
Show resolved
Hide resolved
| if contains_brace(&call.args[0].value) { | ||
| formatted_string_expression.whitespace_before_expression = space(); | ||
| } | ||
|
|
||
| formatted_string_expression.expression = if needs_paren(&call.args[0].value) { | ||
| call.args[0] | ||
| .value | ||
| .clone() | ||
| .with_parens(LeftParen::default(), RightParen::default()) | ||
| } else { | ||
| call.args[0].value.clone() | ||
| }; | ||
|
|
There was a problem hiding this comment.
Can't we move this into the map arm and make it based on Ruff's input AST?
There was a problem hiding this comment.
I think we can't do that without reparsing the f-string expression, because in the map arm we only have the generated string of the fstring CST node.
There was a problem hiding this comment.
but can't we inspect the f_string variable?
There was a problem hiding this comment.
We can, but I don't see how it is going to help. In the map we already have the built fix string, and in this case we don't want to parenthesize the entire f-string, just a part of it.
There was a problem hiding this comment.
That's what I had in mind:
Subject: [PATCH] Rename `knot_benchmark` to `ty_benchmark`
---
Index: crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs b/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs
--- a/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs (revision 13921af61d95c86f1cddfbad51471c0654b72162)
+++ b/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs (date 1750839908748)
@@ -122,7 +122,7 @@
}
diagnostic.try_set_fix(|| {
- convert_call_to_conversion_flag(checker, conversion, f_string, index, element)
+ convert_call_to_conversion_flag(checker, conversion, f_string, index, arg)
});
}
}
@@ -133,7 +133,7 @@
conversion: Conversion,
f_string: &ast::FString,
index: usize,
- element: &InterpolatedStringElement,
+ arg: &Expr,
) -> Result<Fix> {
let source_code = checker.locator().slice(f_string);
transform_expression(source_code, checker.stylist(), |mut expression| {
@@ -145,7 +145,7 @@
formatted_string_expression.conversion = Some(conversion.as_str());
- if contains_brace(checker, element) {
+ if starts_with_lbrace(checker, arg) {
formatted_string_expression.whitespace_before_expression = space();
}
@@ -163,26 +163,19 @@
.map(|output| Fix::safe_edit(Edit::range_replacement(output, f_string.range())))
}
-fn contains_brace(checker: &Checker, element: &InterpolatedStringElement) -> bool {
- let Some(interpolation) = element.as_interpolation() else {
- return false;
- };
- let Some(call) = interpolation.expression.as_call_expr() else {
- return false;
- };
-
+fn starts_with_lbrace(checker: &Checker, arg: &Expr) -> bool {
checker
.tokens()
- .after(call.arguments.start())
+ .in_range(arg.range())
.iter()
// Skip the trivia tokens and the `(` from the arguments
.find(|token| !token.kind().is_trivia() && token.kind() != TokenKind::Lpar)
.is_some_and(|token| matches!(token.kind(), TokenKind::Lbrace))
}
-fn needs_paren(expr: &Expression) -> bool {
- matches!(expr, Expression::Lambda(_) | Expression::NamedExpr(_))
-}
+// fn needs_paren(expr: &Expr) -> bool {
+// matches!(expr, Expr::Lambda(_) | Expr::Named(_))
+// }
/// Represents the three built-in Python conversion functions that can be replaced
/// with f-string conversion flags.| fn needs_paren(expr: &Expression) -> bool { | ||
| matches!(expr, Expression::Lambda(_) | Expression::NamedExpr(_)) | ||
| } |
There was a problem hiding this comment.
We should add tests demonstrating this behavior. I think this is also another place where we could use OperatorPrecedence instead of listing all expressions where any precedence lower or equal to lambda require parenthesizing.
There was a problem hiding this comment.
We should add tests demonstrating this behavior.
We already have it, here:
ruff/crates/ruff_linter/resources/test/fixtures/ruff/RUF010.py
Lines 50 to 52 in 13921af
There was a problem hiding this comment.
It might then be unnecessary? Because I don't see any failing tests if I change the code to:
formatted_string_expression.expression =
// if needs_paren(OperatorPrecedence::from_expr(arg))
// {
// call.args[0]
// .value
// .clone()
// .with_parens(LeftParen::default(), RightParen::default())
// } else {
call.args[0].value.clone();
// };There was a problem hiding this comment.
Oh, the diagnostic was not triggering for those cases in the test file because the ascii binding is being shadowed. I didn't notice that, thanks for pointing that out! Should be fixed now.
| if contains_brace(&call.args[0].value) { | ||
| formatted_string_expression.whitespace_before_expression = space(); | ||
| } | ||
|
|
||
| formatted_string_expression.expression = if needs_paren(&call.args[0].value) { | ||
| call.args[0] | ||
| .value | ||
| .clone() | ||
| .with_parens(LeftParen::default(), RightParen::default()) | ||
| } else { | ||
| call.args[0].value.clone() | ||
| }; | ||
|
|
There was a problem hiding this comment.
That's what I had in mind:
Subject: [PATCH] Rename `knot_benchmark` to `ty_benchmark`
---
Index: crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs b/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs
--- a/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs (revision 13921af61d95c86f1cddfbad51471c0654b72162)
+++ b/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs (date 1750839908748)
@@ -122,7 +122,7 @@
}
diagnostic.try_set_fix(|| {
- convert_call_to_conversion_flag(checker, conversion, f_string, index, element)
+ convert_call_to_conversion_flag(checker, conversion, f_string, index, arg)
});
}
}
@@ -133,7 +133,7 @@
conversion: Conversion,
f_string: &ast::FString,
index: usize,
- element: &InterpolatedStringElement,
+ arg: &Expr,
) -> Result<Fix> {
let source_code = checker.locator().slice(f_string);
transform_expression(source_code, checker.stylist(), |mut expression| {
@@ -145,7 +145,7 @@
formatted_string_expression.conversion = Some(conversion.as_str());
- if contains_brace(checker, element) {
+ if starts_with_lbrace(checker, arg) {
formatted_string_expression.whitespace_before_expression = space();
}
@@ -163,26 +163,19 @@
.map(|output| Fix::safe_edit(Edit::range_replacement(output, f_string.range())))
}
-fn contains_brace(checker: &Checker, element: &InterpolatedStringElement) -> bool {
- let Some(interpolation) = element.as_interpolation() else {
- return false;
- };
- let Some(call) = interpolation.expression.as_call_expr() else {
- return false;
- };
-
+fn starts_with_lbrace(checker: &Checker, arg: &Expr) -> bool {
checker
.tokens()
- .after(call.arguments.start())
+ .in_range(arg.range())
.iter()
// Skip the trivia tokens and the `(` from the arguments
.find(|token| !token.kind().is_trivia() && token.kind() != TokenKind::Lpar)
.is_some_and(|token| matches!(token.kind(), TokenKind::Lbrace))
}
-fn needs_paren(expr: &Expression) -> bool {
- matches!(expr, Expression::Lambda(_) | Expression::NamedExpr(_))
-}
+// fn needs_paren(expr: &Expr) -> bool {
+// matches!(expr, Expr::Lambda(_) | Expr::Named(_))
+// }
/// Represents the three built-in Python conversion functions that can be replaced
/// with f-string conversion flags.
crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs
Show resolved
Hide resolved
MichaReiser
left a comment
There was a problem hiding this comment.
Thank you. This is great!
* main: [ty] Add builtins to completions derived from scope (#18982) [ty] Don't add incorrect subdiagnostic for unresolved reference (#18487) [ty] Simplify `KnownClass::check_call()` and `KnownFunction::check_call()` (#18981) [ty] Add micro-benchmark for #711 (#18979) [`flake8-annotations`] Make `ANN401` example error out-of-the-box (#18974) [`flake8-async`] Make `ASYNC110` example error out-of-the-box (#18975) [pandas]: Fix issue on `non pandas` dataframe `in-place` usage (PD002) (#18963) [`pylint`] Fix `PLC0415` example (#18970) [ty] Add environment variable to dump Salsa memory usage stats (#18928) [`pylint`] Fix `PLW0108` autofix introducing a syntax error when the lambda's body contains an assignment expression (#18678) Bump 0.12.1 (#18969) [`FastAPI`] Add fix safety section to `FAST002` (#18940) [ty] Add regression test for leading tab mis-alignment in diagnostic rendering (#18965) [ty] Resolve python environment in `Options::to_program_settings` (#18960) [`ruff`] Fix false positives and negatives in `RUF010` (#18690) [ty] Fix rendering of long lines that are indented with tabs [ty] Add regression test for diagnostic rendering panic [ty] Move venv and conda env discovery to `SearchPath::from_settings` (#18938)
Summary
The fix is suppressed if the f-string has debug text or call expression arguments contains a starred expression, ex:
Fixes #16325
Test Plan
Add regression tests