Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS] Support [<Mangle>] on interface #3943

Merged
merged 2 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Fable.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,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"
]
Loading