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 diff --git a/compiler/frontend/ast_external_process.ml b/compiler/frontend/ast_external_process.ml index b4efc128ad..386d65c3b9 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 @@ -1008,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 - ( build_uncurried_type ~arity:(List.length args) fn_type, + ( Ast_helper.Typ.arrows ~loc args result_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..ed66d6c77f 100644 --- a/tests/tests/src/AsInUncurriedExternals.mjs +++ b/tests/tests/src/AsInUncurriedExternals.mjs @@ -19,9 +19,12 @@ function shouldNotFail(objectMode, name) { return 3; } +let x = somescope.somefn({foo:true}); + export { mo, options, shouldNotFail, + x, } -/* No side effect */ +/* x Not a pure module */ diff --git a/tests/tests/src/AsInUncurriedExternals.res b/tests/tests/src/AsInUncurriedExternals.res index 97ec319375..b19b91de90 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 x = constantArgOnly()