glutinum-org / cli

https://glutinum.net/
57 stars 4 forks source link

union with function type generates invalid type alias #98

Open joprice opened 3 months ago

joprice commented 3 months ago

When a type parameter appears in a union, the type is generated as a type name, not a type variable, resulting in the error The type 'T' is not defined. Functions are also not yet handled in unions.

export type ValueOrFunction<T> = T | ((req: string) => T)

translates into

Current:

 type ValueOrFunction<'T> =
     U2<T, obj>

Expected:

 type ValueOrFunction<'T> =
     U2<'T, string -> 'T>

Example of npm package using this features:

https://github.com/LionC/express-basic-auth/blob/dd17b4de9fee9558269cdc583310bde5331456e7/express-basic-auth.d.ts#L58

MangelMaxime commented 3 months ago

Something I am not sure is when should we generate a lambda versus an "invokable" type.

For example,

export type ValueOrFunction1<T> = (req?: string) => T

generates

[<AllowNullLiteral>]
[<Interface>]
type ValueOrFunction1<'T> =
    [<Emit("$0($1...)")>]
    abstract member Invoke: ?req: string -> 'T

which has the benefit of support optional arguments which is not possible lambda as we would have something like string option -> 'T

MangelMaxime commented 3 months ago

In Glutinum.ExpressServeStaticCore what I did at the time to have the best of both world was generate some helpers:

https://github.com/glutinum-org/Glutinum/blob/52b2f2487aa2195fb04751df72f607aa6980f4b7/glues/ExpressServeStaticCore/src/Glutinum.ExpressServeStaticCore.fs#L80-L107

Perhaps, we should generate something like that:

open Fable.Core
open System

[<AllowNullLiteral>]
[<Interface>]
type ValueOrFunction<'T> =
    [<Emit("$0($1...)")>]
    abstract member Invoke: req: string * ?nextHandler : obj -> 'T

    [<Emit("$0")>]
    static member inline Create (f : Func<string option, string, 'T>) : ValueOrFunction<'T> = nativeOnly

// Here we can reate an instance to pass to the library
ValueOrFunction.Create(fun s a -> ()) |> ignore

// Imagine we get an instance of the function from the library
let func = ValueOrFunction.Create(fun s a -> ())

func.Invoke("request")
func.Invoke("request", null)
import { defaultOf } from "fable-library-js/Util.js";
import { some } from "fable-library-js/Option.js";

((s, a) => {
});

export const func = ((s, a) => {
});

func("request");

func("request", some(defaultOf()));

It allows us to create instance of the type in an "okay" way and also supports invoking the function when we get an instance of it from somewhere.