fable-compiler / Fable

F# to JavaScript, TypeScript, Python, Rust and Dart Compiler
http://fable.io/
MIT License
2.92k stars 300 forks source link

abstract static member invocation (still) not working in JS #3829

Open DunetsNM opened 4 months ago

DunetsNM commented 4 months ago

Description

It's claimed in Fable 4.18.0 release notes that static interfaces are now supported however my code still fails to compile to JS.

Repro code

It's simplified version of #3822 I reported before (claimed to be fixed by 4.18.0 but it's actually not), you can try to run it in https://fable.io/repl/

type AbstractId<'ImplId when 'ImplId :> AbstractId<'ImplId>> =
    static abstract member Parse: idStr: string -> 'ImplId

type MyId = MyId of int
with 
  interface AbstractId<MyId> with
    static member Parse (idStr: string) =
        System.Int32.Parse idStr |> MyId

let inline parse<'ImplId when 'ImplId :> AbstractId<'ImplId>> (idStr: string) : 'ImplId =
    'ImplId.Parse idStr

printfn "Id constructed: %A" (MyId 1234)
printfn "Id parsed: %A" (parse<MyId> "1234")

Expected and actual results

Expected

Code outputs

Id constructed: MyId 1234 Id parsed: MyId 1234

Just like it does in LINQPad, for example:

image

Actual

It fails to compile. Note that it passes F# compilation step but fails further translation to JS

image

Related information

If I comment out last line it compiles and runs fine:

image

DunetsNM commented 4 months ago

@ncave I did read the other linked issue #3566 and the comment you left on my previous report : https://github.com/fable-compiler/Fable/issues/3822#issuecomment-2126342711

So this is the example code that compiles:

[<Interface>]
type ITesting =
    static member inline Testing x = x

However I still can't understand this pattern nor how to translate my AbstractId<> interface to it. Testing member is not abstract after all, and the ITesting interface has no concrete implementations.

DunetsNM commented 4 months ago

I hope to reuse this code both in the Fable frontend and F# backend, don't mind some conditional compilation if that's the only way currently. But is it even possible at the moment to make it Fable-friendly?

type AbstractId<'ImplId when 'ImplId :> AbstractId<'ImplId>> =
    static abstract member Parse: idStr: string -> 'ImplId

It's defined in a separate library, any concrete "Id" type that implements the interface should be parseable from a string.

ncave commented 4 months ago

@DunetsNM You are correct, this is a different issue than #3566. Also, we can leave #3822 closed and keep this one, as they're the same issue.

3566 was about the F# 8.0 feature concrete static members with implementation in interfaces.

This one is about the F# 7.0 feature abstract static methods in interfaces. I agree it's a feature we should try to support in Fable, despite its drawbacks and misunderstandings.

Until this is fixed, perhaps some other type constraints can work for you as a temporary work-around.

(Side note, please keep in mind that Fable REPL usually lags behind recent Fable releases, so it's not always the best place to check recent Fable release fixes).

MangelMaxime commented 4 months ago

@DunetsNM You are correct, this is a different issue than https://github.com/fable-compiler/Fable/issues/3566. Also, we can leave https://github.com/fable-compiler/Fable/issues/3822 closed and keep this one, as they're the same issue.

Sorry, I thought it was fixed by the latest PR but misunderstood it.

(Side note, please keep in mind that Fable REPL usually lags behind recent Fable releases, so it's not always the best place to check recent Fable release fixes).

In general, now days it is updated with each Fable release. I just need to click 2 buttons so unless I forget about it all is good. I was not able to find a way to trigger it from Fable release unfortunately...

DunetsNM commented 4 months ago

No worries guys and thanks for the context.

Until this is fixed, perhaps some other type constraints can work for you as a temporary work-around.

Sure, we use SRTP in some places however I find it more limited than IWSAM e.g. member-constrained generic type can't be a part of enclosing generic type signature, it must belong to a function or a member signature. Also after upgrading old net6.0 SRTP-heavy code to net7.0 or net8.0 it started to compile much slower (haven't benchmarked it against IWSAM but hope that it'll be faster). Anyway, neither of those are Fable-specific issues.

ncave commented 4 months ago

Some thoughts on JS/TS implementation:

Since TypeScript doesn't support static methods in interfaces, we'll have to change the calling convention for those static calls and pass the class that implements the static interface as the first argument, similar to how class methods work in Python.