fable-compiler / Fable

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

attempting to set a non-property member in `jsOptions` generates in valid js #3861

Open joprice opened 3 months ago

joprice commented 3 months ago

Description

When using jsOptions with an interface, a non-property member (one missing a getter or setting) generates invalid js, a local FSharpRef that references a non-existent identifier copyOfStruct.

Repro code

open Fable.Core.JsInterop

type Response =  
  abstract fn: int -> int
  abstract fnProp: (int -> int) with get, set
  abstract prop: bool with get, set

let res = jsOptions<Response> (
  fun o -> 
           o.fn <- (fun i -> i)
           o.fnProp <- (fun i -> i)
           o.prop <- false
  )
import { jsOptions } from "fable-library-js/Util.js";
import { FSharpRef } from "fable-library-js/Types.js";

export const res = jsOptions((o) => {
    const addr = new FSharpRef(() => copyOfStruct, (v) => {
        copyOfStruct = v;
    });
    addr.contents = ((i) => i);
    o.fnProp = ((i_1) => i_1);
    o.prop = false;
});

Related information

MangelMaxime commented 3 months ago

I can't remember if Is abstract fn: int -> int equivalent to abstract fn: int -> int with get, set in F#?

I guess it is because the F# compiler don't complains during compilation.

For as long, as I remember we always recommended to use with get, set on interfaces used for POJO. However, I don't know if there is a reason for that or if supporting cases without with get, set has just been forgotten.

joprice commented 3 months ago

Going by the syntax tree alone, it seems to be a difference between the flags MemberKind:Member and MemberKind:PropertyGetSet:

https://sharplab.io/#v2:DYLgZgzgNALiCGEZQCYgNQB8YE8AOApgAQBKBEeA9gHYTEC8RRAsAFBPwBGSATvAMYwiYaiCIBLakIC0APglS2HbjD6Dh1AExiAFJJnz9ASiIB3cTAAWROjCVEuvAUJEAFHpTy79ROQpgm5lZEAOYEyDbhbEA===

Adding parens around the function type turns it from a plain member into a gettable property, which causes a compilation error in the example below since there's no setter defined. So it likes like a missing validation to me.

type Response =  
  abstract fn: int -> int
  abstract fn2: (int -> int)
  abstract fn3: (int -> int) with set
  abstract fnProp: (int -> int) with get, set
  abstract prop: bool with get, set

let res = jsOptions<Response> (
  fun o -> 
           o.fn <- (fun i -> i)
           // this line fails to compile
           o.fn2 <- (fun i -> i)
           o.fn3 <- (fun i -> i)
           o.fnProp <- (fun i -> i)
           o.prop <- false
  )