diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs index 7a6a49f37278a..7d1947f4c4d1c 100644 --- a/compiler/rustc_interface/src/interface.rs +++ b/compiler/rustc_interface/src/interface.rs @@ -410,7 +410,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 34ac92faef88c..44d4bb6c8e075 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -56,6 +56,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/lib.rs b/compiler/rustc_span/src/lib.rs index f048fb04af45a..d0bf7b9af9fcb 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -23,6 +23,7 @@ #![feature(diagnostic_on_unknown)] #![feature(map_try_insert)] #![feature(negative_impls)] +#![feature(normalize_lexically)] #![feature(read_buf)] #![feature(rustc_attrs)] // tidy-alphabetical-end @@ -500,6 +501,28 @@ 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) => { + // Remapped names are printed as given: rebuilding them from components + // re-separates synthetic prefixes on Windows (`/rustc/` becomes + // `\rustc\`), changing output `--remap-path-prefix` users depend on. + if self.scopes.contains(scope) { + return self.path(scope).to_string_lossy(); + } + let path = self.path(scope); + // Paths without `.` or `..` are returned unchanged, so their separators + // are not rewritten by a component rebuild. The dot check is on the + // string since `components()` skips interior `.`. + let path_str = path.to_string_lossy(); + if !path_str.split(['/', '\\']).any(|seg| seg == "." || seg == "..") { + return path_str; + } + match path.normalize_lexically() { + Ok(normalized) => { + Cow::Owned(normalized.into_os_string().to_string_lossy().into_owned()) + } + Err(_) => path_str, + } + } } } } @@ -538,6 +561,16 @@ 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<'_> { @@ -558,15 +591,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::*; @@ -613,6 +637,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 f394765ab9f8e..5ddac209518f9 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() } + /// Local paths are normalized lexically, which can name the wrong file if + /// a component is a symlink. Remapped paths and `--verbose` skip + /// normalization and print the path as given. pub fn filename_for_diagnostics<'a>(&self, filename: &'a FileName) -> FileNameDisplay<'a> { - filename.display(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 4cc243667f224..682cb0b52886f 100644 --- a/compiler/rustc_span/src/source_map/tests.rs +++ b/compiler/rustc_span/src/source_map/tests.rs @@ -797,3 +797,55 @@ 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")); + + // No dot segments, so the path comes back verbatim, separators included. + let clean = filename(&sm, "tests/clean.rs"); + assert_eq!(sm.filename_for_diagnostics(&clean).to_string(), "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(), "tests/sub/../helper.rs",); +} + +#[test] +fn filename_for_diagnostics_keeps_separators_without_dot_segments() { + let sm = SourceMap::new(FilePathMapping::empty()); + // On Windows the remap prefix keeps `/` while the local part uses `\`. + // No dot segments, so the path must come back unchanged. + let path = "/rustc/FAKE_PREFIX\\library\\core\\src\\alloc\\layout.rs"; + let name = filename(&sm, path); + assert_eq!(sm.filename_for_diagnostics(&name).to_string(), path); +} + +#[test] +fn filename_for_diagnostics_keeps_remapped_dot_segments() { + let mapping = FilePathMapping::new( + vec![(path("abc/def"), path("fake"))], + RemapPathScopeComponents::all(), + ); + let sm = SourceMap::new(mapping); + + // The remapped name prints as given, `..` included. + let remapped = filename(&sm, "abc/def/sub/../helper.rs"); + assert_eq!( + sm.filename_for_diagnostics(&remapped).to_string(), + path_str("fake/sub/../helper.rs"), + ); +} diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index b7453adc31331..c6b744dd1bce3 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -2552,12 +2552,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 27995e0ab4cd5..de6c2c62d75c7 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -445,6 +445,10 @@ Everything to do with `--diagnostic-width`. Exercises `#[diagnostic::*]` namespaced attributes. See [RFC 3368 Diagnostic attribute namespace](https://rust-lang.github.io/rfcs/3368-diagnostic-attribute-namespace.html). +## `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/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-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 new file mode 100644 index 0000000000000..bc041c790e529 --- /dev/null +++ b/tests/ui/diagnostics/normalize-path.rs @@ -0,0 +1,9 @@ +// 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; + +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`.