fable-compiler/ts2fable

Callback with variable number of arguments, is converted into a function with a single argument

Opened this issue · 0 comments

A function with a variable number of arguments, passed as argument to another function, gets converted into a function with just a single argument:

declare function call(f: (...args: number[]) => void): void;

gets converted to

type [<AllowNullLiteral>] IExports =
    abstract call: f: (ResizeArray<float> -> unit) -> unit

I think the idea is for ResizeArray to contain all passed arguments. But it's actually just the first argument:

function call(f: (...args: number[]) => void): void {
    f(3, 5)
}
let [<Global>] call (f: ResizeArray<float> -> unit): unit = jsNative

call (fun args -> printfn "Args=%A" args)

(Fable repl)

Output:

args=3

Even worse: What is ResizeArray<float> in F#, is actually float during runtime -- which means functions of ResizeArray will fail at runtime or produce unexpected results:

call (fun args -> printfn "Length=%i" args.Count)
// Length=0
call (fun args -> for arg in args do printfn "* %f" arg)
// Uncaught TypeError: xs[Symbol.iterator] is not a function

(Fable repl)

Especially confusing when args is a type compatible to array during runtime like string:

function call(f: (...args: string[]) => void): void {
    f("Alpha", "Beta")
}
let [<Global>] call (f: ResizeArray<string> -> unit): unit = jsNative

call (fun args -> printfn "args=%A" args)
call (fun args -> printfn "Length=%i" args.Count)
call (fun args -> for arg in args do printfn "* %s" arg)

(Fable repl)

Output:

args=Alpha
Length=5
* A
* l
* p
* h
* a

-> only first argument -> iterating over characters of first string

Probably even more confusing with ...args: any[] instead of a fixed type.




I'm not sure what the correct code would be.

... in TS/JS is [<ParamArray>] in F# -- but Attributes aren't allowed here.
Currently the best option is probably to create an overload for each expected call by hand. But for that one must know with how many arguments the function gets called. (And it isn't auto-generated)