diff --git a/.vscode/launch.json b/.vscode/launch.json index 0c4430cf8c..db2a335954 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -46,7 +46,7 @@ "name": "Run bench-compiler (.NET)", "program": "${workspaceFolder}/src/fable-standalone/test/bench-compiler/bin/Debug/netcoreapp3.1/bench-compiler.dll", // "args": ["${workspaceRoot}/tests/Main/Fable.Tests.fsproj", "out-tests"], - "args": ["${workspaceRoot}/../fable-test/fable-test.fsproj", "out-test"], + "args": ["${workspaceRoot}/../fable-test/fable-test.fsproj", "out-test", "--typescript"], "cwd": "${workspaceFolder}/src/fable-standalone/test/bench-compiler" }, { diff --git a/src/Fable.Transforms/AST/AST.Babel.fs b/src/Fable.Transforms/AST/AST.Babel.fs index 0ee4784153..359d5e8969 100644 --- a/src/Fable.Transforms/AST/AST.Babel.fs +++ b/src/Fable.Transforms/AST/AST.Babel.fs @@ -1112,12 +1112,11 @@ type ClassProperty(key, ?value, ?computed_, ?``static``, ?optional, ?typeAnnotat printer.PrintOptional(typeAnnotation) printer.PrintOptional(": ", value) -type ClassImplements(id, ?typeParameters, ?loc) = +type ClassImplements(id, ?typeParameters) = member _.Id: Identifier = id member _.TypeParameters: TypeParameterInstantiation option = typeParameters interface Expression with member _.Print(printer) = - printer.Print(" implements ", ?loc=loc) printer.Print(id) printer.PrintOptional(typeParameters) diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index 63197874ec..76f45c3b7f 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -347,21 +347,6 @@ module Annotation = GenericTypeAnnotation(Identifier name, ?typeParameters=typeParamInst) :> TypeAnnotationInfo |> TypeAnnotation |> Some - let makeInterfaceDecl (com: IBabelCompiler) ctx (ent: Fable.Entity) (entName: string) (baseExpr: Expression option) = - let genTypeParams = getEntityGenParams ent - let newTypeParams = Set.difference genTypeParams ctx.ScopedTypeParams - let ctx = { ctx with ScopedTypeParams = Set.union ctx.ScopedTypeParams newTypeParams } - let attached = Util.getEntityExplicitInterfaceMembers com ctx ent - let extends = - Util.getInterfaceExtends com ctx ent - |> Seq.toArray - |> function [||] -> None | e -> Some e - // Type declaration merging only works well with class declarations, not class expressions - let id = Identifier(entName) - let body = ObjectTypeAnnotation([| yield! attached |]) - let typeParamDecl = genTypeParams |> makeTypeParamDecl - InterfaceDeclaration(id, body, ?extends_=extends, ?typeParameters=typeParamDecl) - let typeAnnotation com ctx typ: TypeAnnotationInfo = match typ with | Fable.MetaType -> upcast AnyTypeAnnotation() @@ -1214,7 +1199,7 @@ module Util = let transformBindingAsStatements (com: IBabelCompiler) ctx (var: Fable.Ident) (value: Fable.Expr) = if isJsStatement ctx false value then - let var = typedIdent com ctx var + let var = ident var let decl = VariableDeclaration(var) :> Statement let body = com.TransformAsStatements(ctx, Some(Assign var), value) Array.append [|decl|] body @@ -1645,7 +1630,7 @@ module Util = let tailcallChance = Option.map (fun name -> NamedTailCallOpportunity(com, ctx, name, args) :> ITailCallOpportunity) name - let args = discardUnitArg args |> List.map (typedIdent com ctx) + let args = discardUnitArg args let declaredVars = ResizeArray() let mutable isTailCallOptimized = false let ctx = @@ -1663,21 +1648,21 @@ module Util = match isTailCallOptimized, tailcallChance with | true, Some tc -> // Replace args, see NamedTailCallOpportunity constructor - let args, body = - let tcArgs = - tc.Args - |> List.map (fun arg -> Identifier(arg)) - let varDecls = - tcArgs - |> List.map (fun arg -> Some (arg :> Expression)) - |> List.zip args - |> multiVarDeclaration Const - tcArgs, BlockStatement(Array.append [|varDecls|] body.Body) + let args' = + List.zip args tc.Args + |> List.map (fun (id, tcArg) -> + makeTypedIdent id.Type tcArg |> typedIdent com ctx) + let varDecls = + List.zip args tc.Args + |> List.map (fun (id, tcArg) -> + id |> typedIdent com ctx, Some (Identifier(tcArg) :> Expression)) + |> multiVarDeclaration Const + let body = BlockStatement(Array.append [|varDecls|] body.Body) // Make sure we don't get trapped in an infinite loop, see #1624 let body = BlockStatement(Array.append body.Body [|BreakStatement()|]) - args, LabeledStatement(Identifier tc.Label, WhileStatement(BooleanLiteral true, body)) + args', LabeledStatement(Identifier tc.Label, WhileStatement(BooleanLiteral true, body)) :> Statement |> Array.singleton |> BlockStatement - | _ -> args, body + | _ -> args |> List.map (typedIdent com ctx), body let body = if declaredVars.Count = 0 then body else @@ -1719,121 +1704,26 @@ module Util = else None - let getInterfaceExtends com ctx (ent: Fable.Entity) = + let getClassImplements com ctx (ent: Fable.Entity) = let mkNative genArgs typeName = let id = Identifier(typeName) let typeParamInst = makeGenTypeParamInst com ctx genArgs - InterfaceExtends(id, ?typeParameters=typeParamInst) |> Some + ClassImplements(id, ?typeParameters=typeParamInst) |> Some let mkImport genArgs moduleName typeName = let id = makeImportTypeId com ctx moduleName typeName let typeParamInst = makeGenTypeParamInst com ctx genArgs - InterfaceExtends(id, ?typeParameters=typeParamInst) |> Some - - // let isIEquatable = FSharp2Fable.Util.hasInterface Types.iequatable ent - // let isIComparable = FSharp2Fable.Util.hasInterface Types.icomparable ent - + ClassImplements(id, ?typeParameters=typeParamInst) |> Some ent.AllInterfaces |> Seq.choose (fun ifc -> match ifc.Definition.FullName with - | Types.ienumerableGeneric -> - mkImport ifc.GenericArgs "Seq" "IEnumerable" - | Types.ienumeratorGeneric -> - mkImport ifc.GenericArgs "Seq" "IEnumerator" - | Types.iequatable -> - mkImport [Fable.Any] "Util" "IEquatable" - | Types.icomparable -> - mkImport [Fable.Any] "Util" "IComparable" - // | Types.iequatableGeneric when not isIEquatable -> - // mkImport ifc.GenericArgs "Util" "IEquatable" - // | Types.icomparableGeneric when not isIComparable -> - // mkImport ifc.GenericArgs "Util" "IComparable" - | Types.comparer -> - mkImport ifc.GenericArgs "Util" "IComparer" - // this is not needed, as it's already included in every object - // | Types.equalityComparer -> - // mkImport ifc.GenericArgs "Util" "IEqualityComparer" - | Types.idisposable -> - mkImport [] "Util" "IDisposable" - | Types.icollectionGeneric -> - mkImport ifc.GenericArgs "Util" "ICollection" - | "Fable.Collections.IMutableSet`1" -> - mkImport ifc.GenericArgs "Util" "IMutableSet" - | "Fable.Collections.IMutableMap`2" -> - mkImport ifc.GenericArgs "Util" "IMutableMap" - // TODO: add other interfaces + | "Fable.Collections.IMutableSet`1" -> mkNative ifc.GenericArgs "Set" + | "Fable.Collections.IMutableMap`2" -> mkNative ifc.GenericArgs "Map" | _ -> None ) - // must match the above list (with the exception of IEqualityComparer) - let alreadyDeclaredInterfaces = - set [ - Types.ienumerableGeneric - Types.ienumeratorGeneric - Types.iequatable - Types.icomparable - // Types.iequatableGeneric - // Types.icomparableGeneric - Types.comparer - Types.equalityComparer - Types.idisposable - Types.icollectionGeneric - "Fable.Collections.IMutableSet`1" - "Fable.Collections.IMutableMap`2" - ] - - let isOtherInterfaceMember (memb: Fable.MemberFunctionOrValue) = - let isInterface, fullName = - if memb.IsExplicitInterfaceImplementation then - true, memb.CompiledName.Replace("-",".") - else - let ent = memb.ApparentEnclosingEntity - ent.IsInterface, memb.FullName - let lastDot = fullName.LastIndexOf(".") - let entName = if lastDot < 0 then fullName else fullName.Substring(0, lastDot) - isInterface && not (alreadyDeclaredInterfaces.Contains entName) - - let getEntityExplicitInterfaceMembers com ctx (ent: Fable.Entity) = - ent.MembersFunctionsAndValues - |> Seq.filter isOtherInterfaceMember - |> Seq.map (fun memb -> - let args = - List.concat memb.CurriedParameterGroups - |> List.mapi (fun i p -> - let name = - defaultArg p.Name ("arg" + (string i)) - |> Naming.sanitizeIdentForbiddenChars |> Naming.checkJsKeywords - name, p.Type - ) - let argTypes = args |> List.map snd - let retType = memb.ReturnParameter.Type - let genTypeParams = getGenericTypeParams (argTypes @ [retType]) - let newTypeParams = Set.difference genTypeParams ctx.ScopedTypeParams - let ctx = { ctx with ScopedTypeParams = Set.union ctx.ScopedTypeParams newTypeParams } - let funcArgs = - args - |> Seq.map (fun (name, typ) -> - let typeInfo = typeAnnotation com ctx typ - FunctionTypeParam(Identifier(name), typeInfo) - ) |> Seq.toArray - let returnType = retType |> typeAnnotation com ctx - let typeParamDecl = makeTypeParamDecl newTypeParams - let funcTypeInfo = - FunctionTypeAnnotation(funcArgs, returnType, ?typeParameters=typeParamDecl) - :> TypeAnnotationInfo - // TODO!!! This should be the compiled name if the interface is not mangled - let name = memb.DisplayName - let membId = Identifier(name) - ObjectTypeProperty(membId, funcTypeInfo) - ) - |> Seq.toArray - - let getEntityFieldsAsProps (com: IBabelCompiler) ctx (ent: Fable.Entity) = - ent.FSharpFields - |> Seq.map (fun field -> - let id, computed = memberFromName field.Name - let ta = typeAnnotation com ctx field.FieldType - let isStatic = if field.IsStatic then Some true else None - ObjectTypeProperty(id, ta, computed_=computed, ?``static``=isStatic)) - |> Seq.toArray + let getUnionFieldsAsIdents (com: IBabelCompiler) ctx (ent: Fable.Entity) = + let tagId = makeTypedIdent (Fable.Number Int32) "tag" + let fieldsId = makeTypedIdent (Fable.Array Fable.Any) "fields" + [| tagId; fieldsId |] let getEntityFieldsAsIdents com (ent: Fable.Entity) = ent.FSharpFields @@ -1844,9 +1734,30 @@ module Util = id) |> Seq.toArray + let getEntityFieldsAsProps (com: IBabelCompiler) ctx (ent: Fable.Entity) = + if (ent.IsFSharpUnion) then + getUnionFieldsAsIdents com ctx ent + |> Array.map (fun id -> + let prop = ident id + let ta = typeAnnotation com ctx id.Type + ObjectTypeProperty(prop, ta)) + else + ent.FSharpFields + |> Seq.map (fun field -> + let prop, computed = memberFromName field.Name + let ta = typeAnnotation com ctx field.FieldType + let isStatic = if field.IsStatic then Some true else None + ObjectTypeProperty(prop, ta, computed_=computed, ?``static``=isStatic)) + |> Seq.toArray + let declareClassType (com: IBabelCompiler) ctx (ent: Fable.Entity) entName (consArgs: Pattern[]) (consBody: BlockStatement) (baseExpr: Expression option) classMembers = let consId = Identifier "constructor" let typeParamDecl = makeEntityTypeParamDecl com ctx ent + let implements = + if com.Options.Typescript then + let implements = Util.getClassImplements com ctx ent |> Seq.toArray + if Array.isEmpty implements then None else Some implements + else None let consBody = if ent.IsFSharpExceptionDeclaration then let super = callSuperConstructor None [] |> ExpressionStatement :> Statement @@ -1862,13 +1773,13 @@ module Util = else Array.empty let classMembers = Array.append [| classCons |] classMembers let classBody = ClassBody([| yield! classFields; yield! classMembers |]) - let classExpr = ClassExpression(classBody, ?superClass=baseExpr, ?typeParameters=typeParamDecl) + let classExpr = ClassExpression(classBody, ?superClass=baseExpr, ?typeParameters=typeParamDecl, ?implements=implements) classExpr |> declareModuleMember ent.IsPublic entName false let declareType (com: IBabelCompiler) ctx (ent: Fable.Entity) entName (consArgs: Pattern[]) (consBody: BlockStatement) baseExpr classMembers: ModuleDeclaration list = let typeDeclaration = declareClassType com ctx ent entName consArgs consBody baseExpr classMembers let reflectionDeclaration = - let genArgs = Array.init (ent.GenericParameters.Length) (fun i -> "gen" + string i |> makeIdent |> typedIdent com ctx) + let genArgs = Array.init (ent.GenericParameters.Length) (fun i -> "gen" + string i |> makeIdent |> ident) let body = transformReflectionInfo com ctx None ent (Array.map (fun x -> x :> _) genArgs) let returnType = if com.Options.Typescript then @@ -1878,12 +1789,7 @@ module Util = let args = genArgs |> Array.map (fun x -> x :> Pattern) makeFunctionExpression None (args, body, returnType, None) |> declareModuleMember ent.IsPublic (entName + Naming.reflectionSuffix) false - if com.Options.Typescript then - let interfaceDecl = makeInterfaceDecl com ctx ent entName baseExpr - let interfaceDeclaration = ExportNamedDeclaration(interfaceDecl) :> ModuleDeclaration - [interfaceDeclaration; typeDeclaration; reflectionDeclaration] - else - [typeDeclaration; reflectionDeclaration] + [typeDeclaration; reflectionDeclaration] let transformModuleFunction (com: IBabelCompiler) ctx (info: Fable.MemberInfo) (membName: string) args body = let args, body, returnType, typeParamDecl = @@ -1930,14 +1836,12 @@ module Util = |] let transformUnion (com: IBabelCompiler) ctx (ent: Fable.Entity) (entName: string) classMembers = - - let tagId = makeTypedIdent (Fable.Number Int32) "tag" - let fieldsId = makeTypedIdent (Fable.Array Fable.Any) "fields" + let fieldIds = getUnionFieldsAsIdents com ctx ent let args = - [| typedIdent com ctx tagId :> Pattern - typedIdent com ctx fieldsId |> restElement |] + [| typedIdent com ctx fieldIds.[0] :> Pattern + typedIdent com ctx fieldIds.[1] |> restElement |] let body = - [| tagId; fieldsId |] + fieldIds |> Array.map (fun id -> let left = get None thisExpr id.Name let right = diff --git a/src/fable-library/Option.ts b/src/fable-library/Option.ts index ce74368619..f4591c1962 100644 --- a/src/fable-library/Option.ts +++ b/src/fable-library/Option.ts @@ -68,13 +68,13 @@ export function value(x: Option) { } } -export function ofNullable(x: T|null): Option { +export function ofNullable(x: T | null): Option { // This will fail with unit probably, an alternative would be: // return x === null ? undefined : (x === undefined ? new Some(x) : x); return x == null ? undefined : x; } -export function toNullable(x: Option): T|null { +export function toNullable(x: Option): T | null { return x == null ? null : value(x); } diff --git a/src/fable-library/Reflection.ts b/src/fable-library/Reflection.ts index bb1ea17895..7479becd81 100644 --- a/src/fable-library/Reflection.ts +++ b/src/fable-library/Reflection.ts @@ -68,9 +68,9 @@ export function compare(t1: TypeInfo, t2: TypeInfo): number { } export function class_type( - fullname: string, - generics?: TypeInfo[], - construct?: Constructor): TypeInfo { + fullname: string, + generics?: TypeInfo[], + construct?: Constructor): TypeInfo { return new TypeInfo(fullname, generics, construct); } @@ -373,26 +373,26 @@ export function makeTuple(values: any[], _t: TypeInfo): any { } export function makeGenericType(t: TypeInfo, generics: TypeInfo[]): TypeInfo { - return new TypeInfo( - t.fullname, - generics, - t.construct, - t.fields, - t.cases); + return new TypeInfo( + t.fullname, + generics, + t.construct, + t.fields, + t.cases); } export function createInstance(t: TypeInfo, consArgs?: any[]): any { - // TODO: Check if consArgs length is same as t.construct? - // (Arg types can still be different) - if (typeof t.construct === "function") { - return new t.construct(...(consArgs ?? [])); - } else { - throw new Error(`Cannot access constructor of ${t.fullname}`); - } + // TODO: Check if consArgs length is same as t.construct? + // (Arg types can still be different) + if (typeof t.construct === "function") { + return new t.construct(...(consArgs ?? [])); + } else { + throw new Error(`Cannot access constructor of ${t.fullname}`); + } } -export function getValue(propertyInfo : PropertyInfo, v : any) : any { - return v[propertyInfo[0]] ; +export function getValue(propertyInfo: PropertyInfo, v: any): any { + return v[propertyInfo[0]]; } // Fable.Core.Reflection @@ -419,48 +419,48 @@ export function getCaseFields(x: any): any[] { } type TypeTester = - | "any" - | "unknown" - | "undefined" - | "function" - | "boolean" - | "number" - | "string" - | ["tuple", TypeTester[]] - | ["array", TypeTester|undefined] - | ["list", TypeTester] - | ["option", TypeTester] - | FunctionConstructor + | "any" + | "unknown" + | "undefined" + | "function" + | "boolean" + | "number" + | "string" + | ["tuple", TypeTester[]] + | ["array", TypeTester | undefined] + | ["list", TypeTester] + | ["option", TypeTester] + | FunctionConstructor export function typeTest(x: any, typeTester: TypeTester): boolean { - if (typeof typeTester === "string") { - if (typeTester === "any") { - return true; - } else if (typeTester === "unknown") { - return false; - } else { - return typeof x === typeTester; - } - } else if (Array.isArray(typeTester)) { - switch (typeTester[0]) { - case "tuple": - return Array.isArray(x) - && x.length === typeTester[1].length - && x.every((x, i) => typeTest(x, typeTester[1][i])); - case "array": - return isArrayLike(x) - && (x.length === 0 - || typeTester[1] == null - || typeTest(x[0], typeTester[1])); - case "list": - return x instanceof List - && (x.tail == null || typeTest(x.head, typeTester[1])); - case "option": - return x == null || typeTest(getOptionValue(x), typeTester[1]); - default: - return false - } + if (typeof typeTester === "string") { + if (typeTester === "any") { + return true; + } else if (typeTester === "unknown") { + return false; } else { - return x instanceof typeTester; + return typeof x === typeTester; + } + } else if (Array.isArray(typeTester)) { + switch (typeTester[0]) { + case "tuple": + return Array.isArray(x) + && x.length === typeTester[1].length + && x.every((x, i) => typeTest(x, typeTester[1][i])); + case "array": + return isArrayLike(x) + && (x.length === 0 + || typeTester[1] == null + || typeTest(x[0], typeTester[1])); + case "list": + return x instanceof List + && (x.tail == null || typeTest(x.head, typeTester[1])); + case "option": + return x == null || typeTest(getOptionValue(x), typeTester[1]); + default: + return false } + } else { + return x instanceof typeTester; + } } \ No newline at end of file diff --git a/src/fable-library/Seq.ts b/src/fable-library/Seq.ts index 7f28227d10..cae8ad65db 100644 --- a/src/fable-library/Seq.ts +++ b/src/fable-library/Seq.ts @@ -86,7 +86,7 @@ function makeSeq(f: () => Iterator): Iterable { return seq; } -function isArrayOrBufferView(xs: Iterable): xs is Array { +function isArrayOrBufferView(xs: Iterable): xs is T[] { return Array.isArray(xs) || ArrayBuffer.isView(xs); } diff --git a/src/fable-library/Util.ts b/src/fable-library/Util.ts index c6bd8b72b0..d2e1004d5e 100644 --- a/src/fable-library/Util.ts +++ b/src/fable-library/Util.ts @@ -56,29 +56,29 @@ export interface ICollection { Remove(item: T): boolean; } -export interface IMutableMap { - readonly size: number; - clear(): void; - delete(key: K): boolean; - get(key: K): V | undefined; - has(key: K): boolean; - set(key: K, value: V): this; - keys(): Iterable; - values(): Iterable; - entries(): Iterable<[K, V]>; -} - -export interface IMutableSet { - readonly size: number; - add(value: T): this; - add_(value: T): boolean; - clear(): void; - delete(value: T): boolean; - has(value: T): boolean; - keys(): Iterable; - values(): Iterable; - entries(): Iterable<[T, T]>; -} +// export interface IMutableMap { +// readonly size: number; +// clear(): void; +// delete(key: K): boolean; +// get(key: K): V | undefined; +// has(key: K): boolean; +// set(key: K, value: V): this; +// keys(): IterableIterator; +// values(): IterableIterator; +// entries(): IterableIterator<[K, V]>; +// } + +// export interface IMutableSet { +// readonly size: number; +// add(value: T): this; +// add_(value: T): boolean; +// clear(): void; +// delete(value: T): boolean; +// has(value: T): boolean; +// keys(): IterableIterator; +// values(): IterableIterator; +// entries(): IterableIterator<[T, T]>; +// } export function isIterable(x: T | Iterable): x is Iterable { return x != null && typeof x === "object" && Symbol.iterator in x; @@ -522,7 +522,7 @@ function changeCase(str: string, caseRule: number) { export function createObj(fields: Iterable<[string, any]>) { const obj: any = {}; - for (let kv of fields) { + for (const kv of fields) { obj[kv[0]] = kv[1]; } return obj; @@ -530,7 +530,7 @@ export function createObj(fields: Iterable<[string, any]>) { export function createObjDebug(fields: Iterable<[string, any]>) { const obj: any = {}; - for (let kv of fields) { + for (const kv of fields) { if (kv[0] in obj) { console.error(new Error(`Property ${kv[0]} is duplicated`)); } @@ -547,11 +547,11 @@ export function keyValueList(fields: Iterable, caseRule = CaseRules.None, i throw new Error("Cannot infer key and value of " + String(kvPair)); } function assign(key: string, caseRule: number, value: any) { - key = changeCase(key, caseRule); - if (isDebug && key in obj) { - console.warn(`Key ${key} is overwritten when creating JS object`); - } - obj[key] = value; + key = changeCase(key, caseRule); + if (isDebug && key in obj) { + console.warn(`Key ${key} is overwritten when creating JS object`); + } + obj[key] = value; } for (let kvPair of fields) {