From 4034ea34c544f3209a0caa3c404b4cc4f0052aeb Mon Sep 17 00:00:00 2001 From: Facundo Lerena Date: Fri, 24 Oct 2025 19:59:17 -0300 Subject: [PATCH 1/5] Update substringing when orig & sugg have equal prefixes --- compiler/rustc_errors/src/lib.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 17cd466f96b87..9cd34a8747fff 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -75,7 +75,7 @@ pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker}; use rustc_span::source_map::SourceMap; use rustc_span::{BytePos, DUMMY_SP, Loc, Span}; pub use snippet::Style; -use tracing::debug; +use tracing::{debug, instrument}; use crate::emitter::TimingEvent; use crate::registry::Registry; @@ -245,6 +245,7 @@ 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. + #[instrument(level = "debug", skip(self, sm))] fn trim_trivial_replacements(self, sm: &SourceMap) -> TrimmedSubstitutionPart { let mut trimmed_part = TrimmedSubstitutionPart { original_span: self.span, @@ -306,6 +307,16 @@ impl TrimmedSubstitutionPart { /// `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)> { + if suggestion.contains("::") + && suggestion.ends_with(original) + && suggestion.len() > original.len() + { + let prefix = &suggestion[..suggestion.len() - original.len()]; + if prefix.ends_with("::") && suggestion.chars().next() == original.chars().next() { + return Some((0, prefix, original.len())); + } + } + let common_prefix = original .chars() .zip(suggestion.chars()) From 052b702a1983de2abc93665106a1dfd35dd09284 Mon Sep 17 00:00:00 2001 From: Facundo Lerena Date: Fri, 31 Oct 2025 21:44:01 -0300 Subject: [PATCH 2/5] Change existing test to reflect new behaviour The change in this test is minimal, as this is a specific case. The benefits for other cases explained in issue 148070 outweight the change. --- tests/ui/test-attrs/inaccessible-test-modules.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/test-attrs/inaccessible-test-modules.stderr b/tests/ui/test-attrs/inaccessible-test-modules.stderr index c66dc0d0fc26c..dfb6985730af5 100644 --- a/tests/ui/test-attrs/inaccessible-test-modules.stderr +++ b/tests/ui/test-attrs/inaccessible-test-modules.stderr @@ -13,7 +13,7 @@ LL | use test as y; help: consider importing this module instead | LL | use test::test as y; - | ++++++ + | ++++++ error: aborting due to 2 previous errors From 5b91fe5ec6695c147fde8b820b5ca5fa92ea22a5 Mon Sep 17 00:00:00 2001 From: Facundo Lerena Date: Sat, 1 Nov 2025 09:55:20 -0300 Subject: [PATCH 3/5] Remove debug `instrument` and added comment explaning the special case --- compiler/rustc_errors/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 9cd34a8747fff..a7982cc7efcc1 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -75,7 +75,7 @@ pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker}; use rustc_span::source_map::SourceMap; use rustc_span::{BytePos, DUMMY_SP, Loc, Span}; pub use snippet::Style; -use tracing::{debug, instrument}; +use tracing::debug; use crate::emitter::TimingEvent; use crate::registry::Registry; @@ -245,7 +245,6 @@ 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. - #[instrument(level = "debug", skip(self, sm))] fn trim_trivial_replacements(self, sm: &SourceMap) -> TrimmedSubstitutionPart { let mut trimmed_part = TrimmedSubstitutionPart { original_span: self.span, @@ -307,6 +306,9 @@ impl TrimmedSubstitutionPart { /// `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)> { + // Case for import paths where the suggestion shares a prefix with the original. + // Without this, suggesting `std::sync` for `sync` would incorrectly highlight `td::s` + // instead of `std::` because of the common 's' prefix. See #148070. if suggestion.contains("::") && suggestion.ends_with(original) && suggestion.len() > original.len() From b21f96fd669aa86175be7783112a6929cb4b4fe7 Mon Sep 17 00:00:00 2001 From: Facundo Lerena Date: Mon, 3 Nov 2025 17:14:08 -0300 Subject: [PATCH 4/5] Move `let` inside `if` condition --- compiler/rustc_errors/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index a7982cc7efcc1..f56398e2f916e 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -312,13 +312,13 @@ fn as_substr<'a>(original: &'a str, suggestion: &'a str) -> Option<(usize, &'a s if suggestion.contains("::") && suggestion.ends_with(original) && suggestion.len() > original.len() + && let prefix = &suggestion[..suggestion.len() - original.len()] + && prefix.ends_with("::") + && suggestion.chars().next() == original.chars().next() { - let prefix = &suggestion[..suggestion.len() - original.len()]; - if prefix.ends_with("::") && suggestion.chars().next() == original.chars().next() { - return Some((0, prefix, original.len())); - } + return Some((0, prefix, original.len())); } - + let common_prefix = original .chars() .zip(suggestion.chars()) From a57969bcaa489c0f5864daf24649c80b9baf552e Mon Sep 17 00:00:00 2001 From: Facundo Lerena Date: Mon, 3 Nov 2025 17:24:03 -0300 Subject: [PATCH 5/5] Added tests for correct highlighting of strings with shared prefix --- compiler/rustc_errors/src/lib.rs | 2 +- .../same-prefix-unresolved-import-148070.rs | 5 +++ ...ame-prefix-unresolved-import-148070.stderr | 43 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 tests/ui/imports/same-prefix-unresolved-import-148070.rs create mode 100644 tests/ui/imports/same-prefix-unresolved-import-148070.stderr diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index f56398e2f916e..dc31f8008114c 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -318,7 +318,7 @@ fn as_substr<'a>(original: &'a str, suggestion: &'a str) -> Option<(usize, &'a s { return Some((0, prefix, original.len())); } - + let common_prefix = original .chars() .zip(suggestion.chars()) diff --git a/tests/ui/imports/same-prefix-unresolved-import-148070.rs b/tests/ui/imports/same-prefix-unresolved-import-148070.rs new file mode 100644 index 0000000000000..e9d991aa0376e --- /dev/null +++ b/tests/ui/imports/same-prefix-unresolved-import-148070.rs @@ -0,0 +1,5 @@ +// https://github.com/rust-lang/rust/issues/148070 +#![no_main] +use stat; //~ ERROR unresolved import `stat` +use str; //~ ERROR unresolved import `str` +use sync; //~ ERROR unresolved import `sync` diff --git a/tests/ui/imports/same-prefix-unresolved-import-148070.stderr b/tests/ui/imports/same-prefix-unresolved-import-148070.stderr new file mode 100644 index 0000000000000..e4a2b060fa435 --- /dev/null +++ b/tests/ui/imports/same-prefix-unresolved-import-148070.stderr @@ -0,0 +1,43 @@ +error[E0432]: unresolved import `stat` + --> $DIR/same-prefix-unresolved-import-148070.rs:3:5 + | +LL | use stat; + | ^^^^ no `stat` in the root + | +help: consider importing this struct instead + | +LL | use std::os::linux::raw::stat; + | +++++++++++++++++++++ + +error[E0432]: unresolved import `str` + --> $DIR/same-prefix-unresolved-import-148070.rs:4:5 + | +LL | use str; + | ^^^ no `str` in the root + | +help: a similar name exists in the module + | +LL - use str; +LL + use std; + | +help: consider importing one of these items instead + | +LL | use std::primitive::str; + | ++++++++++++++++ +LL | use std::str; + | +++++ + +error[E0432]: unresolved import `sync` + --> $DIR/same-prefix-unresolved-import-148070.rs:5:5 + | +LL | use sync; + | ^^^^ no `sync` in the root + | +help: consider importing this module instead + | +LL | use std::sync; + | +++++ + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0432`.