@@ -3,17 +3,22 @@ use rustc_ast::ptr::P;
3
3
use rustc_ast::token;
4
4
use rustc_ast::tokenstream::TokenStream;
5
5
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
+ };
7
11
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt};
8
12
use rustc_expand::base::{MacEager, MacResult, MacroExpanderResult};
9
13
use rustc_expand::module::DirOwnership;
10
14
use rustc_parse::new_parser_from_file;
11
15
use rustc_parse::parser::{ForceCollect, Parser};
12
16
use rustc_session::lint::builtin::INCOMPLETE_INCLUDE;
17
+ use rustc_span::source_map::SourceMap;
13
18
use rustc_span::symbol::Symbol;
14
19
use rustc_span::{Pos, Span};
15
-
16
20
use smallvec::SmallVec;
21
+ use std::path::{Path, PathBuf};
17
22
use std::rc::Rc;
18
23
19
24
// These macros all relate to the file system; they either return
@@ -182,35 +187,26 @@ pub fn expand_include_str(
182
187
tts: TokenStream,
183
188
) -> MacroExpanderResult<'static> {
184
189
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 {
186
192
return ExpandResult::Retry(());
187
193
};
188
- let file = match mac {
189
- Ok(file ) => file ,
194
+ let (path, path_span) = match mac {
195
+ Ok(res ) => res ,
190
196
Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
191
197
};
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) {
200
199
Ok(bytes) => match std::str::from_utf8(&bytes) {
201
200
Ok(src) => {
202
201
let interned_src = Symbol::intern(src);
203
202
MacEager::expr(cx.expr_str(sp, interned_src))
204
203
}
205
204
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"));
207
206
DummyResult::any(sp, guar)
208
207
}
209
208
},
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,
214
210
})
215
211
}
216
212
@@ -220,28 +216,127 @@ pub fn expand_include_bytes(
220
216
tts: TokenStream,
221
217
) -> MacroExpanderResult<'static> {
222
218
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 {
224
221
return ExpandResult::Retry(());
225
222
};
226
- let file = match mac {
227
- Ok(file ) => file ,
223
+ let (path, path_span) = match mac {
224
+ Ok(res ) => res ,
228
225
Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
229
226
};
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,
232
244
Err(err) => {
233
245
let guar = err.emit();
234
- return ExpandResult::Ready (DummyResult::any(sp , guar));
246
+ return Err (DummyResult::any(macro_span , guar));
235
247
}
236
248
};
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.replace('\\', "/").escape_debug()),
270
+ rustc_lint_defs::Applicability::MachineApplicable,
271
+ );
272
+ }
273
+ }
274
+ let guar = err.emit();
275
+ Err(DummyResult::any(macro_span, guar))
241
276
}
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;
245
292
}
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))
246
316
})
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
247
342
}
0 commit comments