From ad67c9d581b7de937398ddea8671d41c9a96dfa0 Mon Sep 17 00:00:00 2001 From: relaxcn Date: Sun, 19 Oct 2025 23:58:04 +0800 Subject: [PATCH] fix: Use untrimmed line numbers for trimmed suggestions --- compiler/rustc_errors/src/emitter.rs | 4 +- compiler/rustc_errors/src/lib.rs | 71 ++++++++++++------- .../trimmed_multiline_suggestion.rs | 14 ++++ .../trimmed_multiline_suggestion.stderr | 25 +++++++ 4 files changed, 87 insertions(+), 27 deletions(-) create mode 100644 tests/ui/error-emitter/trimmed_multiline_suggestion.rs create mode 100644 tests/ui/error-emitter/trimmed_multiline_suggestion.stderr diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs index 0d10f29d31d64..afdb582fea652 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -2154,11 +2154,11 @@ impl HumanEmitter { assert!(!file_lines.lines.is_empty() || parts[0].span.is_dummy()); - let line_start = sm.lookup_char_pos(parts[0].span.lo()).line; + let line_start = sm.lookup_char_pos(parts[0].original_span.lo()).line; let mut lines = complete.lines(); if lines.clone().next().is_none() { // Account for a suggestion to completely remove a line(s) with whitespace (#94192). - let line_end = sm.lookup_char_pos(parts[0].span.hi()).line; + let line_end = sm.lookup_char_pos(parts[0].original_span.hi()).line; for line in line_start..=line_end { self.draw_line_num( &mut buffer, diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 8869799ce90d9..6bec65f2d538c 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -224,6 +224,13 @@ pub struct SubstitutionPart { pub snippet: String, } +#[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)] +pub struct TrimmedSubstitutionPart { + pub original_span: Span, + pub span: Span, + pub snippet: String, +} + /// Used to translate between `Span`s and byte positions within a single output line in highlighted /// code of structured suggestions. #[derive(Debug, Clone, Copy)] @@ -233,6 +240,35 @@ pub(crate) struct SubstitutionHighlight { } impl SubstitutionPart { + /// Try to turn a replacement into an addition when the span that is being + /// overwritten matches either the prefix or suffix of the replacement. + fn trim_trivial_replacements(self, sm: &SourceMap) -> TrimmedSubstitutionPart { + let mut trimmed_part = TrimmedSubstitutionPart { + original_span: self.span, + span: self.span, + snippet: self.snippet, + }; + if trimmed_part.snippet.is_empty() { + return trimmed_part; + } + let Ok(snippet) = sm.span_to_snippet(trimmed_part.span) else { + return trimmed_part; + }; + + if let Some((prefix, substr, suffix)) = as_substr(&snippet, &trimmed_part.snippet) { + trimmed_part.span = Span::new( + trimmed_part.span.lo() + BytePos(prefix as u32), + trimmed_part.span.hi() - BytePos(suffix as u32), + trimmed_part.span.ctxt(), + trimmed_part.span.parent(), + ); + trimmed_part.snippet = substr.to_string(); + } + trimmed_part + } +} + +impl TrimmedSubstitutionPart { pub fn is_addition(&self, sm: &SourceMap) -> bool { !self.snippet.is_empty() && !self.replaces_meaningful_content(sm) } @@ -260,27 +296,6 @@ impl SubstitutionPart { sm.span_to_snippet(self.span) .map_or(!self.span.is_empty(), |snippet| !snippet.trim().is_empty()) } - - /// Try to turn a replacement into an addition when the span that is being - /// overwritten matches either the prefix or suffix of the replacement. - fn trim_trivial_replacements(&mut self, sm: &SourceMap) { - if self.snippet.is_empty() { - return; - } - let Ok(snippet) = sm.span_to_snippet(self.span) else { - return; - }; - - if let Some((prefix, substr, suffix)) = as_substr(&snippet, &self.snippet) { - self.span = Span::new( - self.span.lo() + BytePos(prefix as u32), - self.span.hi() - BytePos(suffix as u32), - self.span.ctxt(), - self.span.parent(), - ); - self.snippet = substr.to_string(); - } - } } /// Given an original string like `AACC`, and a suggestion like `AABBCC`, try to detect @@ -310,7 +325,8 @@ impl CodeSuggestion { pub(crate) fn splice_lines( &self, sm: &SourceMap, - ) -> Vec<(String, Vec, Vec>, ConfusionType)> { + ) -> Vec<(String, Vec, Vec>, ConfusionType)> + { // For the `Vec>` value, the first level of the vector // corresponds to the output snippet's lines, while the second level corresponds to the // substrings within that line that should be highlighted. @@ -428,12 +444,17 @@ impl CodeSuggestion { // or deleted code in order to point at the correct column *after* substitution. let mut acc = 0; let mut confusion_type = ConfusionType::None; - for part in &mut substitution.parts { + + let trimmed_parts = substitution + .parts + .into_iter() // If this is a replacement of, e.g. `"a"` into `"ab"`, adjust the // suggestion and snippet to look as if we just suggested to add // `"b"`, which is typically much easier for the user to understand. - part.trim_trivial_replacements(sm); + .map(|part| part.trim_trivial_replacements(sm)) + .collect::>(); + for part in &trimmed_parts { let part_confusion = detect_confusion_type(sm, &part.snippet, part.span); confusion_type = confusion_type.combine(part_confusion); let cur_lo = sm.lookup_char_pos(part.span.lo()); @@ -521,7 +542,7 @@ impl CodeSuggestion { if highlights.iter().all(|parts| parts.is_empty()) { None } else { - Some((buf, substitution.parts, highlights, confusion_type)) + Some((buf, trimmed_parts, highlights, confusion_type)) } }) .collect() diff --git a/tests/ui/error-emitter/trimmed_multiline_suggestion.rs b/tests/ui/error-emitter/trimmed_multiline_suggestion.rs new file mode 100644 index 0000000000000..c0ae9af4e9b25 --- /dev/null +++ b/tests/ui/error-emitter/trimmed_multiline_suggestion.rs @@ -0,0 +1,14 @@ +//@ compile-flags: -Z ui-testing=no +fn function_with_lots_of_arguments(a: i32, b: char, c: i32, d: i32, e: i32, f: i32) {} + +fn main() { + let variable_name = 42; + function_with_lots_of_arguments( + variable_name, + variable_name, + variable_name, + variable_name, + variable_name, + ); + //~^^^^^^^ ERROR this function takes 6 arguments but 5 arguments were supplied [E0061] +} diff --git a/tests/ui/error-emitter/trimmed_multiline_suggestion.stderr b/tests/ui/error-emitter/trimmed_multiline_suggestion.stderr new file mode 100644 index 0000000000000..6e1ffb153bc2b --- /dev/null +++ b/tests/ui/error-emitter/trimmed_multiline_suggestion.stderr @@ -0,0 +1,25 @@ +error[E0061]: this function takes 6 arguments but 5 arguments were supplied + --> $DIR/trimmed_multiline_suggestion.rs:6:5 + | +6 | function_with_lots_of_arguments( + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +7 | variable_name, +8 | variable_name, + | ------------- argument #2 of type `char` is missing + | +note: function defined here + --> $DIR/trimmed_multiline_suggestion.rs:2:4 + | +2 | fn function_with_lots_of_arguments(a: i32, b: char, c: i32, d: i32, e: i32, f: i32) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ------- +help: provide the argument + | +6 | function_with_lots_of_arguments( +7 | variable_name, +8 ~ /* char */, +9 ~ variable_name, + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0061`.