Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion compiler/rustc_interface/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,13 @@ pub fn run_compiler<R: Send>(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.
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_interface/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, || {
Expand Down
39 changes: 27 additions & 12 deletions compiler/rustc_span/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() {

@estebank estebank Aug 13, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My only concern with lexical normalization is that it doesn't hit the disk and one can craft a volume where ./foo/../bar.rs doesn't map to ./bar.rs. That seems convoluted though.

Could we add support for --verbose to print the path without normalization?

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's desirable, it would be possible to use something like same_file to test if both paths resolve to the same underlying file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can hit the disk here, these paths don't need to exist locally. --verbose should cover the cases where you'd want the raw path anyway.

Ok(normalized) => {
Cow::Owned(normalized.into_os_string().to_string_lossy().into_owned())
}
Err(_) => path.to_string_lossy(),
}
}
}
}
}
Expand Down Expand Up @@ -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, "<cfgspec>"),
MacroExpansion(_) => write!(fmt, "<macro expansion>"),
Anon(_) => write!(fmt, "<anon>"),
Expand All @@ -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::*;
Expand Down Expand Up @@ -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);
Expand Down
20 changes: 18 additions & 2 deletions compiler/rustc_span/src/source_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ pub struct SourceMapInputs {
pub path_mapping: FilePathMapping,
pub hash_kind: SourceFileHashAlgorithm,
pub checksum_hash_kind: Option<SourceFileHashAlgorithm>,
pub verbose: bool,
}

pub struct SourceMap {
Expand All @@ -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<SourceFileHashAlgorithm>,

/// Whether `--verbose` was passed. Diagnostics then print paths without
/// lexical normalization.
verbose: bool,
}

impl std::fmt::Debug for SourceMap {
Expand All @@ -224,6 +229,7 @@ impl std::fmt::Debug for SourceMap {
working_dir,
hash_kind,
checksum_hash_kind,
verbose,
} = self;

f.debug_struct("SourceMap")
Expand All @@ -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()
}
}
Expand All @@ -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()
Expand All @@ -262,6 +270,7 @@ impl SourceMap {
path_mapping,
hash_kind,
checksum_hash_kind,
verbose,
}
}

Expand Down Expand Up @@ -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(RemapPathScopeComponents::DIAGNOSTICS)
if self.verbose {
filename.display(RemapPathScopeComponents::DIAGNOSTICS)
} else {
filename.display_normalized(RemapPathScopeComponents::DIAGNOSTICS)
}
}

pub fn is_multiline(&self, sp: Span) -> bool {
Expand Down
28 changes: 28 additions & 0 deletions compiler/rustc_span/src/source_map/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -797,3 +797,31 @@ 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"));
}

#[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"),
);
}
10 changes: 10 additions & 0 deletions src/tools/compiletest/src/runtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
4 changes: 4 additions & 0 deletions tests/ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/const-generics/generic_arg_infer/issue-91614.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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::<T, N>::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);
Expand Down
3 changes: 3 additions & 0 deletions tests/ui/diagnostics/auxiliary/helper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub fn foo() -> u32 {
"not a u32"
}
2 changes: 2 additions & 0 deletions tests/ui/diagnostics/auxiliary/sub/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#[path = "../helper.rs"]
mod helper;
10 changes: 10 additions & 0 deletions tests/ui/diagnostics/normalize-path-verbose.rs
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions tests/ui/diagnostics/normalize-path-verbose.stderr
Original file line number Diff line number Diff line change
@@ -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`.
9 changes: 9 additions & 0 deletions tests/ui/diagnostics/normalize-path.rs
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions tests/ui/diagnostics/normalize-path.stderr
Original file line number Diff line number Diff line change
@@ -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`.
Loading