@@ -82,16 +82,18 @@ struct Resolver {
8282 /// `SCREAMING_SNAKE_CASE`, so their mangled symbol is upper-cased to stay
8383 /// consistent between declaration and reference.
8484 module_consts : HashMap < String , HashSet < String > > ,
85- /// file -> (imported bare name -> module prefix it was imported from).
86- file_imports : HashMap < String , HashMap < String , String > > ,
85+ /// file -> (local import name -> (module prefix, real symbol name)). The
86+ /// local name is the `as` alias when present, otherwise the path's last
87+ /// segment; the real name is always the path's last segment.
88+ file_imports : HashMap < String , HashMap < String , ( String , String ) > > ,
8789}
8890
8991impl Resolver {
9092 fn build ( program : & Program , file_module : & HashMap < String , String > ) -> Self {
9193 let mut module_defs: HashMap < String , HashSet < String > > = HashMap :: new ( ) ;
9294 let mut module_types: HashMap < String , HashSet < String > > = HashMap :: new ( ) ;
9395 let mut module_consts: HashMap < String , HashSet < String > > = HashMap :: new ( ) ;
94- let mut file_imports: HashMap < String , HashMap < String , String > > = HashMap :: new ( ) ;
96+ let mut file_imports: HashMap < String , HashMap < String , ( String , String ) > > = HashMap :: new ( ) ;
9597
9698 for item in & program. items {
9799 let Some ( file) = item_file ( item) else {
@@ -156,12 +158,16 @@ impl Resolver {
156158 }
157159 Item :: Use ( decl) => {
158160 if decl. path . len ( ) >= 2 {
159- let name = decl. path [ decl. path . len ( ) - 1 ] . clone ( ) ;
161+ let real_name = decl. path [ decl. path . len ( ) - 1 ] . clone ( ) ;
160162 let import_prefix = module_prefix ( & decl. path [ ..decl. path . len ( ) - 1 ] ) ;
163+ let local = decl
164+ . local_name ( )
165+ . map ( str:: to_string)
166+ . unwrap_or_else ( || real_name. clone ( ) ) ;
161167 file_imports
162168 . entry ( decl. span . file . clone ( ) )
163169 . or_default ( )
164- . insert ( name , import_prefix) ;
170+ . insert ( local , ( import_prefix, real_name ) ) ;
165171 }
166172 }
167173 Item :: Module ( _) => { }
@@ -206,42 +212,46 @@ impl Resolver {
206212 {
207213 return Some ( self . mangle_value ( prefix, name) ) ;
208214 }
209- // Otherwise a `use module.name` import that names a known module symbol.
210- if let Some ( import_prefix) = self
215+ // Otherwise a `use module.name [as local]` import that names a known
216+ // module symbol. The reference uses the local name; the mangled symbol
217+ // uses the imported module's real name.
218+ if let Some ( ( import_prefix, real) ) = self
211219 . file_imports
212220 . get ( file)
213221 . and_then ( |imports| imports. get ( name) )
214222 && self
215223 . module_defs
216224 . get ( import_prefix)
217- . is_some_and ( |names| names. contains ( name ) )
225+ . is_some_and ( |names| names. contains ( real ) )
218226 {
219- return Some ( self . mangle_value ( import_prefix, name ) ) ;
227+ return Some ( self . mangle_value ( import_prefix, real ) ) ;
220228 }
221229 None
222230 }
223231
224- /// The module prefix that owns type `name` as seen from `file` (own module,
225- /// then `use` import), or `None` if it is not a module-scoped type.
226- fn resolve_type_module ( & self , file : & str , name : & str ) -> Option < String > {
232+ /// The `(module prefix, real type name)` that owns type `name` as seen from
233+ /// `file` (own module, then `use` import), or `None` if it is not a
234+ /// module-scoped type. The real name differs from `name` when `name` is a
235+ /// `use … as` alias.
236+ fn resolve_type_module ( & self , file : & str , name : & str ) -> Option < ( String , String ) > {
227237 if let Some ( prefix) = self . file_module . get ( file)
228238 && self
229239 . module_types
230240 . get ( prefix)
231241 . is_some_and ( |types| types. contains ( name) )
232242 {
233- return Some ( prefix. clone ( ) ) ;
243+ return Some ( ( prefix. clone ( ) , name . to_string ( ) ) ) ;
234244 }
235- if let Some ( import_prefix) = self
245+ if let Some ( ( import_prefix, real ) ) = self
236246 . file_imports
237247 . get ( file)
238248 . and_then ( |imports| imports. get ( name) )
239249 && self
240250 . module_types
241251 . get ( import_prefix)
242- . is_some_and ( |types| types. contains ( name ) )
252+ . is_some_and ( |types| types. contains ( real ) )
243253 {
244- return Some ( import_prefix. clone ( ) ) ;
254+ return Some ( ( import_prefix. clone ( ) , real . clone ( ) ) ) ;
245255 }
246256 None
247257 }
@@ -250,7 +260,7 @@ impl Resolver {
250260 /// namespace names a module-scoped type, return its mangled type name.
251261 fn resolve_type_namespace ( & self , file : & str , namespace : & str ) -> Option < String > {
252262 self . resolve_type_module ( file, namespace)
253- . map ( |prefix| format ! ( "{prefix}{MODULE_SEP}{namespace }" ) )
263+ . map ( |( prefix, real ) | format ! ( "{prefix}{MODULE_SEP}{real }" ) )
254264 }
255265
256266 fn rewrite ( & self , program : & mut Program ) {
@@ -539,9 +549,9 @@ impl Resolver {
539549 // where the access lives in a different file than the declaration.
540550 if let Expr :: Ident ( type_name, _) = base. as_ref ( )
541551 && !scope. contains ( type_name)
542- && let Some ( type_module) = self . resolve_type_module ( file, type_name)
552+ && let Some ( ( type_module, real_type ) ) = self . resolve_type_module ( file, type_name)
543553 {
544- let flat = flatten_associated_const ( type_name , name) ;
554+ let flat = flatten_associated_const ( & real_type , name) ;
545555 if self
546556 . module_defs
547557 . get ( & type_module)
@@ -634,7 +644,23 @@ impl Resolver {
634644 * callee = Callee :: Name ( format ! ( "{prefix}{MODULE_SEP}{name}" ) ) ;
635645 }
636646 }
637- Callee :: ReceiverCall { receiver, .. } => {
647+ Callee :: ReceiverCall {
648+ receiver, method, ..
649+ } => {
650+ // A lowercase `module.fn()` parses as a receiver call, but if the
651+ // receiver names a module (not a local in scope) whose free
652+ // function is `method`, it is a module-qualified call: collapse it
653+ // to the module's mangled symbol. Otherwise it is a real value
654+ // receiver call and only the receiver is rewritten.
655+ if let Some ( prefix) = module_path_of_receiver ( receiver, scope)
656+ && self
657+ . module_defs
658+ . get ( & prefix)
659+ . is_some_and ( |names| names. contains ( method) )
660+ {
661+ * callee = Callee :: Name ( format ! ( "{prefix}{MODULE_SEP}{method}" ) ) ;
662+ return ;
663+ }
638664 self . rewrite_expr ( receiver, file, scope) ;
639665 }
640666 }
@@ -814,6 +840,80 @@ mod tests {
814840 isolate_module_namespaces ( & mut program) ;
815841 assert ! ( function_names( & program) . contains( & "main" . to_string( ) ) ) ;
816842 }
843+
844+ fn body_of ( program : & Program , name : & str ) -> String {
845+ let function = program
846+ . items
847+ . iter ( )
848+ . find_map ( |item| match item {
849+ Item :: Function ( function) if function. name == name => Some ( function) ,
850+ _ => None ,
851+ } )
852+ . unwrap_or_else ( || panic ! ( "{name} present" ) ) ;
853+ format ! ( "{:?}" , function. body)
854+ }
855+
856+ #[ test]
857+ fn aliased_import_resolves_to_the_imported_module_symbol ( ) {
858+ // `use lib.count as lib_count` lets `app` reference `lib`'s `count` under
859+ // a distinct local name; the reference resolves to the real module symbol.
860+ let lib = parse_source (
861+ "lib.rss" ,
862+ "module lib\n \n fn count() -> Int {\n return 1\n }\n " ,
863+ ) ;
864+ let app = parse_source (
865+ "app.rss" ,
866+ "module app\n \n use lib.count as lib_count\n \n fn run() -> Int {\n return lib_count()\n }\n " ,
867+ ) ;
868+ let mut program = merge_programs ( [ lib, app] ) ;
869+ isolate_module_namespaces ( & mut program) ;
870+ let body = body_of ( & program, "app__run" ) ;
871+ assert ! (
872+ body. contains( "lib__count" ) ,
873+ "alias should resolve to lib__count: {body}"
874+ ) ;
875+ }
876+
877+ #[ test]
878+ fn qualified_module_call_resolves_to_the_module_symbol ( ) {
879+ // `helpers.count()` names the owner at the call site, with no `use`.
880+ let helpers = parse_source (
881+ "helpers.rss" ,
882+ "module helpers\n \n fn count() -> Int {\n return 1\n }\n " ,
883+ ) ;
884+ let app = parse_source (
885+ "app.rss" ,
886+ "module app\n \n fn run() -> Int {\n return helpers.count()\n }\n " ,
887+ ) ;
888+ let mut program = merge_programs ( [ helpers, app] ) ;
889+ isolate_module_namespaces ( & mut program) ;
890+ let body = body_of ( & program, "app__run" ) ;
891+ assert ! (
892+ body. contains( "helpers__count" ) ,
893+ "qualified call should resolve to helpers__count: {body}"
894+ ) ;
895+ }
896+
897+ #[ test]
898+ fn multi_segment_qualified_module_call_resolves ( ) {
899+ // A dotted module path (`a.b`) is mangled to `a_b` and the call collapses
900+ // to its module-qualified symbol.
901+ let inner = parse_source (
902+ "inner.rss" ,
903+ "module a.b\n \n fn count() -> Int {\n return 1\n }\n " ,
904+ ) ;
905+ let app = parse_source (
906+ "app.rss" ,
907+ "module app\n \n fn run() -> Int {\n return a.b.count()\n }\n " ,
908+ ) ;
909+ let mut program = merge_programs ( [ inner, app] ) ;
910+ isolate_module_namespaces ( & mut program) ;
911+ let body = body_of ( & program, "app__run" ) ;
912+ assert ! (
913+ body. contains( "a_b__count" ) ,
914+ "multi-segment qualified call should resolve to a_b__count: {body}"
915+ ) ;
916+ }
817917}
818918
819919/// The flattened symbol for an associated constant `Type.MEMBER`, matching the
@@ -823,6 +923,37 @@ fn flatten_associated_const(type_name: &str, member: &str) -> String {
823923 format ! ( "{type_name}_{member}" ) . to_uppercase ( )
824924}
825925
926+ /// If `receiver` is a chain of plain identifiers (`a` or `a.b.c`) whose root is
927+ /// not shadowed by a local binding, return the candidate module prefix (encoded
928+ /// with the same injective scheme as declarations). Anything else (a method call,
929+ /// an indexed value, a literal, or a root that names an in-scope local) is a
930+ /// value receiver, not a module path.
931+ fn module_path_of_receiver ( receiver : & Expr , scope : & HashSet < String > ) -> Option < String > {
932+ fn collect ( expr : & Expr , segments : & mut Vec < String > ) -> bool {
933+ match expr {
934+ Expr :: Ident ( name, _) => {
935+ segments. push ( name. clone ( ) ) ;
936+ true
937+ }
938+ Expr :: Field { base, name, .. } => {
939+ collect ( base, segments) && {
940+ segments. push ( name. clone ( ) ) ;
941+ true
942+ }
943+ }
944+ _ => false ,
945+ }
946+ }
947+ let mut segments = Vec :: new ( ) ;
948+ if !collect ( receiver, & mut segments) || segments. is_empty ( ) {
949+ return None ;
950+ }
951+ if scope. contains ( & segments[ 0 ] ) {
952+ return None ;
953+ }
954+ Some ( module_prefix ( & segments) )
955+ }
956+
826957fn item_file ( item : & Item ) -> Option < & str > {
827958 Some ( match item {
828959 Item :: Function ( function) => function. span . file . as_str ( ) ,
0 commit comments