iter 23.1.4: E2E fixture — bare LT match via implicit prelude import

This commit is contained in:
2026-05-10 21:32:05 +02:00
parent 47d95d0c60
commit aace5e3ce2
3 changed files with 101 additions and 13 deletions
+13
View File
@@ -895,6 +895,19 @@ fn workspace_build_runs_imported_fn() {
assert_eq!(stdout.trim(), "5", "ws_lib.add(2,3) should print 5");
}
/// Guards Iter 23.1: `examples/ordering_match.ail.json` pattern-matches
/// on the prelude `Ordering` ctor `LT` without an explicit prelude import.
/// End-to-end: typecheck → codegen → link → run → stdout exactly `"1\n"`.
/// Property: the implicit prelude import wired by `build_check_env` /
/// `check_in_workspace` (Iter 23.1 Task 3) propagates through codegen so
/// the bare ctor name `LT` resolves cross-module without an explicit
/// `imports: ["prelude"]` entry in the source module.
#[test]
fn ordering_match_via_prelude_prints_1() {
let stdout = build_and_run("ordering_match.ail.json");
assert_eq!(stdout, "1\n");
}
/// Guards Iter 5d: `ail manifest --workspace --json` lists defs from all
/// modules of the workspace — visible via a `module` field per entry.
#[test]
+40 -13
View File
@@ -1639,6 +1639,16 @@ impl<'a> Emitter<'a> {
/// under a swapped `module_name` (see `emit_specialised_fn`)
/// resolve its bare ctor references against the *owner* module's
/// ctor table, not the consumer's.
///
/// Iter 23.1: bare-type lookup now falls back to imported modules
/// when the ctor is not local — mirroring `lookup_ctor_in_pattern`.
/// The typechecker (Iter 23.1 Task 3) already accepts a bare
/// `Term::Ctor { type_name, ctor }` whose `type_name` is owned by
/// an imported module (the implicit `prelude` import is the
/// motivating case: an `Ordering` ctor `LT` written without an
/// explicit `imports: ["prelude"]`). Codegen must honour the same
/// resolution path so the lockstep `Pattern::Ctor ↔ Term::Ctor ↔
/// codegen` is preserved.
pub(crate) fn lookup_ctor_by_type(
&self,
type_name: &str,
@@ -1669,24 +1679,41 @@ impl<'a> Emitter<'a> {
}
Ok(cref)
} else {
let cref = self
// Local-first: current module's ctor table wins on conflict.
if let Some(cref) = self
.module_ctor_index
.get(self.module_name)
.and_then(|m| m.get(ctor_name))
.cloned()
.ok_or_else(|| {
CodegenError::Internal(format!(
"unknown ctor `{ctor_name}` in module `{}`",
self.module_name
))
})?;
if cref.type_name != type_name {
return Err(CodegenError::Internal(format!(
"ctor `{ctor_name}` belongs to `{}`, not `{type_name}`",
cref.type_name
)));
{
if cref.type_name != type_name {
return Err(CodegenError::Internal(format!(
"ctor `{ctor_name}` belongs to `{}`, not `{type_name}`",
cref.type_name
)));
}
return Ok(cref);
}
Ok(cref)
// Iter 23.1: fallback — scan other modules for a bare ctor
// whose declared `type_name` matches. The typechecker has
// already pinned uniqueness (an ambiguous bare type name
// is rejected with `CheckError::AmbiguousType`), so the
// first hit whose owning type matches the requested
// `type_name` is unique. Mirrors `lookup_ctor_in_pattern`.
for (mname, ctors) in self.module_ctor_index.iter() {
if mname == self.module_name {
continue;
}
if let Some(cref) = ctors.get(ctor_name).cloned() {
if cref.type_name == type_name {
return Ok(cref);
}
}
}
Err(CodegenError::Internal(format!(
"unknown ctor `{ctor_name}` in module `{}`",
self.module_name
)))
}
}