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
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 9 additions & 0 deletions crates/ruff_python_parser/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ pub enum StarTupleKind {
pub enum FStringKind {
Backslash,
Comment,
LineBreak,
NestedQuote,
}

Expand Down Expand Up @@ -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}"}"}"}"}"}"
/// ```
Expand Down Expand Up @@ -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"
}
Expand Down
57 changes: 46 additions & 11 deletions crates/ruff_python_parser/src/parser/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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),
Expand All @@ -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 {
Expand Down
Loading
Loading