When generating an interface file, externals with [@bs] attributes are incorrect
I have a module which includes externals with attributes such as bs.send and bs.module:
type t;
[@bs.scope "thing"] [@bs.module "jsmodule"] external fromJSON: Js.Dict.t('a) => t = "fromJSON";
[@bs.send] external toJSON: t => Js.Dict.t('a) = "toJSON";
However, when I generate an interface file (using either the vscode-reasonml project or bsc -i in 5.1.0) the file it produces is invalid:
type t;
[@bs...] external fromJSON: Js.Dict.t('a) => t = "fromJSON";
[@bs...] external toJSON: t => Js.Dict.t('a) = "toJSON";
Copying the source definitions to the interface file works.
A similar issue happens with generating OCaml interfaces, this syntax:
type t
external fromJSON : 'a Js.Dict.t -> t = "fromJSON"[@@bs.scope "thing"] [@@bs.module "jsmodule"]
external toJSON : t -> 'a Js.Dict.t = "toJSON"[@@bs.send]
produces the generated interface:
type t
external fromJSON : 'a Js.Dict.t -> t = "fromJSON" "BS-EXTERNAL"
external toJSON : t -> 'a Js.Dict.t = "toJSON" "BS-EXTERNAL"
Again copying the source to the interface resolves the issue.
This is a known issue that we would like to fix eventually
I didn't mention it above, but when I remove the invalid syntax I get errors like this:
Values do not match:
[@bs...] external type_: t => string = ""
is not included in
external type_: t => string = ""
I managed to create an interface file with let function signatures instead but now my JS has a function wrapper for every external definition:
function MyModule_000(prim) {
return prim.type;
}
These simple functions shouldn't cause much runtime overhead, but the lack of externals on the interface makes the generated code using these externals a mess. It makes me want to delete the interface, which is really bad because it's full of unsafe things I want to hide.
@TheSpyder I am a bit lost, I think you can always write external with bs annotations in the interface by hand, does not it work
Oh I did say that, didn't I
Copying the source definitions to the interface file works.
I've had many occasions when copy/pasting doesn't work, but I looked a bit deeper and it turns out all of them were just very very confusing. For example cascading syntax errors resulting in a reason-vscode highlight on line 1 instead of at the correct location(s):
File "somefile.rei", line 15, characters 16-17:
Error: Syntax error: '}' expected
File "somefile.rei", line 4, characters 19-20:
Error: This '{' might be unmatched
File "somefile.rei", line 1:
Error: Error while running external preprocessor
I'm also dealing with a lot of complicated things:
- local modules, where the reasonml difference between
module X = {}andmodule X: {}is very subtle and easy to miss when copy/pasting, this is where the incorrect syntax error highlights tripped me up. [@bs.deriving abstract]with[@bs.as "..."]renames -reason-vscodehighlights thebs.asas unused in the interface file, and if I delete it the resulting error is entirely unhelpful:
Values do not match:
[@bs...] external object_: t => string = ""
is not included in
[@bs...] external object_: t => string = ""
I retract my statement above. Sorry. I can make this work.
I had some additional confusion trying to use [@bs.deriving] on a record with normal use in the module but hiding the creation function in the interface, in the end it looks like adding pri on the interface while leaving it out in the implementation does work. I had some weird errors similar to above but it's all lining up now.
This has become a lot worse in 7.2.2 (I'm not sure if it's specifically that version, I haven't tried to generate an interface for a while).
As an example, these externals:
[@bs.send] external empty: t => unit = "empty";
[@bs.send]
external isEmpty: (t, Js.Dict.t(bool), Js.Dict.t(bool), t => bool) => bool = "isEmpty";
Are generated to this mess in the interface:

@TheSpyder fixed in https://github.com/BuckleScript/bucklescript/pull/4284
Awesome, thank you! I'll test it when the next release is available.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
I guess I forgot to follow up two years ago. @bobzhang while it no longer prints binary data to the resi file, it's still wrong
sample implementation:
@scope("Something") @module("my-framework")
external assertHtml: (string, string, string) => unit = "assertHtml"
Generate interface creates:
external assertHtml: (string, string, string) => unit =
"assertHtml" "#rescript-external"