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] Emit interface declaration when encountering a ParamObject pattern #4007

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
29 changes: 29 additions & 0 deletions src/Fable.Transforms/FSharp2Fable.Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,35 @@ module Helpers =
| None -> false
)

// When compiling to TypeScript, we want to captuer classes that use the
// [<Global>] attribute on the type and[<ParamObject>] on the constructors
// so we can transform it into an interface

/// <summary>
/// Check if the entity is decorated with the <code>Global</code> attribute
/// and all its constructors are decorated with <code>ParamObject</code> attribute.
///
/// This is used to identify classes that should be transformed into interfaces.
/// </summary>
/// <param name="entity"></param>
/// <returns>
/// <code>true</code> if the entity is a global type with all constructors as param objects,
/// <code>false</code> otherwise.
/// </returns>
let isParamObjectClassPattern (entity: Fable.Entity) =
let isGlobalType =
entity.Attributes |> Seq.exists (fun att -> att.Entity.FullName = Atts.global_)

let areAllConstructorsParamObject =
entity.MembersFunctionsAndValues
|> Seq.filter _.IsConstructor
|> Seq.forall (fun memb ->
memb.Attributes
|> Seq.exists (fun att -> att.Entity.FullName = Atts.paramObject)
)

isGlobalType && areAllConstructorsParamObject

let tryPickAttrib attFullNames (attributes: FSharpAttribute seq) =
let attFullNames = Map attFullNames

Expand Down
3 changes: 2 additions & 1 deletion src/Fable.Transforms/FSharp2Fable.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2044,7 +2044,8 @@ let rec private transformDeclarations (com: FableCompiler) ctx fsDecls =

if
(isErasedOrStringEnumEntity ent && Compiler.Language <> TypeScript)
|| isGlobalOrImportedEntity ent
|| (isGlobalOrImportedEntity ent
&& (not (Compiler.Language = TypeScript && isParamObjectClassPattern ent)))
then
[]
else
Expand Down
103 changes: 77 additions & 26 deletions src/Fable.Transforms/Fable2Babel.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3782,6 +3782,50 @@ module Util =

declareType com ctx ent entName args body baseExpr classMembers

let transformParamObjectClassPatternToInterface
(com: IBabelCompiler)
ctx
(classDecl: Fable.ClassDecl)
(ent: Fable.Entity)
=
let members =
ent.MembersFunctionsAndValues
|> Seq.filter _.IsConstructor
|> Seq.map (fun constructor ->
constructor.CurriedParameterGroups
|> List.concat
|> List.mapi (fun index arg ->
let name = defaultArg arg.Name $"arg{index}"

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

AbstractMember.abstractProperty (
name |> Identifier.identifier |> Expression.Identifier,
typeAnnotation,
isOptional = arg.IsOptional
)
)
)
|> Seq.concat
|> Seq.toArray


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

Declaration.interfaceDeclaration (Identifier.identifier classDecl.Name, members, [||], typeParameters)
|> asModuleDeclaration ent.IsPublic
|> List.singleton


let transformClassWithPrimaryConstructor
(com: IBabelCompiler)
ctx
Expand Down Expand Up @@ -4124,33 +4168,40 @@ module Util =
else
[]
| ent ->
let classMembers =
decl.AttachedMembers
|> List.toArray
|> Array.collect (fun memb ->
withCurrentScope ctx memb.UsedNames
<| fun ctx ->
memb.ImplementedSignatureRef
|> Option.bind (com.TryGetMember)
|> Option.orElseWith (fun () -> com.TryGetMember(memb.MemberRef))
|> function
| None -> [||]
| Some info ->
if not memb.IsMangled && (info.IsGetter || info.IsSetter) then
transformAttachedProperty com ctx ent info memb
else
transformAttachedMethod com ctx ent info memb
)
if
Compiler.Language = TypeScript
&& FSharp2Fable.Helpers.isParamObjectClassPattern ent
then
transformParamObjectClassPatternToInterface com ctx decl ent
else

match decl.Constructor with
| Some cons ->
withCurrentScope ctx cons.UsedNames
<| fun ctx -> transformClassWithPrimaryConstructor com ctx ent decl classMembers cons
| None ->
if ent.IsFSharpUnion then
transformUnion com ctx ent decl.Name classMembers
else
transformClassWithCompilerGeneratedConstructor com ctx ent decl.Name classMembers
let classMembers =
decl.AttachedMembers
|> List.toArray
|> Array.collect (fun memb ->
withCurrentScope ctx memb.UsedNames
<| fun ctx ->
memb.ImplementedSignatureRef
|> Option.bind (com.TryGetMember)
|> Option.orElseWith (fun () -> com.TryGetMember(memb.MemberRef))
|> function
| None -> [||]
| Some info ->
if not memb.IsMangled && (info.IsGetter || info.IsSetter) then
transformAttachedProperty com ctx ent info memb
else
transformAttachedMethod com ctx ent info memb
)

match decl.Constructor with
| Some cons ->
withCurrentScope ctx cons.UsedNames
<| fun ctx -> transformClassWithPrimaryConstructor com ctx ent decl classMembers cons
| None ->
if ent.IsFSharpUnion then
transformUnion com ctx ent decl.Name classMembers
else
transformClassWithCompilerGeneratedConstructor com ctx ent decl.Name classMembers

let transformImports (imports: Import seq) : ModuleDeclaration list =
let statefulImports = ResizeArray()
Expand Down
Loading