Fable: unexpected name mangling for abstract memebrs

Created on 19 Dec 2020  路  6Comments  路  Source: fable-compiler/Fable

Description

To define a JS class in Fable and call it later from JS, I follow fable docs#name-mangling. The documentation states that there is no name mangling for abstract memebrs, which is expected.

However, when trying out Fable 3 example#nodejs, the program went unexpected.

Repro code

Clone fable3-samples/nodejs, and use code like in the fable repl:
https://fable.io/repl/#?code=LYewJgrgNgpgBAQQA5ILACgMBcCeT4AiAksElADwDkWAfABQCUcAvBnO3LFnGAIZa8WcAMo4AzlhjAAdAGEQUWAGMsASxAA7MdIDiMDTABOqpdIAyqiVVqM2HYFIBGRuAH1X0pBDEALOAA8WOw4efl5pBDAwAIxY9ABtcnlSVVgwADleBzoAIgIchhoAXWw8Qmt6JmY4JSheMTFgzhhuVQ0DQyFiUgpqSqbeRwlDXhU4B2BnTq9fOAAuOGo4AFoaOAgNVSwmsBgAM15obndPbz9A6raO09n-Jox9MCA&html=Q&css=Q

Expected and actual results

expected push method definition:

    push(x) {
        const __ = this;
        DImpl$1__push_2B595(__.inner, x);
    }

the actual:

    ["App.D.push2B595"](x) {
        const __ = this;
        DImpl$1__push_2B595(__.inner, x);
    }

Related information

  • Fable version: dotnet fable --version

     Fable: F# to JS compiler 3.0.0-nagareyama-rc-011
     Thanks to the contributor! @mexx
     3.0.0-nagareyama-rc-011
    
  • Operating system: Win 10

Most helpful comment

Thanks for the explanations @thautwarm! It's great you're using F#/Fable with Leetcode. The fact that Fable doesn't attach members to classes comes from the way F# represents class members in the AST. At one point we tried to "correct" that, but it actually works quite well for tree shaking so we're considering it "a feature" now. However, it's true that for some interop scenarios we need to have classes that look exactly as in JS. Instead of the NoMangle attribute maybe we could add one like AttachMembers so Fable knows members must be attached and non-mangled for that class (overloads won't work in that case though). So you can write something like:

[<AttachMembers>]
type MinStack() =
    let inner = createMinStack()
    member _.push(a) = push(inner, a)
    member _.pop() = pop(inner)
    member _.top() = top(inner)
    member _.getMin() = getMin(inner)

And Fable will translate it as:

  export class MinStack{
    constructor(){
      this.inner = createMinStack()
    }
    push(a){ push(this.inner, a) }
    pop(){ pop(this.inner) }
    top(){ return top(this.inner) }
    getMin(){ return getMin(this.inner) }
  }

What do you think?

All 6 comments

Yes, sorry! Fable 3 supports a Mangle attribute when you need to explicitly mangle an interface (to support overloading or implementing a method with the same name from multiple interfaces), but for abstract classes Fable 3 does the mangling by default (unless the class is decorated with [<Import>] because I was just assuming these would only be used in F#.

We can go back to Fable 2 behavior (so you need to explicitly add Mangle attribute to abstract classes) or update the docs to say now only interface members are not mangled. What do you need to with the class exported to JS? Would it be possible to use an object expression instead? Something like:

type D<'t> =
    abstract push: 't -> unit

let D<'t>() =
    let data = System.Collections.Generic.List<'t>()
    { new D<'t> with
        member _.push x = data.Add(x) }

let d = D<int>()

but for abstract classes Fable 3 does the mangling by default (unless the class is decorated with [] because I was just assuming these would only be used in F#.

Thanks for this instruction.

We can go back to Fable 2 behavior (so you need to explicitly add Mangle attribute to abstract classes) or update the docs to say now only interface members are not mangled.

I remembered I saw you discussed a lot about the default name mangling for abstract members, hence I'm not a fan of rollback.

There may be another option: we keep the current behavior of Fable 3, and add a [<NoMangle>] to allow users to specify JS-interoperable fields for abstract classes. No name mangling for abstract members may introduce some issues(overloading, etc.), users working with [<NoMangle>] have to understand and maintain them.

F# [<CompiledName("D")>] type D<'t>() = [<NoMangle>] abstract push : 't -> unit default _.push x = ...

What do you need to with the class exported to JS?

I'm now teaching my friends F# and they feel like to use Leetcode to train their skills. Hence I made a template project to support writing Leetcode exercises with F#. This is achieved by Fable F#->JS transpilation(you guys are awesome).

However the Leetcode exercises sometimes require defining a JS class with designated shape. This seems to be impossible with pure F#. My current workaround is writing an emit statement manually.

Any instructions or suggestions from a Fable maintainer does help a lot!

Thanks for the explanations @thautwarm! It's great you're using F#/Fable with Leetcode. The fact that Fable doesn't attach members to classes comes from the way F# represents class members in the AST. At one point we tried to "correct" that, but it actually works quite well for tree shaking so we're considering it "a feature" now. However, it's true that for some interop scenarios we need to have classes that look exactly as in JS. Instead of the NoMangle attribute maybe we could add one like AttachMembers so Fable knows members must be attached and non-mangled for that class (overloads won't work in that case though). So you can write something like:

[<AttachMembers>]
type MinStack() =
    let inner = createMinStack()
    member _.push(a) = push(inner, a)
    member _.pop() = pop(inner)
    member _.top() = top(inner)
    member _.getMin() = getMin(inner)

And Fable will translate it as:

  export class MinStack{
    constructor(){
      this.inner = createMinStack()
    }
    push(a){ push(this.inner, a) }
    pop(){ pop(this.inner) }
    top(){ return top(this.inner) }
    getMin(){ return getMin(this.inner) }
  }

What do you think?

@alfonsogarciacaro

What do you think?

Perfect, indeed! Hope to see this sooner and I'll then close this issue!

The AttachMembers attribute is now included in Fable.Core 3.2.2, can you please give it a try? You will also need to update Fable to 3.0.5 in order to use it.

confirmed.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

MangelMaxime picture MangelMaxime  路  3Comments

forki picture forki  路  3Comments

MangelMaxime picture MangelMaxime  路  3Comments

et1975 picture et1975  路  3Comments

MangelMaxime picture MangelMaxime  路  3Comments