Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/ruff_python_parser/src/parser/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1788,7 +1788,7 @@ impl<'src> Parser<'src> {
let spec_start = self.node_start();
let elements = self.parse_interpolated_string_elements(
flags,
InterpolatedStringElementsKind::FormatSpec,
InterpolatedStringElementsKind::FormatSpec(string_kind),
string_kind,
);
Some(Box::new(ast::InterpolatedStringFormatSpec {
Expand Down
59 changes: 30 additions & 29 deletions crates/ruff_python_parser/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ enum InterpolatedStringElementsKind {
/// ```py
/// f"hello {x:.2f} world"
/// ```
FormatSpec,
FormatSpec(InterpolatedStringKind),
}

impl InterpolatedStringElementsKind {
Expand All @@ -827,7 +827,7 @@ impl InterpolatedStringElementsKind {
// test_ok fstring_format_spec_terminator
// f"hello {x:} world"
// f"hello {x:.3f} world"
InterpolatedStringElementsKind::FormatSpec => TokenKind::Rbrace,
InterpolatedStringElementsKind::FormatSpec(_) => TokenKind::Rbrace,
}
}
}
Expand Down Expand Up @@ -1197,23 +1197,13 @@ impl RecoveryContextKind {
) || p.at_name_or_soft_keyword()
}
RecoveryContextKind::WithItems(_) => p.at_expr(),
RecoveryContextKind::InterpolatedStringElements(elements_kind) => {
match elements_kind {
InterpolatedStringElementsKind::Regular(interpolated_string_kind) => {
p.current_token_kind() == interpolated_string_kind.middle_token()
|| p.current_token_kind() == TokenKind::Lbrace
}
InterpolatedStringElementsKind::FormatSpec => {
matches!(
p.current_token_kind(),
// Literal element
TokenKind::FStringMiddle | TokenKind::TStringMiddle
// Expression element
| TokenKind::Lbrace
)
}
RecoveryContextKind::InterpolatedStringElements(elements_kind) => match elements_kind {
InterpolatedStringElementsKind::Regular(interpolated_string_kind)
| InterpolatedStringElementsKind::FormatSpec(interpolated_string_kind) => {
p.current_token_kind() == interpolated_string_kind.middle_token()
|| p.current_token_kind() == TokenKind::Lbrace
}
}
},
}
}

Expand Down Expand Up @@ -1305,9 +1295,11 @@ impl RecoveryContextKind {
InterpolatedStringElementsKind::Regular(string_kind) => ParseErrorType::OtherError(
format!("Expected an element of or the end of the {string_kind}"),
),
InterpolatedStringElementsKind::FormatSpec => ParseErrorType::OtherError(
"Expected an f-string or t-string element or a '}'".to_string(),
),
InterpolatedStringElementsKind::FormatSpec(string_kind) => {
ParseErrorType::OtherError(format!(
"Expected an {string_kind} element or a '}}'"
))
}
},
}
}
Expand Down Expand Up @@ -1345,10 +1337,11 @@ bitflags! {
const LAMBDA_PARAMETERS = 1 << 24;
const WITH_ITEMS_PARENTHESIZED = 1 << 25;
const WITH_ITEMS_PARENTHESIZED_EXPRESSION = 1 << 26;
const WITH_ITEMS_UNPARENTHESIZED = 1 << 28;
const F_STRING_ELEMENTS = 1 << 29;
const T_STRING_ELEMENTS = 1 << 30;
const FT_STRING_ELEMENTS_IN_FORMAT_SPEC = 1 << 31;
const WITH_ITEMS_UNPARENTHESIZED = 1 << 27;
const F_STRING_ELEMENTS = 1 << 28;
const T_STRING_ELEMENTS = 1 << 29;
const F_STRING_ELEMENTS_IN_FORMAT_SPEC = 1 << 30;
const T_STRING_ELEMENTS_IN_FORMAT_SPEC = 1 << 31;
}
}

Expand Down Expand Up @@ -1409,8 +1402,11 @@ impl RecoveryContext {
RecoveryContext::T_STRING_ELEMENTS
}

InterpolatedStringElementsKind::FormatSpec => {
RecoveryContext::FT_STRING_ELEMENTS_IN_FORMAT_SPEC
InterpolatedStringElementsKind::FormatSpec(InterpolatedStringKind::FString) => {
RecoveryContext::F_STRING_ELEMENTS_IN_FORMAT_SPEC
}
InterpolatedStringElementsKind::FormatSpec(InterpolatedStringKind::TString) => {
RecoveryContext::T_STRING_ELEMENTS_IN_FORMAT_SPEC
}
},
}
Expand Down Expand Up @@ -1485,9 +1481,14 @@ impl RecoveryContext {
RecoveryContext::T_STRING_ELEMENTS => RecoveryContextKind::InterpolatedStringElements(
InterpolatedStringElementsKind::Regular(InterpolatedStringKind::TString),
),
RecoveryContext::FT_STRING_ELEMENTS_IN_FORMAT_SPEC => {
RecoveryContext::F_STRING_ELEMENTS_IN_FORMAT_SPEC => {
RecoveryContextKind::InterpolatedStringElements(
InterpolatedStringElementsKind::FormatSpec(InterpolatedStringKind::FString),
)
}
RecoveryContext::T_STRING_ELEMENTS_IN_FORMAT_SPEC => {
RecoveryContextKind::InterpolatedStringElements(
InterpolatedStringElementsKind::FormatSpec,
InterpolatedStringElementsKind::FormatSpec(InterpolatedStringKind::TString),
)
}
_ => return None,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: crates/ruff_python_parser/src/parser/tests.rs
expression: error
---
ParseError {
error: ExpectedExpression,
location: 3..4,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
source: crates/ruff_python_parser/src/parser/tests.rs
expression: error
---
ParseError {
error: Lexical(
UnrecognizedToken {
tok: '\0',
},
),
location: 5..6,
}
20 changes: 20 additions & 0 deletions crates/ruff_python_parser/src/parser/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,23 @@ t"i}'"#;

insta::assert_debug_snapshot!(error);
}

#[test]
fn test_tstring_fstring_middle() {
let source = "t'{:{F'{\0}F";
let parsed = parse_expression(source);

let error = parsed.unwrap_err();

insta::assert_debug_snapshot!(error);
}

#[test]
fn test_tstring_fstring_middle_fuzzer() {
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";
let parsed = parse_expression(source);

let error = parsed.unwrap_err();

insta::assert_debug_snapshot!(error);
}