From bfc783f510b8f46708e36b5f1d7c5c517ac5cc4c Mon Sep 17 00:00:00 2001 From: Souradip Pal Date: Wed, 22 Apr 2026 00:33:37 +0530 Subject: [PATCH 1/6] Respect file-lines range per predicate in where clauses Previously rewrite_bounds_on_where_clause and the Visual-indent path in rewrite_where_clause reformatted every predicate unconditionally, causing the entire where clause to change even when only one predicate's lines were selected via --file-lines. Now each predicate is checked against the file-lines range before rewriting. Predicates outside the range fall back to their original source text; predicates inside the range are formatted normally. Fixes rust-lang/rustfmt#6872 --- src/items.rs | 16 ++++++++++++++-- tests/source/file-lines-where-clause.rs | 9 +++++++++ tests/target/file-lines-where-clause.rs | 9 +++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 tests/source/file-lines-where-clause.rs create mode 100644 tests/target/file-lines-where-clause.rs diff --git a/src/items.rs b/src/items.rs index a2c0e8e0f50..064c8e06c88 100644 --- a/src/items.rs +++ b/src/items.rs @@ -3136,7 +3136,13 @@ fn rewrite_bounds_on_where_clause( ",", |pred| pred.span().lo(), |pred| pred.span().hi(), - |pred| pred.rewrite_result(context, shape), + |pred| { + if out_of_file_lines_range!(context, pred.span()) { + Ok(context.snippet(pred.span()).to_owned()) + } else { + pred.rewrite_result(context, shape) + } + }, span_start, span_end, false, @@ -3222,7 +3228,13 @@ fn rewrite_where_clause( ",", |pred| pred.span().lo(), |pred| pred.span().hi(), - |pred| pred.rewrite_result(context, Shape::legacy(budget, offset)), + |pred| { + if out_of_file_lines_range!(context, pred.span()) { + Ok(context.snippet(pred.span()).to_owned()) + } else { + pred.rewrite_result(context, Shape::legacy(budget, offset)) + } + }, span_start, span_end, false, diff --git a/tests/source/file-lines-where-clause.rs b/tests/source/file-lines-where-clause.rs new file mode 100644 index 00000000000..d3aba15cd32 --- /dev/null +++ b/tests/source/file-lines-where-clause.rs @@ -0,0 +1,9 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-where-clause.rs","range":[5,5]}] + +fn foo() +where + T: Clone + Debug, + U: Copy, + V: Default, +{ +} diff --git a/tests/target/file-lines-where-clause.rs b/tests/target/file-lines-where-clause.rs new file mode 100644 index 00000000000..8e69b566368 --- /dev/null +++ b/tests/target/file-lines-where-clause.rs @@ -0,0 +1,9 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-where-clause.rs","range":[5,5]}] + +fn foo() +where + T: Clone + Debug, + U: Copy, + V: Default, +{ +} From 8f100377e4bf7fe04a916b273e315591898226f4 Mon Sep 17 00:00:00 2001 From: Souradip Pal Date: Wed, 22 Apr 2026 02:01:47 +0530 Subject: [PATCH 2/6] Move file-lines check into ListItems::next() Thread &RewriteContext through itemize_list instead of just &SnippetProvider, then check out_of_file_lines_range! once per item inside Iterator::next(). This covers all call sites automatically rather than requiring per-closure handling at each call site. Revert the per-predicate closures in rewrite_where_clause back to simple one-liners now that the iterator handles it centrally. --- src/attr.rs | 2 +- src/closures.rs | 2 +- src/expr.rs | 4 ++-- src/imports.rs | 2 +- src/items.rs | 25 +++++++------------------ src/lists.rs | 25 ++++++++++++++++--------- src/macros.rs | 2 +- src/matches.rs | 2 +- src/overflow.rs | 2 +- src/patterns.rs | 4 ++-- src/reorder.rs | 4 ++-- src/types.rs | 2 +- src/vertical.rs | 2 +- 13 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/attr.rs b/src/attr.rs index ac9ce2e8796..2dcac117e81 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -93,7 +93,7 @@ fn format_derive( })?; let items = itemize_list( - context.snippet_provider, + context, item_spans, ")", ",", diff --git a/src/closures.rs b/src/closures.rs index 19cd0d9792c..bdc38d02d3f 100644 --- a/src/closures.rs +++ b/src/closures.rs @@ -310,7 +310,7 @@ fn rewrite_closure_fn_decl( let ret_str = fn_decl.output.rewrite_result(context, param_shape)?; let param_items = itemize_list( - context.snippet_provider, + context, fn_decl.inputs.iter(), "|", ",", diff --git a/src/expr.rs b/src/expr.rs index b79137c4442..91215f760cc 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1843,7 +1843,7 @@ fn rewrite_struct_lit<'a>( }; let items = itemize_list( - context.snippet_provider, + context, field_iter, "}", ",", @@ -1995,7 +1995,7 @@ fn rewrite_tuple_in_visual_indent_style<'a, T: 'a + IntoOverflowableItem<'a>>( let list_lo = context.snippet_provider.span_after(span, "("); let nested_shape = shape.sub_width(2, span)?.visual_indent(1); let items = itemize_list( - context.snippet_provider, + context, items, ")", ",", diff --git a/src/imports.rs b/src/imports.rs index 2f26791639a..728aa2bfc2c 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -476,7 +476,7 @@ impl UseTree { // Extract comments between nested use items. // This needs to be done before sorting use items. let items = itemize_list( - context.snippet_provider, + context, list.iter().map(|(tree, _)| tree), "}", ",", diff --git a/src/items.rs b/src/items.rs index 064c8e06c88..a6ea6664655 100644 --- a/src/items.rs +++ b/src/items.rs @@ -614,9 +614,10 @@ impl<'a> FmtVisitor<'a> { .max() .unwrap_or(&0); + let enum_context = self.get_context(); let itemize_list_with = |one_line_width: usize| { itemize_list( - self.snippet_provider, + &enum_context, enum_def.variants.iter(), "}", ",", @@ -2852,7 +2853,7 @@ fn rewrite_params( return Ok(comment.to_owned()); } let param_items: Vec<_> = itemize_list( - context.snippet_provider, + context, params.iter(), ")", ",", @@ -3130,19 +3131,13 @@ fn rewrite_bounds_on_where_clause( let end_of_preds = predicates[len - 1].span().hi(); let span_end = span_end.unwrap_or(end_of_preds); let items = itemize_list( - context.snippet_provider, + context, predicates.iter(), terminator, ",", |pred| pred.span().lo(), |pred| pred.span().hi(), - |pred| { - if out_of_file_lines_range!(context, pred.span()) { - Ok(context.snippet(pred.span()).to_owned()) - } else { - pred.rewrite_result(context, shape) - } - }, + |pred| pred.rewrite_result(context, shape), span_start, span_end, false, @@ -3222,19 +3217,13 @@ fn rewrite_where_clause( let end_of_preds = predicates[len - 1].span().hi(); let span_end = span_end.unwrap_or(end_of_preds); let items = itemize_list( - context.snippet_provider, + context, predicates.iter(), terminator, ",", |pred| pred.span().lo(), |pred| pred.span().hi(), - |pred| { - if out_of_file_lines_range!(context, pred.span()) { - Ok(context.snippet(pred.span()).to_owned()) - } else { - pred.rewrite_result(context, Shape::legacy(budget, offset)) - } - }, + |pred| pred.rewrite_result(context, Shape::legacy(budget, offset)), span_start, span_end, false, diff --git a/src/lists.rs b/src/lists.rs index 9d811e5d9b5..e8e6782ea62 100644 --- a/src/lists.rs +++ b/src/lists.rs @@ -10,11 +10,11 @@ use crate::config::lists::*; use crate::config::{Config, IndentStyle}; use crate::rewrite::{ExceedsMaxWidthError, RewriteContext, RewriteError, RewriteResult}; use crate::shape::{Indent, Shape}; +use crate::source_map::LineRangeUtils; use crate::utils::{ count_newlines, first_line_width, last_line_width, mk_sp, starts_with_newline, unicode_str_width, }; -use crate::visitor::SnippetProvider; pub(crate) struct ListFormatting<'a> { tactic: DefinitiveListTactic, @@ -564,7 +564,7 @@ pub(crate) struct ListItems<'a, I, F1, F2, F3> where I: Iterator, { - snippet_provider: &'a SnippetProvider, + context: &'a RewriteContext<'a>, inner: Peekable, get_lo: F1, get_hi: F2, @@ -751,10 +751,14 @@ where fn next(&mut self) -> Option { self.inner.next().map(|item| { + let lo = (self.get_lo)(&item); + let hi = (self.get_hi)(&item); + let context = self.context; + // Pre-comment - let pre_snippet = self + let pre_snippet = context .snippet_provider - .span_to_snippet(mk_sp(self.prev_span_end, (self.get_lo)(&item))) + .span_to_snippet(mk_sp(self.prev_span_end, lo)) .unwrap_or(""); let (pre_comment, pre_comment_style) = extract_pre_comment(pre_snippet); @@ -763,9 +767,9 @@ where Some(next_item) => (self.get_lo)(next_item), None => self.next_span_start, }; - let post_snippet = self + let post_snippet = context .snippet_provider - .span_to_snippet(mk_sp((self.get_hi)(&item), next_start)) + .span_to_snippet(mk_sp(hi, next_start)) .unwrap_or(""); let is_last = self.inner.peek().is_none(); let comment_end = @@ -774,14 +778,17 @@ where let post_comment = extract_post_comment(post_snippet, comment_end, self.separator, is_last); - self.prev_span_end = (self.get_hi)(&item) + BytePos(comment_end as u32); + self.prev_span_end = hi + BytePos(comment_end as u32); + let item_span = mk_sp(lo, hi); ListItem { pre_comment, pre_comment_style, // leave_last is set to true only for rewrite_items item: if self.inner.peek().is_none() && self.leave_last { Err(RewriteError::SkipFormatting) + } else if out_of_file_lines_range!(context, item_span) { + Ok(context.snippet(item_span).to_owned()) } else { (self.get_item_string)(&item) }, @@ -795,7 +802,7 @@ where #[allow(clippy::too_many_arguments)] // Creates an iterator over a list's items with associated comments. pub(crate) fn itemize_list<'a, T, I, F1, F2, F3>( - snippet_provider: &'a SnippetProvider, + context: &'a RewriteContext<'a>, inner: I, terminator: &'a str, separator: &'a str, @@ -813,7 +820,7 @@ where F3: Fn(&T) -> RewriteResult, { ListItems { - snippet_provider, + context, inner: inner.peekable(), get_lo, get_hi, diff --git a/src/macros.rs b/src/macros.rs index 2d56021069c..f3909628458 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -465,7 +465,7 @@ pub(crate) fn rewrite_macro_def( } let branch_items = itemize_list( - context.snippet_provider, + context, parsed_def.branches.iter(), "}", ";", diff --git a/src/matches.rs b/src/matches.rs index 4741abbe465..ce102e72752 100644 --- a/src/matches.rs +++ b/src/matches.rs @@ -222,7 +222,7 @@ fn rewrite_match_arms( .chain(repeat(true)); let beginning_verts = collect_beginning_verts(context, arms); let items = itemize_list( - context.snippet_provider, + context, arms.iter() .zip(is_last_iter) .zip(beginning_verts.into_iter()) diff --git a/src/overflow.rs b/src/overflow.rs index 4230c89b57b..2a754119f6e 100644 --- a/src/overflow.rs +++ b/src/overflow.rs @@ -625,7 +625,7 @@ impl<'a> Context<'a> { debug!("items: {:?}", self.items); let items = itemize_list( - self.context.snippet_provider, + self.context, self.items.iter(), self.suffix, ",", diff --git a/src/patterns.rs b/src/patterns.rs index df2a8dc5c6f..94a94b82e2a 100644 --- a/src/patterns.rs +++ b/src/patterns.rs @@ -429,7 +429,7 @@ fn rewrite_struct_pat( )?; let items = itemize_list( - context.snippet_provider, + context, fields.iter(), terminator, ",", @@ -651,7 +651,7 @@ fn count_wildcard_suffix_len( let mut suffix_len = 0; let items: Vec<_> = itemize_list( - context.snippet_provider, + context, patterns.iter(), ")", ",", diff --git a/src/reorder.rs b/src/reorder.rs index 6ef6e0bc969..f9285bee1c4 100644 --- a/src/reorder.rs +++ b/src/reorder.rs @@ -110,7 +110,7 @@ fn rewrite_reorderable_or_regroupable_items( let cloned = normalized_items.clone(); // Add comments before merging. let list_items = itemize_list( - context.snippet_provider, + context, cloned.iter(), "", ";", @@ -169,7 +169,7 @@ fn rewrite_reorderable_or_regroupable_items( } _ => { let list_items = itemize_list( - context.snippet_provider, + context, reorderable_items.iter(), "", ";", diff --git a/src/types.rs b/src/types.rs index 94ed42c6ea5..84f9b9f5a33 100644 --- a/src/types.rs +++ b/src/types.rs @@ -380,7 +380,7 @@ where (comment, tactic) } else { let items = itemize_list( - context.snippet_provider, + context, inputs, ")", ",", diff --git a/src/vertical.rs b/src/vertical.rs index 21e34d29710..22c8933c5b2 100644 --- a/src/vertical.rs +++ b/src/vertical.rs @@ -222,7 +222,7 @@ fn rewrite_aligned_items_inner( } let mut items = itemize_list( - context.snippet_provider, + context, fields.iter(), "}", ",", From 528e7ee1b3488f57cb3e03f1e1c366c29455ae41 Mon Sep 17 00:00:00 2001 From: Souradip Pal Date: Wed, 22 Apr 2026 03:28:05 +0530 Subject: [PATCH 3/6] Skip reformatting when entire where clause is out of file-lines range --- src/items.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/items.rs b/src/items.rs index a6ea6664655..f8b225dd15e 100644 --- a/src/items.rs +++ b/src/items.rs @@ -3186,6 +3186,12 @@ fn rewrite_where_clause( return Ok(String::new()); } + if !context.config.file_lines().is_all() && out_of_file_lines_range!(context, where_span) { + return Ok(context + .snippet(mk_sp(span_end_before_where, where_span.hi())) + .to_owned()); + } + if context.config.indent_style() == IndentStyle::Block { return rewrite_where_clause_rfc_style( context, From 638f61c197bc3053dfa40ef5ea2db8681727f7a0 Mon Sep 17 00:00:00 2001 From: Souradip Pal Date: Wed, 22 Apr 2026 03:28:42 +0530 Subject: [PATCH 4/6] Make file-lines-where-clause test diff more obvious --- tests/source/file-lines-where-clause.rs | 6 +++--- tests/target/file-lines-where-clause.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/source/file-lines-where-clause.rs b/tests/source/file-lines-where-clause.rs index d3aba15cd32..31bd865bb7a 100644 --- a/tests/source/file-lines-where-clause.rs +++ b/tests/source/file-lines-where-clause.rs @@ -2,8 +2,8 @@ fn foo() where - T: Clone + Debug, - U: Copy, - V: Default, + T : Clone + Debug, + U : Copy, + V : Default, { } diff --git a/tests/target/file-lines-where-clause.rs b/tests/target/file-lines-where-clause.rs index 8e69b566368..111b6afc3ba 100644 --- a/tests/target/file-lines-where-clause.rs +++ b/tests/target/file-lines-where-clause.rs @@ -3,7 +3,7 @@ fn foo() where T: Clone + Debug, - U: Copy, - V: Default, + U : Copy, + V : Default, { } From be33c5bc8be2528338c0479477ddaf289354acf1 Mon Sep 17 00:00:00 2001 From: Souradip Pal Date: Wed, 22 Apr 2026 03:31:01 +0530 Subject: [PATCH 5/6] Test where-clause preservation when no predicate lines selected --- tests/source/file-lines-where-clause-skip.rs | 9 +++++++++ tests/target/file-lines-where-clause-skip.rs | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 tests/source/file-lines-where-clause-skip.rs create mode 100644 tests/target/file-lines-where-clause-skip.rs diff --git a/tests/source/file-lines-where-clause-skip.rs b/tests/source/file-lines-where-clause-skip.rs new file mode 100644 index 00000000000..bb2fc80ec97 --- /dev/null +++ b/tests/source/file-lines-where-clause-skip.rs @@ -0,0 +1,9 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-where-clause-skip.rs","range":[8,8]}] + +fn foo() where +T: Clone, +T: Copy, +T: Default, +{ + let x=1; +} diff --git a/tests/target/file-lines-where-clause-skip.rs b/tests/target/file-lines-where-clause-skip.rs new file mode 100644 index 00000000000..d9494f60ea0 --- /dev/null +++ b/tests/target/file-lines-where-clause-skip.rs @@ -0,0 +1,9 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-where-clause-skip.rs","range":[8,8]}] + +fn foo() where +T: Clone, +T: Copy, +T: Default, +{ + let x = 1; +} From 130d6dfab1568aeeeafcbd9aee0f41ef5ddd20dd Mon Sep 17 00:00:00 2001 From: Souradip Pal Date: Wed, 22 Apr 2026 04:04:16 +0530 Subject: [PATCH 6/6] Drop redundant is_all() check in where-clause skip guard --- src/items.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/items.rs b/src/items.rs index f8b225dd15e..2237c00dd06 100644 --- a/src/items.rs +++ b/src/items.rs @@ -3186,7 +3186,7 @@ fn rewrite_where_clause( return Ok(String::new()); } - if !context.config.file_lines().is_all() && out_of_file_lines_range!(context, where_span) { + if out_of_file_lines_range!(context, where_span) { return Ok(context .snippet(mk_sp(span_end_before_where, where_span.hi())) .to_owned());