@@ -3,9 +3,10 @@ use std::borrow::Cow;
33use ruff_formatter:: { Buffer , FormatOptions as _, RemoveSoftLinesBuffer , format_args, write} ;
44use 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
1011use crate :: comments:: dangling_open_parenthesis_comments;
1112use crate :: context:: {
@@ -16,7 +17,7 @@ use crate::prelude::*;
1617use crate :: string:: normalize_string;
1718use 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+
271295fn 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.
0 commit comments