-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ruff] Fix false positives and negatives in RUF010
#18690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but can't we inspect the f_string variable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
| f"{ascii(lambda: 1)}" | |
| f"{ascii(x := 2)}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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