Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions lib/codegen_deno.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1685,7 +1685,18 @@ let gen_type_decl ctx (td : type_decl) : unit =
match td.td_body with
| TyEnum variants ->
let exp = if visibility_is_public td.td_vis then "export " else "" in
(* The runtime preamble (see [prelude]) already declares the foundational
Option/Result constructors Some/None/Ok/Err. Re-emitting them here for
a program that *declares* `type Option`/`type Result` (e.g.
stdlib/prelude.affine) redeclared the same `const` and crashed the
emitted module under node with `SyntaxError: Identifier 'Some' has
already been declared`. Skip any variant the preamble already provides.
(The #136 AOT smoke never caught this — it only checks the output is
non-empty, never runs it.) *)
let preamble_ctors = [ "Some"; "None"; "Ok"; "Err" ] in
List.iter (fun (vd : variant_decl) ->
if List.mem vd.vd_name.name preamble_ctors then ()
else begin
let name = mangle vd.vd_name.name in
let arity = List.length vd.vd_fields in
if arity = 0 then
Expand All @@ -1703,6 +1714,7 @@ let gen_type_decl ctx (td : type_decl) : unit =
"%sconst %s = (%s) => ({ tag: \"%s\", values: [%s] });"
exp name (String.concat ", " ps) vd.vd_name.name
(String.concat ", " ps))
end
) variants;
emit ctx "\n"
| TyStruct _ | TyAlias _ | TyExtern ->
Expand Down
44 changes: 43 additions & 1 deletion test/test_stdlib_aot.ml
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,49 @@ let xmod_constructor_tests =
[ Alcotest.test_case "imported Option/Result constructors -> Wasm" `Quick
test_imported_constructors_wasm ]

(* ---- Deno-ESM: no duplicate Option/Result constructor declaration --------

The Deno-ESM runtime preamble already declares Some/None/Ok/Err. A module
that *declares* `type Option`/`type Result` (e.g. stdlib/prelude.affine)
must not re-emit those consts, or the emitted module crashes under node
with `SyntaxError: Identifier 'Some' has already been declared`. The #136
AOT smoke never caught this (it only checks the output is non-empty, never
runs it), so this asserts the foundational constructor is declared exactly
once. Counterpart guard for the codegen-deno run-under-node CI step. *)
let local_option_src = {|
module localopt;
pub type Option<T> = Some(T) | None
pub fn wrap(x: Int) -> Option<Int> { Some(x) }
pub fn empty() -> Option<Int> { None }
|}

let count_substr (needle : string) (hay : string) : int =
let re = Str.regexp_string needle in
let rec loop pos acc =
match Str.search_forward re hay pos with
| exception Not_found -> acc
| i -> loop (i + String.length needle) (acc + 1)
in
loop 0 0

let test_deno_no_duplicate_option_ctor () =
match Parse_driver.parse_string ~file:"<localopt>" local_option_src with
| exception e ->
Alcotest.failf "local-option parse raised: %s" (Printexc.to_string e)
| prog ->
(match pipeline_to_deno prog with
| Error m -> Alcotest.failf "deno codegen failed: %s" m
| Ok js ->
Alcotest.(check int)
"`const Some` declared exactly once (preamble only, not re-emitted)"
1 (count_substr "const Some" js))

let deno_dup_ctor_tests =
[ Alcotest.test_case "declared Option does not duplicate preamble ctor (Deno)"
`Quick test_deno_no_duplicate_option_ctor ]

let tests =
[ ("STAGE-A AOT smoke (#136)", aot_smoke_tests);
("STAGE-A multi-module integration (#137)", integration_tests);
("cross-module constructor linking, Wasm (#138)", xmod_constructor_tests) ]
("cross-module constructor linking, Wasm (#138)", xmod_constructor_tests);
("Deno-ESM no duplicate Option/Result constructor", deno_dup_ctor_tests) ]
Loading