Skip to content

Commit

Permalink
Add support for static member on classes
Browse files Browse the repository at this point in the history
  • Loading branch information
MangelMaxime committed Mar 25, 2024
1 parent 7a20291 commit f7530a7
Show file tree
Hide file tree
Showing 104 changed files with 427 additions and 24 deletions.
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* Add support for argument spread operator ([GH-57](https://github.com/glutinum-org/cli/issues/57))
* Add support for `{ new (...args: any): any}` (`ConstructSignaure`) ([GH-59](https://github.com/glutinum-org/cli/issues/59))
* Add support for `static member` on classes ([GH-60](https://github.com/glutinum-org/cli/issues/60))

```ts
export class Class {
static methodA(): void;
static methodB(arg1 : string, arg2: string): void;
}
```

```fs
[<AllowNullLiteral>]
[<Interface>]
type Class =
static member inline methodA () =
emitJsExpr () $$"""
import { Class } from "module";
Class.methodA()"""
static member inline methodB (arg1: string, arg2: string) =
emitJsExpr (arg1, arg2) $$"""
import { Class } from "module";
Class.methodB($0, $1)"""
```

### Changed

* Replace `Boolean` with `bool`
* Map `Date` type to `JS.Date` ([GH-48](https://github.com/glutinum-org/cli/issues/48))
* Decorate all interface with `[<Interface>]` attribute this is to ensure they are erased at runtime even if they only have `static member` attached to them

## 0.4.0 - 2024-01-08

Expand Down
5 changes: 5 additions & 0 deletions hello.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class Hello {
static sayHello() {
console.log('Hello World');
}
}
29 changes: 29 additions & 0 deletions src/Glutinum.Converter/FsharpAST.fs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ type FSharpAttribute =
| Global
| ParamObject
| ParamArray
| Interface

type FSharpParameter =
{
Expand All @@ -200,15 +201,43 @@ type FSharpMemberInfo =
Accessibility: FSharpAccessibility
}

type FSharpStaticMemberInfo =
{
Attributes: FSharpAttribute list
Name: string
// We need the original because we emit actual JavaScript code
// for interface static members.
OriginalName: string
TypeParameters: FSharpTypeParameter list
Parameters: FSharpParameter list
Type: FSharpType
IsOptional: bool
Accessor: FSharpAccessor option
Accessibility: FSharpAccessibility
}

[<RequireQualifiedAccess>]
type FSharpMember =
| Method of FSharpMemberInfo
| Property of FSharpMemberInfo
/// <summary>
/// Special case for static members used, when generating a static interface
/// binding on a class.
///
/// For binding a static method of a class, we need to generate the body of the
/// method so instead of trying to hack our way via the standard method binding
/// we use this special case.
/// See: https://github.com/glutinum-org/cli/issues/60
/// </summary>
| StaticMember of FSharpStaticMemberInfo

type FSharpInterface =
{
Attributes: FSharpAttribute list
Name: string
// We need the original because we emit actual JavaScript code
// for interface static members.
OriginalName: string
TypeParameters: FSharpTypeParameter list
Members: FSharpMember list
}
Expand Down
2 changes: 1 addition & 1 deletion src/Glutinum.Converter/Generate.fs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ let generateBindingFile (filePath: string) =
let outFile =
{
Name = "Glutinum"
Opens = [ "Fable.Core"; "System" ]
Opens = [ "Fable.Core"; "Fable.Core.JsInterop"; "System" ]
}

Printer.printOutFile printer outFile
Expand Down
86 changes: 86 additions & 0 deletions src/Glutinum.Converter/Printer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ let private attributeToText (fsharpAttribute: FSharpAttribute) =
| FSharpAttribute.ParamObject -> "[<ParamObject>]"
| FSharpAttribute.EmitSelf -> "[<Emit(\"$0\")>]"
| FSharpAttribute.ParamArray -> "[<ParamArray>]"
| FSharpAttribute.Interface -> "[<Interface>]"

let private printInlineAttribute
(printer: Printer)
Expand Down Expand Up @@ -423,6 +424,91 @@ let private printInterface (printer: Printer) (interfaceInfo: FSharpInterface) =
|> Option.iter printer.WriteInline

printer.NewLine

| FSharpMember.StaticMember staticMemberInfo ->
printCompactAttributesAndNewLine printer staticMemberInfo.Attributes

printer.Write($"static member inline {staticMemberInfo.Name} ")

printTypeParameters printer staticMemberInfo.TypeParameters

if staticMemberInfo.Parameters.IsEmpty then
printer.WriteInline("() =")
printer.Indent
printer.NewLine

// I don't know how to print """ from inside an F# string interpolation
// so I am using a list of strings and joining them
// We want to generate the like with newlines ourselves
// like that we can control the indentation and have stuff from the second line
// indented on column 0
[
"emitJsExpr () $$\"\"\""
"import { Class } from \"module\";"
$"%s{interfaceInfo.OriginalName}.%s{staticMemberInfo.OriginalName}()"
]
|> String.concat "\n"
|> printer.Write

// Put the closing """ right after the last line of the list
printer.WriteInline "\"\"\""

printer.NewLine

printer.Unindent

else
printer.WriteInline("(")

staticMemberInfo.Parameters
|> List.iteri (fun index p ->
if index <> 0 then
printer.WriteInline(", ")

printInlineAttributes printer p.Attributes

if p.IsOptional then
printer.WriteInline("?")

printer.WriteInline($"{p.Name}: {printType p.Type}")

if hasParamArrayAttribute p.Attributes then
printer.WriteInline(" []")
)

printer.WriteInline(") =")
printer.NewLine

printer.Indent

let forwardedArgments =
staticMemberInfo.Parameters
|> List.map (fun p -> p.Name)
|> String.concat ", "

let macroArguments =
staticMemberInfo.Parameters
|> List.mapi (fun index _ -> $"$%i{index}")
|> String.concat ", "

// I don't know how to print """ from inside an F# string interpolation
// so I am using a list of strings and joining them
// We want to generate the like with newlines ourselves
// like that we can control the indentation and have stuff from the second line
// indented on column 0
[
$"emitJsExpr (%s{forwardedArgments}) $$\"\"\""
"import { Class } from \"module\";"
$"%s{interfaceInfo.OriginalName}.%s{staticMemberInfo.OriginalName}(%s{macroArguments})"
]
|> String.concat "\n"
|> printer.Write

// Put the closing """ right after the last line of the list
printer.WriteInline "\"\"\""

printer.NewLine
printer.Unindent
)

if interfaceInfo.Members.Length = 0 then
Expand Down
76 changes: 53 additions & 23 deletions src/Glutinum.Converter/Transform.fs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ let private transformExports
{
Attributes = [ FSharpAttribute.Erase ]
Name = "Exports"
OriginalName = "Exports"
Members = members
TypeParameters = []
}
Expand Down Expand Up @@ -449,20 +450,36 @@ module private TransformMembers =
let name, context =
sanitizeNameAndPushScope methodInfo.Name context

{
Attributes = []
Name = name
Parameters =
methodInfo.Parameters
|> List.map (transformParameter context)
Type = transformType context methodInfo.Type
TypeParameters = []
IsOptional = methodInfo.IsOptional
IsStatic = methodInfo.IsStatic
Accessor = None
Accessibility = FSharpAccessibility.Public
}
|> FSharpMember.Method
if methodInfo.IsStatic then
{
Attributes = []
Name = name
OriginalName = methodInfo.Name
Parameters =
methodInfo.Parameters
|> List.map (transformParameter context)
Type = transformType context methodInfo.Type
TypeParameters = []
IsOptional = methodInfo.IsOptional
Accessor = None
Accessibility = FSharpAccessibility.Public
}
|> FSharpMember.StaticMember
else
{
Attributes = []
Name = name
Parameters =
methodInfo.Parameters
|> List.map (transformParameter context)
Type = transformType context methodInfo.Type
TypeParameters = []
IsOptional = methodInfo.IsOptional
IsStatic = methodInfo.IsStatic
Accessor = None
Accessibility = FSharpAccessibility.Public
}
|> FSharpMember.Method

| GlueMember.CallSignature callSignatureInfo ->
let name, context = sanitizeNameAndPushScope "Invoke" context
Expand Down Expand Up @@ -641,8 +658,10 @@ let private transformInterface
let name, context = sanitizeNameAndPushScope info.Name context

{
Attributes = [ FSharpAttribute.AllowNullLiteral ]
Attributes =
[ FSharpAttribute.AllowNullLiteral; FSharpAttribute.Interface ]
Name = name
OriginalName = info.Name
Members = TransformMembers.toFSharpMember context info.Members
TypeParameters = transformTypeParameters context info.TypeParameters
}
Expand Down Expand Up @@ -1074,15 +1093,20 @@ let private transformTypeAliasDeclaration
| FSharpMember.Property property ->
{ property with IsOptional = true }
|> FSharpMember.Property
| FSharpMember.StaticMember staticMember ->
{ staticMember with IsOptional = true }
|> FSharpMember.StaticMember
)
}

FSharpType.Interface partialInterface

| GlueType.FunctionType functionType ->
{
Attributes = [ FSharpAttribute.AllowNullLiteral ]
Attributes =
[ FSharpAttribute.AllowNullLiteral; FSharpAttribute.Interface ]
Name = typeAliasName
OriginalName = glueTypeAliasDeclaration.Name
TypeParameters =
transformTypeParameters
context
Expand Down Expand Up @@ -1120,8 +1144,10 @@ let private transformTypeAliasDeclaration

| GlueType.IntersectionType members ->
{
Attributes = [ FSharpAttribute.AllowNullLiteral ]
Attributes =
[ FSharpAttribute.AllowNullLiteral; FSharpAttribute.Interface ]
Name = typeAliasName
OriginalName = glueTypeAliasDeclaration.Name
TypeParameters =
transformTypeParameters
context
Expand All @@ -1132,8 +1158,10 @@ let private transformTypeAliasDeclaration

| GlueType.TypeLiteral typeLiteralInfo ->
{
Attributes = [ FSharpAttribute.AllowNullLiteral ]
Attributes =
[ FSharpAttribute.AllowNullLiteral; FSharpAttribute.Interface ]
Name = typeAliasName
OriginalName = glueTypeAliasDeclaration.Name
TypeParameters =
transformTypeParameters
context
Expand Down Expand Up @@ -1174,8 +1202,10 @@ let private transformClassDeclaration
let name, context = sanitizeNameAndPushScope classDeclaration.Name context

({
Attributes = [ FSharpAttribute.AllowNullLiteral ]
Attributes =
[ FSharpAttribute.AllowNullLiteral; FSharpAttribute.Interface ]
Name = name
OriginalName = classDeclaration.Name
Members =
TransformMembers.toFSharpMember context classDeclaration.Members
TypeParameters =
Expand All @@ -1187,8 +1217,10 @@ let private transformClassDeclaration
let private transformFunctionType (functionTypeInfo: GlueFunctionType) =

({
Attributes = [ FSharpAttribute.AllowNullLiteral ]
Attributes =
[ FSharpAttribute.AllowNullLiteral; FSharpAttribute.Interface ]
Name = "dwdw"
OriginalName = "dwdw"
Members = []
TypeParameters = []
}
Expand Down Expand Up @@ -1219,9 +1251,7 @@ let rec private transformToFsharp
| GlueType.ClassDeclaration classInfo ->
transformClassDeclaration context classInfo

| GlueType.FunctionType functionTypeInfo ->
transformFunctionType functionTypeInfo

| GlueType.FunctionType _
| GlueType.TypeParameter _
| GlueType.Partial _
| GlueType.Array _
Expand Down
1 change: 1 addition & 0 deletions src/Glutinum.Web/Pages/Editors.FSharpCode.fs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ let private generateFile
Opens =
[
"Fable.Core"
"Fable.Core.JsInterop"
"System"
]
}
Expand Down
17 changes: 17 additions & 0 deletions test.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Bindings
(***)
#r "nuget: Fable.Core"
(***)

open Fable.Core
open Fable.Core.JsInterop

[<AllowNullLiteral>]
[<Interface>]
type Hello =
static member inline sayHello () : unit =
emitJsExpr () $$"""
import { Hello } from "./hello.js";
Hello.sayHello()"""

Hello.sayHello()
2 changes: 2 additions & 0 deletions tests/specs/references/class/constructorsWithSpread.fsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module rec Glutinum

open Fable.Core
open Fable.Core.JsInterop
open System

[<Erase>]
Expand All @@ -11,6 +12,7 @@ type Exports =
static member Logger ([<ParamArray>] args: string []) : Logger = nativeOnly

[<AllowNullLiteral>]
[<Interface>]
type Logger =
interface end

Expand Down
Loading

0 comments on commit f7530a7

Please sign in to comment.