From dea80f716cabc76f42621a062f3ad4b2b7a0dca2 Mon Sep 17 00:00:00 2001 From: arferreira Date: Mon, 20 Apr 2026 13:14:29 -0400 Subject: [PATCH 1/2] Normalize .. and . in diagnostic file paths --- compiler/rustc_span/src/lib.rs | 39 +++++++++++++------ compiler/rustc_span/src/source_map.rs | 2 +- compiler/rustc_span/src/source_map/tests.rs | 11 ++++++ src/tools/compiletest/src/runtest.rs | 10 +++++ tests/ui/README.md | 4 ++ .../generic_arg_infer/issue-91614.stderr | 4 +- tests/ui/diagnostics/auxiliary/helper.rs | 3 ++ tests/ui/diagnostics/auxiliary/sub/mod.rs | 2 + tests/ui/diagnostics/normalize-path.rs | 9 +++++ tests/ui/diagnostics/normalize-path.stderr | 11 ++++++ 10 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 tests/ui/diagnostics/auxiliary/helper.rs create mode 100644 tests/ui/diagnostics/auxiliary/sub/mod.rs create mode 100644 tests/ui/diagnostics/normalize-path.rs create mode 100644 tests/ui/diagnostics/normalize-path.stderr diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index f6ae748560750..b23d072a9253e 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -22,6 +22,7 @@ #![feature(diagnostic_on_unknown)] #![feature(map_try_insert)] #![feature(negative_impls)] +#![feature(normalize_lexically)] #![feature(read_buf)] #![feature(rustc_attrs)] // tidy-alphabetical-end @@ -499,6 +500,15 @@ impl RealFileName { .file_name() .map_or_else(|| "".into(), |f| f.to_string_lossy()), FileNameDisplayPreference::Scope(scope) => self.path(scope).to_string_lossy(), + FileNameDisplayPreference::Diagnostics(scope) => { + let path = self.path(scope); + match path.normalize_lexically() { + Ok(normalized) => { + Cow::Owned(normalized.into_os_string().to_string_lossy().into_owned()) + } + Err(_) => path.to_string_lossy(), + } + } } } } @@ -536,15 +546,23 @@ enum FileNameDisplayPreference { Local, Short, Scope(RemapPathScopeComponents), + Diagnostics(RemapPathScopeComponents), +} + +impl<'a> FileNameDisplay<'a> { + pub fn to_string_lossy(&self) -> Cow<'a, str> { + match self.inner { + FileName::Real(inner) => inner.to_string_lossy(self.display_pref), + _ => Cow::from(self.to_string()), + } + } } impl fmt::Display for FileNameDisplay<'_> { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use FileName::*; match *self.inner { - Real(ref name) => { - write!(fmt, "{}", name.to_string_lossy(self.display_pref)) - } + Real(ref name) => write!(fmt, "{}", name.to_string_lossy(self.display_pref)), CfgSpec(_) => write!(fmt, ""), MacroExpansion(_) => write!(fmt, ""), Anon(_) => write!(fmt, ""), @@ -557,15 +575,6 @@ impl fmt::Display for FileNameDisplay<'_> { } } -impl<'a> FileNameDisplay<'a> { - pub fn to_string_lossy(&self) -> Cow<'a, str> { - match self.inner { - FileName::Real(inner) => inner.to_string_lossy(self.display_pref), - _ => Cow::from(self.to_string()), - } - } -} - impl FileName { pub fn is_real(&self) -> bool { use FileName::*; @@ -612,6 +621,12 @@ impl FileName { FileNameDisplay { inner: self, display_pref: FileNameDisplayPreference::Scope(scope) } } + /// Like `display`, but with `.` and `..` resolved lexically. See #51349. + #[inline] + pub fn display_normalized(&self, scope: RemapPathScopeComponents) -> FileNameDisplay<'_> { + FileNameDisplay { inner: self, display_pref: FileNameDisplayPreference::Diagnostics(scope) } + } + pub fn macro_expansion_source_code(src: &str) -> FileName { let mut hasher = StableHasher::new(); src.hash(&mut hasher); diff --git a/compiler/rustc_span/src/source_map.rs b/compiler/rustc_span/src/source_map.rs index 80d1bae71ae89..cf92e3386cde1 100644 --- a/compiler/rustc_span/src/source_map.rs +++ b/compiler/rustc_span/src/source_map.rs @@ -521,7 +521,7 @@ impl SourceMap { } pub fn filename_for_diagnostics<'a>(&self, filename: &'a FileName) -> FileNameDisplay<'a> { - filename.display(RemapPathScopeComponents::DIAGNOSTICS) + filename.display_normalized(RemapPathScopeComponents::DIAGNOSTICS) } pub fn is_multiline(&self, sp: Span) -> bool { diff --git a/compiler/rustc_span/src/source_map/tests.rs b/compiler/rustc_span/src/source_map/tests.rs index 4cc243667f224..acc186a007fca 100644 --- a/compiler/rustc_span/src/source_map/tests.rs +++ b/compiler/rustc_span/src/source_map/tests.rs @@ -797,3 +797,14 @@ fn read_binary_file_handles_lying_stat() { let bin = RealFileLoader.read_binary_file(kernel_max).unwrap(); assert_eq!(&real[..], &bin[..]); } + +#[test] +fn filename_for_diagnostics_resolves_parent_dir() { + let sm = SourceMap::new(FilePathMapping::empty()); + + let with_parent = filename(&sm, "tests/sub/../helper.rs"); + assert_eq!(sm.filename_for_diagnostics(&with_parent).to_string(), path_str("tests/helper.rs")); + + let clean = filename(&sm, "tests/clean.rs"); + assert_eq!(sm.filename_for_diagnostics(&clean).to_string(), path_str("tests/clean.rs")); +} diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 8f45e037d0fc9..6a3372def36ac 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -2491,12 +2491,22 @@ impl<'test> TestCx<'test> { let parent_dir = self.testpaths.file.parent().unwrap(); normalize_path(parent_dir, "$DIR"); + // After #51349, rustc normalizes `tests/x/y/../aux/foo.rs` to + // `tests/x/aux/foo.rs`. Replace the grandparent with `$DIR/..` so + // stderrs keep the pre-normalization form. + if let Some(grandparent_dir) = parent_dir.parent() { + normalize_path(grandparent_dir, "$DIR/.."); + } + if self.props.remap_src_base { let mut remapped_parent_dir = Utf8PathBuf::from(FAKE_SRC_BASE); if self.testpaths.relative_dir != Utf8Path::new("") { remapped_parent_dir.push(&self.testpaths.relative_dir); } normalize_path(&remapped_parent_dir, "$DIR"); + if let Some(remapped_grandparent) = remapped_parent_dir.parent() { + normalize_path(remapped_grandparent, "$DIR/.."); + } } let base_dir = Utf8Path::new("/rustc/FAKE_PREFIX"); diff --git a/tests/ui/README.md b/tests/ui/README.md index a3617fb6b07c9..e91b3b4b75e80 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -446,6 +446,10 @@ Everything to do with `--diagnostic-width`. Exercises `#[diagnostic::*]` namespaced attributes. See [RFC 3368 Diagnostic attribute namespace](https://github.com/rust-lang/rfcs/blob/master/text/3368-diagnostic-attribute-namespace.md). +## `tests/ui/diagnostics/` + +Tests for diagnostic output quality, such as path normalization in error messages. + ## `tests/ui/did_you_mean/` Tests for miscellaneous suggestions. diff --git a/tests/ui/const-generics/generic_arg_infer/issue-91614.stderr b/tests/ui/const-generics/generic_arg_infer/issue-91614.stderr index 164bcc7111ca6..6a2952604587e 100644 --- a/tests/ui/const-generics/generic_arg_infer/issue-91614.stderr +++ b/tests/ui/const-generics/generic_arg_infer/issue-91614.stderr @@ -5,7 +5,7 @@ LL | let y = Mask::<_, _>::splat(false); | ^ ------------ type must be known at this point | note: required by a const generic parameter in `Mask` - --> $SRC_DIR/core/src/../../portable-simd/crates/core_simd/src/masks.rs:LL:COL + --> $SRC_DIR/portable-simd/crates/core_simd/src/masks.rs:LL:COL help: consider giving `y` an explicit type, where the value of const parameter `N` is specified | LL | let y: Mask<_, N> = Mask::<_, _>::splat(false); @@ -18,7 +18,7 @@ LL | let y = Mask::<_, _>::splat(false); | ^ -------------------------- type must be known at this point | note: required by a const generic parameter in `Mask::::splat` - --> $SRC_DIR/core/src/../../portable-simd/crates/core_simd/src/masks.rs:LL:COL + --> $SRC_DIR/portable-simd/crates/core_simd/src/masks.rs:LL:COL help: consider giving `y` an explicit type, where the value of const parameter `N` is specified | LL | let y: Mask<_, N> = Mask::<_, _>::splat(false); diff --git a/tests/ui/diagnostics/auxiliary/helper.rs b/tests/ui/diagnostics/auxiliary/helper.rs new file mode 100644 index 0000000000000..83103ab7bd71f --- /dev/null +++ b/tests/ui/diagnostics/auxiliary/helper.rs @@ -0,0 +1,3 @@ +pub fn foo() -> u32 { + "not a u32" +} diff --git a/tests/ui/diagnostics/auxiliary/sub/mod.rs b/tests/ui/diagnostics/auxiliary/sub/mod.rs new file mode 100644 index 0000000000000..dd531abbce552 --- /dev/null +++ b/tests/ui/diagnostics/auxiliary/sub/mod.rs @@ -0,0 +1,2 @@ +#[path = "../helper.rs"] +mod helper; diff --git a/tests/ui/diagnostics/normalize-path.rs b/tests/ui/diagnostics/normalize-path.rs new file mode 100644 index 0000000000000..1b1fb998abf01 --- /dev/null +++ b/tests/ui/diagnostics/normalize-path.rs @@ -0,0 +1,9 @@ +// Verify that diagnostic file paths are lexically normalized. +// Without the fix for #51349, the error location would show +// `auxiliary/sub/../helper.rs` instead of `auxiliary/helper.rs`. +#[path = "auxiliary/sub/mod.rs"] +mod sub; + +fn main() {} + +//~? ERROR mismatched types diff --git a/tests/ui/diagnostics/normalize-path.stderr b/tests/ui/diagnostics/normalize-path.stderr new file mode 100644 index 0000000000000..9eb92e54853e1 --- /dev/null +++ b/tests/ui/diagnostics/normalize-path.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/auxiliary/helper.rs:2:5 + | +LL | pub fn foo() -> u32 { + | --- expected `u32` because of return type +LL | "not a u32" + | ^^^^^^^^^^^ expected `u32`, found `&str` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. From 0da8bd366f03535a639c195c0eba539dd53205f1 Mon Sep 17 00:00:00 2001 From: arferreira Date: Thu, 13 Aug 2026 19:41:20 -0400 Subject: [PATCH 2/2] Print unnormalized diagnostic paths under --verbose Signed-off-by: arferreira --- compiler/rustc_interface/src/interface.rs | 8 +++++++- compiler/rustc_interface/src/tests.rs | 1 + compiler/rustc_span/src/source_map.rs | 20 +++++++++++++++++-- compiler/rustc_span/src/source_map/tests.rs | 17 ++++++++++++++++ .../ui/diagnostics/normalize-path-verbose.rs | 10 ++++++++++ .../diagnostics/normalize-path-verbose.stderr | 11 ++++++++++ tests/ui/diagnostics/normalize-path.rs | 6 +++--- 7 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 tests/ui/diagnostics/normalize-path-verbose.rs create mode 100644 tests/ui/diagnostics/normalize-path-verbose.stderr diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs index 2737d2ca854a5..f145b1129aa60 100644 --- a/compiler/rustc_interface/src/interface.rs +++ b/compiler/rustc_interface/src/interface.rs @@ -401,7 +401,13 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se config.opts.edition, jobs, &config.extra_symbols, - SourceMapInputs { file_loader, path_mapping, hash_kind, checksum_hash_kind }, + SourceMapInputs { + file_loader, + path_mapping, + hash_kind, + checksum_hash_kind, + verbose: config.opts.verbose, + }, |current_gcx| { // The previous `early_dcx` can't be reused here because it doesn't // impl `Send`. Creating a new one is fine. diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 24c7ff8484a5e..8d2e167c81238 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -53,6 +53,7 @@ where path_mapping: sessopts.file_path_mapping(), hash_kind, checksum_hash_kind, + verbose: sessopts.verbose, }); rustc_span::create_session_globals_then(DEFAULT_EDITION, &[], sm_inputs, || { diff --git a/compiler/rustc_span/src/source_map.rs b/compiler/rustc_span/src/source_map.rs index cf92e3386cde1..88fc0922791ad 100644 --- a/compiler/rustc_span/src/source_map.rs +++ b/compiler/rustc_span/src/source_map.rs @@ -192,6 +192,7 @@ pub struct SourceMapInputs { pub path_mapping: FilePathMapping, pub hash_kind: SourceFileHashAlgorithm, pub checksum_hash_kind: Option, + pub verbose: bool, } pub struct SourceMap { @@ -213,6 +214,10 @@ pub struct SourceMap { /// /// If this is equal to `hash_kind` then the checksum won't be computed twice. checksum_hash_kind: Option, + + /// Whether `--verbose` was passed. Diagnostics then print paths without + /// lexical normalization. + verbose: bool, } impl std::fmt::Debug for SourceMap { @@ -224,6 +229,7 @@ impl std::fmt::Debug for SourceMap { working_dir, hash_kind, checksum_hash_kind, + verbose, } = self; f.debug_struct("SourceMap") @@ -233,6 +239,7 @@ impl std::fmt::Debug for SourceMap { .field("working_dir", working_dir) .field("hash_kind", hash_kind) .field("checksum_hash_kind", checksum_hash_kind) + .field("verbose", verbose) .finish() } } @@ -244,11 +251,12 @@ impl SourceMap { path_mapping, hash_kind: SourceFileHashAlgorithm::Md5, checksum_hash_kind: None, + verbose: false, }) } pub fn with_inputs( - SourceMapInputs { file_loader, path_mapping, hash_kind, checksum_hash_kind }: SourceMapInputs, + SourceMapInputs { file_loader, path_mapping, hash_kind, checksum_hash_kind, verbose }: SourceMapInputs, ) -> SourceMap { let cwd = file_loader .current_directory() @@ -262,6 +270,7 @@ impl SourceMap { path_mapping, hash_kind, checksum_hash_kind, + verbose, } } @@ -520,8 +529,15 @@ impl SourceMap { self.lookup_char_pos(sp.lo()).file.name.clone() } + /// Paths are normalized lexically, which can name the wrong file if a + /// component is a symlink. `--verbose` skips normalization and prints the + /// path as given. pub fn filename_for_diagnostics<'a>(&self, filename: &'a FileName) -> FileNameDisplay<'a> { - filename.display_normalized(RemapPathScopeComponents::DIAGNOSTICS) + if self.verbose { + filename.display(RemapPathScopeComponents::DIAGNOSTICS) + } else { + filename.display_normalized(RemapPathScopeComponents::DIAGNOSTICS) + } } pub fn is_multiline(&self, sp: Span) -> bool { diff --git a/compiler/rustc_span/src/source_map/tests.rs b/compiler/rustc_span/src/source_map/tests.rs index acc186a007fca..b97c96787a96b 100644 --- a/compiler/rustc_span/src/source_map/tests.rs +++ b/compiler/rustc_span/src/source_map/tests.rs @@ -808,3 +808,20 @@ fn filename_for_diagnostics_resolves_parent_dir() { let clean = filename(&sm, "tests/clean.rs"); assert_eq!(sm.filename_for_diagnostics(&clean).to_string(), path_str("tests/clean.rs")); } + +#[test] +fn filename_for_diagnostics_verbose_keeps_parent_dir() { + let sm = SourceMap::with_inputs(SourceMapInputs { + file_loader: Box::new(RealFileLoader), + path_mapping: FilePathMapping::empty(), + hash_kind: SourceFileHashAlgorithm::Md5, + checksum_hash_kind: None, + verbose: true, + }); + + let with_parent = filename(&sm, "tests/sub/../helper.rs"); + assert_eq!( + sm.filename_for_diagnostics(&with_parent).to_string(), + path_str("tests/sub/../helper.rs"), + ); +} diff --git a/tests/ui/diagnostics/normalize-path-verbose.rs b/tests/ui/diagnostics/normalize-path-verbose.rs new file mode 100644 index 0000000000000..8955ce10da98b --- /dev/null +++ b/tests/ui/diagnostics/normalize-path-verbose.rs @@ -0,0 +1,10 @@ +//@ compile-flags: --verbose + +// Check that `--verbose` prints diagnostic paths as given, without lexical +// normalization. See #51349. +#[path = "auxiliary/sub/mod.rs"] +mod sub; + +fn main() {} + +//~? ERROR mismatched types diff --git a/tests/ui/diagnostics/normalize-path-verbose.stderr b/tests/ui/diagnostics/normalize-path-verbose.stderr new file mode 100644 index 0000000000000..ef1c9416f46c1 --- /dev/null +++ b/tests/ui/diagnostics/normalize-path-verbose.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/auxiliary/sub/../helper.rs:2:5 + | +LL | pub fn foo() -> u32 { + | --- expected `u32` because of return type +LL | "not a u32" + | ^^^^^^^^^^^ expected `u32`, found `&str` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/diagnostics/normalize-path.rs b/tests/ui/diagnostics/normalize-path.rs index 1b1fb998abf01..bc041c790e529 100644 --- a/tests/ui/diagnostics/normalize-path.rs +++ b/tests/ui/diagnostics/normalize-path.rs @@ -1,6 +1,6 @@ -// Verify that diagnostic file paths are lexically normalized. -// Without the fix for #51349, the error location would show -// `auxiliary/sub/../helper.rs` instead of `auxiliary/helper.rs`. +// Check that diagnostic file paths are lexically normalized: +// the error below points at `auxiliary/helper.rs`, not `auxiliary/sub/../helper.rs`. +// See #51349. #[path = "auxiliary/sub/mod.rs"] mod sub;