Skip to content

Commit 31dbebb

Browse files
Merge branch 'main' into claude/tw-verify-v2-parse
2 parents 6573406 + a22a74c commit 31dbebb

5 files changed

Lines changed: 152 additions & 0 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// SPDX-FileCopyrightText: 2026 hyperpolymath
3+
//
4+
// Inline-extern fixture (effectful): an `extern fn` declaration
5+
// whose return type carries an effect row. Exercises the same path
6+
// as inline_extern_pure plus the effect-row resolution branch in
7+
// typecheck. Pinned against the gate so a regression to the
8+
// pre-#346 silent-failure shape would fail loudly.
9+
//
10+
// See .claude/CLAUDE.md §"Test-fixture hygiene for latent bug
11+
// surfaces".
12+
13+
effect IO;
14+
15+
extern fn host_log(msg: String) -> Unit / IO;
16+
17+
pub fn main() -> Int {
18+
return 0;
19+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// SPDX-FileCopyrightText: 2026 hyperpolymath
3+
//
4+
// Inline-extern fixture (polymorphic): an `extern fn` declaration
5+
// with type parameters. Exercises the polymorphic-scheme registration
6+
// path that the FnExtern special case in typecheck handles
7+
// (see lib/typecheck.ml `check_fn_decl` `FnExtern` arm).
8+
//
9+
// See .claude/CLAUDE.md §"Test-fixture hygiene for latent bug
10+
// surfaces".
11+
12+
extern fn host_identity[T](x: T) -> T;
13+
14+
pub fn main() -> Int {
15+
return 0;
16+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// SPDX-FileCopyrightText: 2026 hyperpolymath
3+
//
4+
// Inline-extern fixture (pure): an `extern fn` declaration with no
5+
// effect row, fed to the full pipeline (parse → resolve → typecheck
6+
// → interp). Counterpart to the missing `FnExtern` arm in
7+
// Interp.eval_decl that PR #346 fixed; this fixture pins the
8+
// "no-effect inline extern" shape against the gate so a regression
9+
// to the silent-pattern-match-failure path of the kind that broke
10+
// main between #334 and #346 would fail loudly here instead.
11+
//
12+
// See .claude/CLAUDE.md §"Test-fixture hygiene for latent bug
13+
// surfaces" and the comment on PR #346 for the rationale.
14+
15+
extern fn host_pure_identity(x: Int) -> Int;
16+
17+
pub fn main() -> Int {
18+
// The call itself doesn't have to succeed at runtime — the
19+
// interpreter's VBuiltin table doesn't know about
20+
// host_pure_identity — but parse + resolve + typecheck + the
21+
// eval_decl arm for FnExtern MUST all return Ok. The check_program
22+
// test driver asserts that, not the runtime value.
23+
return 0;
24+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// SPDX-FileCopyrightText: 2026 hyperpolymath
3+
//
4+
// Inline-extern fixture (extern type + extern fn together):
5+
// an opaque host type declared inline and consumed by an extern
6+
// function in the same unit. Exercises both TyExtern registration
7+
// and the FnExtern path that consumes the resulting type.
8+
//
9+
// See .claude/CLAUDE.md §"Test-fixture hygiene for latent bug
10+
// surfaces".
11+
12+
extern type Handle;
13+
14+
extern fn host_use(h: Handle) -> Int;
15+
16+
pub fn main() -> Int {
17+
return 0;
18+
}

test/test_e2e.ml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3882,6 +3882,80 @@ let qualified_value_tests = [
38823882
Alcotest.test_case "use Mod as M; M::fn(x) resolves (#178 follow-up)" `Quick test_qualval_coloncolon_alias_call;
38833883
]
38843884

3885+
(* ---- Inline `extern fn` / `extern type` shape-coverage fixtures ----
3886+
Class-level coverage for the "first user of an inline extern shape
3887+
feeds it to every downstream consumer" surface that produced the
3888+
PR #346 FnExtern interp bug (eval_decl missing match arm — survived
3889+
since the interpreter was written, fired the moment STDLIB-04a's
3890+
tests became the first to hand an inline extern fn to
3891+
Interp.eval_program).
3892+
3893+
See .claude/CLAUDE.md §"Test-fixture hygiene for latent bug
3894+
surfaces" for the rationale. Each fixture is fed through
3895+
parse → resolve → typecheck → interp; the assertion is that ALL
3896+
four return Ok. A regression that re-introduces the silent
3897+
pattern-match-failure path of the kind that broke main between
3898+
#334 and #346 would fail loudly here. *)
3899+
3900+
let inline_extern_pipeline_ok path : bool =
3901+
let loader = Module_loader.create {
3902+
Module_loader.stdlib_path = "stdlib";
3903+
search_paths = [];
3904+
current_dir = fixture_dir;
3905+
} in
3906+
match parse_fixture path with
3907+
| Error _ -> false
3908+
| Ok raw ->
3909+
let prog = Resolve.lower_qualified_value_paths raw in
3910+
(match Resolve.resolve_program_with_loader prog loader with
3911+
| Error _ -> false
3912+
| Ok (resolve_ctx, import_type_ctx) ->
3913+
(match Typecheck.check_program
3914+
~import_types:import_type_ctx.Typecheck.name_types
3915+
resolve_ctx.symbols prog with
3916+
| Error _ -> false
3917+
| Ok _ ->
3918+
(* The PR #346 root cause was here: Interp.eval_decl's TopFn
3919+
arm didn't match FnExtern, raising Match_failure. The
3920+
fixtures don't *call* the extern at runtime (the host
3921+
impl isn't registered), but eval_program walks every
3922+
TopFn through eval_decl as part of building the initial
3923+
env — that's the path that fired the missing arm. *)
3924+
(match Interp.eval_program prog with
3925+
| Ok _ -> true
3926+
| Error _ -> false)))
3927+
3928+
let test_inline_extern_pure () =
3929+
Alcotest.(check bool)
3930+
"inline `extern fn host_pure_identity(x: Int) -> Int;` passes the pipeline"
3931+
true
3932+
(inline_extern_pipeline_ok (fixture "inline_extern_pure.affine"))
3933+
3934+
let test_inline_extern_effectful () =
3935+
Alcotest.(check bool)
3936+
"inline `extern fn host_log(msg) -> Unit / IO;` passes the pipeline"
3937+
true
3938+
(inline_extern_pipeline_ok (fixture "inline_extern_effectful.affine"))
3939+
3940+
let test_inline_extern_polymorphic () =
3941+
Alcotest.(check bool)
3942+
"inline `extern fn host_identity[T](x: T) -> T;` passes the pipeline"
3943+
true
3944+
(inline_extern_pipeline_ok (fixture "inline_extern_polymorphic.affine"))
3945+
3946+
let test_inline_extern_type_consumed () =
3947+
Alcotest.(check bool)
3948+
"inline `extern type Handle; extern fn host_use(h: Handle) -> Int;` passes the pipeline"
3949+
true
3950+
(inline_extern_pipeline_ok (fixture "inline_extern_type_consumed.affine"))
3951+
3952+
let inline_extern_shape_tests = [
3953+
Alcotest.test_case "pure (no effects)" `Quick test_inline_extern_pure;
3954+
Alcotest.test_case "effectful (effect row)" `Quick test_inline_extern_effectful;
3955+
Alcotest.test_case "polymorphic (type params)" `Quick test_inline_extern_polymorphic;
3956+
Alcotest.test_case "extern type + consuming fn" `Quick test_inline_extern_type_consumed;
3957+
]
3958+
38853959
(* ---- Type-syntax sugars: fn(...) -> T, Option<T>, (A, B) -> C ---- *)
38863960

38873961
let parse_check_passes src : bool =
@@ -4352,6 +4426,7 @@ let tests =
43524426
("E2E Array Type Sugar", array_type_tests);
43534427
("E2E Qualified Paths #228", qualified_path_tests);
43544428
("E2E Qualified Value #178", qualified_value_tests);
4429+
("E2E Inline Extern Shapes (Refs #346)", inline_extern_shape_tests);
43554430
("E2E WasmGC PatCon Destructure", wasm_gc_patcon_tests);
43564431
("E2E Type Syntax Sugar", type_syntax_sugar_tests);
43574432
]

0 commit comments

Comments
 (0)