@@ -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. 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