Skip to content

Commit 1ac515a

Browse files
committed
Avoid emitting multi-line f-string elements before Python 3.12
1 parent 03d8338 commit 1ac515a

2 files changed

Lines changed: 38 additions & 62 deletions

File tree

crates/ruff_python_formatter/src/other/interpolated_string_element.rs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ use std::borrow::Cow;
33
use ruff_formatter::{Buffer, FormatOptions as _, RemoveSoftLinesBuffer, format_args, write};
44
use ruff_python_ast::{
55
AnyStringFlags, ConversionFlag, Expr, InterpolatedElement, InterpolatedStringElement,
6-
InterpolatedStringLiteralElement,
6+
InterpolatedStringLiteralElement, StringFlags,
77
};
8-
use ruff_text_size::{Ranged, TextSlice};
8+
use ruff_source_file::LineRanges;
9+
use ruff_text_size::{Ranged, TextRange, TextSlice};
910

1011
use crate::comments::dangling_open_parenthesis_comments;
1112
use crate::context::{
@@ -16,7 +17,7 @@ use crate::prelude::*;
1617
use crate::string::normalize_string;
1718
use crate::verbatim::verbatim_text;
1819

19-
use super::interpolated_string::InterpolatedStringContext;
20+
use super::interpolated_string::{InterpolatedStringContext, InterpolatedStringLayout};
2021

2122
/// Formats an f-string element which is either a literal or a formatted expression.
2223
///
@@ -155,8 +156,23 @@ impl Format<PyFormatContext<'_>> for FormatInterpolatedElement<'_> {
155156
} else {
156157
let comments = f.context().comments().clone();
157158
let dangling_item_comments = comments.dangling(self.element);
158-
159-
let multiline = self.context.is_multiline();
159+
let flags = self.context.flags();
160+
161+
// Before Python 3.12, non-triple-quoted f-strings cannot introduce new multiline
162+
// replacement fields. Preserve existing multiline fields from unsupported syntax
163+
// inputs, but keep originally flat fields flat.
164+
let multiline = self.context.is_multiline()
165+
&& (f.options().target_version().supports_pep_701()
166+
|| flags.is_triple_quoted()
167+
|| f.context()
168+
.source()
169+
.contains_line_break(interpolated_element_expression_range(self.element)));
170+
171+
let context = if multiline {
172+
self.context
173+
} else {
174+
InterpolatedStringContext::new(flags, InterpolatedStringLayout::Flat)
175+
};
160176

161177
// If an expression starts with a `{`, we need to add a space before the
162178
// curly brace to avoid turning it into a literal curly with `{{`.
@@ -184,10 +200,10 @@ impl Format<PyFormatContext<'_>> for FormatInterpolatedElement<'_> {
184200
let state = match f.context().interpolated_string_state() {
185201
InterpolatedStringState::InsideInterpolatedElement(_)
186202
| InterpolatedStringState::NestedInterpolatedElement(_) => {
187-
InterpolatedStringState::NestedInterpolatedElement(self.context)
203+
InterpolatedStringState::NestedInterpolatedElement(context)
188204
}
189205
InterpolatedStringState::Outside => {
190-
InterpolatedStringState::InsideInterpolatedElement(self.context)
206+
InterpolatedStringState::InsideInterpolatedElement(context)
191207
}
192208
};
193209
let f = &mut WithInterpolatedStringState::new(state, f);
@@ -216,7 +232,7 @@ impl Format<PyFormatContext<'_>> for FormatInterpolatedElement<'_> {
216232
token(":").fmt(f)?;
217233

218234
for element in &format_spec.elements {
219-
FormatInterpolatedStringElement::new(element, self.context).fmt(f)?;
235+
FormatInterpolatedStringElement::new(element, context).fmt(f)?;
220236
}
221237
}
222238

@@ -268,6 +284,14 @@ impl Format<PyFormatContext<'_>> for FormatInterpolatedElement<'_> {
268284
}
269285
}
270286

287+
fn interpolated_element_expression_range(element: &InterpolatedElement) -> TextRange {
288+
element
289+
.format_spec
290+
.as_deref()
291+
.map(|format_spec| TextRange::new(element.start(), format_spec.start()))
292+
.unwrap_or_else(|| element.range())
293+
}
294+
271295
fn needs_bracket_spacing(expr: &Expr, context: &PyFormatContext) -> bool {
272296
// Ruff parenthesizes single element tuples, that's why we shouldn't insert
273297
// a space around the curly braces for those.

crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap

Lines changed: 6 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,27 +1337,15 @@ if f"aaaaaaaaaaa {ttttteeeeeeeeest} more { # comment
13371337
}":
13381338
pass
13391339

1340-
if f"aaaaaaaaaaa {
1341-
[
1342-
ttttteeeeeeeeest,
1343-
]
1344-
} more {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}":
1340+
if f"aaaaaaaaaaa {[ttttteeeeeeeeest]} more {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}":
13451341
pass
13461342

1347-
if f"aaaaaaaaaaa {
1348-
[
1349-
ttttteeeeeeeeest,
1350-
]
1351-
} more {
1343+
if f"aaaaaaaaaaa {[ttttteeeeeeeeest]} more {
13521344
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
13531345
}":
13541346
pass
13551347

1356-
if f"aaaaaaaaaaa {
1357-
[
1358-
ttttteeeeeeeeest,
1359-
]
1360-
} more {
1348+
if f"aaaaaaaaaaa {[ttttteeeeeeeeest]} more {
13611349
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
13621350
}":
13631351
pass
@@ -2169,27 +2157,15 @@ if f"aaaaaaaaaaa {ttttteeeeeeeeest} more { # comment
21692157
}":
21702158
pass
21712159

2172-
if f"aaaaaaaaaaa {
2173-
[
2174-
ttttteeeeeeeeest,
2175-
]
2176-
} more {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}":
2160+
if f"aaaaaaaaaaa {[ttttteeeeeeeeest]} more {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}":
21772161
pass
21782162

2179-
if f"aaaaaaaaaaa {
2180-
[
2181-
ttttteeeeeeeeest,
2182-
]
2183-
} more {
2163+
if f"aaaaaaaaaaa {[ttttteeeeeeeeest]} more {
21842164
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
21852165
}":
21862166
pass
21872167

2188-
if f"aaaaaaaaaaa {
2189-
[
2190-
ttttteeeeeeeeest,
2191-
]
2192-
} more {
2168+
if f"aaaaaaaaaaa {[ttttteeeeeeeeest]} more {
21932169
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
21942170
}":
21952171
pass
@@ -2435,27 +2411,3 @@ error[invalid-syntax]: Cannot reuse outer quote character in f-strings on Python
24352411
179 | f"foo {'"bar"'}"
24362412
|
24372413
warning: Only accept new syntax errors if they are also present in the input. The formatter should not introduce syntax errors.
2438-
2439-
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)
2440-
--> fstring.py:572:8
2441-
|
2442-
570 | ttttteeeeeeeeest,
2443-
571 | ]
2444-
572 | } more {
2445-
| ^
2446-
573 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
2447-
574 | }":
2448-
|
2449-
warning: Only accept new syntax errors if they are also present in the input. The formatter should not introduce syntax errors.
2450-
2451-
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)
2452-
--> fstring.py:581:8
2453-
|
2454-
579 | ttttteeeeeeeeest,
2455-
580 | ]
2456-
581 | } more {
2457-
| ^
2458-
582 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
2459-
583 | }":
2460-
|
2461-
warning: Only accept new syntax errors if they are also present in the input. The formatter should not introduce syntax errors.

0 commit comments

Comments
 (0)