@@ -3,18 +3,20 @@ use rustc_ast::ptr::P;
33use rustc_ast:: token;
44use rustc_ast:: tokenstream:: TokenStream ;
55use rustc_ast_pretty:: pprust;
6+ use rustc_data_structures:: sync:: Lrc ;
67use rustc_expand:: base:: {
7- check_zero_tts, get_single_str_from_tts, parse_expr , resolve_path , DummyResult , ExtCtxt ,
8- MacEager , MacResult ,
8+ check_zero_tts, get_single_str_from_tts, get_single_str_spanned_from_tts , parse_expr ,
9+ resolve_path , DummyResult , ExtCtxt , MacEager , MacResult ,
910} ;
1011use rustc_expand:: module:: DirOwnership ;
1112use rustc_parse:: new_parser_from_file;
1213use rustc_parse:: parser:: { ForceCollect , Parser } ;
1314use rustc_session:: lint:: builtin:: INCOMPLETE_INCLUDE ;
15+ use rustc_span:: source_map:: SourceMap ;
1416use rustc_span:: symbol:: Symbol ;
1517use rustc_span:: { Pos , Span } ;
16-
1718use smallvec:: SmallVec ;
19+ use std:: path:: { Path , PathBuf } ;
1820use std:: rc:: Rc ;
1921
2022// These macros all relate to the file system; they either return
@@ -180,32 +182,22 @@ pub fn expand_include_str(
180182 tts : TokenStream ,
181183) -> Box < dyn MacResult + ' static > {
182184 let sp = cx. with_def_site_ctxt ( sp) ;
183- let file = match get_single_str_from_tts ( cx, sp, tts, "include_str!" ) {
184- Ok ( file ) => file ,
185+ let ( path , path_span ) = match get_single_str_spanned_from_tts ( cx, sp, tts, "include_str!" ) {
186+ Ok ( res ) => res ,
185187 Err ( guar) => return DummyResult :: any ( sp, guar) ,
186188 } ;
187- let file = match resolve_path ( & cx. sess , file. as_str ( ) , sp) {
188- Ok ( f) => f,
189- Err ( err) => {
190- let guar = err. emit ( ) ;
191- return DummyResult :: any ( sp, guar) ;
192- }
193- } ;
194- match cx. source_map ( ) . load_binary_file ( & file) {
189+ match load_binary_file ( cx, path. as_str ( ) . as_ref ( ) , sp, path_span) {
195190 Ok ( bytes) => match std:: str:: from_utf8 ( & bytes) {
196191 Ok ( src) => {
197192 let interned_src = Symbol :: intern ( src) ;
198193 MacEager :: expr ( cx. expr_str ( sp, interned_src) )
199194 }
200195 Err ( _) => {
201- let guar = cx. dcx ( ) . span_err ( sp, format ! ( "{} wasn't a utf-8 file" , file . display ( ) ) ) ;
196+ let guar = cx. dcx ( ) . span_err ( sp, format ! ( "`{path}` wasn't a utf-8 file" ) ) ;
202197 DummyResult :: any ( sp, guar)
203198 }
204199 } ,
205- Err ( e) => {
206- let guar = cx. dcx ( ) . span_err ( sp, format ! ( "couldn't read {}: {}" , file. display( ) , e) ) ;
207- DummyResult :: any ( sp, guar)
208- }
200+ Err ( dummy) => dummy,
209201 }
210202}
211203
@@ -215,25 +207,123 @@ pub fn expand_include_bytes(
215207 tts : TokenStream ,
216208) -> Box < dyn MacResult + ' static > {
217209 let sp = cx. with_def_site_ctxt ( sp) ;
218- let file = match get_single_str_from_tts ( cx, sp, tts, "include_bytes!" ) {
219- Ok ( file ) => file ,
210+ let ( path , path_span ) = match get_single_str_spanned_from_tts ( cx, sp, tts, "include_bytes!" ) {
211+ Ok ( res ) => res ,
220212 Err ( guar) => return DummyResult :: any ( sp, guar) ,
221213 } ;
222- let file = match resolve_path ( & cx. sess , file. as_str ( ) , sp) {
223- Ok ( f) => f,
214+ match load_binary_file ( cx, path. as_str ( ) . as_ref ( ) , sp, path_span) {
215+ Ok ( bytes) => {
216+ let expr = cx. expr ( sp, ast:: ExprKind :: IncludedBytes ( bytes) ) ;
217+ MacEager :: expr ( expr)
218+ }
219+ Err ( dummy) => dummy,
220+ }
221+ }
222+
223+ fn load_binary_file (
224+ cx : & mut ExtCtxt < ' _ > ,
225+ original_path : & Path ,
226+ macro_span : Span ,
227+ path_span : Span ,
228+ ) -> Result < Lrc < [ u8 ] > , Box < dyn MacResult > > {
229+ let resolved_path = match resolve_path ( & cx. sess , original_path, macro_span) {
230+ Ok ( path) => path,
224231 Err ( err) => {
225232 let guar = err. emit ( ) ;
226- return DummyResult :: any ( sp , guar) ;
233+ return Err ( DummyResult :: any ( macro_span , guar) ) ;
227234 }
228235 } ;
229- match cx. source_map ( ) . load_binary_file ( & file) {
230- Ok ( bytes) => {
231- let expr = cx. expr ( sp, ast:: ExprKind :: IncludedBytes ( bytes) ) ;
232- MacEager :: expr ( expr)
236+ match cx. source_map ( ) . load_binary_file ( & resolved_path) {
237+ Ok ( data) => Ok ( data) ,
238+ Err ( io_err) => {
239+ let mut err = cx. dcx ( ) . struct_span_err (
240+ macro_span,
241+ format ! ( "couldn't read `{}`: {io_err}" , resolved_path. display( ) ) ,
242+ ) ;
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 with the same name in a different directory" ,
256+ format ! ( "\" {}\" " , new_path. escape_debug( ) ) ,
257+ rustc_lint_defs:: Applicability :: MachineApplicable ,
258+ ) ;
259+ }
260+ }
261+ let guar = err. emit ( ) ;
262+ Err ( DummyResult :: any ( macro_span, guar) )
233263 }
234- Err ( e) => {
235- let guar = cx. dcx ( ) . span_err ( sp, format ! ( "couldn't read {}: {}" , file. display( ) , e) ) ;
236- DummyResult :: any ( sp, guar)
264+ }
265+ }
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) ;
237326 }
238327 }
328+ None
239329}
0 commit comments