Skip to content

Commit 502e254

Browse files
committed
Suggest correct path in include_bytes!
1 parent 4ef10f4 commit 502e254

File tree

6 files changed

+207
-35
lines changed

6 files changed

+207
-35
lines changed

compiler/rustc_builtin_macros/src/source_util.rs

+126-31
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,22 @@ use rustc_ast::ptr::P;
33
use rustc_ast::token;
44
use rustc_ast::tokenstream::TokenStream;
55
use rustc_ast_pretty::pprust;
6-
use rustc_expand::base::{check_zero_tts, get_single_str_from_tts, parse_expr, resolve_path};
6+
use rustc_data_structures::sync::Lrc;
7+
use rustc_expand::base::{
8+
check_zero_tts, get_single_str_from_tts, get_single_str_spanned_from_tts, parse_expr,
9+
resolve_path,
10+
};
711
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt};
812
use rustc_expand::base::{MacEager, MacResult, MacroExpanderResult};
913
use rustc_expand::module::DirOwnership;
1014
use rustc_parse::new_parser_from_file;
1115
use rustc_parse::parser::{ForceCollect, Parser};
1216
use rustc_session::lint::builtin::INCOMPLETE_INCLUDE;
17+
use rustc_span::source_map::SourceMap;
1318
use rustc_span::symbol::Symbol;
1419
use rustc_span::{Pos, Span};
15-
1620
use smallvec::SmallVec;
21+
use std::path::{Path, PathBuf};
1722
use std::rc::Rc;
1823

1924
// These macros all relate to the file system; they either return
@@ -182,35 +187,26 @@ pub fn expand_include_str(
182187
tts: TokenStream,
183188
) -> MacroExpanderResult<'static> {
184189
let sp = cx.with_def_site_ctxt(sp);
185-
let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "include_str!") else {
190+
let ExpandResult::Ready(mac) = get_single_str_spanned_from_tts(cx, sp, tts, "include_str!")
191+
else {
186192
return ExpandResult::Retry(());
187193
};
188-
let file = match mac {
189-
Ok(file) => file,
194+
let (path, path_span) = match mac {
195+
Ok(res) => res,
190196
Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
191197
};
192-
let file = match resolve_path(&cx.sess, file.as_str(), sp) {
193-
Ok(f) => f,
194-
Err(err) => {
195-
let guar = err.emit();
196-
return ExpandResult::Ready(DummyResult::any(sp, guar));
197-
}
198-
};
199-
ExpandResult::Ready(match cx.source_map().load_binary_file(&file) {
198+
ExpandResult::Ready(match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) {
200199
Ok(bytes) => match std::str::from_utf8(&bytes) {
201200
Ok(src) => {
202201
let interned_src = Symbol::intern(src);
203202
MacEager::expr(cx.expr_str(sp, interned_src))
204203
}
205204
Err(_) => {
206-
let guar = cx.dcx().span_err(sp, format!("{} wasn't a utf-8 file", file.display()));
205+
let guar = cx.dcx().span_err(sp, format!("`{path}` wasn't a utf-8 file"));
207206
DummyResult::any(sp, guar)
208207
}
209208
},
210-
Err(e) => {
211-
let guar = cx.dcx().span_err(sp, format!("couldn't read {}: {}", file.display(), e));
212-
DummyResult::any(sp, guar)
213-
}
209+
Err(dummy) => dummy,
214210
})
215211
}
216212

@@ -220,28 +216,127 @@ pub fn expand_include_bytes(
220216
tts: TokenStream,
221217
) -> MacroExpanderResult<'static> {
222218
let sp = cx.with_def_site_ctxt(sp);
223-
let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "include_bytes!") else {
219+
let ExpandResult::Ready(mac) = get_single_str_spanned_from_tts(cx, sp, tts, "include_bytes!")
220+
else {
224221
return ExpandResult::Retry(());
225222
};
226-
let file = match mac {
227-
Ok(file) => file,
223+
let (path, path_span) = match mac {
224+
Ok(res) => res,
228225
Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
229226
};
230-
let file = match resolve_path(&cx.sess, file.as_str(), sp) {
231-
Ok(f) => f,
227+
ExpandResult::Ready(match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) {
228+
Ok(bytes) => {
229+
let expr = cx.expr(sp, ast::ExprKind::IncludedBytes(bytes));
230+
MacEager::expr(expr)
231+
}
232+
Err(dummy) => dummy,
233+
})
234+
}
235+
236+
fn load_binary_file(
237+
cx: &mut ExtCtxt<'_>,
238+
original_path: &Path,
239+
macro_span: Span,
240+
path_span: Span,
241+
) -> Result<Lrc<[u8]>, Box<dyn MacResult>> {
242+
let resolved_path = match resolve_path(&cx.sess, original_path, macro_span) {
243+
Ok(path) => path,
232244
Err(err) => {
233245
let guar = err.emit();
234-
return ExpandResult::Ready(DummyResult::any(sp, guar));
246+
return Err(DummyResult::any(macro_span, guar));
235247
}
236248
};
237-
ExpandResult::Ready(match cx.source_map().load_binary_file(&file) {
238-
Ok(bytes) => {
239-
let expr = cx.expr(sp, ast::ExprKind::IncludedBytes(bytes));
240-
MacEager::expr(expr)
249+
match cx.source_map().load_binary_file(&resolved_path) {
250+
Ok(data) => Ok(data),
251+
Err(io_err) => {
252+
let mut err = cx.dcx().struct_span_err(
253+
macro_span,
254+
format!("couldn't read `{}`: {io_err}", resolved_path.display()),
255+
);
256+
257+
if original_path.is_relative() {
258+
let source_map = cx.sess.source_map();
259+
let new_path = source_map
260+
.span_to_filename(macro_span.source_callsite())
261+
.into_local_path()
262+
.and_then(|src| find_path_suggestion(source_map, src.parent()?, original_path))
263+
.and_then(|path| path.into_os_string().into_string().ok());
264+
265+
if let Some(new_path) = new_path {
266+
err.span_suggestion(
267+
path_span,
268+
"there is a file with the same name in a different directory",
269+
format!("\"{}\"", new_path.escape_debug()),
270+
rustc_lint_defs::Applicability::MachineApplicable,
271+
);
272+
}
273+
}
274+
let guar = err.emit();
275+
Err(DummyResult::any(macro_span, guar))
241276
}
242-
Err(e) => {
243-
let guar = cx.dcx().span_err(sp, format!("couldn't read {}: {}", file.display(), e));
244-
DummyResult::any(sp, guar)
277+
}
278+
}
279+
280+
fn find_path_suggestion(
281+
source_map: &SourceMap,
282+
base_dir: &Path,
283+
wanted_path: &Path,
284+
) -> Option<PathBuf> {
285+
// Fix paths that assume they're relative to cargo manifest dir
286+
let mut base_c = base_dir.components();
287+
let mut wanted_c = wanted_path.components();
288+
let mut without_base = None;
289+
while let Some(wanted_next) = wanted_c.next() {
290+
if wanted_c.as_path().file_name().is_none() {
291+
break;
245292
}
293+
// base_dir may be absolute
294+
while let Some(base_next) = base_c.next() {
295+
if base_next == wanted_next {
296+
without_base = Some(wanted_c.as_path());
297+
break;
298+
}
299+
}
300+
}
301+
let root_absolute = without_base.into_iter().map(PathBuf::from);
302+
303+
let base_dir_components = base_dir.components().count();
304+
// Avoid going all the way to the root dir
305+
let max_parent_components = if base_dir.is_relative() {
306+
base_dir_components + 1
307+
} else {
308+
base_dir_components.saturating_sub(1)
309+
};
310+
311+
// Try with additional leading ../
312+
let mut prefix = PathBuf::new();
313+
let add = std::iter::from_fn(|| {
314+
prefix.push("..");
315+
Some(prefix.join(wanted_path))
246316
})
317+
.take(max_parent_components.min(3));
318+
319+
// Try without leading directories
320+
let mut trimmed_path = wanted_path;
321+
let remove = std::iter::from_fn(|| {
322+
let mut components = trimmed_path.components();
323+
let removed = components.next()?;
324+
trimmed_path = components.as_path();
325+
let _ = trimmed_path.file_name()?; // ensure there is a file name left
326+
Some([
327+
Some(trimmed_path.to_path_buf()),
328+
(removed != std::path::Component::ParentDir)
329+
.then(|| Path::new("..").join(trimmed_path)),
330+
])
331+
})
332+
.flatten()
333+
.flatten()
334+
.take(4);
335+
336+
for new_path in root_absolute.chain(add).chain(remove) {
337+
if source_map.file_exists(&base_dir.join(&new_path)) {
338+
return Some(new_path);
339+
}
340+
}
341+
None
247342
}

compiler/rustc_expand/src/base.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,15 @@ pub fn get_single_str_from_tts(
13761376
tts: TokenStream,
13771377
name: &str,
13781378
) -> ExpandResult<Result<Symbol, ErrorGuaranteed>, ()> {
1379+
get_single_str_spanned_from_tts(cx, span, tts, name).map(|res| res.map(|(s, _)| s))
1380+
}
1381+
1382+
pub fn get_single_str_spanned_from_tts(
1383+
cx: &mut ExtCtxt<'_>,
1384+
span: Span,
1385+
tts: TokenStream,
1386+
name: &str,
1387+
) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> {
13791388
let mut p = cx.new_parser_from_tts(tts);
13801389
if p.token == token::Eof {
13811390
let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
@@ -1390,7 +1399,13 @@ pub fn get_single_str_from_tts(
13901399
if p.token != token::Eof {
13911400
cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
13921401
}
1393-
expr_to_string(cx, ret, "argument must be a string literal").map(|s| s.map(|(s, _)| s))
1402+
expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| {
1403+
res.map_err(|err| match err {
1404+
Ok((err, _)) => err.emit(),
1405+
Err(guar) => guar,
1406+
})
1407+
.map(|(symbol, _style, span)| (symbol, span))
1408+
})
13941409
}
13951410

13961411
/// Extracts comma-separated expressions from `tts`.

tests/ui/include-macros/parent_dir.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//@ normalize-stderr-test: "`: .*\(os error" -> "`: $$FILE_NOT_FOUND_MSG (os error"
2+
3+
fn main() {
4+
let _ = include_str!("include-macros/file.txt"); //~ ERROR couldn't read
5+
//~^HELP different directory
6+
let _ = include_str!("hello.rs"); //~ ERROR couldn't read
7+
//~^HELP different directory
8+
let _ = include_bytes!("../../data.bin"); //~ ERROR couldn't read
9+
//~^HELP different directory
10+
let _ = include_str!("tests/ui/include-macros/file.txt"); //~ ERROR couldn't read
11+
//~^HELP different directory
12+
}
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
error: couldn't read `$DIR/include-macros/file.txt`: $FILE_NOT_FOUND_MSG (os error 2)
2+
--> $DIR/parent_dir.rs:4:13
3+
|
4+
LL | let _ = include_str!("include-macros/file.txt");
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: this error originates in the macro `include_str` (in Nightly builds, run with -Z macro-backtrace for more info)
8+
help: there is a file with the same name in a different directory
9+
|
10+
LL | let _ = include_str!("file.txt");
11+
| ~~~~~~~~~~
12+
13+
error: couldn't read `$DIR/hello.rs`: $FILE_NOT_FOUND_MSG (os error 2)
14+
--> $DIR/parent_dir.rs:6:13
15+
|
16+
LL | let _ = include_str!("hello.rs");
17+
| ^^^^^^^^^^^^^^^^^^^^^^^^
18+
|
19+
= note: this error originates in the macro `include_str` (in Nightly builds, run with -Z macro-backtrace for more info)
20+
help: there is a file with the same name in a different directory
21+
|
22+
LL | let _ = include_str!("../hello.rs");
23+
| ~~~~~~~~~~~~~
24+
25+
error: couldn't read `$DIR/../../data.bin`: $FILE_NOT_FOUND_MSG (os error 2)
26+
--> $DIR/parent_dir.rs:8:13
27+
|
28+
LL | let _ = include_bytes!("../../data.bin");
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30+
|
31+
= note: this error originates in the macro `include_bytes` (in Nightly builds, run with -Z macro-backtrace for more info)
32+
help: there is a file with the same name in a different directory
33+
|
34+
LL | let _ = include_bytes!("data.bin");
35+
| ~~~~~~~~~~
36+
37+
error: couldn't read `$DIR/tests/ui/include-macros/file.txt`: $FILE_NOT_FOUND_MSG (os error 2)
38+
--> $DIR/parent_dir.rs:10:13
39+
|
40+
LL | let _ = include_str!("tests/ui/include-macros/file.txt");
41+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
42+
|
43+
= note: this error originates in the macro `include_str` (in Nightly builds, run with -Z macro-backtrace for more info)
44+
help: there is a file with the same name in a different directory
45+
|
46+
LL | let _ = include_str!("file.txt");
47+
| ~~~~~~~~~~
48+
49+
error: aborting due to 4 previous errors
50+

tests/ui/macros/macros-nonfatal-errors.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//@ normalize-stderr-test: "existed:.*\(" -> "existed: $$FILE_NOT_FOUND_MSG ("
1+
//@ normalize-stderr-test: "`: .*\(" -> "`: $$FILE_NOT_FOUND_MSG ("
22

33
// test that errors in a (selection) of macros don't kill compilation
44
// immediately, so that we get more errors listed at a time.

tests/ui/macros/macros-nonfatal-errors.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ error: argument must be a string literal
200200
LL | include_str!(invalid);
201201
| ^^^^^^^
202202

203-
error: couldn't read $DIR/i'd be quite surprised if a file with this name existed: $FILE_NOT_FOUND_MSG (os error 2)
203+
error: couldn't read `$DIR/i'd be quite surprised if a file with this name existed`: $FILE_NOT_FOUND_MSG (os error 2)
204204
--> $DIR/macros-nonfatal-errors.rs:113:5
205205
|
206206
LL | include_str!("i'd be quite surprised if a file with this name existed");
@@ -214,7 +214,7 @@ error: argument must be a string literal
214214
LL | include_bytes!(invalid);
215215
| ^^^^^^^
216216

217-
error: couldn't read $DIR/i'd be quite surprised if a file with this name existed: $FILE_NOT_FOUND_MSG (os error 2)
217+
error: couldn't read `$DIR/i'd be quite surprised if a file with this name existed`: $FILE_NOT_FOUND_MSG (os error 2)
218218
--> $DIR/macros-nonfatal-errors.rs:115:5
219219
|
220220
LL | include_bytes!("i'd be quite surprised if a file with this name existed");

0 commit comments

Comments
 (0)