diff --git a/crates/ail/tests/e2e.rs b/crates/ail/tests/e2e.rs index 186457c..273f280 100644 --- a/crates/ail/tests/e2e.rs +++ b/crates/ail/tests/e2e.rs @@ -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] diff --git a/crates/ailang-codegen/src/lib.rs b/crates/ailang-codegen/src/lib.rs index ee0dc8d..3625bda 100644 --- a/crates/ailang-codegen/src/lib.rs +++ b/crates/ailang-codegen/src/lib.rs @@ -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 + ))) } } diff --git a/examples/ordering_match.ail.json b/examples/ordering_match.ail.json new file mode 100644 index 0000000..f7d8e7b --- /dev/null +++ b/examples/ordering_match.ail.json @@ -0,0 +1,48 @@ +{ + "schema": "ailang/v0", + "name": "ordering_match", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "main", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "doc": "Iter 23.1 fixture: pattern-match on prelude Ordering ctor without explicit prelude import.", + "body": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "match", + "scrutinee": { + "t": "ctor", + "type": "Ordering", + "ctor": "LT", + "args": [] + }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "LT", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 1 } } + }, + { + "pat": { "p": "ctor", "ctor": "EQ", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 2 } } + }, + { + "pat": { "p": "ctor", "ctor": "GT", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 3 } } + } + ] + } + ] + } + } + ] +}