diff --git a/src/chains.rs b/src/chains.rs index 77e8341970f..b325d069ec6 100644 --- a/src/chains.rs +++ b/src/chains.rs @@ -571,6 +571,7 @@ impl Rewrite for Chain { formatter.format_root(&self.parent, context, shape)?; if let Some(result) = formatter.pure_root() { return wrap_str( + context.config.style_edition(), result, context.config.max_width(), context.config.tab_spaces(), @@ -592,6 +593,7 @@ impl Rewrite for Chain { let result = formatter.join_rewrites(context, child_shape)?; wrap_str( + context.config.style_edition(), result, context.config.max_width(), context.config.tab_spaces(), @@ -992,6 +994,7 @@ impl<'a> ChainFormatter for ChainFormatterVisual<'a> { .sub_width(self.offset, item.span)?; let rewrite = item.rewrite_result(context, child_shape)?; if filtered_str_fits( + context.config.style_edition(), &rewrite, context.config.max_width(), context.config.tab_spaces(), diff --git a/src/expr.rs b/src/expr.rs index 48f17fb0fd1..ab8edd9aaa5 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -294,6 +294,7 @@ pub(crate) fn format_expr( ast::ExprKind::MacCall(ref mac) => { rewrite_macro(mac, context, shape, MacroPosition::Expression).or_else(|_| { wrap_str( + context.config.style_edition(), context.snippet(expr.span).to_owned(), context.config.max_width(), context.config.tab_spaces(), @@ -1372,6 +1373,7 @@ pub(crate) fn rewrite_literal( token::LitKind::Integer => rewrite_int_lit(context, token_lit, span, shape), token::LitKind::Float => rewrite_float_lit(context, token_lit, span, shape), _ => wrap_str( + context.config.style_edition(), context.snippet(span).to_owned(), context.config.max_width(), context.config.tab_spaces(), @@ -1394,6 +1396,7 @@ fn rewrite_string_lit(context: &RewriteContext<'_>, span: Span, shape: Shape) -> return Ok(string_lit.to_owned()); } else { return wrap_str( + context.config.style_edition(), string_lit.to_owned(), context.config.max_width(), context.config.tab_spaces(), @@ -1434,6 +1437,7 @@ fn rewrite_int_lit( }; if let Some(hex_lit) = hex_lit { return wrap_str( + context.config.style_edition(), format!( "0x{}{}", hex_lit, @@ -1448,6 +1452,7 @@ fn rewrite_int_lit( } wrap_str( + context.config.style_edition(), context.snippet(span).to_owned(), context.config.max_width(), context.config.tab_spaces(), @@ -1467,6 +1472,7 @@ fn rewrite_float_lit( FloatLiteralTrailingZero::Preserve ) { return wrap_str( + context.config.style_edition(), context.snippet(span).to_owned(), context.config.max_width(), context.config.tab_spaces(), @@ -1509,6 +1515,7 @@ fn rewrite_float_lit( "" }; wrap_str( + context.config.style_edition(), format!( "{}{}{}{}{}", integer_part, @@ -2373,6 +2380,7 @@ fn choose_rhs( match (orig_rhs, new_rhs) { (Ok(ref orig_rhs), Ok(ref new_rhs)) if !filtered_str_fits( + context.config.style_edition(), &new_rhs, context.config.max_width(), context.config.tab_spaces(), diff --git a/src/macros.rs b/src/macros.rs index 9a88569b790..52782d34b6c 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1381,6 +1381,7 @@ impl MacroBranch { }; if !filtered_str_fits( + config.style_edition(), &new_body_snippet.snippet, config.max_width(), context.config.tab_spaces(), diff --git a/src/pairs.rs b/src/pairs.rs index b4e390f2061..717c7787a13 100644 --- a/src/pairs.rs +++ b/src/pairs.rs @@ -104,6 +104,7 @@ fn rewrite_pairs_one_line( } wrap_str( + context.config.style_edition(), result, context.config.max_width(), context.config.tab_spaces(), diff --git a/src/string.rs b/src/string.rs index af90bb055c8..faeb0c61e7a 100644 --- a/src/string.rs +++ b/src/string.rs @@ -151,6 +151,7 @@ pub(crate) fn rewrite_string<'a>( result.push_str(fmt.closer); wrap_str( + fmt.config.style_edition(), result, fmt.config.max_width(), fmt.config.tab_spaces(), diff --git a/src/utils.rs b/src/utils.rs index f7933ac52f7..4a144ae7e6e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -453,12 +453,13 @@ macro_rules! skip_out_of_file_lines_range_visitor { // Wraps String in an Option. Returns Some when the string adheres to the // Rewrite constraints defined for the Rewrite trait and None otherwise. pub(crate) fn wrap_str( + style_edition: StyleEdition, s: String, max_width: usize, tab_spaces: usize, shape: Shape, ) -> Option { - if filtered_str_fits(&s, max_width, tab_spaces, shape) { + if filtered_str_fits(style_edition, &s, max_width, tab_spaces, shape) { Some(s) } else { None @@ -466,32 +467,88 @@ pub(crate) fn wrap_str( } pub(crate) fn filtered_str_fits( + style_edition: StyleEdition, snippet: &str, max_width: usize, tab_spaces: usize, shape: Shape, ) -> bool { + use crate::comment::{FullCodeCharKind, LineClasses}; + let snippet = &filter_normal_code(snippet); if !snippet.is_empty() { - // First line must fits with `shape.width`. - if first_line_width(snippet) > shape.width { - return false; - } + let line_classes = { + if style_edition >= StyleEdition::Edition2027 { + // Collect line classifications to check for string content. + let line_classes: Vec<_> = LineClasses::new(snippet).collect(); + + // First line must fit with `shape.width`, unless it's a multi-line string + // literal that starts on this line - string content cannot be shortened. + let first_line_width = first_line_width(snippet); + if first_line_width > shape.width { + // Only allow the exception for multi-line strings (StartString indicates + // the string continues on subsequent lines). + let is_multiline = line_classes.len() > 1; + let first_is_multiline_string = is_multiline + && line_classes + .first() + .is_some_and(|(kind, _)| *kind == FullCodeCharKind::StartString); + if !first_is_multiline_string { + return false; + } + } + line_classes + } else { + // First line must fits with `shape.width`. + if first_line_width(snippet) > shape.width { + return false; + } + vec![] + } + }; + // If the snippet does not include newline, we are done. if is_single_line(snippet) { return true; } + // The other lines must fit within the maximum width. - if snippet - .lines() - .skip(1) - .any(|line| unicode_str_width(line) > max_width) - { - return false; + if style_edition < StyleEdition::Edition2027 { + if snippet + .lines() + .skip(1) + .any(|line| unicode_str_width(line) > max_width) + { + return false; + } } + + // Exception: lines that are inside or end a multi-line string literal + // may exceed max_width since string content cannot be reformatted. + let mut last_line_is_string = false; + for (i, (kind, line)) in line_classes.iter().enumerate() { + if i == 0 { + continue; // First line already checked above + } + // Track if the last line is string content + last_line_is_string = + *kind == FullCodeCharKind::InString || *kind == FullCodeCharKind::EndString; + + if unicode_str_width(line) > max_width { + // Allow lines that are string continuations (InString) or + // end a string (EndString) to exceed max_width. + if !last_line_is_string { + return false; + } + } + } + // A special check for the last line, since the caller may - // place trailing characters on this line. - if last_line_width(snippet, tab_spaces) > shape.used_width() + shape.width { + // place trailing characters on this line. Skip this check if the + // last line is string content (which cannot be reformatted). + if !last_line_is_string + && last_line_width(snippet, tab_spaces) > shape.used_width() + shape.width + { return false; } } diff --git a/tests/source/issue-6769.rs b/tests/source/issue-6769.rs new file mode 100644 index 00000000000..340d7b9c408 --- /dev/null +++ b/tests/source/issue-6769.rs @@ -0,0 +1,39 @@ +//@ rustfmt-style_edition: 2027 + +fn test() { + let foo = [ + (b"Welcome to Ringboard!" as &[u8], &em), + ( + b"Ringboard is a fast, efficient, and composable clipboard manager for Linux." + , &em, + ), + ( + b"It supports both Wayland and X11 along with multiple clients to manage your \ + clipboard history." + , &em, + ), + ( + b"Clients include a standard GUI, an interactive TUI, and a CLI for all your scripting \ + needs." + , &em, + ), + ( + "Ringboard can copy arbitrary bytes—that includes images!".as_bytes(), + &em, + ), + ( + b"Plaintext and RegEx search are available for fast entry retrieval.", + &em, + ), + (b"Enjoy this image from our AI overlords:", &em), + ( + include_bytes!("../logo.jpeg") as &[u8], + &MimeType::from("image/jpeg").unwrap(), + ), + ( + b"Finally, it's worth mentioning that Ringboard is extremely efficient, performant, \ + and scalable." + , &em, + ), + ]; +} diff --git a/tests/source/issue-6778.rs b/tests/source/issue-6778.rs new file mode 100644 index 00000000000..115f99ee3bd --- /dev/null +++ b/tests/source/issue-6778.rs @@ -0,0 +1,28 @@ +//@ rustfmt-style_edition: 2027 + +#![allow(dead_code)] + +struct Parameter { + required: bool, + description: &'static str, +} + +pub struct Test; + +impl Test { + fn parameters(&self) -> &'static [Parameter] { + &[ + Parameter { + required: true, + description: "Foo", + }, + Parameter { + required: false, + description: "Bar +This string is exactly 100 chars long. Delete one character to make it 99 chars long and it'll work!", + }, + ] + } +} + +fn main() {} diff --git a/tests/target/issue-6769.rs b/tests/target/issue-6769.rs new file mode 100644 index 00000000000..8bbf5721383 --- /dev/null +++ b/tests/target/issue-6769.rs @@ -0,0 +1,39 @@ +//@ rustfmt-style_edition: 2027 + +fn test() { + let foo = [ + (b"Welcome to Ringboard!" as &[u8], &em), + ( + b"Ringboard is a fast, efficient, and composable clipboard manager for Linux.", + &em, + ), + ( + b"It supports both Wayland and X11 along with multiple clients to manage your \ + clipboard history.", + &em, + ), + ( + b"Clients include a standard GUI, an interactive TUI, and a CLI for all your scripting \ + needs.", + &em, + ), + ( + "Ringboard can copy arbitrary bytes—that includes images!".as_bytes(), + &em, + ), + ( + b"Plaintext and RegEx search are available for fast entry retrieval.", + &em, + ), + (b"Enjoy this image from our AI overlords:", &em), + ( + include_bytes!("../logo.jpeg") as &[u8], + &MimeType::from("image/jpeg").unwrap(), + ), + ( + b"Finally, it's worth mentioning that Ringboard is extremely efficient, performant, \ + and scalable.", + &em, + ), + ]; +} diff --git a/tests/target/issue-6778.rs b/tests/target/issue-6778.rs new file mode 100644 index 00000000000..4af9e813c23 --- /dev/null +++ b/tests/target/issue-6778.rs @@ -0,0 +1,28 @@ +//@ rustfmt-style_edition: 2027 + +#![allow(dead_code)] + +struct Parameter { + required: bool, + description: &'static str, +} + +pub struct Test; + +impl Test { + fn parameters(&self) -> &'static [Parameter] { + &[ + Parameter { + required: true, + description: "Foo", + }, + Parameter { + required: false, + description: "Bar +This string is exactly 100 chars long. Delete one character to make it 99 chars long and it'll work!", + }, + ] + } +} + +fn main() {}