1- use std:: collections:: BTreeMap ;
1+ use std:: collections:: { BTreeMap , BTreeSet } ;
22use std:: fs:: { self , File } ;
33use std:: io:: Write ;
4+ use std:: ops:: Bound :: { Excluded , Unbounded } ;
45use std:: path:: { Path , PathBuf } ;
56use std:: str:: FromStr ;
67
@@ -107,7 +108,7 @@ pub fn emit_build_files<'lcmd>(
107108 } )
108109}
109110
110- #[ derive( Serialize ) ]
111+ #[ derive( Clone , Debug , Serialize ) ]
111112struct Module {
112113 path : Option < String > ,
113114 name : String ,
@@ -116,19 +117,23 @@ struct Module {
116117}
117118
118119#[ derive( Debug , Default ) ]
119- struct ModuleTree ( BTreeMap < String , ModuleTree > ) ;
120+ struct ModuleTree {
121+ children : BTreeMap < String , ModuleTree > ,
122+ path_modules : Vec < Module > ,
123+ }
120124
121125impl ModuleTree {
122126 /// Convert the tree representation into a linear vector
123127 /// and push it into `res`
124128 fn linearize ( & self , res : & mut Vec < Module > ) {
125- for ( name, child) in self . 0 . iter ( ) {
129+ res. extend ( self . path_modules . iter ( ) . cloned ( ) ) ;
130+ for ( name, child) in self . children . iter ( ) {
126131 child. linearize_internal ( name, res) ;
127132 }
128133 }
129134
130135 fn linearize_internal ( & self , name : & str , res : & mut Vec < Module > ) {
131- if self . 0 . is_empty ( ) {
136+ if self . path_modules . is_empty ( ) && self . children . is_empty ( ) {
132137 res. push ( Module {
133138 name : name. to_string ( ) ,
134139 path : None ,
@@ -151,6 +156,55 @@ impl ModuleTree {
151156 } ) ;
152157 }
153158 }
159+
160+ fn insert_nested ( & mut self , path : Vec < String > ) {
161+ let mut cur = self ;
162+ for name in path {
163+ cur = cur. children . entry ( name) . or_default ( ) ;
164+ }
165+ }
166+
167+ fn insert_path_module (
168+ & mut self ,
169+ parent_path : & [ String ] ,
170+ file_name : String ,
171+ module_name : String ,
172+ ) {
173+ let mut cur = self ;
174+ for name in parent_path {
175+ cur = cur. children . entry ( name. clone ( ) ) . or_default ( ) ;
176+ }
177+ let name = cur. unique_child_name ( & module_name) ;
178+ cur. path_modules . push ( Module {
179+ path : Some ( file_name) ,
180+ name,
181+ open : false ,
182+ close : false ,
183+ } ) ;
184+ }
185+
186+ fn unique_child_name ( & self , module_name : & str ) -> String {
187+ if !self . child_name_exists ( module_name) {
188+ return module_name. to_owned ( ) ;
189+ }
190+
191+ for i in 1 .. {
192+ let candidate = format ! ( "{module_name}_{i}" ) ;
193+ if !self . child_name_exists ( & candidate) {
194+ return candidate;
195+ }
196+ }
197+
198+ unreachable ! ( "We tried all the numbers and couldn't find one that didn't collide" )
199+ }
200+
201+ fn child_name_exists ( & self , module_name : & str ) -> bool {
202+ self . children . contains_key ( module_name)
203+ || self
204+ . path_modules
205+ . iter ( )
206+ . any ( |module| module. name == module_name)
207+ }
154208}
155209
156210#[ derive( Debug , PartialEq , Eq ) ]
@@ -182,17 +236,51 @@ fn convert_module_list(
182236 } ) ;
183237
184238 let mut res = vec ! [ ] ;
185- let mut module_tree = ModuleTree ( BTreeMap :: new ( ) ) ;
239+ let mut module_tree = ModuleTree :: default ( ) ;
240+ // A C file can have the same stem as a sibling directory, such as
241+ // `hash.c` and `hash/sha1.c`. The current nested module emitter cannot
242+ // represent both at `mod hash`, so emit the file module with an explicit
243+ // path and keep the directory in the nested tree.
244+ let internal_modules = modules
245+ . iter ( )
246+ . filter_map ( |m| {
247+ if tcfg. is_binary ( m) {
248+ return None ;
249+ }
250+ let relpath = m. strip_prefix ( build_dir) . ok ( ) ?;
251+ let path = module_path ( relpath) ;
252+ Some ( ( m, path) )
253+ } )
254+ . collect :: < Vec < _ > > ( ) ;
255+ // A module collides when another module's path strictly extends it, i.e. a
256+ // file whose stem matches a sibling directory (`hash.c` next to `hash/`).
257+ // Every strict extension of a path sorts immediately after it, so checking
258+ // each path's immediate successor in the sorted set settles it without
259+ // comparing every pair.
260+ let module_paths = internal_modules
261+ . iter ( )
262+ . map ( |( _, path) | path. clone ( ) )
263+ . collect :: < BTreeSet < _ > > ( ) ;
264+ let collision_modules = internal_modules
265+ . iter ( )
266+ . filter ( |( _, path) | {
267+ module_paths
268+ . range :: < Vec < String > , _ > ( ( Excluded ( path) , Unbounded ) )
269+ . next ( )
270+ . is_some_and ( |next| next. len ( ) > path. len ( ) && next. starts_with ( path) )
271+ } )
272+ . map ( |( module, _) | * module)
273+ . collect :: < BTreeSet < _ > > ( ) ;
274+
275+ let mut collision_relpaths = vec ! [ ] ;
186276 for m in & modules {
187277 match m. strip_prefix ( build_dir) {
188- Ok ( relpath) if !tcfg. is_binary ( m) => {
278+ Ok ( relpath) if !tcfg. is_binary ( m) && !collision_modules . contains ( m ) => {
189279 // The module is inside the build directory, use nested modules
190- let mut cur = & mut module_tree;
191- for sm in relpath. iter ( ) {
192- let path = Path :: new ( sm) ;
193- let name = get_module_name ( path, true , false , false ) . unwrap ( ) ;
194- cur = cur. 0 . entry ( name) . or_default ( ) ;
195- }
280+ module_tree. insert_nested ( module_path ( relpath) ) ;
281+ }
282+ Ok ( relpath) if !tcfg. is_binary ( m) => {
283+ collision_relpaths. push ( relpath. to_path_buf ( ) ) ;
196284 }
197285 _ => {
198286 let relpath = diff_paths ( m, build_dir) . unwrap ( ) ;
@@ -207,10 +295,28 @@ fn convert_module_list(
207295 }
208296 }
209297 }
298+ for relpath in collision_relpaths {
299+ let mut path = module_path ( & relpath) ;
300+ // Insert the shadowed file as an explicit `#[path]` module in its
301+ // parent. `insert_path_module` renames it if the stem collides with the
302+ // sibling directory (or another module), so pass the plain stem.
303+ let module_name = path. pop ( ) . unwrap ( ) ;
304+ let file_name = relpath. file_name ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ;
305+ module_tree. insert_path_module ( & path, file_name, module_name) ;
306+ }
210307 module_tree. linearize ( & mut res) ;
211308 res
212309}
213310
311+ fn module_path ( path : & Path ) -> Vec < String > {
312+ path. iter ( )
313+ . map ( |component| {
314+ let path = Path :: new ( component) ;
315+ get_module_name ( path, true , false , false ) . unwrap ( )
316+ } )
317+ . collect ( )
318+ }
319+
214320fn emit_thin_binaries (
215321 tcfg : & TranspilerConfig ,
216322 build_dir : & Path ,
0 commit comments