@@ -4,7 +4,7 @@ use std::collections::{HashSet, VecDeque};
44use std:: error:: Error ;
55use std:: fs:: File ;
66use std:: io:: Read ;
7- use std:: path:: PathBuf ;
7+ use std:: path:: { Path , PathBuf } ;
88
99use syn:: ext:: IdentExt ;
1010use syn:: parse_file;
@@ -16,6 +16,7 @@ pub struct RustImports {
1616 pub imports : Vec < String > ,
1717 pub test_imports : Vec < String > ,
1818 pub extern_mods : Vec < String > ,
19+ pub compile_data : Vec < String > ,
1920}
2021
2122#[ derive( Debug , Default ) ]
@@ -44,15 +45,16 @@ pub fn parse_imports(
4445 let mut contents = String :: new ( ) ;
4546 file. read_to_string ( & mut contents) ?;
4647
47- parse_imports_from_str ( & contents, enabled_features)
48+ parse_imports_from_str ( & contents, enabled_features, path )
4849}
4950
5051pub fn parse_imports_from_str (
5152 contents : & str ,
5253 enabled_features : & [ String ] ,
54+ path : PathBuf ,
5355) -> Result < RustImports , Box < dyn Error > > {
5456 let ast = parse_file ( contents) ?;
55- let mut visitor = AstVisitor :: new ( enabled_features) ;
57+ let mut visitor = AstVisitor :: new ( enabled_features, path ) ;
5658 visitor. visit_file ( & ast) ;
5759
5860 let mut root_scope = visitor. mod_stack . pop_back ( ) . expect ( "no root scope" ) ;
@@ -70,7 +72,8 @@ pub fn parse_imports_from_str(
7072 hints : visitor. hints ,
7173 imports : filter_imports ( root_scope. imports ) ,
7274 test_imports : filter_imports ( root_scope. test_imports ) ,
73- extern_mods : visitor. extern_mods ,
75+ extern_mods : visitor. extern_mods . into_iter ( ) . collect ( ) ,
76+ compile_data : visitor. compile_data . into_iter ( ) . collect ( ) ,
7477 } )
7578}
7679
@@ -160,33 +163,39 @@ impl Scope<'_> {
160163
161164#[ derive( Debug ) ]
162165struct AstVisitor < ' ast > {
166+ /// The path of the file we are visiting.
167+ this_file : PathBuf ,
163168 /// stack of mods in scope
164169 mod_stack : VecDeque < Scope < ' ast > > ,
165170 /// all mods that are currently in scope (including parent scopes)
166171 scope_mods : HashSet < Ident < ' ast > > ,
167172 /// collected hints
168173 hints : Hints ,
169174 /// bare mods defined in external files
170- extern_mods : Vec < String > ,
175+ extern_mods : HashSet < String > ,
171176 /// mods that are disallowed from being added to the current scope; this is currently only used
172177 /// for a hack, see below
173178 mod_denylist : HashSet < Ident < ' ast > > ,
174179 /// Enabled features
175180 enabled_features : HashSet < String > ,
181+ /// Files that are included via include_str! and include_bytes! macros.
182+ compile_data : HashSet < String > ,
176183}
177184
178185impl AstVisitor < ' _ > {
179- fn new ( enabled_features : & [ String ] ) -> Self {
186+ fn new ( enabled_features : & [ String ] , path : PathBuf ) -> Self {
180187 let mut mod_stack = VecDeque :: new ( ) ;
181188 mod_stack. push_back ( Scope :: default ( ) ) ;
182189
183190 Self {
191+ this_file : path,
184192 mod_stack,
185193 scope_mods : HashSet :: default ( ) ,
186194 hints : Hints :: default ( ) ,
187- extern_mods : Vec :: default ( ) ,
195+ extern_mods : HashSet :: new ( ) ,
188196 mod_denylist : HashSet :: new ( ) ,
189197 enabled_features : enabled_features. iter ( ) . cloned ( ) . collect ( ) ,
198+ compile_data : HashSet :: new ( ) ,
190199 }
191200 }
192201}
@@ -535,7 +544,7 @@ impl<'ast> Visit<'ast> for AstVisitor<'ast> {
535544
536545 if self . is_root_scope ( ) && node. content . is_none ( ) {
537546 // this mod is defined in a different file
538- self . extern_mods . push ( node. ident . unraw ( ) . to_string ( ) ) ;
547+ self . extern_mods . insert ( node. ident . unraw ( ) . to_string ( ) ) ;
539548 }
540549
541550 self . add_mod ( & node. ident ) ;
@@ -605,6 +614,48 @@ impl<'ast> Visit<'ast> for AstVisitor<'ast> {
605614 }
606615 visit:: visit_item_macro ( self , node) ;
607616 }
617+
618+ fn visit_expr_macro ( & mut self , node : & ' ast syn:: ExprMacro ) {
619+ let macro_ident = node. mac . path . get_ident ( ) ;
620+
621+ if let Some ( ident) = macro_ident {
622+ if ident == "include_str" || ident == "include_bytes" {
623+ // Respect ignored scopes
624+ if self . is_ignored_scope ( ) {
625+ return ;
626+ }
627+
628+ // Parse macro input: expect a single string literal
629+ if let Ok ( syn:: Expr :: Lit ( syn:: ExprLit {
630+ lit : syn:: Lit :: Str ( lit) ,
631+ ..
632+ } ) ) = syn:: parse2 :: < syn:: Expr > ( node. mac . tokens . clone ( ) )
633+ {
634+ let included_path = PathBuf :: from ( lit. value ( ) ) ;
635+
636+ if included_path. is_absolute ( ) {
637+ panic ! (
638+ "included paths must not be absolute: {}" ,
639+ included_path. display( )
640+ )
641+ } else {
642+ let dir = self
643+ . this_file
644+ . parent ( )
645+ . unwrap_or_else ( || Path :: new ( "" ) )
646+ . to_path_buf ( ) ;
647+ let combined = dir. join ( included_path) ;
648+ match normalize_path ( & combined) . to_str ( ) {
649+ None => panic ! ( "Invalid unicode in the path: {}" , combined. display( ) ) ,
650+ Some ( x) => self . compile_data . insert ( x. to_string ( ) ) ,
651+ } ;
652+ }
653+ }
654+ }
655+ }
656+
657+ visit:: visit_expr_macro ( self , node) ;
658+ }
608659}
609660
610661fn parse_use_imports < ' ast > ( use_tree : & ' ast syn:: UseTree , imports : & mut HashSet < Ident < ' ast > > ) {
@@ -623,3 +674,30 @@ fn parse_use_imports<'ast>(use_tree: &'ast syn::UseTree, imports: &mut HashSet<I
623674 _ => ( ) ,
624675 }
625676}
677+
678+ /// Normalize a path by resolving `.` and `..` without touching the filesystem
679+ fn normalize_path ( path : & Path ) -> PathBuf {
680+ // let components = path.components().peekable();
681+ let mut stack = Vec :: new ( ) ;
682+
683+ for component in path. components ( ) {
684+ match component {
685+ std:: path:: Component :: ParentDir => {
686+ if let Some ( last) = stack. last ( ) {
687+ // Only pop if last is a normal component, not RootDir
688+ if matches ! ( last, std:: path:: Component :: Normal ( _) ) {
689+ stack. pop ( ) ;
690+ } else {
691+ stack. push ( component) ;
692+ }
693+ } else {
694+ stack. push ( component) ;
695+ }
696+ }
697+ std:: path:: Component :: CurDir => { /* skip `.` */ }
698+ _ => stack. push ( component) ,
699+ }
700+ }
701+
702+ stack. iter ( ) . collect ( )
703+ }
0 commit comments