From 4e6f176cb433505866c480d79d671269cdec19b9 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Wed, 22 Oct 2025 17:27:26 +0200 Subject: [PATCH 1/4] Fix fatal error for external with @as --- compiler/frontend/ast_external_process.ml | 2 +- tests/tests/src/AsInUncurriedExternals.mjs | 5 ++++- tests/tests/src/AsInUncurriedExternals.res | 5 +++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/compiler/frontend/ast_external_process.ml b/compiler/frontend/ast_external_process.ml index b4efc128ad..c69d2413ff 100644 --- a/compiler/frontend/ast_external_process.ml +++ b/compiler/frontend/ast_external_process.ml @@ -1009,7 +1009,7 @@ let handle_attributes (loc : Bs_loc.t) (type_annotation : Parsetree.core_type) check_return_wrapper loc external_desc.return_wrapper result_type in let fn_type = Ast_helper.Typ.arrows ~loc args result_type in - ( build_uncurried_type ~arity:(List.length args) fn_type, + ( fn_type, External_ffi_types.ffi_bs arg_type_specs return_wrapper ffi, unused_attrs, relative ) diff --git a/tests/tests/src/AsInUncurriedExternals.mjs b/tests/tests/src/AsInUncurriedExternals.mjs index 28e4aa1779..a4b40b409b 100644 --- a/tests/tests/src/AsInUncurriedExternals.mjs +++ b/tests/tests/src/AsInUncurriedExternals.mjs @@ -19,9 +19,12 @@ function shouldNotFail(objectMode, name) { return 3; } +let constantValue = somescope.somefn({foo:true}); + export { mo, options, shouldNotFail, + constantValue, } -/* No side effect */ +/* constantValue Not a pure module */ diff --git a/tests/tests/src/AsInUncurriedExternals.res b/tests/tests/src/AsInUncurriedExternals.res index 97ec319375..a31ead4e4d 100644 --- a/tests/tests/src/AsInUncurriedExternals.res +++ b/tests/tests/src/AsInUncurriedExternals.res @@ -13,3 +13,8 @@ let mo = makeOptions let options = mo(~name="foo", ()) let shouldNotFail: (~objectMode: _, ~name: string) => int = (~objectMode, ~name) => 3 + +@scope("somescope") +external constantArgOnly: @as(json`{foo:true}`) _ => string = "somefn" + +let constantValue = constantArgOnly From 43761e12e29382c7331694b32f0fd5ec40dcfc02 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Thu, 23 Oct 2025 10:26:54 +0200 Subject: [PATCH 2/4] Add a synthetic unit argument when all external parameters collapse to constants --- compiler/frontend/ast_external_process.ml | 18 ++++++++++++++++++ tests/tests/src/AsInUncurriedExternals.mjs | 6 +++--- tests/tests/src/AsInUncurriedExternals.res | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/compiler/frontend/ast_external_process.ml b/compiler/frontend/ast_external_process.ml index c69d2413ff..d729dd9004 100644 --- a/compiler/frontend/ast_external_process.ml +++ b/compiler/frontend/ast_external_process.ml @@ -997,6 +997,24 @@ let handle_attributes (loc : Bs_loc.t) (type_annotation : Parsetree.core_type) new_arg_types, if arg_type = Ignore then i else i + 1 )) in + (* If every original argument was erased (e.g. all `@as(json ...) _`), + keep the external binding callable by threading a final `unit` + parameter through the type and arg specs. *) + let args, arg_type_specs = + match (args, arg_type_specs_length) with + | [], n when n > 0 -> + let unit_type = + Ast_helper.Typ.constr ~loc + (Location.mkloc (Longident.Lident "unit") loc) + [] + in + let unit_arg = {Parsetree.attrs = []; lbl = Nolabel; typ = unit_type} in + ( [unit_arg], + arg_type_specs + @ [{External_arg_spec.arg_label = Arg_empty; arg_type = Extern_unit}] + ) + | _ -> (args, arg_type_specs) + in let ffi : External_ffi_types.external_spec = external_desc_of_non_obj loc external_desc prim_name_with_source arg_type_specs_length arg_types_ty arg_type_specs diff --git a/tests/tests/src/AsInUncurriedExternals.mjs b/tests/tests/src/AsInUncurriedExternals.mjs index a4b40b409b..ed66d6c77f 100644 --- a/tests/tests/src/AsInUncurriedExternals.mjs +++ b/tests/tests/src/AsInUncurriedExternals.mjs @@ -19,12 +19,12 @@ function shouldNotFail(objectMode, name) { return 3; } -let constantValue = somescope.somefn({foo:true}); +let x = somescope.somefn({foo:true}); export { mo, options, shouldNotFail, - constantValue, + x, } -/* constantValue Not a pure module */ +/* x Not a pure module */ diff --git a/tests/tests/src/AsInUncurriedExternals.res b/tests/tests/src/AsInUncurriedExternals.res index a31ead4e4d..b19b91de90 100644 --- a/tests/tests/src/AsInUncurriedExternals.res +++ b/tests/tests/src/AsInUncurriedExternals.res @@ -17,4 +17,4 @@ let shouldNotFail: (~objectMode: _, ~name: string) => int = (~objectMode, ~name) @scope("somescope") external constantArgOnly: @as(json`{foo:true}`) _ => string = "somefn" -let constantValue = constantArgOnly +let x = constantArgOnly() From 878179b17837654034600257bcc2eb520026c31f Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Thu, 23 Oct 2025 10:28:48 +0200 Subject: [PATCH 3/4] Simplify --- compiler/frontend/ast_external_process.ml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/frontend/ast_external_process.ml b/compiler/frontend/ast_external_process.ml index d729dd9004..386d65c3b9 100644 --- a/compiler/frontend/ast_external_process.ml +++ b/compiler/frontend/ast_external_process.ml @@ -1026,8 +1026,7 @@ let handle_attributes (loc : Bs_loc.t) (type_annotation : Parsetree.core_type) let return_wrapper = check_return_wrapper loc external_desc.return_wrapper result_type in - let fn_type = Ast_helper.Typ.arrows ~loc args result_type in - ( fn_type, + ( Ast_helper.Typ.arrows ~loc args result_type, External_ffi_types.ffi_bs arg_type_specs return_wrapper ffi, unused_attrs, relative ) From 1badbece5a9d73e3fcdaacd0c2a18b8b2d23cf1b Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Thu, 23 Oct 2025 11:06:49 +0200 Subject: [PATCH 4/4] CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bc8de9f5e..00efe2a99a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ #### :bug: Bug fix - Fix @directive on function level with async and multiple parameters. https://github.com/rescript-lang/rescript/pull/7977 +- Fix fatal error for external with @as. https://github.com/rescript-lang/rescript/pull/7978 #### :memo: Documentation