fix(codegen): resolve the dotted T.new user-ADT callee, symmetric to check
A monomorphic `(new Counter 42)` over a user-defined ADT that declares a `new` fn passed `ail check` but crashed `ail build --alloc=rc` with `unknown variable: Counter.new` (RED-pinned in1eff055). Root: the `Term::New` desugar lowers a no-type-arg monomorphic `(new Counter 42)` to `(app Counter.new 42)` — a type-scoped `Var` callee `Counter.new`. The checker resolves that dotted callee via its TypeDef-first ladder (so check is clean), but codegen had no equivalent resolution: the name was absent from the import map and the current module's def list, so it fell through to `CodegenError::UnknownVar`. The user `new` fn body IS codegen'd — only the dotted call-site failed to resolve. The error surfaced first in the arg-type pre-pass (`synth_with_extras`'s dotted-Var branch), which `lower_term`'s `Term::Let` arm runs before lowering the call. Fix (crates/ailang-codegen/src/lib.rs, all `// #53`): - New `Emitter::type_home_module(type_name)`: returns the module declaring the named TypeDef (current module first, then any import target) by scanning `module_ctor_index` for a matching `CtorRef.type_name`. This mirrors the checker's TypeDef-first ladder. - A dotted `T.def` fallback (resolve via the type's home module) added to the three sites that needed it: `resolve_top_level_fn` (the fn-pointer / lower_app resolution path), `is_static_callee` (its lockstep partner), and the `synth_with_extras` dotted-Var branch (the path that actually fired for this fixture). Lockstep (CLAUDE.md `lower_app` ↔ `is_static_callee`): `is_static_callee` now returns true for a dotted `T.def` whose `T` is a user ADT declared in this or an imported module, exactly matching `resolve_top_level_fn`'s new fallback — so no name is claimed static without being lowerable. Distinct root from #51 (the polymorphic `(new T <elem> …)` type-arg drop,ee4107c) — that fix deliberately left the monomorphic app-lowering path unchanged; this completes it. Verification: the #53 pin (crates/ail/tests/user_adt_new_mono_pin.rs) is green; the original 3-module reproducer examples/fieldtest/kem_2_counter_new.ail builds, runs, prints 42; ailang-codegen/check/core suites, the intercepts bijection, and both hash-pins are green; full workspace green. closes #53
This commit is contained in:
@@ -2547,11 +2547,23 @@ impl<'a> Emitter<'a> {
|
|||||||
let target_module = match self.import_map.get(prefix).cloned() {
|
let target_module = match self.import_map.get(prefix).cloned() {
|
||||||
Some(m) => m,
|
Some(m) => m,
|
||||||
None if self.module_user_fns.contains_key(prefix) => prefix.to_string(),
|
None if self.module_user_fns.contains_key(prefix) => prefix.to_string(),
|
||||||
|
// #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 => {
|
None => {
|
||||||
return Err(CodegenError::Internal(format!(
|
return Err(CodegenError::Internal(format!(
|
||||||
"cross-module call `{name}`: prefix `{prefix}` not in import map"
|
"cross-module call `{name}`: prefix `{prefix}` not in import map"
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
// iter 23.4: codegen-time poly-call dispatch removed. Post-mono
|
// iter 23.4: codegen-time poly-call dispatch removed. Post-mono
|
||||||
// every poly call site has been rewritten by `rewrite_mono_calls`
|
// 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<String> {
|
||||||
|
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
|
/// is `name` a callee that can be resolved at compile time
|
||||||
/// (no fn-pointer needed)? True for builtin operators, the
|
/// (no fn-pointer needed)? True for builtin operators, the
|
||||||
/// current-module top-level fns (mono or poly), and qualified
|
/// 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
|
// a valid post-mono construct because both ends were
|
||||||
// independently typechecked under their original module
|
// independently typechecked under their original module
|
||||||
// contexts before mono ran).
|
// contexts before mono ran).
|
||||||
let target = match self.import_map.get(prefix) {
|
// #53: a type-scoped `T.def` prefix (e.g. `Counter.new`)
|
||||||
Some(m) => m,
|
// resolves to the type's home module, symmetric to the
|
||||||
None => prefix,
|
// 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));
|
return Some((format!("@ail_{target}_{suffix}_clos"), sig));
|
||||||
}
|
}
|
||||||
if let Some(sig) = self
|
if let Some(sig) = self
|
||||||
@@ -3234,6 +3276,14 @@ impl<'a> Emitter<'a> {
|
|||||||
// post-mono synthesised cross-module references
|
// post-mono synthesised cross-module references
|
||||||
// (see `resolve_top_level_fn` and the cross-module
|
// (see `resolve_top_level_fn` and the cross-module
|
||||||
// call arm in `lower_app` for matching fallbacks).
|
// 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
|
let target_opt: Option<&str> = self
|
||||||
.import_map
|
.import_map
|
||||||
.get(prefix)
|
.get(prefix)
|
||||||
@@ -3244,7 +3294,8 @@ impl<'a> Emitter<'a> {
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.or(type_home.as_deref());
|
||||||
if let Some(target) = target_opt {
|
if let Some(target) = target_opt {
|
||||||
if let Some(ty) = self
|
if let Some(ty) = self
|
||||||
.module_def_ail_types
|
.module_def_ail_types
|
||||||
|
|||||||
Reference in New Issue
Block a user