Skip to content

Commit dee40b1

Browse files
committed
Suggest correct path in include_bytes!
1 parent 387e8ca commit dee40b1

File tree

3 files changed

+125
-22
lines changed

3 files changed

+125
-22
lines changed

compiler/rustc_builtin_macros/src/source_util.rs

+85-18
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ use rustc_expand::module::DirOwnership;
1212
use rustc_parse::new_parser_from_file;
1313
use rustc_parse::parser::{ForceCollect, Parser};
1414
use rustc_session::lint::builtin::INCOMPLETE_INCLUDE;
15+
use rustc_span::source_map::SourceMap;
1516
use rustc_span::symbol::Symbol;
1617
use rustc_span::{Pos, Span};
1718
use smallvec::SmallVec;
18-
use std::path::Path;
19+
use std::path::{Path, PathBuf};
1920
use std::rc::Rc;
2021

2122
// These macros all relate to the file system; they either return
@@ -185,7 +186,7 @@ pub fn expand_include_str(
185186
Ok(res) => res,
186187
Err(guar) => return DummyResult::any(sp, guar),
187188
};
188-
match load_binary_file(cx, path.as_str(), sp, path_span) {
189+
match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) {
189190
Ok(bytes) => match std::str::from_utf8(&bytes) {
190191
Ok(src) => {
191192
let interned_src = Symbol::intern(src);
@@ -210,7 +211,7 @@ pub fn expand_include_bytes(
210211
Ok(res) => res,
211212
Err(guar) => return DummyResult::any(sp, guar),
212213
};
213-
match load_binary_file(cx, path.as_str(), sp, path_span) {
214+
match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) {
214215
Ok(bytes) => {
215216
let expr = cx.expr(sp, ast::ExprKind::IncludedBytes(bytes));
216217
MacEager::expr(expr)
@@ -221,7 +222,7 @@ pub fn expand_include_bytes(
221222

222223
fn load_binary_file(
223224
cx: &mut ExtCtxt<'_>,
224-
original_path: &str,
225+
original_path: &Path,
225226
macro_span: Span,
226227
path_span: Span,
227228
) -> Result<Lrc<[u8]>, Box<dyn MacResult>> {
@@ -239,24 +240,90 @@ fn load_binary_file(
239240
macro_span,
240241
format!("couldn't read {}: {io_err}", resolved_path.display()),
241242
);
242-
if Path::new(original_path).is_relative() {
243-
for prefix in ["..", "../.."] {
244-
let parent_path = Path::new(prefix).join(original_path);
245-
if resolve_path(&cx.sess, &parent_path, macro_span)
246-
.map_or(false, |p| p.exists())
247-
{
248-
err.span_suggestion(
249-
path_span,
250-
"it's in a parent directory",
251-
format!("\"{}\"", parent_path.display().to_string().escape_debug()),
252-
rustc_lint_defs::Applicability::MachineApplicable,
253-
);
254-
break;
255-
}
243+
244+
if original_path.is_relative() {
245+
let source_map = cx.sess.source_map();
246+
let new_path = source_map
247+
.span_to_filename(macro_span.source_callsite())
248+
.into_local_path()
249+
.and_then(|src| find_path_suggestion(source_map, src.parent()?, original_path))
250+
.and_then(|path| path.into_os_string().into_string().ok());
251+
252+
if let Some(new_path) = new_path {
253+
err.span_suggestion(
254+
path_span,
255+
"there is a file in another directory",
256+
format!("\"{}\"", new_path.escape_debug()),
257+
rustc_lint_defs::Applicability::MachineApplicable,
258+
);
256259
}
257260
}
258261
let guar = err.emit();
259262
Err(DummyResult::any(macro_span, guar))
260263
}
261264
}
262265
}
266+
267+
fn find_path_suggestion(
268+
source_map: &SourceMap,
269+
base_dir: &Path,
270+
wanted_path: &Path,
271+
) -> Option<PathBuf> {
272+
// Fix paths that assume they're relative to cargo manifest dir
273+
let mut base_c = base_dir.components();
274+
let mut wanted_c = wanted_path.components();
275+
let mut without_base = None;
276+
while let Some(wanted_next) = wanted_c.next() {
277+
if wanted_c.as_path().file_name().is_none() {
278+
break;
279+
}
280+
// base_dir may be absolute
281+
while let Some(base_next) = base_c.next() {
282+
if base_next == wanted_next {
283+
without_base = Some(wanted_c.as_path());
284+
break;
285+
}
286+
}
287+
}
288+
let root_absolute = without_base.into_iter().map(PathBuf::from);
289+
290+
let base_dir_components = base_dir.components().count();
291+
// Avoid going all the way to the root dir
292+
let max_parent_components = if base_dir.is_relative() {
293+
base_dir_components + 1
294+
} else {
295+
base_dir_components.saturating_sub(1)
296+
};
297+
298+
// Try with additional leading ../
299+
let mut prefix = PathBuf::new();
300+
let add = std::iter::from_fn(|| {
301+
prefix.push("..");
302+
Some(prefix.join(wanted_path))
303+
})
304+
.take(max_parent_components.min(3));
305+
306+
// Try without leading directories
307+
let mut trimmed_path = wanted_path;
308+
let remove = std::iter::from_fn(|| {
309+
let mut components = trimmed_path.components();
310+
let removed = components.next()?;
311+
trimmed_path = components.as_path();
312+
let _ = trimmed_path.file_name()?; // ensure there is a file name left
313+
Some([
314+
Some(trimmed_path.to_path_buf()),
315+
(removed != std::path::Component::ParentDir)
316+
.then(|| Path::new("..").join(trimmed_path)),
317+
])
318+
})
319+
.flatten()
320+
.flatten()
321+
.take(4);
322+
323+
for new_path in root_absolute.chain(add).chain(remove) {
324+
if source_map.file_exists(&base_dir.join(&new_path)) {
325+
return Some(new_path);
326+
}
327+
}
328+
None
329+
}

tests/ui/include-macros/parent_dir.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
fn main() {
2-
let _ = include_str!("include-macros/file.txt"); //~ ERROR coudln't read
3-
//~^HELP parent directory
2+
let _ = include_str!("include-macros/file.txt"); //~ ERROR couldn't read
3+
//~^HELP another directory
4+
let _ = include_str!("hello.rs"); //~ ERROR couldn't read
5+
//~^HELP another directory
6+
let _ = include_bytes!("../../data.bin"); //~ ERROR couldn't read
7+
//~^HELP another directory
8+
let _ = include_str!("tests/ui/include-macros/file.txt"); //~ ERROR couldn't read
9+
//~^HELP another directory
410
}

tests/ui/include-macros/parent_dir.stderr

+32-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,39 @@ error: couldn't read $DIR/include-macros/file.txt: No such file or directory (os
44
LL | let _ = include_str!("include-macros/file.txt");
55
| ^^^^^^^^^^^^^-------------------------^
66
| |
7-
| help: it's in a parent directory: `"../include-macros/file.txt"`
7+
| help: there is a file in another directory: `"file.txt"`
88
|
99
= note: this error originates in the macro `include_str` (in Nightly builds, run with -Z macro-backtrace for more info)
1010

11-
error: aborting due to 1 previous error
11+
error: couldn't read $DIR/hello.rs: No such file or directory (os error 2)
12+
--> $DIR/parent_dir.rs:4:13
13+
|
14+
LL | let _ = include_str!("hello.rs");
15+
| ^^^^^^^^^^^^^----------^
16+
| |
17+
| help: there is a file in another directory: `"../hello.rs"`
18+
|
19+
= note: this error originates in the macro `include_str` (in Nightly builds, run with -Z macro-backtrace for more info)
20+
21+
error: couldn't read $DIR/../../data.bin: No such file or directory (os error 2)
22+
--> $DIR/parent_dir.rs:6:13
23+
|
24+
LL | let _ = include_bytes!("../../data.bin");
25+
| ^^^^^^^^^^^^^^^----------------^
26+
| |
27+
| help: there is a file in another directory: `"data.bin"`
28+
|
29+
= note: this error originates in the macro `include_bytes` (in Nightly builds, run with -Z macro-backtrace for more info)
30+
31+
error: couldn't read $DIR/tests/ui/include-macros/file.txt: No such file or directory (os error 2)
32+
--> $DIR/parent_dir.rs:8:13
33+
|
34+
LL | let _ = include_str!("tests/ui/include-macros/file.txt");
35+
| ^^^^^^^^^^^^^----------------------------------^
36+
| |
37+
| help: there is a file in another directory: `"file.txt"`
38+
|
39+
= note: this error originates in the macro `include_str` (in Nightly builds, run with -Z macro-backtrace for more info)
40+
41+
error: aborting due to 4 previous errors
1242

0 commit comments

Comments
 (0)