@@ -3,17 +3,22 @@ use rustc_ast::ptr::P;
33use rustc_ast:: token;
44use rustc_ast:: tokenstream:: TokenStream ;
55use 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+ } ;
711use rustc_expand:: base:: { DummyResult , ExpandResult , ExtCtxt } ;
812use rustc_expand:: base:: { MacEager , MacResult , MacroExpanderResult } ;
913use rustc_expand:: module:: DirOwnership ;
1014use rustc_parse:: new_parser_from_file;
1115use rustc_parse:: parser:: { ForceCollect , Parser } ;
1216use rustc_session:: lint:: builtin:: INCOMPLETE_INCLUDE ;
17+ use rustc_span:: source_map:: SourceMap ;
1318use rustc_span:: symbol:: Symbol ;
1419use rustc_span:: { Pos , Span } ;
15-
1620use smallvec:: SmallVec ;
21+ use std:: path:: { Path , PathBuf } ;
1722use 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. replace( '\\' , "/" ) . 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}
0 commit comments