Skip to content

Commit

Permalink
[TS] Support [<Mangle>] on interface (#3943)
Browse files Browse the repository at this point in the history
  • Loading branch information
MangelMaxime authored Oct 28, 2024
1 parent 09117e0 commit dffde1a
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 70 deletions.
1 change: 1 addition & 0 deletions src/Fable.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [All/Rust] Removed Regex.Replace from hot paths (by @ncave)
* [JS] Fix regression, generate `let` variable when using `import` on a private mutable variable (by @MangelMaxime)
* [TS] Prevent generics to be duplicated (by @MangelMaxime)
* [TS] Fix interface generation when decorated with `Mangle` (by @MangelMaxime)

## 4.22.0 - 2024-10-02

Expand Down
191 changes: 122 additions & 69 deletions src/Fable.Transforms/Fable2Babel.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3826,91 +3826,144 @@ module Util =
]

let transformInterfaceDeclaration (com: IBabelCompiler) ctx (decl: Fable.ClassDecl) (ent: Fable.Entity) =
let getters, methods =
let makeAbstractMembers (info: Fable.MemberFunctionOrValue) (prop: Expression) (isComputed: bool) =
let args =
info.CurriedParameterGroups
|> List.concat
// |> FSharp2Fable.Util.discardUnitArg
|> List.toArray

let argsLen = Array.length args

let args =
args
|> Array.mapi (fun i a ->
let name = defaultArg a.Name $"arg{i}"

let ta =
if a.IsOptional then
unwrapOptionalType a.Type
else
a.Type
|> FableTransforms.uncurryType
|> makeTypeAnnotation com ctx

Parameter
.parameter(name, ta)
.WithFlags(
ParameterFlags(
isOptional = a.IsOptional,
isSpread = (i = argsLen - 1 && info.HasSpread),
isNamed = a.IsNamed
)
)
)

let typeParams =
info.GenericParameters
|> List.map (fun g -> Fable.GenericParam(g.Name, g.IsMeasure, g.Constraints))
|> makeTypeParamDecl com ctx

let returnType = makeTypeAnnotation com ctx info.ReturnParameter.Type

AbstractMember.abstractMethod (
ObjectMeth,
prop,
args,
returnType = returnType,
typeParameters = typeParams,
isComputed = isComputed,
?doc = info.XmlDoc
)

let filterdMembers =
ent.MembersFunctionsAndValues
// It's not usual to have getters/setters in TS interfaces, so let's ignore setters
// and compile getters as fields
|> Seq.filter (fun info ->
not (info.IsProperty || info.IsSetter)
// TODO: Deal with other emit attributes like EmitMethod or EmitConstructor
&& not (hasAttribute Atts.emitAttr info.Attributes)
)
|> Seq.toArray
|> Array.partition (fun info -> info.IsGetter)

let getters =
getters
|> Array.map (fun info ->
let prop, isComputed = memberFromName info.DisplayName

let isOptional, typ =
makeAbstractPropertyAnnotation com ctx info.ReturnParameter.Type

AbstractMember.abstractProperty (
prop,
typ,
isComputed = isComputed,
isOptional = isOptional,
?doc = info.XmlDoc
)
)

let methods =
methods
|> Array.map (fun info ->
let prop, isComputed = memberFromName info.CompiledName
let members =
if hasAttribute Atts.mangle ent.Attributes then
let generateMemberName (info: Fable.MemberFunctionOrValue) =
let entityGenericParameters = ent.GenericParameters |> List.map (fun g -> g.Name)

let args =
info.CurriedParameterGroups
|> List.concat
// |> FSharp2Fable.Util.discardUnitArg
|> List.toArray
let overloadSuffix =
if info.IsGetter || info.IsSetter then
""
else
info.CurriedParameterGroups
|> List.map (fun groups -> groups |> List.map (fun group -> group.Type))
|> OverloadSuffix.getHash entityGenericParameters

let argsLen = Array.length args
let genericSuffix =
if ent.GenericParameters.Length > 0 then
$"`{ent.GenericParameters.Length}"
else
""

let args =
args
|> Array.mapi (fun i a ->
let name = defaultArg a.Name $"arg{i}"
let getterOrSetterPrefix =
if info.IsGetter then
"get_"
else if info.IsSetter then
"set_"
else
""

let ta =
if a.IsOptional then
unwrapOptionalType a.Type
else
a.Type
|> FableTransforms.uncurryType
|> makeTypeAnnotation com ctx

Parameter
.parameter(name, ta)
.WithFlags(
ParameterFlags(
isOptional = a.IsOptional,
isSpread = (i = argsLen - 1 && info.HasSpread),
isNamed = a.IsNamed
)
)
)
let fullNamePath, memberName =
let lastDotIndex = info.FullName.LastIndexOf('.')

let typeParams =
info.GenericParameters
|> List.map (fun g -> Fable.GenericParam(g.Name, g.IsMeasure, g.Constraints))
|> makeTypeParamDecl com ctx
info.FullName.Substring(0, lastDotIndex), info.FullName.Substring(lastDotIndex + 1)

let returnType = makeTypeAnnotation com ctx info.ReturnParameter.Type
let name =
fullNamePath
+ genericSuffix
+ "."
+ getterOrSetterPrefix
+ memberName
+ overloadSuffix

AbstractMember.abstractMethod (
ObjectMeth,
prop,
args,
returnType = returnType,
typeParameters = typeParams,
isComputed = isComputed,
?doc = info.XmlDoc
memberFromName name

filterdMembers
|> Seq.map (fun info ->
let prop, isComputed = generateMemberName info

makeAbstractMembers info prop isComputed
)
)
|> Seq.toArray

else
let getters, methods =
filterdMembers |> Seq.toArray |> Array.partition (fun info -> info.IsGetter)

let getters =
getters
|> Array.map (fun info ->
let prop, isComputed = memberFromName info.DisplayName

let isOptional, typ =
makeAbstractPropertyAnnotation com ctx info.ReturnParameter.Type

AbstractMember.abstractProperty (
prop,
typ,
isComputed = isComputed,
isOptional = isOptional,
?doc = info.XmlDoc
)
)

let methods =
methods
|> Array.map (fun info ->
let prop, isComputed = memberFromName info.CompiledName

makeAbstractMembers info prop isComputed
)

let members = Array.append getters methods
Array.append getters methods

let extends =
ent.DeclaredInterfaces
Expand Down
73 changes: 72 additions & 1 deletion tests/Js/Main/TypeTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,53 @@ type InfoB = {
Bar: string
}

[<Mangle>]
type IUpperMangledInterface =
abstract Upper: string -> string
abstract Ping: unit -> string
abstract Value: string with get, set
abstract ReadOnlyValue: string with get
abstract SetterOnlyValue: string with set

type UpperMangledClass() =
let mutable innerValue = ""

interface IUpperMangledInterface with
member _.Upper s = s.ToUpper()
member _.Ping() = "pong"

member _.Value
with get () = innerValue
and set v = innerValue <- v

member _.ReadOnlyValue = innerValue
member _.SetterOnlyValue
with set v = innerValue <- v

[<Mangle>]
type IGenericMangledInterface<'T> =
abstract Upper: string -> string
abstract Ping: unit -> string
abstract Value: 'T with get, set
abstract ReadOnlyValue: string with get
abstract SetterOnlyValue: 'T with set

type GenericMangledClass() =
let mutable innerValue = ""

interface IGenericMangledInterface<string> with
member _.Upper s = s.ToUpper()
member _.Ping() = "pong"

member _.Value
with get () = innerValue
and set v = innerValue <- v

member _.ReadOnlyValue = innerValue

member _.SetterOnlyValue
with set v = innerValue <- v

#if !FABLE_COMPILER_TYPESCRIPT
[<AbstractClass>]
type InfoAClass(info: InfoA) =
Expand All @@ -370,7 +417,7 @@ type FooInterface =
abstract Item: int -> char with get, set
abstract Sum: [<ParamArray>] items: string[] -> string

[<Fable.Core.Mangle>]
[<Mangle>]
type BarInterface =
abstract Bar: string with get, set
abstract DoSomething: f: (float -> float -> float) * v: float -> float
Expand Down Expand Up @@ -1322,4 +1369,28 @@ let tests =
let mutable arr = [| 1; 2; 3 |]
let result = genericByrefFunc &arr
result |> equal 3

testCase "mangled method on interface works"
<| fun () ->
let upper = UpperMangledClass()
(upper :> IUpperMangledInterface).Upper("hello") |> equal "HELLO"
(upper :> IUpperMangledInterface).Ping() |> equal "pong"
(upper :> IUpperMangledInterface).Value |> equal ""
(upper :> IUpperMangledInterface).Value <- "value"
(upper :> IUpperMangledInterface).Value |> equal "value"
(upper :> IUpperMangledInterface).ReadOnlyValue |> equal "value"
(upper :> IUpperMangledInterface).SetterOnlyValue <- "setter only value"
(upper :> IUpperMangledInterface).Value |> equal "setter only value"

testCase "mangled method on generic interface works"
<| fun () ->
let upper = GenericMangledClass()
(upper :> IGenericMangledInterface<string>).Upper("hello") |> equal "HELLO"
(upper :> IGenericMangledInterface<string>).Ping() |> equal "pong"
(upper :> IGenericMangledInterface<string>).Value |> equal ""
(upper :> IGenericMangledInterface<string>).Value <- "value"
(upper :> IGenericMangledInterface<string>).Value |> equal "value"
(upper :> IGenericMangledInterface<string>).ReadOnlyValue |> equal "value"
(upper :> IGenericMangledInterface<string>).SetterOnlyValue <- "setter only value"
(upper :> IGenericMangledInterface<string>).Value |> equal "setter only value"
]

0 comments on commit dffde1a

Please sign in to comment.