@@ -12,10 +12,11 @@ use rustc_expand::module::DirOwnership;
1212use rustc_parse:: new_parser_from_file;
1313use rustc_parse:: parser:: { ForceCollect , Parser } ;
1414use rustc_session:: lint:: builtin:: INCOMPLETE_INCLUDE ;
15+ use rustc_span:: source_map:: SourceMap ;
1516use rustc_span:: symbol:: Symbol ;
1617use rustc_span:: { Pos , Span } ;
1718use smallvec:: SmallVec ;
18- use std:: path:: Path ;
19+ use std:: path:: { Path , PathBuf } ;
1920use std:: rc:: Rc ;
2021
2122// These macros all relate to the file system; they either return
@@ -185,7 +186,7 @@ pub fn expand_include_str(
185186 Ok ( res) => res,
186187 Err ( guar) => return DummyResult :: any ( sp, guar) ,
187188 } ;
188- match load_binary_file ( cx, path. as_str ( ) , sp, path_span) {
189+ match load_binary_file ( cx, path. as_str ( ) . as_ref ( ) , sp, path_span) {
189190 Ok ( bytes) => match std:: str:: from_utf8 ( & bytes) {
190191 Ok ( src) => {
191192 let interned_src = Symbol :: intern ( src) ;
@@ -210,7 +211,7 @@ pub fn expand_include_bytes(
210211 Ok ( res) => res,
211212 Err ( guar) => return DummyResult :: any ( sp, guar) ,
212213 } ;
213- match load_binary_file ( cx, path. as_str ( ) , sp, path_span) {
214+ match load_binary_file ( cx, path. as_str ( ) . as_ref ( ) , sp, path_span) {
214215 Ok ( bytes) => {
215216 let expr = cx. expr ( sp, ast:: ExprKind :: IncludedBytes ( bytes) ) ;
216217 MacEager :: expr ( expr)
@@ -221,7 +222,7 @@ pub fn expand_include_bytes(
221222
222223fn load_binary_file (
223224 cx : & mut ExtCtxt < ' _ > ,
224- original_path : & str ,
225+ original_path : & Path ,
225226 macro_span : Span ,
226227 path_span : Span ,
227228) -> Result < Lrc < [ u8 ] > , Box < dyn MacResult > > {
@@ -239,24 +240,90 @@ fn load_binary_file(
239240 macro_span,
240241 format ! ( "couldn't read {}: {io_err}" , resolved_path. display( ) ) ,
241242 ) ;
242- if Path :: new ( original_path) . is_relative ( ) {
243- for prefix in [ ".." , "../.." ] {
244- let parent_path = Path :: new ( prefix) . join ( original_path) ;
245- if resolve_path ( & cx. sess , & parent_path, macro_span)
246- . map_or ( false , |p| p. exists ( ) )
247- {
248- err. span_suggestion (
249- path_span,
250- "it's in a parent directory" ,
251- format ! ( "\" {}\" " , parent_path. display( ) . to_string( ) . escape_debug( ) ) ,
252- rustc_lint_defs:: Applicability :: MachineApplicable ,
253- ) ;
254- break ;
255- }
243+
244+ if original_path. is_relative ( ) {
245+ let source_map = cx. sess . source_map ( ) ;
246+ let new_path = source_map
247+ . span_to_filename ( macro_span. source_callsite ( ) )
248+ . into_local_path ( )
249+ . and_then ( |src| find_path_suggestion ( source_map, src. parent ( ) ?, original_path) )
250+ . and_then ( |path| path. into_os_string ( ) . into_string ( ) . ok ( ) ) ;
251+
252+ if let Some ( new_path) = new_path {
253+ err. span_suggestion (
254+ path_span,
255+ "there is a file in another directory" ,
256+ format ! ( "\" {}\" " , new_path. escape_debug( ) ) ,
257+ rustc_lint_defs:: Applicability :: MachineApplicable ,
258+ ) ;
256259 }
257260 }
258261 let guar = err. emit ( ) ;
259262 Err ( DummyResult :: any ( macro_span, guar) )
260263 }
261264 }
262265}
266+
267+ fn find_path_suggestion (
268+ source_map : & SourceMap ,
269+ base_dir : & Path ,
270+ wanted_path : & Path ,
271+ ) -> Option < PathBuf > {
272+ // Fix paths that assume they're relative to cargo manifest dir
273+ let mut base_c = base_dir. components ( ) ;
274+ let mut wanted_c = wanted_path. components ( ) ;
275+ let mut without_base = None ;
276+ while let Some ( wanted_next) = wanted_c. next ( ) {
277+ if wanted_c. as_path ( ) . file_name ( ) . is_none ( ) {
278+ break ;
279+ }
280+ // base_dir may be absolute
281+ while let Some ( base_next) = base_c. next ( ) {
282+ if base_next == wanted_next {
283+ without_base = Some ( wanted_c. as_path ( ) ) ;
284+ break ;
285+ }
286+ }
287+ }
288+ let root_absolute = without_base. into_iter ( ) . map ( PathBuf :: from) ;
289+
290+ let base_dir_components = base_dir. components ( ) . count ( ) ;
291+ // Avoid going all the way to the root dir
292+ let max_parent_components = if base_dir. is_relative ( ) {
293+ base_dir_components + 1
294+ } else {
295+ base_dir_components. saturating_sub ( 1 )
296+ } ;
297+
298+ // Try with additional leading ../
299+ let mut prefix = PathBuf :: new ( ) ;
300+ let add = std:: iter:: from_fn ( || {
301+ prefix. push ( ".." ) ;
302+ Some ( prefix. join ( wanted_path) )
303+ } )
304+ . take ( max_parent_components. min ( 3 ) ) ;
305+
306+ // Try without leading directories
307+ let mut trimmed_path = wanted_path;
308+ let remove = std:: iter:: from_fn ( || {
309+ let mut components = trimmed_path. components ( ) ;
310+ let removed = components. next ( ) ?;
311+ trimmed_path = components. as_path ( ) ;
312+ let _ = trimmed_path. file_name ( ) ?; // ensure there is a file name left
313+ Some ( [
314+ Some ( trimmed_path. to_path_buf ( ) ) ,
315+ ( removed != std:: path:: Component :: ParentDir )
316+ . then ( || Path :: new ( ".." ) . join ( trimmed_path) ) ,
317+ ] )
318+ } )
319+ . flatten ( )
320+ . flatten ( )
321+ . take ( 4 ) ;
322+
323+ for new_path in root_absolute. chain ( add) . chain ( remove) {
324+ if source_map. file_exists ( & base_dir. join ( & new_path) ) {
325+ return Some ( new_path) ;
326+ }
327+ }
328+ None
329+ }
0 commit comments