Skip to content

Commit 6ded4be

Browse files
authored
Fix f-string middle panic when parsing t-strings (#23232)
Summary -- This PR fixes #23198 by copying Dylan's changes from #19183 to the other `InterpolatedStringElementsKind` variant, in line with Micha's prophetic comment (https://github.com/astral-sh/ruff/pull/19183/changes#r2217325217). Test Plan -- Two new tests derived from the issue
1 parent 647f660 commit 6ded4be

5 files changed

Lines changed: 71 additions & 30 deletions

File tree

crates/ruff_python_parser/src/parser/expression.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1788,7 +1788,7 @@ impl<'src> Parser<'src> {
17881788
let spec_start = self.node_start();
17891789
let elements = self.parse_interpolated_string_elements(
17901790
flags,
1791-
InterpolatedStringElementsKind::FormatSpec,
1791+
InterpolatedStringElementsKind::FormatSpec(string_kind),
17921792
string_kind,
17931793
);
17941794
Some(Box::new(ast::InterpolatedStringFormatSpec {

crates/ruff_python_parser/src/parser/mod.rs

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ enum InterpolatedStringElementsKind {
817817
/// ```py
818818
/// f"hello {x:.2f} world"
819819
/// ```
820-
FormatSpec,
820+
FormatSpec(InterpolatedStringKind),
821821
}
822822

823823
impl InterpolatedStringElementsKind {
@@ -827,7 +827,7 @@ impl InterpolatedStringElementsKind {
827827
// test_ok fstring_format_spec_terminator
828828
// f"hello {x:} world"
829829
// f"hello {x:.3f} world"
830-
InterpolatedStringElementsKind::FormatSpec => TokenKind::Rbrace,
830+
InterpolatedStringElementsKind::FormatSpec(_) => TokenKind::Rbrace,
831831
}
832832
}
833833
}
@@ -1197,23 +1197,13 @@ impl RecoveryContextKind {
11971197
) || p.at_name_or_soft_keyword()
11981198
}
11991199
RecoveryContextKind::WithItems(_) => p.at_expr(),
1200-
RecoveryContextKind::InterpolatedStringElements(elements_kind) => {
1201-
match elements_kind {
1202-
InterpolatedStringElementsKind::Regular(interpolated_string_kind) => {
1203-
p.current_token_kind() == interpolated_string_kind.middle_token()
1204-
|| p.current_token_kind() == TokenKind::Lbrace
1205-
}
1206-
InterpolatedStringElementsKind::FormatSpec => {
1207-
matches!(
1208-
p.current_token_kind(),
1209-
// Literal element
1210-
TokenKind::FStringMiddle | TokenKind::TStringMiddle
1211-
// Expression element
1212-
| TokenKind::Lbrace
1213-
)
1214-
}
1200+
RecoveryContextKind::InterpolatedStringElements(elements_kind) => match elements_kind {
1201+
InterpolatedStringElementsKind::Regular(interpolated_string_kind)
1202+
| InterpolatedStringElementsKind::FormatSpec(interpolated_string_kind) => {
1203+
p.current_token_kind() == interpolated_string_kind.middle_token()
1204+
|| p.current_token_kind() == TokenKind::Lbrace
12151205
}
1216-
}
1206+
},
12171207
}
12181208
}
12191209

@@ -1305,9 +1295,11 @@ impl RecoveryContextKind {
13051295
InterpolatedStringElementsKind::Regular(string_kind) => ParseErrorType::OtherError(
13061296
format!("Expected an element of or the end of the {string_kind}"),
13071297
),
1308-
InterpolatedStringElementsKind::FormatSpec => ParseErrorType::OtherError(
1309-
"Expected an f-string or t-string element or a '}'".to_string(),
1310-
),
1298+
InterpolatedStringElementsKind::FormatSpec(string_kind) => {
1299+
ParseErrorType::OtherError(format!(
1300+
"Expected an {string_kind} element or a '}}'"
1301+
))
1302+
}
13111303
},
13121304
}
13131305
}
@@ -1345,10 +1337,11 @@ bitflags! {
13451337
const LAMBDA_PARAMETERS = 1 << 24;
13461338
const WITH_ITEMS_PARENTHESIZED = 1 << 25;
13471339
const WITH_ITEMS_PARENTHESIZED_EXPRESSION = 1 << 26;
1348-
const WITH_ITEMS_UNPARENTHESIZED = 1 << 28;
1349-
const F_STRING_ELEMENTS = 1 << 29;
1350-
const T_STRING_ELEMENTS = 1 << 30;
1351-
const FT_STRING_ELEMENTS_IN_FORMAT_SPEC = 1 << 31;
1340+
const WITH_ITEMS_UNPARENTHESIZED = 1 << 27;
1341+
const F_STRING_ELEMENTS = 1 << 28;
1342+
const T_STRING_ELEMENTS = 1 << 29;
1343+
const F_STRING_ELEMENTS_IN_FORMAT_SPEC = 1 << 30;
1344+
const T_STRING_ELEMENTS_IN_FORMAT_SPEC = 1 << 31;
13521345
}
13531346
}
13541347

@@ -1409,8 +1402,11 @@ impl RecoveryContext {
14091402
RecoveryContext::T_STRING_ELEMENTS
14101403
}
14111404

1412-
InterpolatedStringElementsKind::FormatSpec => {
1413-
RecoveryContext::FT_STRING_ELEMENTS_IN_FORMAT_SPEC
1405+
InterpolatedStringElementsKind::FormatSpec(InterpolatedStringKind::FString) => {
1406+
RecoveryContext::F_STRING_ELEMENTS_IN_FORMAT_SPEC
1407+
}
1408+
InterpolatedStringElementsKind::FormatSpec(InterpolatedStringKind::TString) => {
1409+
RecoveryContext::T_STRING_ELEMENTS_IN_FORMAT_SPEC
14141410
}
14151411
},
14161412
}
@@ -1485,9 +1481,14 @@ impl RecoveryContext {
14851481
RecoveryContext::T_STRING_ELEMENTS => RecoveryContextKind::InterpolatedStringElements(
14861482
InterpolatedStringElementsKind::Regular(InterpolatedStringKind::TString),
14871483
),
1488-
RecoveryContext::FT_STRING_ELEMENTS_IN_FORMAT_SPEC => {
1484+
RecoveryContext::F_STRING_ELEMENTS_IN_FORMAT_SPEC => {
1485+
RecoveryContextKind::InterpolatedStringElements(
1486+
InterpolatedStringElementsKind::FormatSpec(InterpolatedStringKind::FString),
1487+
)
1488+
}
1489+
RecoveryContext::T_STRING_ELEMENTS_IN_FORMAT_SPEC => {
14891490
RecoveryContextKind::InterpolatedStringElements(
1490-
InterpolatedStringElementsKind::FormatSpec,
1491+
InterpolatedStringElementsKind::FormatSpec(InterpolatedStringKind::TString),
14911492
)
14921493
}
14931494
_ => return None,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
source: crates/ruff_python_parser/src/parser/tests.rs
3+
expression: error
4+
---
5+
ParseError {
6+
error: ExpectedExpression,
7+
location: 3..4,
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
source: crates/ruff_python_parser/src/parser/tests.rs
3+
expression: error
4+
---
5+
ParseError {
6+
error: Lexical(
7+
UnrecognizedToken {
8+
tok: '\0',
9+
},
10+
),
11+
location: 5..6,
12+
}

crates/ruff_python_parser/src/parser/tests.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,23 @@ t"i}'"#;
157157

158158
insta::assert_debug_snapshot!(error);
159159
}
160+
161+
#[test]
162+
fn test_tstring_fstring_middle() {
163+
let source = "t'{:{F'{\0}F";
164+
let parsed = parse_expression(source);
165+
166+
let error = parsed.unwrap_err();
167+
168+
insta::assert_debug_snapshot!(error);
169+
}
170+
171+
#[test]
172+
fn test_tstring_fstring_middle_fuzzer() {
173+
let source = "A1[A\u{c}\0:+,>1t'{:f\0:{f\"f\0:\0{fm\0:{f:\u{10}\0\0\0:bb\0{@f>f\u{1}'\0f";
174+
let parsed = parse_expression(source);
175+
176+
let error = parsed.unwrap_err();
177+
178+
insta::assert_debug_snapshot!(error);
179+
}

0 commit comments

Comments
 (0)