diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap index b98df7bdc89bd..421bf68c6ad1f 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_python_formatter/tests/fixtures.rs -input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.py --- ## Input ```python @@ -2436,3 +2435,27 @@ error[invalid-syntax]: Cannot reuse outer quote character in f-strings on Python 179 | f"foo {'"bar"'}" | warning: Only accept new syntax errors if they are also present in the input. The formatter should not introduce syntax errors. + +error[invalid-syntax]: Cannot use line breaks in non-triple-quoted f-string replacement fields on Python 3.10 (syntax was added in Python 3.12) + --> fstring.py:572:8 + | +570 | ttttteeeeeeeeest, +571 | ] +572 | } more { + | ^ +573 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +574 | }": + | +warning: Only accept new syntax errors if they are also present in the input. The formatter should not introduce syntax errors. + +error[invalid-syntax]: Cannot use line breaks in non-triple-quoted f-string replacement fields on Python 3.10 (syntax was added in Python 3.12) + --> fstring.py:581:8 + | +579 | ttttteeeeeeeeest, +580 | ] +581 | } more { + | ^ +582 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +583 | }": + | +warning: Only accept new syntax errors if they are also present in the input. The formatter should not introduce syntax errors. diff --git a/crates/ruff_python_parser/resources/inline/err/pep701_f_string_py311.py b/crates/ruff_python_parser/resources/inline/err/pep701_f_string_py311.py index 91d8d8a6c4383..15613cb0fd051 100644 --- a/crates/ruff_python_parser/resources/inline/err/pep701_f_string_py311.py +++ b/crates/ruff_python_parser/resources/inline/err/pep701_f_string_py311.py @@ -6,6 +6,9 @@ }''' f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes +f"{ + 1 +}" f"test {a \ } more" # line continuation f"""{f"""{x}"""}""" # mark the whole triple quote diff --git a/crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py311.py b/crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py311.py index a50bc7593b0cc..c749c8fe7667e 100644 --- a/crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py311.py +++ b/crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py311.py @@ -3,6 +3,9 @@ f'outer {x:{"# not a comment"} }' f"""{f'''{f'{"# not a comment"}'}'''}""" f"""{f'''# before expression {f'# aro{f"#{1+1}#"}und #'}'''} # after expression""" +f"""{ + 1 +}""" f"escape outside of \t {expr}\n" f"test\"abcd" f"{1:\x64}" # escapes are valid in the format spec diff --git a/crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py312.py b/crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py312.py index 8a8b7a469dc76..8251a757c0ff5 100644 --- a/crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py312.py +++ b/crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py312.py @@ -6,5 +6,8 @@ }''' f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes +f"{ + 1 +}" f"test {a \ } more" # line continuation diff --git a/crates/ruff_python_parser/src/error.rs b/crates/ruff_python_parser/src/error.rs index 2ea046d29c262..d54f799441432 100644 --- a/crates/ruff_python_parser/src/error.rs +++ b/crates/ruff_python_parser/src/error.rs @@ -501,6 +501,7 @@ pub enum StarTupleKind { pub enum FStringKind { Backslash, Comment, + LineBreak, NestedQuote, } @@ -732,6 +733,11 @@ pub enum UnsupportedSyntaxErrorKind { /// bag['bag'] # recursive bags! /// }''' /// + /// # line breaks in a non-triple-quoted replacement field + /// f"{ + /// 1 + /// }" + /// /// # arbitrary nesting /// f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" /// ``` @@ -991,6 +997,9 @@ impl Display for UnsupportedSyntaxError { UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::Comment) => { "Cannot use comments in f-strings" } + UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::LineBreak) => { + "Cannot use line breaks in non-triple-quoted f-string replacement fields" + } UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::NestedQuote) => { "Cannot reuse outer quote character in f-strings" } diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index 0e40e5186fe92..7c0d169b3a091 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -1562,16 +1562,27 @@ impl<'src> Parser<'src> { } } - /// Check `range` for comment tokens and report an `UnsupportedSyntaxError` for each one found. - fn check_fstring_comments(&mut self, range: TextRange) { - self.unsupported_syntax_errors - .extend(self.tokens.in_range(range).iter().filter_map(|token| { - token.kind().is_comment().then_some(UnsupportedSyntaxError { - kind: UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::Comment), - range: token.range(), - target_version: self.options.target_version, - }) - })); + /// Check `range` for comment tokens, report an `UnsupportedSyntaxError` for each one found, + /// and return whether any comments were found. + fn check_fstring_comments(&mut self, range: TextRange) -> bool { + let mut has_comments = false; + + self.unsupported_syntax_errors.extend( + self.tokens + .in_range(range) + .iter() + .filter(|token| token.kind().is_comment()) + .map(|token| { + has_comments = true; + UnsupportedSyntaxError { + kind: UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::Comment), + range: token.range(), + target_version: self.options.target_version, + } + }), + ); + + has_comments } /// Parses a list of f/t-string elements. @@ -1852,6 +1863,9 @@ impl<'src> Parser<'src> { // }''' // f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting // f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes + // f"{ + // 1 + // }" // f"test {a \ // } more" # line continuation @@ -1873,6 +1887,9 @@ impl<'src> Parser<'src> { // f'outer {x:{"# not a comment"} }' // f"""{f'''{f'{"# not a comment"}'}'''}""" // f"""{f'''# before expression {f'# aro{f"#{1+1}#"}und #'}'''} # after expression""" + // f"""{ + // 1 + // }""" // f"escape outside of \t {expr}\n" // f"test\"abcd" // f"{1:\x64}" # escapes are valid in the format spec @@ -1887,6 +1904,9 @@ impl<'src> Parser<'src> { // }''' // f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting // f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes + // f"{ + // 1 + // }" // f"test {a \ // } more" # line continuation // f"""{f"""{x}"""}""" # mark the whole triple quote @@ -1920,7 +1940,10 @@ impl<'src> Parser<'src> { let quote_bytes = flags.quote_str().as_bytes(); let quote_len = flags.quote_len(); + let mut has_backslash_or_comment = false; + for slash_position in memchr::memchr_iter(b'\\', self.source[range].as_bytes()) { + has_backslash_or_comment = true; let slash_position = TextSize::try_from(slash_position).unwrap(); self.add_unsupported_syntax_error( UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::Backslash), @@ -1938,7 +1961,19 @@ impl<'src> Parser<'src> { ); } - self.check_fstring_comments(range); + has_backslash_or_comment |= self.check_fstring_comments(range); + + // Before Python 3.12, replacement fields could only span physical lines when the + // outer f-string was triple-quoted. + if !flags.is_triple_quoted() + && !has_backslash_or_comment + && memchr::memchr2(b'\n', b'\r', self.source[range].as_bytes()).is_some() + { + self.add_unsupported_syntax_error( + UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::LineBreak), + TextRange::at(range.start(), '{'.text_len()), + ); + } } ast::InterpolatedElement { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pep701_f_string_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pep701_f_string_py311.py.snap index 0551f2e9914a8..7e9520896ec8f 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pep701_f_string_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@pep701_f_string_py311.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs -input_file: crates/ruff_python_parser/resources/inline/err/pep701_f_string_py311.py --- ## AST @@ -8,7 +7,7 @@ input_file: crates/ruff_python_parser/resources/inline/err/pep701_f_string_py311 Module( ModModule { node_index: NodeIndex(None), - range: 0..549, + range: 0..562, body: [ Expr( StmtExpr { @@ -606,33 +605,81 @@ Module( Expr( StmtExpr { node_index: NodeIndex(None), - range: 336..359, + range: 336..348, value: FString( ExprFString { node_index: NodeIndex(None), - range: 336..359, + range: 336..348, value: FStringValue { inner: Single( FString( FString { - range: 336..359, + range: 336..348, + node_index: NodeIndex(None), + elements: [ + Interpolation( + InterpolatedElement { + range: 338..347, + node_index: NodeIndex(None), + expression: NumberLiteral( + ExprNumberLiteral { + node_index: NodeIndex(None), + range: 344..345, + value: Int( + 1, + ), + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: FStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + unclosed: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 349..372, + value: FString( + ExprFString { + node_index: NodeIndex(None), + range: 349..372, + value: FStringValue { + inner: Single( + FString( + FString { + range: 349..372, node_index: NodeIndex(None), elements: [ Literal( InterpolatedStringLiteralElement { - range: 338..343, + range: 351..356, node_index: NodeIndex(None), value: "test ", }, ), Interpolation( InterpolatedElement { - range: 343..353, + range: 356..366, node_index: NodeIndex(None), expression: Name( ExprName { node_index: NodeIndex(None), - range: 344..345, + range: 357..358, id: Name("a"), ctx: Load, }, @@ -644,7 +691,7 @@ Module( ), Literal( InterpolatedStringLiteralElement { - range: 353..358, + range: 366..371, node_index: NodeIndex(None), value: " more", }, @@ -667,41 +714,41 @@ Module( Expr( StmtExpr { node_index: NodeIndex(None), - range: 403..422, + range: 416..435, value: FString( ExprFString { node_index: NodeIndex(None), - range: 403..422, + range: 416..435, value: FStringValue { inner: Single( FString( FString { - range: 403..422, + range: 416..435, node_index: NodeIndex(None), elements: [ Interpolation( InterpolatedElement { - range: 407..419, + range: 420..432, node_index: NodeIndex(None), expression: FString( ExprFString { node_index: NodeIndex(None), - range: 408..418, + range: 421..431, value: FStringValue { inner: Single( FString( FString { - range: 408..418, + range: 421..431, node_index: NodeIndex(None), elements: [ Interpolation( InterpolatedElement { - range: 412..415, + range: 425..428, node_index: NodeIndex(None), expression: Name( ExprName { node_index: NodeIndex(None), - range: 413..414, + range: 426..427, id: Name("x"), ctx: Load, }, @@ -747,38 +794,38 @@ Module( Expr( StmtExpr { node_index: NodeIndex(None), - range: 468..502, + range: 481..515, value: FString( ExprFString { node_index: NodeIndex(None), - range: 468..502, + range: 481..515, value: FStringValue { inner: Single( FString( FString { - range: 468..502, + range: 481..515, node_index: NodeIndex(None), elements: [ Interpolation( InterpolatedElement { - range: 470..501, + range: 483..514, node_index: NodeIndex(None), expression: Call( ExprCall { node_index: NodeIndex(None), - range: 471..500, + range: 484..513, func: Attribute( ExprAttribute { node_index: NodeIndex(None), - range: 471..480, + range: 484..493, value: StringLiteral( ExprStringLiteral { node_index: NodeIndex(None), - range: 471..475, + range: 484..488, value: StringLiteralValue { inner: Single( StringLiteral { - range: 471..475, + range: 484..488, node_index: NodeIndex(None), value: "\n", flags: StringLiteralFlags { @@ -794,29 +841,29 @@ Module( ), attr: Identifier { id: Name("join"), - range: 476..480, + range: 489..493, node_index: NodeIndex(None), }, ctx: Load, }, ), arguments: Arguments { - range: 480..500, + range: 493..513, node_index: NodeIndex(None), args: [ List( ExprList { node_index: NodeIndex(None), - range: 481..499, + range: 494..512, elts: [ StringLiteral( ExprStringLiteral { node_index: NodeIndex(None), - range: 482..486, + range: 495..499, value: StringLiteralValue { inner: Single( StringLiteral { - range: 482..486, + range: 495..499, node_index: NodeIndex(None), value: "\t", flags: StringLiteralFlags { @@ -833,11 +880,11 @@ Module( StringLiteral( ExprStringLiteral { node_index: NodeIndex(None), - range: 488..492, + range: 501..505, value: StringLiteralValue { inner: Single( StringLiteral { - range: 488..492, + range: 501..505, node_index: NodeIndex(None), value: "\u{b}", flags: StringLiteralFlags { @@ -854,11 +901,11 @@ Module( StringLiteral( ExprStringLiteral { node_index: NodeIndex(None), - range: 494..498, + range: 507..511, value: StringLiteralValue { inner: Single( StringLiteral { - range: 494..498, + range: 507..511, node_index: NodeIndex(None), value: "\r", flags: StringLiteralFlags { @@ -942,7 +989,7 @@ Module( 7 | f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting | ^ Syntax Error: Cannot reuse outer quote character in f-strings on Python 3.11 (syntax was added in Python 3.12) 8 | f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes -9 | f"test {a \ +9 | f"{ | @@ -952,7 +999,7 @@ Module( 7 | f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting | ^ Syntax Error: Cannot reuse outer quote character in f-strings on Python 3.11 (syntax was added in Python 3.12) 8 | f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes -9 | f"test {a \ +9 | f"{ | @@ -962,7 +1009,7 @@ Module( 7 | f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting | ^ Syntax Error: Cannot reuse outer quote character in f-strings on Python 3.11 (syntax was added in Python 3.12) 8 | f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes -9 | f"test {a \ +9 | f"{ | @@ -972,7 +1019,7 @@ Module( 7 | f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting | ^ Syntax Error: Cannot reuse outer quote character in f-strings on Python 3.11 (syntax was added in Python 3.12) 8 | f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes -9 | f"test {a \ +9 | f"{ | @@ -982,7 +1029,7 @@ Module( 7 | f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting | ^ Syntax Error: Cannot reuse outer quote character in f-strings on Python 3.11 (syntax was added in Python 3.12) 8 | f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes -9 | f"test {a \ +9 | f"{ | @@ -991,57 +1038,67 @@ Module( 7 | f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting 8 | f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes | ^ Syntax Error: Cannot reuse outer quote character in f-strings on Python 3.11 (syntax was added in Python 3.12) - 9 | f"test {a \ -10 | } more" # line continuation + 9 | f"{ +10 | 1 | | 7 | f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}" # arbitrary nesting 8 | f"{f'''{"nested"} inner'''} outer" # nested (triple) quotes - 9 | f"test {a \ + 9 | f"{ + | ^ Syntax Error: Cannot use line breaks in non-triple-quoted f-string replacement fields on Python 3.11 (syntax was added in Python 3.12) +10 | 1 +11 | }" + | + + + | +10 | 1 +11 | }" +12 | f"test {a \ | ^ Syntax Error: Cannot use an escape sequence (backslash) in f-strings on Python 3.11 (syntax was added in Python 3.12) -10 | } more" # line continuation -11 | f"""{f"""{x}"""}""" # mark the whole triple quote +13 | } more" # line continuation +14 | f"""{f"""{x}"""}""" # mark the whole triple quote | | - 9 | f"test {a \ -10 | } more" # line continuation -11 | f"""{f"""{x}"""}""" # mark the whole triple quote +12 | f"test {a \ +13 | } more" # line continuation +14 | f"""{f"""{x}"""}""" # mark the whole triple quote | ^^^ Syntax Error: Cannot reuse outer quote character in f-strings on Python 3.11 (syntax was added in Python 3.12) -12 | f"{'\n'.join(['\t', '\v', '\r'])}" # multiple escape sequences, multiple errors +15 | f"{'\n'.join(['\t', '\v', '\r'])}" # multiple escape sequences, multiple errors | | -10 | } more" # line continuation -11 | f"""{f"""{x}"""}""" # mark the whole triple quote -12 | f"{'\n'.join(['\t', '\v', '\r'])}" # multiple escape sequences, multiple errors +13 | } more" # line continuation +14 | f"""{f"""{x}"""}""" # mark the whole triple quote +15 | f"{'\n'.join(['\t', '\v', '\r'])}" # multiple escape sequences, multiple errors | ^ Syntax Error: Cannot use an escape sequence (backslash) in f-strings on Python 3.11 (syntax was added in Python 3.12) | | -10 | } more" # line continuation -11 | f"""{f"""{x}"""}""" # mark the whole triple quote -12 | f"{'\n'.join(['\t', '\v', '\r'])}" # multiple escape sequences, multiple errors +13 | } more" # line continuation +14 | f"""{f"""{x}"""}""" # mark the whole triple quote +15 | f"{'\n'.join(['\t', '\v', '\r'])}" # multiple escape sequences, multiple errors | ^ Syntax Error: Cannot use an escape sequence (backslash) in f-strings on Python 3.11 (syntax was added in Python 3.12) | | -10 | } more" # line continuation -11 | f"""{f"""{x}"""}""" # mark the whole triple quote -12 | f"{'\n'.join(['\t', '\v', '\r'])}" # multiple escape sequences, multiple errors +13 | } more" # line continuation +14 | f"""{f"""{x}"""}""" # mark the whole triple quote +15 | f"{'\n'.join(['\t', '\v', '\r'])}" # multiple escape sequences, multiple errors | ^ Syntax Error: Cannot use an escape sequence (backslash) in f-strings on Python 3.11 (syntax was added in Python 3.12) | | -10 | } more" # line continuation -11 | f"""{f"""{x}"""}""" # mark the whole triple quote -12 | f"{'\n'.join(['\t', '\v', '\r'])}" # multiple escape sequences, multiple errors +13 | } more" # line continuation +14 | f"""{f"""{x}"""}""" # mark the whole triple quote +15 | f"{'\n'.join(['\t', '\v', '\r'])}" # multiple escape sequences, multiple errors | ^ Syntax Error: Cannot use an escape sequence (backslash) in f-strings on Python 3.11 (syntax was added in Python 3.12) | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py311.py.snap index 676cadad44bc1..aa8635c0b307e 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py311.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs -input_file: crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py311.py --- ## AST @@ -8,7 +7,7 @@ input_file: crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py311. Module( ModModule { node_index: NodeIndex(None), - range: 0..398, + range: 0..415, body: [ Expr( StmtExpr { @@ -509,33 +508,81 @@ Module( Expr( StmtExpr { node_index: NodeIndex(None), - range: 231..263, + range: 231..247, value: FString( ExprFString { node_index: NodeIndex(None), - range: 231..263, + range: 231..247, value: FStringValue { inner: Single( FString( FString { - range: 231..263, + range: 231..247, + node_index: NodeIndex(None), + elements: [ + Interpolation( + InterpolatedElement { + range: 235..244, + node_index: NodeIndex(None), + expression: NumberLiteral( + ExprNumberLiteral { + node_index: NodeIndex(None), + range: 241..242, + value: Int( + 1, + ), + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: FStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: true, + unclosed: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 248..280, + value: FString( + ExprFString { + node_index: NodeIndex(None), + range: 248..280, + value: FStringValue { + inner: Single( + FString( + FString { + range: 248..280, node_index: NodeIndex(None), elements: [ Literal( InterpolatedStringLiteralElement { - range: 233..254, + range: 250..271, node_index: NodeIndex(None), value: "escape outside of \t ", }, ), Interpolation( InterpolatedElement { - range: 254..260, + range: 271..277, node_index: NodeIndex(None), expression: Name( ExprName { node_index: NodeIndex(None), - range: 255..259, + range: 272..276, id: Name("expr"), ctx: Load, }, @@ -547,7 +594,7 @@ Module( ), Literal( InterpolatedStringLiteralElement { - range: 260..262, + range: 277..279, node_index: NodeIndex(None), value: "\n", }, @@ -570,21 +617,21 @@ Module( Expr( StmtExpr { node_index: NodeIndex(None), - range: 264..277, + range: 281..294, value: FString( ExprFString { node_index: NodeIndex(None), - range: 264..277, + range: 281..294, value: FStringValue { inner: Single( FString( FString { - range: 264..277, + range: 281..294, node_index: NodeIndex(None), elements: [ Literal( InterpolatedStringLiteralElement { - range: 266..276, + range: 283..293, node_index: NodeIndex(None), value: "test\"abcd", }, @@ -607,26 +654,26 @@ Module( Expr( StmtExpr { node_index: NodeIndex(None), - range: 278..289, + range: 295..306, value: FString( ExprFString { node_index: NodeIndex(None), - range: 278..289, + range: 295..306, value: FStringValue { inner: Single( FString( FString { - range: 278..289, + range: 295..306, node_index: NodeIndex(None), elements: [ Interpolation( InterpolatedElement { - range: 280..288, + range: 297..305, node_index: NodeIndex(None), expression: NumberLiteral( ExprNumberLiteral { node_index: NodeIndex(None), - range: 281..282, + range: 298..299, value: Int( 1, ), @@ -636,12 +683,12 @@ Module( conversion: None, format_spec: Some( InterpolatedStringFormatSpec { - range: 283..287, + range: 300..304, node_index: NodeIndex(None), elements: [ Literal( InterpolatedStringLiteralElement { - range: 283..287, + range: 300..304, node_index: NodeIndex(None), value: "d", }, @@ -669,26 +716,26 @@ Module( Expr( StmtExpr { node_index: NodeIndex(None), - range: 330..342, + range: 347..359, value: FString( ExprFString { node_index: NodeIndex(None), - range: 330..342, + range: 347..359, value: FStringValue { inner: Single( FString( FString { - range: 330..342, + range: 347..359, node_index: NodeIndex(None), elements: [ Interpolation( InterpolatedElement { - range: 332..341, + range: 349..358, node_index: NodeIndex(None), expression: NumberLiteral( ExprNumberLiteral { node_index: NodeIndex(None), - range: 333..334, + range: 350..351, value: Int( 1, ), @@ -698,12 +745,12 @@ Module( conversion: None, format_spec: Some( InterpolatedStringFormatSpec { - range: 335..340, + range: 352..357, node_index: NodeIndex(None), elements: [ Literal( InterpolatedStringLiteralElement { - range: 335..340, + range: 352..357, node_index: NodeIndex(None), value: "\"d\"", }, diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py312.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py312.py.snap index 91ec761efa0eb..e12413bb564f0 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py312.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@pep701_f_string_py312.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_python_parser/tests/fixtures.rs -input_file: crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py312.py --- ## AST @@ -8,7 +7,7 @@ input_file: crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py312. Module( ModModule { node_index: NodeIndex(None), - range: 0..403, + range: 0..416, body: [ Expr( StmtExpr { @@ -606,33 +605,81 @@ Module( Expr( StmtExpr { node_index: NodeIndex(None), - range: 336..359, + range: 336..348, value: FString( ExprFString { node_index: NodeIndex(None), - range: 336..359, + range: 336..348, value: FStringValue { inner: Single( FString( FString { - range: 336..359, + range: 336..348, + node_index: NodeIndex(None), + elements: [ + Interpolation( + InterpolatedElement { + range: 338..347, + node_index: NodeIndex(None), + expression: NumberLiteral( + ExprNumberLiteral { + node_index: NodeIndex(None), + range: 344..345, + value: Int( + 1, + ), + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + ], + flags: FStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + unclosed: false, + }, + }, + ), + ), + }, + }, + ), + }, + ), + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 349..372, + value: FString( + ExprFString { + node_index: NodeIndex(None), + range: 349..372, + value: FStringValue { + inner: Single( + FString( + FString { + range: 349..372, node_index: NodeIndex(None), elements: [ Literal( InterpolatedStringLiteralElement { - range: 338..343, + range: 351..356, node_index: NodeIndex(None), value: "test ", }, ), Interpolation( InterpolatedElement { - range: 343..353, + range: 356..366, node_index: NodeIndex(None), expression: Name( ExprName { node_index: NodeIndex(None), - range: 344..345, + range: 357..358, id: Name("a"), ctx: Load, }, @@ -644,7 +691,7 @@ Module( ), Literal( InterpolatedStringLiteralElement { - range: 353..358, + range: 366..371, node_index: NodeIndex(None), value: " more", },