1
Fork 0

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::stable_hasher::StableHasher;
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 @@ impl SourceMap {
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 @@ impl FilePathMapping {
}
}
}
/// 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());