Skip to content

Commit 869df76

Browse files
committed
Heuristically undo path prefix mappings.
Because the compiler produces better diagnostics if it can find the source of (potentially remapped) dependencies.
1 parent 44a500c commit 869df76

8 files changed

+162
-7
lines changed

compiler/rustc_span/src/source_map.rs

+57-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use rustc_data_structures::stable_hasher::StableHasher;
1717
use rustc_data_structures::sync::{AtomicU32, Lrc, MappedReadGuard, ReadGuard, RwLock};
1818
use std::cmp;
1919
use std::hash::Hash;
20-
use std::path::{Path, PathBuf};
20+
use std::path::{self, Path, PathBuf};
2121
use std::sync::atomic::Ordering;
2222

2323
use std::fs;
@@ -1071,12 +1071,24 @@ impl SourceMap {
10711071

10721072
pub fn ensure_source_file_source_present(&self, source_file: Lrc<SourceFile>) -> bool {
10731073
source_file.add_external_src(|| {
1074-
match source_file.name {
1075-
FileName::Real(ref name) if let Some(local_path) = name.local_path() => {
1076-
self.file_loader.read_file(local_path).ok()
1074+
let FileName::Real(ref name) = source_file.name else {
1075+
return None;
1076+
};
1077+
1078+
let local_path: Cow<'_, Path> = match name {
1079+
RealFileName::LocalPath(local_path) => local_path.into(),
1080+
RealFileName::Remapped { local_path: Some(local_path), .. } => local_path.into(),
1081+
RealFileName::Remapped { local_path: None, virtual_name } => {
1082+
// The compiler produces better error messages if the sources of dependencies
1083+
// are available. Attempt to undo any path mapping so we can find remapped
1084+
// dependencies.
1085+
// We can only use the heuristic because `add_external_src` checks the file
1086+
// content hash.
1087+
self.path_mapping.reverse_map_prefix_heuristically(virtual_name)?.into()
10771088
}
1078-
_ => None,
1079-
}
1089+
};
1090+
1091+
self.file_loader.read_file(&local_path).ok()
10801092
})
10811093
}
10821094

@@ -1277,4 +1289,43 @@ impl FilePathMapping {
12771289
}
12781290
}
12791291
}
1292+
1293+
/// Attempts to (heuristically) reverse a prefix mapping.
1294+
///
1295+
/// Returns [`Some`] if there is exactly one mapping where the "to" part is
1296+
/// a prefix of `path` and has at least one non-empty
1297+
/// [`Normal`](path::Component::Normal) component. The component
1298+
/// restriction exists to avoid reverse mapping overly generic paths like
1299+
/// `/` or `.`).
1300+
///
1301+
/// This is a heuristic and not guaranteed to return the actual original
1302+
/// path! Do not rely on the result unless you have other means to verify
1303+
/// that the mapping is correct (e.g. by checking the file content hash).
1304+
#[instrument(level = "debug", skip(self), ret)]
1305+
fn reverse_map_prefix_heuristically(&self, path: &Path) -> Option<PathBuf> {
1306+
let mut found = None;
1307+
1308+
for (from, to) in self.mapping.iter() {
1309+
let has_normal_component = to.components().any(|c| match c {
1310+
path::Component::Normal(s) => !s.is_empty(),
1311+
_ => false,
1312+
});
1313+
1314+
if !has_normal_component {
1315+
continue;
1316+
}
1317+
1318+
let Ok(rest) = path.strip_prefix(to) else {
1319+
continue;
1320+
};
1321+
1322+
if found.is_some() {
1323+
return None;
1324+
}
1325+
1326+
found = Some(from.join(rest));
1327+
}
1328+
1329+
found
1330+
}
12801331
}

compiler/rustc_span/src/source_map/tests.rs

+43
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,10 @@ fn map_path_prefix(mapping: &FilePathMapping, p: &str) -> String {
344344
mapping.map_prefix(path(p)).0.to_string_lossy().to_string()
345345
}
346346

347+
fn reverse_map_prefix(mapping: &FilePathMapping, p: &str) -> Option<String> {
348+
mapping.reverse_map_prefix_heuristically(&path(p)).map(|q| q.to_string_lossy().to_string())
349+
}
350+
347351
#[test]
348352
fn path_prefix_remapping() {
349353
// Relative to relative
@@ -480,6 +484,45 @@ fn path_prefix_remapping_expand_to_absolute() {
480484
);
481485
}
482486

487+
#[test]
488+
fn path_prefix_remapping_reverse() {
489+
// Ignores options without alphanumeric chars.
490+
{
491+
let mapping =
492+
&FilePathMapping::new(vec![(path("abc"), path("/")), (path("def"), path("."))]);
493+
494+
assert_eq!(reverse_map_prefix(mapping, "/hello.rs"), None);
495+
assert_eq!(reverse_map_prefix(mapping, "./hello.rs"), None);
496+
}
497+
498+
// Returns `None` if multiple options match.
499+
{
500+
let mapping = &FilePathMapping::new(vec![
501+
(path("abc"), path("/redacted")),
502+
(path("def"), path("/redacted")),
503+
]);
504+
505+
assert_eq!(reverse_map_prefix(mapping, "/redacted/hello.rs"), None);
506+
}
507+
508+
// Distinct reverse mappings.
509+
{
510+
let mapping = &FilePathMapping::new(vec![
511+
(path("abc"), path("/redacted")),
512+
(path("def/ghi"), path("/fake/dir")),
513+
]);
514+
515+
assert_eq!(
516+
reverse_map_prefix(mapping, "/redacted/path/hello.rs"),
517+
Some(path_str("abc/path/hello.rs"))
518+
);
519+
assert_eq!(
520+
reverse_map_prefix(mapping, "/fake/dir/hello.rs"),
521+
Some(path_str("def/ghi/hello.rs"))
522+
);
523+
}
524+
}
525+
483526
#[test]
484527
fn test_next_point() {
485528
let sm = SourceMap::new(FilePathMapping::empty());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// compile-flags: --remap-path-prefix={{src-base}}/errors/auxiliary=remapped-aux
2+
3+
pub struct SomeStruct {} // This line should be show as part of the error.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error[E0423]: expected value, found struct `remapped_dep::SomeStruct`
2+
--> $DIR/remap-path-prefix-reverse.rs:22:13
3+
|
4+
LL | let _ = remapped_dep::SomeStruct;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: use struct literal syntax instead: `remapped_dep::SomeStruct {}`
6+
|
7+
::: remapped-aux/remapped_dep.rs:3:1
8+
|
9+
LL | pub struct SomeStruct {} // This line should be show as part of the error.
10+
| --------------------- `remapped_dep::SomeStruct` defined here
11+
12+
error: aborting due to previous error
13+
14+
For more information about this error, try `rustc --explain E0423`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error[E0423]: expected value, found struct `remapped_dep::SomeStruct`
2+
--> remapped/errors/remap-path-prefix-reverse.rs:22:13
3+
|
4+
LL | let _ = remapped_dep::SomeStruct;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: use struct literal syntax instead: `remapped_dep::SomeStruct {}`
6+
|
7+
::: remapped-aux/remapped_dep.rs:3:1
8+
|
9+
LL | pub struct SomeStruct {} // This line should be show as part of the error.
10+
| --------------------- `remapped_dep::SomeStruct` defined here
11+
12+
error: aborting due to previous error
13+
14+
For more information about this error, try `rustc --explain E0423`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// aux-build:remapped_dep.rs
2+
// compile-flags: --remap-path-prefix={{src-base}}/errors/auxiliary=remapped-aux
3+
4+
// The remapped paths are not normalized by compiletest.
5+
// normalize-stderr-test: "\\(errors)" -> "/$1"
6+
7+
// revisions: local-self remapped-self
8+
// [remapped-self]compile-flags: --remap-path-prefix={{src-base}}=remapped
9+
10+
// The paths from `remapped-self` aren't recognized by compiletest, so we
11+
// cannot use line-specific patterns for the actual error.
12+
// error-pattern: E0423
13+
14+
// Verify that the expected source code is shown.
15+
// error-pattern: pub struct SomeStruct {} // This line should be show
16+
17+
extern crate remapped_dep;
18+
19+
fn main() {
20+
// The actual error is irrelevant. The important part it that is should show
21+
// a snippet of the dependency's source.
22+
let _ = remapped_dep::SomeStruct;
23+
}

tests/ui/remap-path-prefix.rs tests/ui/errors/remap-path-prefix.rs

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
// compile-flags: --remap-path-prefix={{src-base}}=remapped
22

3+
// The remapped paths are not normalized by compiletest.
4+
// normalize-stderr-test: "\\(errors)" -> "/$1"
5+
6+
// The remapped paths aren't recognized by compiletest, so we
7+
// cannot use line-specific patterns.
8+
// error-pattern: E0425
9+
310
fn main() {
411
// We cannot actually put an ERROR marker here because
512
// the file name in the error message is not what the

tests/ui/remap-path-prefix.stderr tests/ui/errors/remap-path-prefix.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0425]: cannot find value `ferris` in this scope
2-
--> remapped/remap-path-prefix.rs:8:5
2+
--> remapped/errors/remap-path-prefix.rs:15:5
33
|
44
LL | ferris
55
| ^^^^^^ not found in this scope

0 commit comments

Comments
 (0)