Heuristically undo path prefix mappings.

Because the compiler produces better diagnostics if it can find the
source of (potentially remapped) dependencies.
This commit is contained in:
Tim Neumann 2023-01-06 11:28:05 +00:00
parent 44a500c8c1
commit 869df76764
8 changed files with 162 additions and 7 deletions

View File

@ -17,7 +17,7 @@
use rustc_data_structures::sync::{AtomicU32, Lrc, MappedReadGuard, ReadGuard, RwLock};
use std::cmp;
use std::hash::Hash;
use std::path::{Path, PathBuf};
use std::path::{self, Path, PathBuf};
use std::sync::atomic::Ordering;
use std::fs;
@ -1071,12 +1071,24 @@ pub fn count_lines(&self) -> usize {
pub fn ensure_source_file_source_present(&self, source_file: Lrc<SourceFile>) -> bool {
source_file.add_external_src(|| {
match source_file.name {
FileName::Real(ref name) if let Some(local_path) = name.local_path() => {
self.file_loader.read_file(local_path).ok()
let FileName::Real(ref name) = source_file.name else {
return None;
};
let local_path: Cow<'_, Path> = match name {
RealFileName::LocalPath(local_path) => local_path.into(),
RealFileName::Remapped { local_path: Some(local_path), .. } => local_path.into(),
RealFileName::Remapped { local_path: None, virtual_name } => {
// The compiler produces better error messages if the sources of dependencies
// are available. Attempt to undo any path mapping so we can find remapped
// dependencies.
// We can only use the heuristic because `add_external_src` checks the file
// content hash.
self.path_mapping.reverse_map_prefix_heuristically(virtual_name)?.into()
}
_ => None,
}
};
self.file_loader.read_file(&local_path).ok()
})
}
@ -1277,4 +1289,43 @@ pub fn to_embeddable_absolute_path(
}
}
}
/// Attempts to (heuristically) reverse a prefix mapping.
///
/// Returns [`Some`] if there is exactly one mapping where the "to" part is
/// a prefix of `path` and has at least one non-empty
/// [`Normal`](path::Component::Normal) component. The component
/// restriction exists to avoid reverse mapping overly generic paths like
/// `/` or `.`).
///
/// This is a heuristic and not guaranteed to return the actual original
/// path! Do not rely on the result unless you have other means to verify
/// that the mapping is correct (e.g. by checking the file content hash).
#[instrument(level = "debug", skip(self), ret)]
fn reverse_map_prefix_heuristically(&self, path: &Path) -> Option<PathBuf> {
let mut found = None;
for (from, to) in self.mapping.iter() {
let has_normal_component = to.components().any(|c| match c {
path::Component::Normal(s) => !s.is_empty(),
_ => false,
});
if !has_normal_component {
continue;
}
let Ok(rest) = path.strip_prefix(to) else {
continue;
};
if found.is_some() {
return None;
}
found = Some(from.join(rest));
}
found
}
}

View File

@ -344,6 +344,10 @@ fn map_path_prefix(mapping: &FilePathMapping, p: &str) -> String {
mapping.map_prefix(path(p)).0.to_string_lossy().to_string()
}
fn reverse_map_prefix(mapping: &FilePathMapping, p: &str) -> Option<String> {
mapping.reverse_map_prefix_heuristically(&path(p)).map(|q| q.to_string_lossy().to_string())
}
#[test]
fn path_prefix_remapping() {
// Relative to relative
@ -480,6 +484,45 @@ fn path_prefix_remapping_expand_to_absolute() {
);
}
#[test]
fn path_prefix_remapping_reverse() {
// Ignores options without alphanumeric chars.
{
let mapping =
&FilePathMapping::new(vec![(path("abc"), path("/")), (path("def"), path("."))]);
assert_eq!(reverse_map_prefix(mapping, "/hello.rs"), None);
assert_eq!(reverse_map_prefix(mapping, "./hello.rs"), None);
}
// Returns `None` if multiple options match.
{
let mapping = &FilePathMapping::new(vec![
(path("abc"), path("/redacted")),
(path("def"), path("/redacted")),
]);
assert_eq!(reverse_map_prefix(mapping, "/redacted/hello.rs"), None);
}
// Distinct reverse mappings.
{
let mapping = &FilePathMapping::new(vec![
(path("abc"), path("/redacted")),
(path("def/ghi"), path("/fake/dir")),
]);
assert_eq!(
reverse_map_prefix(mapping, "/redacted/path/hello.rs"),
Some(path_str("abc/path/hello.rs"))
);
assert_eq!(
reverse_map_prefix(mapping, "/fake/dir/hello.rs"),
Some(path_str("def/ghi/hello.rs"))
);
}
}
#[test]
fn test_next_point() {
let sm = SourceMap::new(FilePathMapping::empty());

View File

@ -0,0 +1,3 @@
// compile-flags: --remap-path-prefix={{src-base}}/errors/auxiliary=remapped-aux
pub struct SomeStruct {} // This line should be show as part of the error.

View File

@ -0,0 +1,14 @@
error[E0423]: expected value, found struct `remapped_dep::SomeStruct`
--> $DIR/remap-path-prefix-reverse.rs:22:13
|
LL | let _ = remapped_dep::SomeStruct;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: use struct literal syntax instead: `remapped_dep::SomeStruct {}`
|
::: remapped-aux/remapped_dep.rs:3:1
|
LL | pub struct SomeStruct {} // This line should be show as part of the error.
| --------------------- `remapped_dep::SomeStruct` defined here
error: aborting due to previous error
For more information about this error, try `rustc --explain E0423`.

View File

@ -0,0 +1,14 @@
error[E0423]: expected value, found struct `remapped_dep::SomeStruct`
--> remapped/errors/remap-path-prefix-reverse.rs:22:13
|
LL | let _ = remapped_dep::SomeStruct;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: use struct literal syntax instead: `remapped_dep::SomeStruct {}`
|
::: remapped-aux/remapped_dep.rs:3:1
|
LL | pub struct SomeStruct {} // This line should be show as part of the error.
| --------------------- `remapped_dep::SomeStruct` defined here
error: aborting due to previous error
For more information about this error, try `rustc --explain E0423`.

View File

@ -0,0 +1,23 @@
// aux-build:remapped_dep.rs
// compile-flags: --remap-path-prefix={{src-base}}/errors/auxiliary=remapped-aux
// The remapped paths are not normalized by compiletest.
// normalize-stderr-test: "\\(errors)" -> "/$1"
// revisions: local-self remapped-self
// [remapped-self]compile-flags: --remap-path-prefix={{src-base}}=remapped
// The paths from `remapped-self` aren't recognized by compiletest, so we
// cannot use line-specific patterns for the actual error.
// error-pattern: E0423
// Verify that the expected source code is shown.
// error-pattern: pub struct SomeStruct {} // This line should be show
extern crate remapped_dep;
fn main() {
// The actual error is irrelevant. The important part it that is should show
// a snippet of the dependency's source.
let _ = remapped_dep::SomeStruct;
}

View File

@ -1,5 +1,12 @@
// compile-flags: --remap-path-prefix={{src-base}}=remapped
// The remapped paths are not normalized by compiletest.
// normalize-stderr-test: "\\(errors)" -> "/$1"
// The remapped paths aren't recognized by compiletest, so we
// cannot use line-specific patterns.
// error-pattern: E0425
fn main() {
// We cannot actually put an ERROR marker here because
// the file name in the error message is not what the

View File

@ -1,5 +1,5 @@
error[E0425]: cannot find value `ferris` in this scope
--> remapped/remap-path-prefix.rs:8:5
--> remapped/errors/remap-path-prefix.rs:15:5
|
LL | ferris
| ^^^^^^ not found in this scope