@@ -510,6 +510,7 @@ def compile_file_detailed(
510510 type_map = type_checker .node_types
511511
512512 if tc_ok :
513+ self ._lower_file_module_bindings (ast , symbol_table )
513514 validator = SemanticValidationPass (
514515 symbol_table , type_checker .node_types
515516 )
@@ -1019,32 +1020,90 @@ def _combined_program_for_file_modules(
10191020 module_resolver : Any ,
10201021 ) -> ASTNode :
10211022 module_decls : list [ASTNode ] = []
1022- seen_paths : set [str ] = set ()
1023- for module_info in loaded_modules :
1024- if module_info .ast is None or module_resolver .is_virtual_module (module_info .path ):
1025- continue
1026- # Key on the resolved file path, not the import spelling, so one
1027- # file loaded under two spellings still merges once. Same-file
1028- # double imports are rejected earlier as a compile error.
1029- if module_info .file_path in seen_paths :
1023+ files = [(info .ast , info .file_path ) for info in loaded_modules
1024+ if info .ast is not None and not module_resolver .is_virtual_module (info .path )]
1025+ files .append ((ast , str (module_resolver .entry_root / "<entry>" )))
1026+ written_names = set ()
1027+ work = [program for program , _ in files ]
1028+ while work :
1029+ node = work .pop ()
1030+ if node .name :
1031+ written_names .add (node .name )
1032+ for value in vars (node ).values ():
1033+ if isinstance (value , ASTNode ):
1034+ work .append (value )
1035+ elif isinstance (value , list ):
1036+ work .extend (child for child in value if isinstance (child , ASTNode ))
1037+ seen_paths = set ()
1038+ for index , (program , path ) in enumerate (files ):
1039+ if path in seen_paths :
10301040 continue
1031- seen_paths .add (module_info .file_path )
1032- prefix = self ._module_emit_prefix (module_info .path )
1033- for decl in module_info .ast .declarations or []:
1034- if decl .kind == NodeKind .IMPORT :
1035- continue
1036- if decl .kind == NodeKind .FUNCTION :
1037- decl .module_emit_prefix = prefix
1041+ seen_paths .add (path )
1042+ for decl in program .declarations or []:
1043+ decl .file_scope = path
1044+ decl .module_index = index
1045+ decl .module_entry = program is ast
1046+ if decl .name and program is not ast :
1047+ module_path = Path (path ).relative_to (module_resolver .entry_root ).with_suffix ("" ).as_posix ()
1048+ identity = self ._module_emit_prefix (module_path ) + decl .name
1049+ while identity in written_names :
1050+ identity += "_"
1051+ written_names .add (identity )
1052+ decl .module_identity_name = identity
1053+ if decl .kind == NodeKind .IMPORT and not module_resolver .is_virtual_module (decl .module_path or "" ):
1054+ module = module_resolver .get_module (decl .module_path , path if program is not ast else None )
1055+ decl .target_file_scope = module .file_path
10381056 module_decls .append (decl )
1039-
1040- if not module_decls :
1041- return ast
1042-
1043- return ASTNode (
1044- kind = NodeKind .PROGRAM ,
1045- declarations = module_decls + list (ast .declarations or []),
1046- span = ast .span ,
1047- )
1057+ work = list (program .declarations or [])
1058+ while work :
1059+ node = work .pop ()
1060+ vars (node ).pop ("file_module_call" , None )
1061+ vars (node ).pop ("module_emit_prefix" , None )
1062+ for value in vars (node ).values ():
1063+ if isinstance (value , ASTNode ):
1064+ work .append (value )
1065+ elif isinstance (value , list ):
1066+ work .extend (child for child in value if isinstance (child , ASTNode ))
1067+ return ASTNode (kind = NodeKind .PROGRAM , declarations = module_decls , span = ast .span )
1068+
1069+ def _lower_file_module_bindings (self , program : ASTNode , symbols : Any ) -> None :
1070+ # File scopes have already resolved every use. The later whole-program
1071+ # passes receive unique declaration keys in original emission order.
1072+ if not symbols .file_scopes :
1073+ return
1074+ work = list (program .declarations or [])
1075+ while work :
1076+ node = work .pop ()
1077+ for value in vars (node ).values ():
1078+ if isinstance (value , ASTNode ):
1079+ work .append (value )
1080+ elif isinstance (value , list ):
1081+ work .extend (child for child in value if isinstance (child , ASTNode ))
1082+ bound = symbols .resolved_uses .get (id (node ))
1083+ target = bound .node if bound is not None else None
1084+ identity = getattr (target , "module_identity_name" , None )
1085+ if identity :
1086+ node .source_name = node .name or node .field or node .struct_type
1087+ if node .kind == NodeKind .FIELD_ACCESS :
1088+ node .kind = NodeKind .IDENTIFIER
1089+ node .name = identity
1090+ node .object = None
1091+ node .field = None
1092+ elif node .kind in {NodeKind .IDENTIFIER , NodeKind .TYPE_IDENTIFIER , NodeKind .PATTERN_IDENTIFIER }:
1093+ node .name = identity
1094+ elif node .kind == NodeKind .STRUCT_INIT :
1095+ node .struct_type = identity
1096+ elif node .kind == NodeKind .PATTERN_ENUM :
1097+ node .enum_type = identity
1098+ if getattr (node , "module_identity_name" , None ):
1099+ node .source_name = node .name
1100+ node .name = node .module_identity_name
1101+ for scope in symbols .file_scopes .values ():
1102+ for symbol in scope .symbols .values ():
1103+ identity = getattr (symbol .node , "module_identity_name" , None )
1104+ if identity :
1105+ symbols .global_scope .symbols [identity ] = symbol
1106+ symbols .global_scope .symbols .update (symbols .current_scope .symbols )
10481107
10491108 def _backend_unsupported_feature_errors (
10501109 self ,
0 commit comments