Suggest correct path in include_bytes!
This commit is contained in:
parent
89ceced6f6
commit
826ddb3018
6 changed files with 209 additions and 37 deletions
|
@ -3,17 +3,22 @@ use rustc_ast::ptr::P;
|
||||||
use rustc_ast::token;
|
use rustc_ast::token;
|
||||||
use rustc_ast::tokenstream::TokenStream;
|
use rustc_ast::tokenstream::TokenStream;
|
||||||
use rustc_ast_pretty::pprust;
|
use rustc_ast_pretty::pprust;
|
||||||
use rustc_expand::base::{check_zero_tts, get_single_str_from_tts, parse_expr, resolve_path};
|
use rustc_data_structures::sync::Lrc;
|
||||||
|
use rustc_expand::base::{
|
||||||
|
check_zero_tts, get_single_str_from_tts, get_single_str_spanned_from_tts, parse_expr,
|
||||||
|
resolve_path,
|
||||||
|
};
|
||||||
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt};
|
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt};
|
||||||
use rustc_expand::base::{MacEager, MacResult, MacroExpanderResult};
|
use rustc_expand::base::{MacEager, MacResult, MacroExpanderResult};
|
||||||
use rustc_expand::module::DirOwnership;
|
use rustc_expand::module::DirOwnership;
|
||||||
use rustc_parse::new_parser_from_file;
|
use rustc_parse::new_parser_from_file;
|
||||||
use rustc_parse::parser::{ForceCollect, Parser};
|
use rustc_parse::parser::{ForceCollect, Parser};
|
||||||
use rustc_session::lint::builtin::INCOMPLETE_INCLUDE;
|
use rustc_session::lint::builtin::INCOMPLETE_INCLUDE;
|
||||||
|
use rustc_span::source_map::SourceMap;
|
||||||
use rustc_span::symbol::Symbol;
|
use rustc_span::symbol::Symbol;
|
||||||
use rustc_span::{Pos, Span};
|
use rustc_span::{Pos, Span};
|
||||||
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
// These macros all relate to the file system; they either return
|
// These macros all relate to the file system; they either return
|
||||||
|
@ -182,35 +187,26 @@ pub fn expand_include_str(
|
||||||
tts: TokenStream,
|
tts: TokenStream,
|
||||||
) -> MacroExpanderResult<'static> {
|
) -> MacroExpanderResult<'static> {
|
||||||
let sp = cx.with_def_site_ctxt(sp);
|
let sp = cx.with_def_site_ctxt(sp);
|
||||||
let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "include_str!") else {
|
let ExpandResult::Ready(mac) = get_single_str_spanned_from_tts(cx, sp, tts, "include_str!")
|
||||||
|
else {
|
||||||
return ExpandResult::Retry(());
|
return ExpandResult::Retry(());
|
||||||
};
|
};
|
||||||
let file = match mac {
|
let (path, path_span) = match mac {
|
||||||
Ok(file) => file,
|
Ok(res) => res,
|
||||||
Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
|
Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
|
||||||
};
|
};
|
||||||
let file = match resolve_path(&cx.sess, file.as_str(), sp) {
|
ExpandResult::Ready(match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) {
|
||||||
Ok(f) => f,
|
|
||||||
Err(err) => {
|
|
||||||
let guar = err.emit();
|
|
||||||
return ExpandResult::Ready(DummyResult::any(sp, guar));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ExpandResult::Ready(match cx.source_map().load_binary_file(&file) {
|
|
||||||
Ok(bytes) => match std::str::from_utf8(&bytes) {
|
Ok(bytes) => match std::str::from_utf8(&bytes) {
|
||||||
Ok(src) => {
|
Ok(src) => {
|
||||||
let interned_src = Symbol::intern(src);
|
let interned_src = Symbol::intern(src);
|
||||||
MacEager::expr(cx.expr_str(sp, interned_src))
|
MacEager::expr(cx.expr_str(sp, interned_src))
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let guar = cx.dcx().span_err(sp, format!("{} wasn't a utf-8 file", file.display()));
|
let guar = cx.dcx().span_err(sp, format!("`{path}` wasn't a utf-8 file"));
|
||||||
DummyResult::any(sp, guar)
|
DummyResult::any(sp, guar)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(dummy) => dummy,
|
||||||
let guar = cx.dcx().span_err(sp, format!("couldn't read {}: {}", file.display(), e));
|
|
||||||
DummyResult::any(sp, guar)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,28 +216,127 @@ pub fn expand_include_bytes(
|
||||||
tts: TokenStream,
|
tts: TokenStream,
|
||||||
) -> MacroExpanderResult<'static> {
|
) -> MacroExpanderResult<'static> {
|
||||||
let sp = cx.with_def_site_ctxt(sp);
|
let sp = cx.with_def_site_ctxt(sp);
|
||||||
let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "include_bytes!") else {
|
let ExpandResult::Ready(mac) = get_single_str_spanned_from_tts(cx, sp, tts, "include_bytes!")
|
||||||
|
else {
|
||||||
return ExpandResult::Retry(());
|
return ExpandResult::Retry(());
|
||||||
};
|
};
|
||||||
let file = match mac {
|
let (path, path_span) = match mac {
|
||||||
Ok(file) => file,
|
Ok(res) => res,
|
||||||
Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
|
Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
|
||||||
};
|
};
|
||||||
let file = match resolve_path(&cx.sess, file.as_str(), sp) {
|
ExpandResult::Ready(match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) {
|
||||||
Ok(f) => f,
|
|
||||||
Err(err) => {
|
|
||||||
let guar = err.emit();
|
|
||||||
return ExpandResult::Ready(DummyResult::any(sp, guar));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ExpandResult::Ready(match cx.source_map().load_binary_file(&file) {
|
|
||||||
Ok(bytes) => {
|
Ok(bytes) => {
|
||||||
let expr = cx.expr(sp, ast::ExprKind::IncludedBytes(bytes));
|
let expr = cx.expr(sp, ast::ExprKind::IncludedBytes(bytes));
|
||||||
MacEager::expr(expr)
|
MacEager::expr(expr)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(dummy) => dummy,
|
||||||
let guar = cx.dcx().span_err(sp, format!("couldn't read {}: {}", file.display(), e));
|
|
||||||
DummyResult::any(sp, guar)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_binary_file(
|
||||||
|
cx: &mut ExtCtxt<'_>,
|
||||||
|
original_path: &Path,
|
||||||
|
macro_span: Span,
|
||||||
|
path_span: Span,
|
||||||
|
) -> Result<Lrc<[u8]>, Box<dyn MacResult>> {
|
||||||
|
let resolved_path = match resolve_path(&cx.sess, original_path, macro_span) {
|
||||||
|
Ok(path) => path,
|
||||||
|
Err(err) => {
|
||||||
|
let guar = err.emit();
|
||||||
|
return Err(DummyResult::any(macro_span, guar));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match cx.source_map().load_binary_file(&resolved_path) {
|
||||||
|
Ok(data) => Ok(data),
|
||||||
|
Err(io_err) => {
|
||||||
|
let mut err = cx.dcx().struct_span_err(
|
||||||
|
macro_span,
|
||||||
|
format!("couldn't read `{}`: {io_err}", resolved_path.display()),
|
||||||
|
);
|
||||||
|
|
||||||
|
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 with the same name in a different directory",
|
||||||
|
format!("\"{}\"", new_path.replace('\\', "/").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
|
||||||
|
}
|
||||||
|
|
|
@ -1376,6 +1376,15 @@ pub fn get_single_str_from_tts(
|
||||||
tts: TokenStream,
|
tts: TokenStream,
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> ExpandResult<Result<Symbol, ErrorGuaranteed>, ()> {
|
) -> ExpandResult<Result<Symbol, ErrorGuaranteed>, ()> {
|
||||||
|
get_single_str_spanned_from_tts(cx, span, tts, name).map(|res| res.map(|(s, _)| s))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_single_str_spanned_from_tts(
|
||||||
|
cx: &mut ExtCtxt<'_>,
|
||||||
|
span: Span,
|
||||||
|
tts: TokenStream,
|
||||||
|
name: &str,
|
||||||
|
) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> {
|
||||||
let mut p = cx.new_parser_from_tts(tts);
|
let mut p = cx.new_parser_from_tts(tts);
|
||||||
if p.token == token::Eof {
|
if p.token == token::Eof {
|
||||||
let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
|
let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
|
||||||
|
@ -1390,7 +1399,13 @@ pub fn get_single_str_from_tts(
|
||||||
if p.token != token::Eof {
|
if p.token != token::Eof {
|
||||||
cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
|
cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
|
||||||
}
|
}
|
||||||
expr_to_string(cx, ret, "argument must be a string literal").map(|s| s.map(|(s, _)| s))
|
expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| {
|
||||||
|
res.map_err(|err| match err {
|
||||||
|
Ok((err, _)) => err.emit(),
|
||||||
|
Err(guar) => guar,
|
||||||
|
})
|
||||||
|
.map(|(symbol, _style, span)| (symbol, span))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts comma-separated expressions from `tts`.
|
/// Extracts comma-separated expressions from `tts`.
|
||||||
|
|
12
tests/ui/include-macros/parent_dir.rs
Normal file
12
tests/ui/include-macros/parent_dir.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
//@ normalize-stderr-test: "`: .*" -> "`: $$FILE_NOT_FOUND_MSG"
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let _ = include_str!("include-macros/file.txt"); //~ ERROR couldn't read
|
||||||
|
//~^HELP different directory
|
||||||
|
let _ = include_str!("hello.rs"); //~ ERROR couldn't read
|
||||||
|
//~^HELP different directory
|
||||||
|
let _ = include_bytes!("../../data.bin"); //~ ERROR couldn't read
|
||||||
|
//~^HELP different directory
|
||||||
|
let _ = include_str!("tests/ui/include-macros/file.txt"); //~ ERROR couldn't read
|
||||||
|
//~^HELP different directory
|
||||||
|
}
|
50
tests/ui/include-macros/parent_dir.stderr
Normal file
50
tests/ui/include-macros/parent_dir.stderr
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
error: couldn't read `$DIR/include-macros/file.txt`: $FILE_NOT_FOUND_MSG
|
||||||
|
--> $DIR/parent_dir.rs:4:13
|
||||||
|
|
|
||||||
|
LL | let _ = include_str!("include-macros/file.txt");
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in the macro `include_str` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
help: there is a file with the same name in a different directory
|
||||||
|
|
|
||||||
|
LL | let _ = include_str!("file.txt");
|
||||||
|
| ~~~~~~~~~~
|
||||||
|
|
||||||
|
error: couldn't read `$DIR/hello.rs`: $FILE_NOT_FOUND_MSG
|
||||||
|
--> $DIR/parent_dir.rs:6:13
|
||||||
|
|
|
||||||
|
LL | let _ = include_str!("hello.rs");
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in the macro `include_str` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
help: there is a file with the same name in a different directory
|
||||||
|
|
|
||||||
|
LL | let _ = include_str!("../hello.rs");
|
||||||
|
| ~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
error: couldn't read `$DIR/../../data.bin`: $FILE_NOT_FOUND_MSG
|
||||||
|
--> $DIR/parent_dir.rs:8:13
|
||||||
|
|
|
||||||
|
LL | let _ = include_bytes!("../../data.bin");
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in the macro `include_bytes` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
help: there is a file with the same name in a different directory
|
||||||
|
|
|
||||||
|
LL | let _ = include_bytes!("data.bin");
|
||||||
|
| ~~~~~~~~~~
|
||||||
|
|
||||||
|
error: couldn't read `$DIR/tests/ui/include-macros/file.txt`: $FILE_NOT_FOUND_MSG
|
||||||
|
--> $DIR/parent_dir.rs:10:13
|
||||||
|
|
|
||||||
|
LL | let _ = include_str!("tests/ui/include-macros/file.txt");
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in the macro `include_str` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
help: there is a file with the same name in a different directory
|
||||||
|
|
|
||||||
|
LL | let _ = include_str!("file.txt");
|
||||||
|
| ~~~~~~~~~~
|
||||||
|
|
||||||
|
error: aborting due to 4 previous errors
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//@ normalize-stderr-test: "existed:.*\(" -> "existed: $$FILE_NOT_FOUND_MSG ("
|
//@ normalize-stderr-test: "`: .*" -> "`: $$FILE_NOT_FOUND_MSG"
|
||||||
|
|
||||||
// test that errors in a (selection) of macros don't kill compilation
|
// test that errors in a (selection) of macros don't kill compilation
|
||||||
// immediately, so that we get more errors listed at a time.
|
// immediately, so that we get more errors listed at a time.
|
||||||
|
|
|
@ -200,7 +200,7 @@ error: argument must be a string literal
|
||||||
LL | include_str!(invalid);
|
LL | include_str!(invalid);
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
|
|
||||||
error: couldn't read $DIR/i'd be quite surprised if a file with this name existed: $FILE_NOT_FOUND_MSG (os error 2)
|
error: couldn't read `$DIR/i'd be quite surprised if a file with this name existed`: $FILE_NOT_FOUND_MSG
|
||||||
--> $DIR/macros-nonfatal-errors.rs:113:5
|
--> $DIR/macros-nonfatal-errors.rs:113:5
|
||||||
|
|
|
|
||||||
LL | include_str!("i'd be quite surprised if a file with this name existed");
|
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
|
||||||
LL | include_bytes!(invalid);
|
LL | include_bytes!(invalid);
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
|
|
||||||
error: couldn't read $DIR/i'd be quite surprised if a file with this name existed: $FILE_NOT_FOUND_MSG (os error 2)
|
error: couldn't read `$DIR/i'd be quite surprised if a file with this name existed`: $FILE_NOT_FOUND_MSG
|
||||||
--> $DIR/macros-nonfatal-errors.rs:115:5
|
--> $DIR/macros-nonfatal-errors.rs:115:5
|
||||||
|
|
|
|
||||||
LL | include_bytes!("i'd be quite surprised if a file with this name existed");
|
LL | include_bytes!("i'd be quite surprised if a file with this name existed");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue