about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_errors/src/lib.rs45
1 files changed, 34 insertions, 11 deletions
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 6fce1fade26..0016dacc8b2 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -71,7 +71,7 @@ use rustc_macros::{Decodable, Encodable};
 pub use rustc_span::ErrorGuaranteed;
 pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker};
 use rustc_span::source_map::SourceMap;
-use rustc_span::{DUMMY_SP, Loc, Span};
+use rustc_span::{BytePos, DUMMY_SP, Loc, Span};
 pub use snippet::Style;
 // Used by external projects such as `rust-gpu`.
 // See https://github.com/rust-lang/rust/pull/115393.
@@ -237,10 +237,9 @@ impl SubstitutionPart {
     /// it with "abx" is, since the "c" character is lost.
     pub fn is_destructive_replacement(&self, sm: &SourceMap) -> bool {
         self.is_replacement(sm)
-            && !sm.span_to_snippet(self.span).is_ok_and(|snippet| {
-                self.snippet.trim_start().starts_with(snippet.trim_start())
-                    || self.snippet.trim_end().ends_with(snippet.trim_end())
-            })
+            && !sm
+                .span_to_snippet(self.span)
+                .is_ok_and(|snippet| as_substr(snippet.trim(), self.snippet.trim()).is_some())
     }
 
     fn replaces_meaningful_content(&self, sm: &SourceMap) -> bool {
@@ -257,16 +256,40 @@ impl SubstitutionPart {
         let Ok(snippet) = sm.span_to_snippet(self.span) else {
             return;
         };
-        if self.snippet.starts_with(&snippet) {
-            self.span = self.span.shrink_to_hi();
-            self.snippet = self.snippet[snippet.len()..].to_string();
-        } else if self.snippet.ends_with(&snippet) {
-            self.span = self.span.shrink_to_lo();
-            self.snippet = self.snippet[..self.snippet.len() - snippet.len()].to_string();
+
+        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
+/// the case where a substring of the suggestion is "sandwiched" in the original, like
+/// `BB` is. Return the length of the prefix, the "trimmed" suggestion, and the length
+/// of the suffix.
+fn as_substr<'a>(original: &'a str, suggestion: &'a str) -> Option<(usize, &'a str, usize)> {
+    let common_prefix = original
+        .chars()
+        .zip(suggestion.chars())
+        .take_while(|(c1, c2)| c1 == c2)
+        .map(|(c, _)| c.len_utf8())
+        .sum();
+    let original = &original[common_prefix..];
+    let suggestion = &suggestion[common_prefix..];
+    if suggestion.ends_with(original) {
+        let common_suffix = original.len();
+        Some((common_prefix, &suggestion[..suggestion.len() - original.len()], common_suffix))
+    } else {
+        None
+    }
+}
+
 impl CodeSuggestion {
     /// Returns the assembled code suggestions, whether they should be shown with an underline
     /// and whether the substitution only differs in capitalization.