diff --git a/crates/ailang-codegen/src/lib.rs b/crates/ailang-codegen/src/lib.rs index a77599d..685db00 100644 --- a/crates/ailang-codegen/src/lib.rs +++ b/crates/ailang-codegen/src/lib.rs @@ -2547,11 +2547,23 @@ impl<'a> Emitter<'a> { let target_module = match self.import_map.get(prefix).cloned() { Some(m) => m, None if self.module_user_fns.contains_key(prefix) => prefix.to_string(), - None => { - return Err(CodegenError::Internal(format!( - "cross-module call `{name}`: prefix `{prefix}` not in import map" - ))) - } + // #53: type-scoped `T.def` callee (e.g. `Counter.new`, + // produced by the monomorphic `Term::New` desugar). When + // `prefix` is not an import alias or module name but + // names a TypeDef in scope, the call resolves to the + // `def` fn in that type's home module — symmetric to the + // checker's TypeDef-first ladder. Keeps the documented + // `lower_app` ↔ `is_static_callee` lockstep honest: + // `is_static_callee` already returns true for every + // single-dot name, so this branch MUST resolve it. + None => match self.type_home_module(prefix) { + Some(home) => home, + None => { + return Err(CodegenError::Internal(format!( + "cross-module call `{name}`: prefix `{prefix}` not in import map" + ))) + } + }, }; // iter 23.4: codegen-time poly-call dispatch removed. Post-mono // every poly call site has been rewritten by `rewrite_mono_calls` @@ -2786,6 +2798,30 @@ impl<'a> Emitter<'a> { } + /// #53: returns the module that declares the named TypeDef — the + /// current module first, then imported modules. `None` if no module + /// in scope declares a TypeDef of that name. A type's presence in a + /// module is read from `module_ctor_index` (a TypeDef declares at + /// least one ctor, each `CtorRef` carrying its `type_name`). This + /// mirrors the checker's TypeDef-first ladder so codegen resolves a + /// type-scoped dotted callee (`T.def`) exactly where the checker + /// does. Used by `lower_app` and `resolve_top_level_fn`. + fn type_home_module(&self, type_name: &str) -> Option { + if let Some(ctors) = self.module_ctor_index.get(self.module_name) { + if ctors.values().any(|c| c.type_name == type_name) { + return Some(self.module_name.to_string()); + } + } + for target in self.import_map.values() { + if let Some(ctors) = self.module_ctor_index.get(target) { + if ctors.values().any(|c| c.type_name == type_name) { + return Some(target.clone()); + } + } + } + None + } + /// is `name` a callee that can be resolved at compile time /// (no fn-pointer needed)? True for builtin operators, the /// current-module top-level fns (mono or poly), and qualified @@ -2872,11 +2908,17 @@ impl<'a> Emitter<'a> { // a valid post-mono construct because both ends were // independently typechecked under their original module // contexts before mono ran). - let target = match self.import_map.get(prefix) { - Some(m) => m, - None => prefix, + // #53: a type-scoped `T.def` prefix (e.g. `Counter.new`) + // resolves to the type's home module, symmetric to the + // checker's TypeDef-first ladder and the matching arm in + // `lower_app`'s cross-module branch. Tried after import_map + // and the literal-module-name fallback. + let target: String = match self.import_map.get(prefix) { + Some(m) => m.clone(), + None if self.module_user_fns.contains_key(prefix) => prefix.to_string(), + None => self.type_home_module(prefix).unwrap_or_else(|| prefix.to_string()), }; - let sig = self.module_user_fns.get(target)?.get(suffix)?.clone(); + let sig = self.module_user_fns.get(&target)?.get(suffix)?.clone(); return Some((format!("@ail_{target}_{suffix}_clos"), sig)); } if let Some(sig) = self @@ -3234,6 +3276,14 @@ impl<'a> Emitter<'a> { // post-mono synthesised cross-module references // (see `resolve_top_level_fn` and the cross-module // call arm in `lower_app` for matching fallbacks). + // #53: a type-scoped `T.def` prefix (e.g. `Counter.new`, + // the monomorphic `Term::New` desugar output) resolves + // to the type's home module, after import_map and the + // literal-module-name fallback. Mirrors the checker's + // TypeDef-first ladder and the matching arms in + // `lower_app` / `resolve_top_level_fn`, so arg-type + // synthesis of the dotted callee doesn't hit `UnknownVar`. + let type_home = self.type_home_module(prefix); let target_opt: Option<&str> = self .import_map .get(prefix) @@ -3244,7 +3294,8 @@ impl<'a> Emitter<'a> { } else { None } - }); + }) + .or(type_home.as_deref()); if let Some(target) = target_opt { if let Some(ty) = self .module_def_ail_types