Skip to content

Commit 20217e9

Browse files
authored
Fix panic on RUF027 (#9990)
## Summary Fixes #9895 The cause for this panic came from an offset error in the code. When analyzing a hypothetical f-string, we attempt to re-parse it as an f-string, and use the AST data to determine, among other things, whether the format specifiers are correct. To determine the 'correctness' of a format specifier, we actually have to re-parse the format specifier, and this is where the issue lies. To get the source text for the specifier, we were taking a slice from the original file source text... even though the AST data for the specifier belongs to the standalone parsed f-string expression, meaning that the ranges are going to be way off. In a file with Unicode, this can cause panics if the slice is inside a char boundary. To fix this, we now slice from the temporary source we created earlier to parse the literal as an f-string. ## Test Plan The RUF027 snapshot test was amended to include a string with format specifiers which we _should_ be calling out. This is to ensure we do slice format specifiers from the source text correctly.
1 parent 72bf1c2 commit 20217e9

File tree

6 files changed

+37
-3
lines changed

6 files changed

+37
-3
lines changed

crates/ruff_linter/resources/test/fixtures/ruff/RUF027_0.py

+4
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,7 @@ def method_calls():
6868
first = "Wendy"
6969
last = "Appleseed"
7070
value.method("{first} {last}") # RUF027
71+
72+
def format_specifiers():
73+
a = 4
74+
b = "{a:b} {a:^5}"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# 测试eval函数,eval()函数用来执行一个字符串表达式,并返t表达式的值。另外,可以讲字符串转换成列表或元组或字典
2+
a = "{1: 'a', 2: 'b'}"

crates/ruff_linter/src/rules/ruff/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ mod tests {
4848
#[test_case(Rule::DefaultFactoryKwarg, Path::new("RUF026.py"))]
4949
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_0.py"))]
5050
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_1.py"))]
51+
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_2.py"))]
5152
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
5253
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
5354
let diagnostics = test_path(

crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,10 @@ fn should_be_fstring(
8888
return false;
8989
}
9090

91-
let Ok(ast::Expr::FString(ast::ExprFString { value, .. })) =
92-
parse_expression(&format!("f{}", locator.slice(literal.range())))
91+
let fstring_expr = format!("f{}", locator.slice(literal));
92+
93+
// Note: Range offsets for `value` are based on `fstring_expr`
94+
let Ok(ast::Expr::FString(ast::ExprFString { value, .. })) = parse_expression(&fstring_expr)
9395
else {
9496
return false;
9597
};
@@ -159,7 +161,7 @@ fn should_be_fstring(
159161
has_name = true;
160162
}
161163
if let Some(spec) = &element.format_spec {
162-
let spec = locator.slice(spec.range());
164+
let spec = &fstring_expr[spec.range()];
163165
if FormatSpec::parse(spec).is_err() {
164166
return false;
165167
}

crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF027_RUF027_0.py.snap

+21
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ RUF027_0.py:70:18: RUF027 [*] Possible f-string without an `f` prefix
285285
69 | last = "Appleseed"
286286
70 | value.method("{first} {last}") # RUF027
287287
| ^^^^^^^^^^^^^^^^ RUF027
288+
71 |
289+
72 | def format_specifiers():
288290
|
289291
= help: Add `f` prefix
290292

@@ -294,5 +296,24 @@ RUF027_0.py:70:18: RUF027 [*] Possible f-string without an `f` prefix
294296
69 69 | last = "Appleseed"
295297
70 |- value.method("{first} {last}") # RUF027
296298
70 |+ value.method(f"{first} {last}") # RUF027
299+
71 71 |
300+
72 72 | def format_specifiers():
301+
73 73 | a = 4
302+
303+
RUF027_0.py:74:9: RUF027 [*] Possible f-string without an `f` prefix
304+
|
305+
72 | def format_specifiers():
306+
73 | a = 4
307+
74 | b = "{a:b} {a:^5}"
308+
| ^^^^^^^^^^^^^^ RUF027
309+
|
310+
= help: Add `f` prefix
311+
312+
Unsafe fix
313+
71 71 |
314+
72 72 | def format_specifiers():
315+
73 73 | a = 4
316+
74 |- b = "{a:b} {a:^5}"
317+
74 |+ b = f"{a:b} {a:^5}"
297318

298319

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
source: crates/ruff_linter/src/rules/ruff/mod.rs
3+
---
4+

0 commit comments

Comments
 (0)