Skip to content

Commit

Permalink
Suggest correct path in include_bytes!
Browse files Browse the repository at this point in the history
  • Loading branch information
kornelski committed Mar 1, 2024
1 parent 387e8ca commit dee40b1
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 22 deletions.
103 changes: 85 additions & 18 deletions compiler/rustc_builtin_macros/src/source_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ use rustc_expand::module::DirOwnership;
use rustc_parse::new_parser_from_file;
use rustc_parse::parser::{ForceCollect, Parser};
use rustc_session::lint::builtin::INCOMPLETE_INCLUDE;
use rustc_span::source_map::SourceMap;
use rustc_span::symbol::Symbol;
use rustc_span::{Pos, Span};
use smallvec::SmallVec;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::rc::Rc;

// These macros all relate to the file system; they either return
Expand Down Expand Up @@ -185,7 +186,7 @@ pub fn expand_include_str(
Ok(res) => res,
Err(guar) => return DummyResult::any(sp, guar),
};
match load_binary_file(cx, path.as_str(), sp, path_span) {
match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) {
Ok(bytes) => match std::str::from_utf8(&bytes) {
Ok(src) => {
let interned_src = Symbol::intern(src);
Expand All @@ -210,7 +211,7 @@ pub fn expand_include_bytes(
Ok(res) => res,
Err(guar) => return DummyResult::any(sp, guar),
};
match load_binary_file(cx, path.as_str(), sp, path_span) {
match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) {
Ok(bytes) => {
let expr = cx.expr(sp, ast::ExprKind::IncludedBytes(bytes));
MacEager::expr(expr)
Expand All @@ -221,7 +222,7 @@ pub fn expand_include_bytes(

fn load_binary_file(
cx: &mut ExtCtxt<'_>,
original_path: &str,
original_path: &Path,
macro_span: Span,
path_span: Span,
) -> Result<Lrc<[u8]>, Box<dyn MacResult>> {
Expand All @@ -239,24 +240,90 @@ fn load_binary_file(
macro_span,
format!("couldn't read {}: {io_err}", resolved_path.display()),
);
if Path::new(original_path).is_relative() {
for prefix in ["..", "../.."] {
let parent_path = Path::new(prefix).join(original_path);
if resolve_path(&cx.sess, &parent_path, macro_span)
.map_or(false, |p| p.exists())
{
err.span_suggestion(
path_span,
"it's in a parent directory",
format!("\"{}\"", parent_path.display().to_string().escape_debug()),
rustc_lint_defs::Applicability::MachineApplicable,
);
break;
}

if original_path.is_relative() {
let source_map = cx.sess.source_map();
let new_path = source_map
.span_to_filename(macro_span.source_callsite())
.into_local_path()
.and_then(|src| find_path_suggestion(source_map, src.parent()?, original_path))
.and_then(|path| path.into_os_string().into_string().ok());

if let Some(new_path) = new_path {
err.span_suggestion(
path_span,
"there is a file in another directory",
format!("\"{}\"", new_path.escape_debug()),
rustc_lint_defs::Applicability::MachineApplicable,
);
}
}
let guar = err.emit();
Err(DummyResult::any(macro_span, guar))
}
}
}

fn find_path_suggestion(
source_map: &SourceMap,
base_dir: &Path,
wanted_path: &Path,
) -> Option<PathBuf> {
// Fix paths that assume they're relative to cargo manifest dir
let mut base_c = base_dir.components();
let mut wanted_c = wanted_path.components();
let mut without_base = None;
while let Some(wanted_next) = wanted_c.next() {
if wanted_c.as_path().file_name().is_none() {
break;
}
// base_dir may be absolute
while let Some(base_next) = base_c.next() {
if base_next == wanted_next {
without_base = Some(wanted_c.as_path());
break;
}
}
}
let root_absolute = without_base.into_iter().map(PathBuf::from);

let base_dir_components = base_dir.components().count();
// Avoid going all the way to the root dir
let max_parent_components = if base_dir.is_relative() {
base_dir_components + 1
} else {
base_dir_components.saturating_sub(1)
};

// Try with additional leading ../
let mut prefix = PathBuf::new();
let add = std::iter::from_fn(|| {
prefix.push("..");
Some(prefix.join(wanted_path))
})
.take(max_parent_components.min(3));

// Try without leading directories
let mut trimmed_path = wanted_path;
let remove = std::iter::from_fn(|| {
let mut components = trimmed_path.components();
let removed = components.next()?;
trimmed_path = components.as_path();
let _ = trimmed_path.file_name()?; // ensure there is a file name left
Some([
Some(trimmed_path.to_path_buf()),
(removed != std::path::Component::ParentDir)
.then(|| Path::new("..").join(trimmed_path)),
])
})
.flatten()
.flatten()
.take(4);

for new_path in root_absolute.chain(add).chain(remove) {
if source_map.file_exists(&base_dir.join(&new_path)) {
return Some(new_path);
}
}
None
}
10 changes: 8 additions & 2 deletions tests/ui/include-macros/parent_dir.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
fn main() {
let _ = include_str!("include-macros/file.txt"); //~ ERROR coudln't read
//~^HELP parent directory
let _ = include_str!("include-macros/file.txt"); //~ ERROR couldn't read
//~^HELP another directory
let _ = include_str!("hello.rs"); //~ ERROR couldn't read
//~^HELP another directory
let _ = include_bytes!("../../data.bin"); //~ ERROR couldn't read
//~^HELP another directory
let _ = include_str!("tests/ui/include-macros/file.txt"); //~ ERROR couldn't read
//~^HELP another directory
}
34 changes: 32 additions & 2 deletions tests/ui/include-macros/parent_dir.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,39 @@ error: couldn't read $DIR/include-macros/file.txt: No such file or directory (os
LL | let _ = include_str!("include-macros/file.txt");
| ^^^^^^^^^^^^^-------------------------^
| |
| help: it's in a parent directory: `"../include-macros/file.txt"`
| help: there is a file in another directory: `"file.txt"`
|
= note: this error originates in the macro `include_str` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 1 previous error
error: couldn't read $DIR/hello.rs: No such file or directory (os error 2)
--> $DIR/parent_dir.rs:4:13
|
LL | let _ = include_str!("hello.rs");
| ^^^^^^^^^^^^^----------^
| |
| help: there is a file in another directory: `"../hello.rs"`
|
= note: this error originates in the macro `include_str` (in Nightly builds, run with -Z macro-backtrace for more info)

error: couldn't read $DIR/../../data.bin: No such file or directory (os error 2)
--> $DIR/parent_dir.rs:6:13
|
LL | let _ = include_bytes!("../../data.bin");
| ^^^^^^^^^^^^^^^----------------^
| |
| help: there is a file in another directory: `"data.bin"`
|
= note: this error originates in the macro `include_bytes` (in Nightly builds, run with -Z macro-backtrace for more info)

error: couldn't read $DIR/tests/ui/include-macros/file.txt: No such file or directory (os error 2)
--> $DIR/parent_dir.rs:8:13
|
LL | let _ = include_str!("tests/ui/include-macros/file.txt");
| ^^^^^^^^^^^^^----------------------------------^
| |
| help: there is a file in another directory: `"file.txt"`
|
= note: this error originates in the macro `include_str` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 4 previous errors

0 comments on commit dee40b1

Please sign in to comment.