diff --git a/.vscode/launch.json b/.vscode/launch.json index 679990a9c6..67b0f3b266 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -50,7 +50,7 @@ "name": "bench-compile (Node)", "program": "${workspaceRoot}/src/fable-standalone/test/bench-compiler/out-node/app.js", // "args": ["${workspaceRoot}/tests/Main/Fable.Tests.fsproj", "out-tests", "--commonjs"], - "args": ["${workspaceRoot}/../fable-test/fable-test.fsproj", "out-test", "--commonjs", "--optimize-fcs"], + "args": ["${workspaceRoot}/../fable-test/fable-test.fsproj", "out-test", "--typescript"], "cwd": "${workspaceRoot}/src/fable-standalone/test/bench-compiler" }, { @@ -59,7 +59,7 @@ "name": "bench-compile (.NET)", "program": "${workspaceFolder}/src/fable-standalone/test/bench-compiler/bin/Debug/netcoreapp2.1/bench-compiler.dll", // "args": ["${workspaceRoot}/tests/Main/Fable.Tests.fsproj", "out-tests", "--commonjs"], - "args": ["${workspaceRoot}/../fable-test/fable-test.fsproj", "out-test", "--commonjs", "--optimize-fcs"], + "args": ["${workspaceRoot}/../fable-test/fable-test.fsproj", "out-test", "--typescript"], "cwd": "${workspaceFolder}/src/fable-standalone/test/bench-compiler" }, { diff --git a/src/Fable.Cli/Parser.fs b/src/Fable.Cli/Parser.fs index f041ae225f..63434b4a38 100644 --- a/src/Fable.Cli/Parser.fs +++ b/src/Fable.Cli/Parser.fs @@ -53,6 +53,7 @@ let private parseDic (key: string) (o: JObject): IDictionary = let toCompilerOptions (msg: Message): CompilerOptions = { typedArrays = msg.typedArrays clampByteArrays = msg.clampByteArrays + typeDecls = msg.extra.ContainsKey("typescript") debugMode = Array.contains "DEBUG" msg.define verbosity = GlobalParams.Singleton.Verbosity outputPublicInlinedFunctions = Array.contains "FABLE_REPL_LIB" msg.define diff --git a/src/Fable.Transforms/AST/AST.Babel.fs b/src/Fable.Transforms/AST/AST.Babel.fs index 1f66ad9d50..6ebca597c3 100644 --- a/src/Fable.Transforms/AST/AST.Babel.fs +++ b/src/Fable.Transforms/AST/AST.Babel.fs @@ -17,42 +17,43 @@ type Node(``type``, ?loc) = member __.Loc: SourceLocation option = loc /// Since the left-hand side of an assignment may be any expression in general, an expression can also be a pattern. -[] type Expression(typ, ?loc) = inherit Node(typ, ?loc = loc) +[] type Expression(``type``, ?loc) = inherit Node(``type``, ?loc = loc) -[] type PatternNode(typ, ?loc) = inherit Node(typ, ?loc = loc) -[] type PatternExpression(typ, ?loc) = inherit Expression(typ, ?loc = loc) +[] type PatternNode(``type``, ?loc) = inherit Node(``type``, ?loc = loc) +[] type PatternExpression(``type``, ?loc) = inherit Expression(``type``, ?loc = loc) type Pattern = U2 -[] type Literal(typ, ?loc) = inherit Expression(typ, ?loc = loc) +[] type Literal(``type``, ?loc) = inherit Expression(``type``, ?loc = loc) -[] type Statement(typ, ?loc) = inherit Node(typ, ?loc = loc) +[] type Statement(``type``, ?loc) = inherit Node(``type``, ?loc = loc) /// Note that declarations are considered statements; this is because declarations can appear in any statement context. -[] type Declaration(typ, ?loc) = inherit Statement(typ, ?loc = loc) +[] type Declaration(``type``, ?loc) = inherit Statement(``type``, ?loc = loc) /// A module import or export declaration. -[] type ModuleDeclaration(typ, ?loc) = inherit Node(typ, ?loc = loc) +[] type ModuleDeclaration(``type``, ?loc) = inherit Node(``type``, ?loc = loc) [] type TypeAnnotationInfo(``type``) = member __.Type: string = ``type`` type TypeAnnotation(typeAnnotation) = - member __.Type = "TypeAnnotation" + inherit Node("TypeAnnotation") member __.TypeAnnotation: TypeAnnotationInfo = typeAnnotation -// TODO: TypeParameter can also have `variance` and `bound` properties -type TypeParameter(name) = - member __.Type = "TypeParameter" +type TypeParameter(name, ?bound, ?``default``) = + inherit Node("TypeParameter") member __.Name: string = name + member __.Bound: TypeAnnotation option = bound + member __.Default: TypeAnnotationInfo option = ``default`` type TypeParameterDeclaration(``params``) = - member __.Type = "TypeParameterDeclaration" + inherit Node("TypeParameterDeclaration") member __.Params: TypeParameter array = ``params`` type TypeParameterInstantiation(``params``) = - member __.Type = "TypeParameterInstantiation" + inherit Node("TypeParameterInstantiation") member __.Params: TypeAnnotationInfo array = ``params`` /// Not in Babel specs, disguised as StringLiteral @@ -61,7 +62,7 @@ type MacroExpression(value, args, ?loc) = let macro = true member __.Value: string = value member __.Args: Expression array = args - member __.Macro = macro + member __.Macro: bool = macro // Template Literals type TemplateElement(value: string, tail, ?loc) = @@ -97,7 +98,7 @@ type RegExpLiteral(pattern, flags_, ?loc) = | RegexMultiline -> "m" | RegexSticky -> "y") |> Seq.fold (+) "" member __.Pattern: string = pattern - member __.Flags = flags + member __.Flags: string = flags type NullLiteral(?loc) = inherit Literal("NullLiteral", ?loc = loc) @@ -141,7 +142,7 @@ type Program(fileName, body, ?directives_, ?logs_, ?dependencies_, ?sourceFiles_ let logs = defaultArg logs_ Map.empty let dependencies = defaultArg dependencies_ [||] let sourceFiles = defaultArg sourceFiles_ [||] - member __.SourceType = sourceType + member __.SourceType: string = sourceType member __.Body: U2 array = body member __.Directives: Directive array = directives // Properties below don't belong to babel specs @@ -247,7 +248,7 @@ type VariableDeclaration(kind_, declarations, ?loc) = new (var, ?init, ?kind, ?loc) = VariableDeclaration(defaultArg kind Let, [|VariableDeclarator(var, ?init=init, ?loc=loc)|], ?loc=loc) member __.Declarations: VariableDeclarator array = declarations - member __.Kind = kind + member __.Kind: string = kind // Loops type WhileStatement(test, body, ?loc) = @@ -284,15 +285,18 @@ type ForOfStatement(left, right, body, ?loc) = member __.Right: Expression = right /// A function declaration. Note that id cannot be null. -type FunctionDeclaration(id, ``params``, body, ?generator_, ?async_, ?loc) = +type FunctionDeclaration(``params``, body, ?id, ?async_, ?generator_, ?declare, ?returnType, ?typeParameters, ?loc) = inherit Declaration("FunctionDeclaration", ?loc = loc) - let generator = defaultArg generator_ false let async = defaultArg async_ false - member __.Id: Identifier = id + let generator = defaultArg generator_ false member __.Params: Pattern array = ``params`` member __.Body: BlockStatement = body - member __.Generator = generator - member __.Async = async + member __.Id: Identifier option = id + member __.Async: bool = async + member __.Generator: bool = generator + member __.Declare: bool option = declare + member __.ReturnType: TypeAnnotation option = returnType + member __.TypeParameters: TypeParameterDeclaration option = typeParameters // Expressions @@ -304,24 +308,30 @@ type ThisExpression(?loc) = inherit Expression("ThisExpression", ?loc = loc) /// A fat arrow function expression, e.g., let foo = (bar) => { /* body */ }. -type ArrowFunctionExpression(``params``, body, ?async_, ?loc) = +type ArrowFunctionExpression(``params``, body, ?async_, ?generator_, ?returnType, ?typeParameters, ?loc) = inherit Expression("ArrowFunctionExpression", ?loc = loc) let async = defaultArg async_ false - let expression = match body with U2.Case1 _ -> false | U2.Case2 _ -> true - member __.Expression = expression + let generator = defaultArg generator_ false + let expression = Some (match body with U2.Case1 _ -> false | U2.Case2 _ -> true) member __.Params: Pattern array = ``params`` member __.Body: U2 = body member __.Async: bool = async + member __.Generator: bool = generator + member __.Expression: bool option = expression + member __.ReturnType: TypeAnnotation option = returnType + member __.TypeParameters: TypeParameterDeclaration option = typeParameters -type FunctionExpression(``params``, body, ?generator_, ?async_, ?id, ?loc) = +type FunctionExpression(``params``, body, ?generator_, ?async_, ?id, ?returnType, ?typeParameters, ?loc) = inherit Expression("FunctionExpression", ?loc = loc) - let generator = defaultArg generator_ false let async = defaultArg async_ false + let generator = defaultArg generator_ false member __.Id: Identifier option = id member __.Params: Pattern array = ``params`` member __.Body: BlockStatement = body - member __.Generator: bool = generator member __.Async: bool = async + member __.Generator: bool = generator + member __.ReturnType: TypeAnnotation option = returnType + member __.TypeParameters: TypeParameterDeclaration option = typeParameters /// e.g., x = do { var t = f(); t * t + 1 }; /// http://wiki.ecmascript.org/doku.php?id=strawman:do_expressions @@ -356,46 +366,48 @@ type SpreadElement(argument, ?loc) = type ArrayExpression(elements, ?loc) = inherit Expression("ArrayExpression", ?loc = loc) - // member __.elements: U2 option array = elements + // member __.Elements: U2 option array = elements member __.Elements: Expression array = elements -[] -type ObjectMember(typ, key, ?value, ?computed_, ?loc) = - inherit Node(typ, ?loc = loc) +type ObjectProperty(key, value, ?computed_, ?shorthand_, ?loc) = + inherit Node("ObjectProperty", ?loc = loc) let computed = defaultArg computed_ false + let shorthand = defaultArg shorthand_ false member __.Key: Expression = key - member __.Value: Expression option = value + member __.Value: Expression = value member __.Computed: bool = computed - // member __.decorators: Decorator array = defaultArg decorators [] - -type ObjectProperty(key, value, ?shorthand_, ?computed_, ?loc) = - inherit ObjectMember("ObjectProperty", key, value, ?computed_=computed_, ?loc=loc) - let shorthand = defaultArg shorthand_ false member __.Shorthand: bool = shorthand + // member __.Decorators: Decorator array option = decorators type ObjectMethodKind = ObjectGetter | ObjectSetter | ObjectMeth -type ObjectMethod(kind_, key, ``params``, body, ?computed_, ?generator_, ?async_, ?loc) = - inherit ObjectMember("ObjectMethod", key, ?computed_=computed_, ?loc=loc) - let generator = defaultArg generator_ false - let async = defaultArg async_ false +type ObjectMethod(kind_, key, ``params``, body, ?computed_, ?async_, ?generator_, ?returnType, ?typeParameters, ?loc) = + inherit Node("ObjectMethod", ?loc = loc) let kind = match kind_ with | ObjectGetter -> "get" | ObjectSetter -> "set" | ObjectMeth -> "method" - member __.Kind = kind + let computed = defaultArg computed_ false + let async = defaultArg async_ false + let generator = defaultArg generator_ false + member __.Kind: string = kind + member __.Key: Expression = key member __.Params: Pattern array = ``params`` member __.Body: BlockStatement = body - member __.Generator: bool = generator + member __.Computed: bool = computed member __.Async: bool = async + member __.Generator: bool = generator + member __.ReturnType: TypeAnnotation option = returnType + member __.TypeParameters: TypeParameterDeclaration option = typeParameters + // member __.Decorators: Decorator array option = decorators /// If computed is true, the node corresponds to a computed (a[b]) member expression and property is an Expression. /// If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier. -type MemberExpression(``object``, property, ?computed_, ?loc) = +type MemberExpression(object, property, ?computed_, ?loc) = inherit PatternExpression("MemberExpression", ?loc = loc) let computed = defaultArg computed_ false - member __.Object: Expression = ``object`` + member __.Object: Expression = object member __.Property: Expression = property member __.Computed: bool = computed @@ -443,7 +455,7 @@ type UnaryExpression(operator_, argument, ?prefix_, ?loc) = | UnaryDelete -> "delete" member __.Prefix: bool = prefix member __.Argument: Expression = argument - member __.Operator = operator + member __.Operator: string = operator type UpdateExpression(operator_, prefix, argument, ?loc) = inherit Expression("UpdateExpression", ?loc = loc) @@ -453,7 +465,7 @@ type UpdateExpression(operator_, prefix, argument, ?loc) = | UpdatePlus -> "++" member __.Prefix: bool = prefix member __.Argument: Expression = argument - member __.Operator = operator + member __.Operator: string = operator // Binary Operations type BinaryExpression(operator_, left, right, ?loc) = @@ -484,7 +496,7 @@ type BinaryExpression(operator_, left, right, ?loc) = | BinaryInstanceOf -> "instanceof" member __.Left: Expression = left member __.Right: Expression = right - member __.Operator = operator + member __.Operator: string = operator type AssignmentExpression(operator_, left, right, ?loc) = inherit Expression("AssignmentExpression", ?loc = loc) @@ -504,7 +516,7 @@ type AssignmentExpression(operator_, left, right, ?loc) = | AssignAndBitwise -> "&=" member __.Left: Expression = left member __.Right: Expression = right - member __.Operator = operator + member __.Operator: string = operator type LogicalExpression(operator_, left, right, ?loc) = inherit Expression("LogicalExpression", ?loc = loc) @@ -514,7 +526,7 @@ type LogicalExpression(operator_, left, right, ?loc) = | LogicalAnd-> "&&" member __.Left: Expression = left member __.Right: Expression = right - member __.Operator = operator + member __.Operator: string = operator // Patterns // type AssignmentProperty(key, value, ?loc) = @@ -523,27 +535,31 @@ type LogicalExpression(operator_, left, right, ?loc) = // type ObjectPattern(properties, ?loc) = // inherit Node("ObjectPattern", ?loc = loc) -// member __.properties: U2 array = properties +// member __.Properties: U2 array = properties // interface Pattern -type ArrayPattern(elements, ?loc) = +type ArrayPattern(elements, ?typeAnnotation, ?loc) = inherit PatternNode("ArrayPattern", ?loc = loc) member __.Elements: Pattern option array = elements + member __.TypeAnnotation: TypeAnnotation option = typeAnnotation -type AssignmentPattern(left, right, ?loc) = +type AssignmentPattern(left, right, ?typeAnnotation, ?loc) = inherit PatternNode("AssignmentPattern", ?loc = loc) member __.Left: Pattern = left member __.Right: Expression = right + member __.TypeAnnotation: TypeAnnotation option = typeAnnotation + // member __.Decorators: Decorator array option = decorators -type RestElement(argument, ?loc) = +type RestElement(argument, ?typeAnnotation, ?loc) = inherit PatternNode("RestElement", ?loc = loc) member __.Argument: Pattern = argument + member __.TypeAnnotation: TypeAnnotation option = typeAnnotation // Classes type ClassMethodKind = | ClassImplicitConstructor | ClassFunction | ClassGetter | ClassSetter -type ClassMethod(kind_, key, ``params``, body, computed, ``static``, ?loc) = +type ClassMethod(kind_, key, ``params``, body, computed, ?``static``, ?``abstract``, ?returnType, ?typeParameters, ?loc) = inherit Node("ClassMethod", ?loc = loc) let kind = match kind_ with @@ -556,10 +572,13 @@ type ClassMethod(kind_, key, ``params``, body, computed, ``static``, ?loc) = member __.Params: Pattern array = ``params`` member __.Body: BlockStatement = body member __.Computed: bool = computed - member __.Static: bool = ``static`` - // member __.decorators: Decorator array = defaultArg decorators [] + member __.Static: bool option = ``static`` + member __.Abstract: bool option = ``abstract`` + member __.ReturnType: TypeAnnotation option = returnType + member __.TypeParameters: TypeParameterDeclaration option = typeParameters + // member __.Decorators: Decorator array option = decorators // This appears in astexplorer.net but it's not documented - // member __.expression: bool = false + // member __.Expression: bool = false /// ES Class Fields & Static Properties /// https://github.com/jeffmo/es-class-fields-and-static-properties @@ -574,13 +593,13 @@ type ClassBody(body, ?loc) = inherit Node("ClassBody", ?loc = loc) member __.Body: U2 array = body -type ClassDeclaration(body, id, ?superClass, ?typeParameters, ?loc) = +type ClassDeclaration(body, ?id, ?superClass, ?typeParameters, ?loc) = inherit Declaration("ClassDeclaration", ?loc = loc) member __.Body: ClassBody = body - member __.Id: Identifier = id + member __.Id: Identifier option = id member __.SuperClass: Expression option = superClass member __.TypeParameters: TypeParameterDeclaration option = typeParameters - // member __.decorators: Decorator array = defaultArg decorators [] + // member __.Decorators: Decorator array option = decorators /// Anonymous class: e.g., var myClass = class { } type ClassExpression(body, ?id, ?superClass, ?typeParameters, ?loc) = @@ -589,18 +608,23 @@ type ClassExpression(body, ?id, ?superClass, ?typeParameters, ?loc) = member __.Id: Identifier option = id member __.SuperClass: Expression option = superClass member __.TypeParameters: TypeParameterDeclaration option = typeParameters - // member __.decorators: Decorator array = defaultArg decorators [] + // member __.Decorators: Decorator array option = decorators + +type ClassImplements(id, ?typeParameters, ?loc) = + inherit Expression("ClassImplements", ?loc = loc) + member __.Id: Identifier option = id + member __.TypeParameters: TypeParameterInstantiation option = typeParameters // type MetaProperty(meta, property, ?loc) = // inherit Expression("MetaProperty", ?loc = loc) -// member __.meta: Identifier = meta +// member __.Meta: Identifier = meta // member __.Property: Expression = property // Modules /// A specifier in an import or export declaration. [] -type ModuleSpecifier(typ, local, ?loc) = - inherit Node(typ, ?loc = loc) +type ModuleSpecifier(``type``, local, ?loc) = + inherit Node(``type``, ?loc = loc) member __.Local: Identifier = local /// An imported variable binding, e.g., {foo} in import {foo} from "mod" or {foo as bar} in import {foo as bar} from "mod". @@ -654,3 +678,108 @@ type ExportDefaultDeclaration(declaration, ?loc) = type ExportAllDeclaration(source, ?loc) = inherit ModuleDeclaration("ExportAllDeclaration", ?loc = loc) member __.Source: Literal = source + +// Type Annotations + +type StringTypeAnnotation() = + inherit TypeAnnotationInfo("StringTypeAnnotation") + +type NumberTypeAnnotation() = + inherit TypeAnnotationInfo("NumberTypeAnnotation") + +type BooleanTypeAnnotation() = + inherit TypeAnnotationInfo("BooleanTypeAnnotation") + +type AnyTypeAnnotation() = + inherit TypeAnnotationInfo("AnyTypeAnnotation") + +type VoidTypeAnnotation() = + inherit TypeAnnotationInfo("VoidTypeAnnotation") + +type TupleTypeAnnotation(types) = + inherit TypeAnnotationInfo("TupleTypeAnnotation") + member __.Types: TypeAnnotationInfo array = types + +type UnionTypeAnnotation(types) = + inherit TypeAnnotationInfo("UnionTypeAnnotation") + member __.Types: TypeAnnotationInfo array = types + +type FunctionTypeParam(name, typeInfo, ?optional) = + inherit Node("FunctionTypeParam") + member __.Name: Identifier = name + member __.TypeAnnotation: TypeAnnotationInfo = typeInfo + member __.Optional: bool option = optional + +type FunctionTypeAnnotation(``params``, returnType, ?typeParameters, ?rest) = + inherit TypeAnnotationInfo("FunctionTypeAnnotation") + member __.Params: FunctionTypeParam array = ``params`` + member __.ReturnType: TypeAnnotationInfo = returnType + member __.TypeParameters: TypeParameterDeclaration option = typeParameters + member __.Rest: FunctionTypeParam option = rest + +type NullableTypeAnnotation(``type``) = + inherit TypeAnnotationInfo("NullableTypeAnnotation") + member __.TypeAnnotation: TypeAnnotationInfo = ``type`` + +type GenericTypeAnnotation(id, ?typeParameters) = + inherit TypeAnnotationInfo("GenericTypeAnnotation") + member __.Id: Identifier = id + member __.TypeParameters: TypeParameterInstantiation option = typeParameters + +type ObjectTypeProperty(key, value, ?kind, ?``static``, ?optional, ?proto) = + inherit Node("ObjectTypeProperty") + member __.Key: U2 = key + member __.Value: TypeAnnotationInfo = value + member __.Kind: string option = kind + member __.Static: bool option = ``static`` + member __.Optional: bool option = optional + member __.Proto: bool option = proto + +type ObjectTypeIndexer(key, value, ?id, ?``static``) = + inherit Node("ObjectTypeIndexer") + member __.Id: Identifier option = id + member __.Key: Identifier = key + member __.Value: TypeAnnotationInfo = value + member __.Static: bool option = ``static`` + +type ObjectTypeCallProperty(value, ?``static``) = + inherit Node("ObjectTypeCallProperty") + member __.Value: TypeAnnotationInfo = value + member __.Static: bool option = ``static`` + +type ObjectTypeInternalSlot(id, value, optional, ``static``, method) = + inherit Node("ObjectTypeInternalSlot") + member __.Id: Identifier = id + member __.Value: TypeAnnotationInfo = value + member __.Optional: bool = optional + member __.Static: bool = ``static`` + member __.Method: bool = method + +type ObjectTypeAnnotation(properties, ?indexers_, ?callProperties_, ?internalSlots_, ?exact_) = + inherit TypeAnnotationInfo("ObjectTypeAnnotation") + let exact = defaultArg exact_ false + let indexers = defaultArg indexers_ [||] + let callProperties = defaultArg callProperties_ [||] + let internalSlots = defaultArg internalSlots_ [||] + member __.Properties: ObjectTypeProperty array = properties + member __.Indexers: ObjectTypeIndexer array = indexers + member __.CallProperties: ObjectTypeCallProperty array = callProperties + member __.InternalSlots: ObjectTypeInternalSlot array = internalSlots + member __.Exact: bool = exact + +type InterfaceExtends(id, ?typeParameters) = + inherit Node("InterfaceExtends") + member __.Id: Identifier = id + member __.TypeParameters: TypeParameterInstantiation option = typeParameters + +type InterfaceDeclaration(id, body, ?extends_, ?implements_, ?mixins_, ?typeParameters, ?loc) = + inherit Declaration("InterfaceDeclaration", ?loc = loc) + let extends = defaultArg extends_ [||] + let implements = defaultArg implements_ [||] + let mixins = defaultArg mixins_ [||] + member __.Id: Identifier = id + member __.Body: ObjectTypeAnnotation = body + member __.Extends: InterfaceExtends array = extends + member __.Implements: ClassImplements array = implements + member __.Mixins: InterfaceExtends array = mixins + member __.TypeParameters: TypeParameterDeclaration option = typeParameters diff --git a/src/Fable.Transforms/FSharp2Fable.fs b/src/Fable.Transforms/FSharp2Fable.fs index 207d268d96..86283d596a 100644 --- a/src/Fable.Transforms/FSharp2Fable.fs +++ b/src/Fable.Transforms/FSharp2Fable.fs @@ -457,6 +457,15 @@ let private transformExpr (com: IFableCompiler) (ctx: Context) fsExpr = | BasicPatterns.DefaultValue (FableType com ctx typ) -> return Replacements.defaultof com ctx typ + // Capture variable generic type mapping + | BasicPatterns.Let((var, value), (BasicPatterns.Application(_body, genArgs, _args) as expr)) -> + let genArgs = Seq.map (makeType com ctx.GenericArgs) genArgs + let ctx = { ctx with GenericArgs = matchGenericParamsFrom var genArgs |> Map } + let! value = transformExpr com ctx value + let ctx, ident = putBindingInScope com ctx var value + let! body = transformExpr com ctx expr + return Fable.Let([ident, value], body) + // Assignments | BasicPatterns.Let((var, value), body) -> if isInline var then diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index 882419e434..ff006132bc 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -28,7 +28,8 @@ type Context = DecisionTargets: (Fable.Ident list * Fable.Expr) list HoistVars: Fable.Ident list -> bool TailCallOpportunity: ITailCallOpportunity option - OptimizeTailCall: unit -> unit } + OptimizeTailCall: unit -> unit + ScopedTypeParams: Set } type IBabelCompiler = inherit ICompiler @@ -107,11 +108,11 @@ module Util = let ident (id: Fable.Ident) = Identifier(id.Name, ?loc=id.Range) - let identAsPattern (id: Fable.Ident): Pattern = - Identifier(id.Name, ?loc=id.Range) |> toPattern + let identAsPattern (id: Fable.Ident) = + ident id |> toPattern let identAsExpr (id: Fable.Ident) = - Identifier(id.Name, ?loc=id.Range) :> Expression + (ident id) :> Expression let thisExpr = ThisExpression() :> Expression @@ -163,6 +164,14 @@ module Util = let coreValue (com: IBabelCompiler) ctx moduleName memberName = com.TransformImport(ctx, memberName, moduleName, Fable.Library) + let coreReflectionCall (com: IBabelCompiler) ctx r memberName args = + coreLibCall com ctx r "Reflection" (memberName + "_type") args + + let tryJsConstructor (com: IBabelCompiler) ctx ent = + match Replacements.tryJsConstructor com ent with + | Some e -> com.TransformAsExpr(ctx, e) |> Some + | None -> None + let jsConstructor (com: IBabelCompiler) ctx ent = let entRef = Replacements.jsConstructor com ent com.TransformAsExpr(ctx, entRef) @@ -216,12 +225,13 @@ module Util = // Use an arrow function in case we need to capture `this` CallExpression(ArrowFunctionExpression([||], body), [||]) - let multiVarDeclaration kind (namesAndValue: (string * Expression option) list) = + let multiVarDeclaration kind (variables: (Identifier * Expression option) list) = let varDeclarators = // TODO: Log error if there're duplicated non-empty var declarations - List.distinctBy fst namesAndValue - |> List.mapToArray (fun (name, value) -> - VariableDeclarator(Identifier name |> toPattern, ?init=value)) + variables + |> List.distinctBy (fun (id, value) -> id.Name) + |> List.mapToArray (fun (id, value) -> + VariableDeclarator(id |> toPattern, ?init=value)) VariableDeclaration(kind, varDeclarators) :> Statement let varDeclaration (var: Identifier) (isMutable: bool) value = @@ -231,22 +241,249 @@ module Util = let restElement (var: Identifier) = RestElement(toPattern var) :> PatternNode |> U2.Case1 + let callSuperConstructor r _funcExpr _thisArg (args: Expression list) = + CallExpression(Super(?loc=r), List.toArray args, ?loc=r) :> Expression + let callFunctionWithThisContext r funcExpr thisArg (args: Expression list) = CallExpression(get None funcExpr "call", List.toArray (thisArg::args), ?loc=r) :> Expression let macroExpression range (txt: string) args = MacroExpression(txt, List.toArray args, ?loc=range) :> Expression + let getGenericTypeParams (types: Fable.Type list) = + let rec getGenParams = function + | Fable.GenericParam name -> [name] + | t -> t.Generics |> List.collect getGenParams + types + |> List.collect getGenParams + |> Set.ofList + + let getEntityGenParams (ent: FSharpEntity) = + ent.GenericParameters + |> Seq.map (fun x -> x.Name) + |> Set.ofSeq + + let makeTypeParamDecl genParams = + if (Set.isEmpty genParams) then + None + else + genParams + |> Set.toArray + |> Array.map TypeParameter + |> TypeParameterDeclaration |> Some + + let makeTypeParamInst genParams = + if (Set.isEmpty genParams) then + None + else + genParams + |> Set.toArray + |> Array.map (fun x -> GenericTypeAnnotation(Identifier(x)) :> TypeAnnotationInfo) + |> TypeParameterInstantiation |> Some + + let mergeTypeParamDecls (decl1: TypeParameterDeclaration option) (decl2: TypeParameterDeclaration option) = + match decl1, decl2 with + | Some d1, Some d2 -> + Array.append + (d1.Params |> Array.map (fun x -> x.Name)) + (d2.Params |> Array.map (fun x -> x.Name)) + |> Array.distinct + |> Array.map TypeParameter + |> TypeParameterDeclaration |> Some + | Some _, None -> decl1 + | None, Some _ -> decl2 + | None, None -> None + + let uncurryLambdaType t = + let rec uncurryLambdaArgs acc = function + | Fable.FunctionType(Fable.LambdaType paramType, returnType) -> + uncurryLambdaArgs (paramType::acc) returnType + | t -> List.rev acc, t + uncurryLambdaArgs [] t + + let rec typeAnnotation com ctx typ: TypeAnnotationInfo = + match typ with + | Fable.MetaType -> upcast AnyTypeAnnotation() + | Fable.Any -> upcast AnyTypeAnnotation() + | Fable.Unit -> upcast VoidTypeAnnotation() + | Fable.Boolean -> upcast BooleanTypeAnnotation() + | Fable.Char -> upcast StringTypeAnnotation() + | Fable.String -> upcast StringTypeAnnotation() + | Fable.Regex -> upcast AnyTypeAnnotation() + | Fable.Number kind -> makeNumericTypeAnnotation com ctx kind + | Fable.Enum _ent -> upcast NumberTypeAnnotation() + | Fable.Option genArg -> makeOptionTypeAnnotation com ctx genArg + | Fable.Tuple genArgs -> makeTupleTypeAnnotation com ctx genArgs + | Fable.Array genArg -> makeArrayTypeAnnotation com ctx genArg + | Fable.List genArg -> makeListTypeAnnotation com ctx genArg + | Replacements.Builtin kind -> makeBuiltinTypeAnnotation com ctx kind + | Fable.FunctionType(kind, returnType) -> + makeFunctionTypeAnnotation com ctx typ kind returnType + | Fable.GenericParam name -> makeSimpleTypeAnnotation com ctx name + | Fable.ErasedUnion genArgs -> makeUnionTypeAnnotation com ctx genArgs + | Fable.DeclaredType(ent, genArgs) -> + makeEntityTypeAnnotation com ctx ent genArgs + | Fable.AnonymousRecordType(fieldNames, genArgs) -> + makeAnonymousRecordTypeAnnotation com ctx fieldNames genArgs + + and makeSimpleTypeAnnotation _com _ctx name = + GenericTypeAnnotation(Identifier(name)) + :> TypeAnnotationInfo + + and makeGenTypeParamInst com ctx genArgs = + match genArgs |> List.map (typeAnnotation com ctx) with + | [] -> None + | xs -> xs |> List.toArray |> TypeParameterInstantiation |> Some + + and makeGenericTypeAnnotation com ctx genArgs id = + let typeParamInst = makeGenTypeParamInst com ctx genArgs + GenericTypeAnnotation(id, ?typeParameters=typeParamInst) + :> TypeAnnotationInfo + + and makeNativeTypeAnnotation com ctx genArgs typeName = + Identifier(typeName) + |> makeGenericTypeAnnotation com ctx genArgs + + and makeImportTypeId (com: IBabelCompiler) ctx moduleName typeName = + let expr = com.GetImportExpr(ctx, typeName, moduleName, Fable.Library) + match expr with + | :? Identifier as id -> id + | _ -> Identifier(typeName) + + and makeImportTypeAnnotation com ctx genArgs moduleName typeName = + let id = makeImportTypeId com ctx moduleName typeName + makeGenericTypeAnnotation com ctx genArgs id + + and makeNumericTypeAnnotation com ctx kind = + let typeName = getNumberKindName kind + makeImportTypeAnnotation com ctx [] "Int32" typeName + + and makeOptionTypeAnnotation com ctx genArg = + makeImportTypeAnnotation com ctx [genArg] "Option" "Option" + + and makeTupleTypeAnnotation com ctx genArgs = + List.map (typeAnnotation com ctx) genArgs + |> List.toArray |> TupleTypeAnnotation + :> TypeAnnotationInfo + + and makeArrayTypeAnnotation com ctx genArg = + match genArg with + | Fable.Number kind when com.Options.typedArrays -> + let name = getTypedArrayName com kind + makeSimpleTypeAnnotation com ctx name + | _ -> + makeNativeTypeAnnotation com ctx [genArg] "Array" + + and makeListTypeAnnotation com ctx genArg = + makeImportTypeAnnotation com ctx [genArg] "Types" "List" + + and makeUnionTypeAnnotation com ctx genArgs = + List.map (typeAnnotation com ctx) genArgs + |> List.toArray |> UnionTypeAnnotation + :> TypeAnnotationInfo + + and makeBuiltinTypeAnnotation com ctx kind = + match kind with + | Replacements.BclGuid -> upcast StringTypeAnnotation() + | Replacements.BclTimeSpan -> upcast NumberTypeAnnotation() + | Replacements.BclDateTime -> makeSimpleTypeAnnotation com ctx "Date" + | Replacements.BclDateTimeOffset -> makeSimpleTypeAnnotation com ctx "Date" + | Replacements.BclTimer -> makeImportTypeAnnotation com ctx [] "Timer" "Timer" + | Replacements.BclInt64 -> makeImportTypeAnnotation com ctx [] "Long" "int64" + | Replacements.BclUInt64 -> makeImportTypeAnnotation com ctx [] "Long" "uint64" + | Replacements.BclDecimal -> makeImportTypeAnnotation com ctx [] "Decimal" "decimal" + | Replacements.BclBigInt -> makeImportTypeAnnotation com ctx [] "BigInt/z" "BigInteger" + | Replacements.BclHashSet key -> makeNativeTypeAnnotation com ctx [key] "Set" + | Replacements.BclDictionary (key, value) -> makeNativeTypeAnnotation com ctx [key; value] "Map" + | Replacements.BclKeyValuePair (key, value) -> makeTupleTypeAnnotation com ctx [key; value] + | Replacements.FSharpSet key -> makeImportTypeAnnotation com ctx [key] "Set" "FSharpSet" + | Replacements.FSharpMap (key, value) -> makeImportTypeAnnotation com ctx [key; value] "Map" "FSharpMap" + | Replacements.FSharpResult (ok, err) -> makeImportTypeAnnotation com ctx [ok; err] "Option" "Result" + | Replacements.FSharpChoice genArgs -> makeUnionTypeAnnotation com ctx genArgs + | Replacements.FSharpReference genArg -> makeImportTypeAnnotation com ctx [genArg] "Types" "FSharpRef" + + and makeFunctionTypeAnnotation com ctx typ kind returnType = + let argTypes, returnType = + match kind with + | Fable.LambdaType _argType -> uncurryLambdaType typ + | Fable.DelegateType argTypes -> argTypes, returnType + let funcTypeParams = + argTypes + |> List.mapi (fun i argType -> + FunctionTypeParam( + Identifier("arg" + (string i)), + typeAnnotation com ctx argType)) + |> List.toArray + let genTypeParams = getGenericTypeParams (argTypes @ [returnType]) + let newTypeParams = Set.difference genTypeParams ctx.ScopedTypeParams + let ctx = { ctx with ScopedTypeParams = Set.union ctx.ScopedTypeParams newTypeParams } + let returnType = typeAnnotation com ctx returnType + let typeParamDecl = makeTypeParamDecl newTypeParams + FunctionTypeAnnotation(funcTypeParams, returnType, ?typeParameters=typeParamDecl) + :> TypeAnnotationInfo + + and makeEntityTypeAnnotation com ctx ent genArgs = + match ent.TryFullName with + | Some Types.ienumerableGeneric -> + makeNativeTypeAnnotation com ctx genArgs "Iterable" + | Some Types.result -> + makeUnionTypeAnnotation com ctx genArgs + | Some entName when entName.StartsWith(Types.choiceNonGeneric) -> + makeUnionTypeAnnotation com ctx genArgs + | _ when ent.IsInterface -> + upcast AnyTypeAnnotation() // TODO: + | _ -> + match tryJsConstructor com ctx ent with + | Some entRef -> + match entRef with + | :? StringLiteral as str -> + match str.Value with + | "number" -> upcast NumberTypeAnnotation() + | "boolean" -> upcast BooleanTypeAnnotation() + | "string" -> upcast StringTypeAnnotation() + | _ -> upcast AnyTypeAnnotation() + | :? Identifier as id -> + makeGenericTypeAnnotation com ctx genArgs id + // TODO: Resolve references to types in nested modules + | _ -> upcast AnyTypeAnnotation() + | None -> upcast AnyTypeAnnotation() + + and makeAnonymousRecordTypeAnnotation com ctx fieldNames genArgs = + upcast AnyTypeAnnotation() // TODO: + + let typedIdent (com: IBabelCompiler) ctx (id: Fable.Ident) = + let typeAnnotation = + if com.Options.typeDecls then + typeAnnotation com ctx id.Type + |> TypeAnnotation |> Some + else None + Identifier(id.Name, ?typeAnnotation=typeAnnotation, ?loc=id.Range) + + let transformFunc (com: IBabelCompiler) ctx name (args: Fable.Ident list) (body: Fable.Expr) = + if com.Options.typeDecls then + let argTypes = args |> List.map (fun id -> id.Type) + let genTypeParams = getGenericTypeParams (argTypes @ [body.Type]) + let newTypeParams = Set.difference genTypeParams ctx.ScopedTypeParams + let ctx = { ctx with ScopedTypeParams = Set.union ctx.ScopedTypeParams newTypeParams } + let args', body' = com.TransformFunction(ctx, name, args, body) + let returnType = TypeAnnotation(typeAnnotation com ctx body.Type) |> Some + let typeParamDecl = makeTypeParamDecl newTypeParams + args', body', returnType, typeParamDecl + else + let args', body' = com.TransformFunction(ctx, name, args, body) + args', body', None, None + let getMemberArgsAndBody (com: IBabelCompiler) ctx name boundThis args hasSpread (body: Fable.Expr) = - let args, body = + let args, body, genTypeParams = match boundThis with | Some(boundThis, thisArg: Fable.Ident) -> + let genTypeParams = Set.difference (getGenericTypeParams [thisArg.Type]) ctx.ScopedTypeParams let isThisUsed = body |> FableTransforms.deepExists (function | Fable.IdentExpr id when id.Name = thisArg.Name -> true | _ -> false) if not isThisUsed - then args, body + then args, body, genTypeParams else // If the boundThis is the actual JS `this` keyword bind it at the beginning // to prevent problems in closures. If not, replace thisArg in body with boundThis. @@ -255,9 +492,14 @@ module Util = if boundThis = "this" then Fable.Let([thisArg, boundThisExpr], body) else FableTransforms.replaceValues (Map [thisArg.Name, boundThisExpr]) body - args, body - | None -> args, body - let args, body = com.TransformFunction(ctx, name, args, body) + args, body, genTypeParams + | None -> args, body, Set.empty + let ctx = { ctx with ScopedTypeParams = Set.union ctx.ScopedTypeParams genTypeParams } + let args, body, returnType, typeParamDecl = transformFunc com ctx name args body + let typeParamDecl = + if com.Options.typeDecls then + makeTypeParamDecl genTypeParams |> mergeTypeParamDecls typeParamDecl + else typeParamDecl let args = if not hasSpread then args @@ -265,9 +507,11 @@ module Util = let args = Array.rev args let restEl = RestElement(Array.head args) :> PatternNode |> U2.Case1 Array.append [|restEl|] (Array.tail args) |> Array.rev - match body with - | U2.Case1 e -> args, e - | U2.Case2 e -> args, BlockStatement [|ReturnStatement(e, ?loc=e.Loc)|] + let body = + match body with + | U2.Case1 e -> e + | U2.Case2 e -> BlockStatement [|ReturnStatement(e, ?loc=e.Loc)|] + args, body, returnType, typeParamDecl let getUnionCaseName uci = FSharp2Fable.Helpers.unionCaseCompiledName uci @@ -289,13 +533,13 @@ module Util = BinaryExpression(BinaryOrBitwise, e, NumericLiteral(0.)) :> Expression | _ -> e - let makeFunctionExpression name args (body: U2): Expression = + let makeFunctionExpression name (args, (body: U2), returnType, typeParamDecl): Expression = let id = name |> Option.map Identifier let body = match body with | U2.Case1 body -> body | U2.Case2 e -> BlockStatement [|ReturnStatement(e, ?loc=e.Loc)|] - upcast FunctionExpression(args, body, ?id=id) + upcast FunctionExpression(args, body, ?id=id, ?returnType=returnType, ?typeParameters=typeParamDecl) let optimizeTailCall (com: IBabelCompiler) (ctx: Context) range (tc: ITailCallOpportunity) args = let rec checkCrossRefs tempVars allArgs = function @@ -361,15 +605,15 @@ module Util = let genParamNames = ent.GenericParameters |> Seq.map (fun x -> x.Name) |> Seq.toArray Array.zip genParamNames generics |> Map let fields = - ent.FSharpFields |> Seq.map (fun x -> + ent.FSharpFields |> Seq.map (fun fi -> let typeInfo = - FSharp2Fable.TypeHelpers.makeType com Map.empty x.FieldType + FSharp2Fable.TypeHelpers.makeType com Map.empty fi.FieldType |> transformTypeInfo com ctx r genMap - (ArrayExpression [|StringLiteral x.Name; typeInfo|] :> Expression)) + (ArrayExpression [|StringLiteral fi.Name; typeInfo|] :> Expression)) |> Seq.toArray let fields = ArrowFunctionExpression([||], ArrayExpression fields :> Expression |> U2.Case2) :> Expression [|fullnameExpr; upcast ArrayExpression generics; jsConstructor com ctx ent; fields|] - |> coreLibCall com ctx None "Reflection" "record" + |> coreReflectionCall com ctx None "record" and transformUnionReflectionInfo com ctx r (ent: FSharpEntity) generics = let fullname = defaultArg ent.TryFullName Naming.unknown @@ -402,38 +646,30 @@ module Util = caseInfo) |> Seq.toArray let cases = ArrowFunctionExpression([||], ArrayExpression cases :> Expression |> U2.Case2) :> Expression [|fullnameExpr; upcast ArrayExpression generics; jsConstructor com ctx ent; cases|] - |> coreLibCall com ctx None "Reflection" "union" + |> coreReflectionCall com ctx None "union" and transformTypeInfo (com: IBabelCompiler) ctx r (genMap: Map) t: Expression = let primitiveTypeInfo name = - coreValue com ctx "Reflection" name + coreValue com ctx "Reflection" (name + "_type") let numberInfo kind = - match kind with - | Int8 -> "int8" - | UInt8 -> "uint8" - | Int16 -> "int16" - | UInt16 -> "uint16" - | Int32 -> "int32" - | UInt32 -> "uint32" - | Float32 -> "float32" - | Float64 -> "float64" + getNumberKindName kind |> primitiveTypeInfo let nonGenericTypeInfo fullname = [| StringLiteral fullname :> Expression |] - |> coreLibCall com ctx None "Reflection" "type" + |> coreReflectionCall com ctx None "generic" let resolveGenerics generics: Expression[] = generics |> Array.map (transformTypeInfo com ctx r genMap) let genericTypeInfo name genArgs = let resolved = resolveGenerics genArgs - coreLibCall com ctx None "Reflection" name resolved + coreReflectionCall com ctx None name resolved let genericEntity (ent: FSharpEntity) generics = let fullname = defaultArg ent.TryFullName Naming.unknown let fullnameExpr = StringLiteral fullname :> Expression let args = if Array.isEmpty generics then [|fullnameExpr|] else [|fullnameExpr; ArrayExpression generics :> Expression|] - coreLibCall com ctx None "Reflection" "type" args + coreReflectionCall com ctx None "generic" args match t with - // TODO: Type info forErasedUnion? - | Fable.ErasedUnion _ | Fable.Any -> primitiveTypeInfo "obj" + | Fable.ErasedUnion _genArgs -> primitiveTypeInfo "obj" // TODO: Type info for ErasedUnion? + | Fable.Any -> primitiveTypeInfo "obj" | Fable.GenericParam name -> match Map.tryFind name genMap with | Some t -> t @@ -462,7 +698,7 @@ module Util = |> Seq.toArray |> ArrayExpression [|StringLiteral fullName :> Expression; numberInfo numberKind; cases :> _|] - |> coreLibCall com ctx None "Reflection" "enumType" + |> coreReflectionCall com ctx None "enum" | Fable.Number kind -> numberInfo kind | Fable.FunctionType(Fable.LambdaType argType, returnType) -> @@ -470,16 +706,16 @@ module Util = | Fable.FunctionType(Fable.DelegateType argTypes, returnType) -> genericTypeInfo "delegate" ([|yield! argTypes; yield returnType|]) | Fable.Tuple genArgs -> genericTypeInfo "tuple" (List.toArray genArgs) - | Fable.Option gen -> genericTypeInfo "option" [|gen|] - | Fable.Array gen -> genericTypeInfo "array" [|gen|] - | Fable.List gen -> genericTypeInfo "list" [|gen|] + | Fable.Option genArg -> genericTypeInfo "option" [|genArg|] + | Fable.Array genArg -> genericTypeInfo "array" [|genArg|] + | Fable.List genArg -> genericTypeInfo "list" [|genArg|] | Fable.Regex -> nonGenericTypeInfo Types.regex | Fable.MetaType -> nonGenericTypeInfo Types.type_ | Fable.AnonymousRecordType(fieldNames, genArgs) -> let genArgs = resolveGenerics (List.toArray genArgs) Array.zip fieldNames genArgs |> Array.map (fun (k, t) -> ArrayExpression [|StringLiteral k; t|] :> Expression) - |> coreLibCall com ctx None "Reflection" "anonRecord" + |> coreReflectionCall com ctx None "anonRecord" | Fable.DeclaredType(ent, generics) -> match ent, generics with | Replacements.BuiltinEntity kind -> @@ -534,13 +770,13 @@ module Util = let fullname = defaultArg ent.TryFullName Naming.unknown let fullnameExpr = StringLiteral fullname :> Expression let args = if Array.isEmpty generics then [|fullnameExpr|] else [|fullnameExpr; ArrayExpression generics :> Expression|] - coreLibCall com ctx None "Reflection" "type" args + coreReflectionCall com ctx None "generic" args let transformValue (com: IBabelCompiler) (ctx: Context) r value: Expression = match value with | Fable.TypeInfo t -> transformTypeInfo com ctx r Map.empty t | Fable.Null _ -> upcast NullLiteral(?loc=r) - | Fable.UnitConstant -> upcast NullLiteral(?loc=r) // TODO: Use `void 0`? + | Fable.UnitConstant -> upcast UnaryExpression(UnaryVoid, NullLiteral(), ?loc=r) | Fable.BoolConstant x -> upcast BooleanLiteral(x, ?loc=r) | Fable.CharConstant x -> upcast StringLiteral(string x, ?loc=r) | Fable.StringConstant x -> upcast StringLiteral(x, ?loc=r) @@ -595,16 +831,19 @@ module Util = let transformObjectExpr (com: IBabelCompiler) ctx members (boundThis: string) baseCall: Expression = let makeObjMethod kind prop computed hasSpread args body = let boundThis, args = prepareBoundThis boundThis args - let args, body = getMemberArgsAndBody com ctx None boundThis args hasSpread body - ObjectMethod(kind, prop, args, body, computed_=computed) |> U3.Case2 |> Some + let args, body, returnType, typeParamDecl = + getMemberArgsAndBody com ctx None boundThis args hasSpread body + ObjectMethod(kind, prop, args, body, computed_=computed, + ?returnType=returnType, ?typeParameters=typeParamDecl) |> U3.Case2 |> Some let pojo = members |> List.choose (fun (Fable.ObjectMember(key, expr, kind)) -> match kind, expr with | Fable.ObjectValue, Fable.Function(Fable.Delegate args, body, _) -> // Don't call the `makeObjMethod` helper here because function as values don't bind `this` arg - let args, body' = getMemberArgsAndBody com ctx None None args false body + let args, body', returnType, typeParamDecl = getMemberArgsAndBody com ctx None None args false body let prop, computed = memberFromExpr com ctx key - ObjectMethod(ObjectMeth, prop, args, body', computed_=computed) |> U3.Case2 |> Some + ObjectMethod(ObjectMeth, prop, args, body', computed, + ?returnType=returnType, ?typeParameters=typeParamDecl) |> U3.Case2 |> Some | Fable.ObjectValue, TransformExpr com ctx value -> let prop, computed = memberFromExpr com ctx key ObjectProperty(prop, value, computed_=computed) |> U3.Case1 |> Some @@ -707,7 +946,7 @@ module Util = |> getExpr None (get None baseClassExpr "prototype") callFunctionWithThisContext range baseProtoMember (ident thisIdent) args | Some thisArg, None -> - upcast CallExpression(com.TransformAsExpr(ctx, thisArg),List.toArray args, ?loc=range) + upcast CallExpression(com.TransformAsExpr(ctx, thisArg), List.toArray args, ?loc=range) | Some thisArg, Some(TransformExpr com ctx m) -> let thisArg = com.TransformAsExpr(ctx, thisArg) upcast CallExpression(getExpr None thisArg m, List.toArray args, ?loc=range) @@ -750,7 +989,7 @@ module Util = let ctx = { ctx with TailCallOpportunity = None } let handler = catch |> Option.map (fun (param, body) -> - CatchClause (ident param |> toPattern, + CatchClause (identAsPattern param, transformBlock com ctx returnStrategy body)) let finalizer = finalizer |> Option.map (transformBlock com ctx None) @@ -791,7 +1030,7 @@ module Util = get range expr fieldName | Fable.TupleGet index -> getExpr range expr (ofInt index) | Fable.OptionValue -> - if mustWrapOption typ + if mustWrapOption typ || com.Options.typeDecls then coreLibCall com ctx None "Option" "value" [|expr|] else expr | Fable.UnionTag -> getUnionExprTag range expr @@ -828,8 +1067,9 @@ module Util = match args with | Fable.Lambda arg -> [arg] | Fable.Delegate args -> args - com.TransformFunction(ctx, Some var.Name, args, body) - ||> makeFunctionExpression (Some var.Name) + let name = Some var.Name + transformFunc com ctx name args body + |> makeFunctionExpression name | _ -> com.TransformAsExpr(ctx, value) |> wrapIntExpression value.Type @@ -839,13 +1079,13 @@ module Util = let transformBindingAsStatements (com: IBabelCompiler) ctx (var: Fable.Ident) (value: Fable.Expr) = if isJsStatement ctx false value then - let var = ident var + let var = typedIdent com ctx var let decl = VariableDeclaration(toPattern var) :> Statement let body = com.TransformAsStatements(ctx, Some(Assign var), value) Array.append [|decl|] body else let value = transformBindingExprBody com ctx var value - [|varDeclaration (ident var) var.IsMutable value :> Statement|] + [|varDeclaration (typedIdent com ctx var) var.IsMutable value :> Statement|] let transformTypeTest (com: IBabelCompiler) ctx range expr (typ: Fable.Type): Expression = let fail msg = @@ -870,29 +1110,28 @@ module Util = | Fable.FunctionType _ -> jsTypeof "function" expr | Fable.Array _ | Fable.Tuple _ -> coreLibCall com ctx None "Util" "isArrayLike" [|com.TransformAsExpr(ctx, expr)|] - | Fable.List _ -> - jsInstanceof (coreValue com ctx "Types" "List") expr + | Fable.List _ -> jsInstanceof (coreValue com ctx "Types" "List") expr | Replacements.Builtin kind -> match kind with | Replacements.BclGuid -> jsTypeof "string" expr | Replacements.BclTimeSpan -> jsTypeof "number" expr - | Replacements.BclDateTime + | Replacements.BclDateTime -> jsInstanceof (Identifier "Date") expr | Replacements.BclDateTimeOffset -> jsInstanceof (Identifier "Date") expr | Replacements.BclTimer -> jsInstanceof (coreValue com ctx "Timer" "default") expr - | Replacements.BclInt64 + | Replacements.BclInt64 -> jsInstanceof (coreValue com ctx "Long" "default") expr | Replacements.BclUInt64 -> jsInstanceof (coreValue com ctx "Long" "default") expr | Replacements.BclDecimal -> jsInstanceof (coreValue com ctx "Decimal" "default") expr | Replacements.BclBigInt -> coreLibCall com ctx None "BigInt" "isBigInt" [|com.TransformAsExpr(ctx, expr)|] - | Replacements.BclHashSet _ - | Replacements.BclDictionary _ - | Replacements.BclKeyValuePair _ - | Replacements.FSharpSet _ - | Replacements.FSharpMap _ -> fail "set/maps" - | Replacements.FSharpResult _ - | Replacements.FSharpChoice _ - | Replacements.FSharpReference _ -> fail "result/choice/reference" + | Replacements.BclHashSet _ -> fail "MutableSet" // TODO: + | Replacements.BclDictionary _ -> fail "MutableMap" // TODO: + | Replacements.BclKeyValuePair _ -> fail "KeyValuePair" // TODO: + | Replacements.FSharpSet _ -> fail "Set" // TODO: + | Replacements.FSharpMap _ -> fail "Map" // TODO: + | Replacements.FSharpResult _ -> fail "Result" // TODO: + | Replacements.FSharpChoice _ -> fail "Choice" // TODO: + | Replacements.FSharpReference _ -> fail "FSharpRef" // TODO: | Fable.AnonymousRecordType _ -> - "Type testing is not yet supported for anonymous records" // TODO + "Type testing is not yet supported for anonymous records" // TODO: |> addWarning com [] range upcast BooleanLiteral false | Fable.DeclaredType (ent, genArgs) -> @@ -1088,18 +1327,18 @@ module Util = let transformDecisionTreeWithTwoSwitches (com: IBabelCompiler) ctx returnStrategy (targets: (Fable.Ident list * Fable.Expr) list) treeExpr = // Declare $target and bound idents - let targetId = com.GetUniqueVar("target") + let targetId = makeIdentUnique com "target" let varDeclaration = let boundIdents = targets |> List.collect (fun (idents,_) -> - idents |> List.map (fun i -> i.Name, None)) - multiVarDeclaration Var ((targetId, None)::boundIdents) + idents |> List.map (fun id -> typedIdent com ctx id, None)) + multiVarDeclaration Var ((typedIdent com ctx targetId, None)::boundIdents) // Transform targets as switch let switch2 = // TODO: Declare the last case as the default case? let cases = targets |> List.mapi (fun i (_,target) -> [makeIntConst i], target) - transformSwitch com ctx true returnStrategy (makeIdentNonMangled targetId |> Fable.IdentExpr) cases None + transformSwitch com ctx true returnStrategy (targetId |> Fable.IdentExpr) cases None // Transform decision tree - let targetAssign = Target(Identifier targetId) + let targetAssign = Target(ident targetId) let ctx = { ctx with DecisionTargets = targets } match transformDecisionTreeAsSwitch treeExpr with | Some(evalExpr, cases, (defaultIndex, defaultBoundValues)) -> @@ -1111,11 +1350,14 @@ module Util = let decisionTree = com.TransformAsStatements(ctx, Some targetAssign, treeExpr) [| yield varDeclaration; yield! decisionTree; yield switch2 |] - let transformDecisionTreeAsStaments (com: IBabelCompiler) (ctx: Context) returnStrategy + let transformDecisionTreeAsStatements (com: IBabelCompiler) (ctx: Context) returnStrategy (targets: (Fable.Ident list * Fable.Expr) list) (treeExpr: Fable.Expr): Statement[] = - // If some targets are referenced multiple times, host bound idents, + // If some targets are referenced multiple times, hoist bound idents, // resolve the decision index and compile the targets as a switch - match getTargetsWithMultipleReferences treeExpr with + let targetsWithMultiRefs = + if com.Options.typeDecls then [] // no hoisting when compiled with types + else getTargetsWithMultipleReferences treeExpr + match targetsWithMultiRefs with | [] -> let ctx = { ctx with DecisionTargets = targets } match transformDecisionTreeAsSwitch treeExpr with @@ -1169,7 +1411,7 @@ module Util = transformTest com ctx range kind expr | Fable.Function(FunctionArgs args, body, name) -> - com.TransformFunction(ctx, name, args, body) ||> makeFunctionExpression name + transformFunc com ctx name args body |> makeFunctionExpression name | Fable.ObjectExpr (members, _, baseCall) -> transformObjectExpr com ctx members "this" baseCall @@ -1222,7 +1464,7 @@ module Util = [|transformValue com ctx r kind |> resolveExpr kind.Type returnStrategy|] | Fable.IdentExpr id -> - [|ident id :> Expression |> resolveExpr id.Type returnStrategy|] + [|identAsExpr id |> resolveExpr id.Type returnStrategy|] | Fable.Import(selector, path, kind, t, r) -> [|transformImport com ctx r selector path kind |> resolveExpr t returnStrategy|] @@ -1231,8 +1473,8 @@ module Util = [|transformTest com ctx range kind expr |> resolveExpr Fable.Boolean returnStrategy|] | Fable.Function(FunctionArgs args, body, name) -> - [|com.TransformFunction(ctx, name, args, body) - ||> makeFunctionExpression name |> resolveExpr expr.Type returnStrategy|] + [|transformFunc com ctx name args body |> makeFunctionExpression name + |> resolveExpr expr.Type returnStrategy|] | Fable.ObjectExpr (members, t, baseCall) -> [|transformObjectExpr com ctx members "this" baseCall |> resolveExpr t returnStrategy|] @@ -1290,7 +1532,7 @@ module Util = transformTryCatch com ctx r returnStrategy (body, catch, finalizer) | Fable.DecisionTree(expr, targets) -> - transformDecisionTreeAsStaments com ctx returnStrategy targets expr + transformDecisionTreeAsStatements com ctx returnStrategy targets expr | Fable.DecisionTreeSuccess(idx, boundValues, _) -> transformDecisionTreeSuccessAsStatements com ctx returnStrategy idx boundValues @@ -1306,7 +1548,7 @@ module Util = else BinaryOperator.BinaryGreaterOrEqual, UpdateOperator.UpdateMinus ForStatement( transformBlock com ctx None body, - start |> varDeclaration (ident var) true |> U2.Case1, + start |> varDeclaration (typedIdent com ctx var) true |> U2.Case1, BinaryExpression (op1, ident var, limit), UpdateExpression (op2, false, ident var), ?loc=range) :> Statement |> Array.singleton @@ -1315,7 +1557,7 @@ module Util = let tailcallChance = Option.map (fun name -> NamedTailCallOpportunity(com, name, args) :> ITailCallOpportunity) name - let args = discardUnitArg args |> List.map ident + let args = discardUnitArg args |> List.map (typedIdent com ctx) let declaredVars = ResizeArray() let mutable isTailCallOptimized = false let ctx = @@ -1333,11 +1575,17 @@ module Util = | true, Some tc, U2.Case1 body -> // Replace args, see NamedTailCallOpportunity constructor let args, body = - let varDeclaration = - List.zip args tc.Args |> List.map (fun (arg, tempVar) -> - arg.Name, Some(Identifier tempVar :> Expression)) + let tcArgs = + tc.Args + |> List.zip args + |> List.map (fun (arg, tcArg) -> + Identifier(tcArg, ?typeAnnotation=arg.TypeAnnotation)) + let varDecls = + tcArgs + |> List.map (fun arg -> Some (arg :> Expression)) + |> List.zip args |> multiVarDeclaration Const - tc.Args |> List.map Identifier, BlockStatement(Array.append [|varDeclaration|] body.Body) + tcArgs, 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)) @@ -1348,7 +1596,7 @@ module Util = then body else let varDeclStatement = - multiVarDeclaration Var [for v in declaredVars -> v.Name, None] + multiVarDeclaration Var [for v in declaredVars -> typedIdent com ctx v, None] let bodyStatements = match body with | U2.Case1 bodyBlock -> bodyBlock.Body @@ -1368,10 +1616,11 @@ module Util = let decl: Declaration = match expr with | :? ClassExpression as e -> - upcast ClassDeclaration(e.Body, privateIdent, + upcast ClassDeclaration(e.Body, ?id=Some privateIdent, ?superClass=e.SuperClass, ?typeParameters=e.TypeParameters, ?loc=r) | :? FunctionExpression as e -> - upcast FunctionDeclaration(privateIdent, e.Params, e.Body, ?loc=r) + upcast FunctionDeclaration(e.Params, e.Body, ?id=Some privateIdent, + ?returnType=e.ReturnType, ?typeParameters=e.TypeParameters, ?loc=r) | _ -> upcast varDeclaration privateIdent isMutable expr if not isPublic then U2.Case1 (decl :> Statement) @@ -1379,29 +1628,123 @@ module Util = ExportNamedDeclaration(decl) :> ModuleDeclaration |> U2.Case2 - let declareType com ctx r isPublic (ent: FSharpEntity) name consArgs consBody baseExpr: U2 list = + let makeEntityTypeParamDecl (com: IBabelCompiler) ctx (ent: FSharpEntity) = + if com.Options.typeDecls then + getEntityGenParams ent |> makeTypeParamDecl + else + None + + let makeInterfaceExtends com ctx (ent: FSharpEntity) = + ent.AllInterfaces + |> Seq.choose (fun typ -> + match FSharp2Fable.TypeHelpers.makeType com Map.empty typ with + | Fable.DeclaredType(ent, genArgs) -> + match ent.TryFullName with + | Some Types.ienumerableGeneric -> + let id = Identifier("Iterable") + let typeParamInst = makeGenTypeParamInst com ctx genArgs + InterfaceExtends(id, ?typeParameters=typeParamInst) |> Some + // | Some Types.iequatableGeneric -> + // let id = makeImportTypeId com ctx "Util" "IEquatable" + // let typeParamInst = makeGenTypeParamInst com ctx genArgs + // InterfaceExtends(id, ?typeParameters=typeParamInst) |> Some + | Some Types.iequatable -> + let id = makeImportTypeId com ctx "Types" "IEquatable" + InterfaceExtends(id) |> Some + | Some Types.icomparable -> + let id = makeImportTypeId com ctx "Types" "IComparable" + InterfaceExtends(id) |> Some + | Some Types.idisposable -> + let id = makeImportTypeId com ctx "Util" "IDisposable" + InterfaceExtends(id) |> Some + // TODO: other interfaces + | _ -> None + | _ -> None + ) + + let makeInterfaceExpression com ctx r (ent: FSharpEntity) name (baseExpr: Expression option) = + let properties = + ent.FSharpFields + |> Seq.map (fun field -> + let id = + if Naming.hasIdentForbiddenChars field.Name + then StringLiteral(field.Name) |> U2.Case2 + else Identifier(field.Name) |> U2.Case1 + let ta = + FSharp2Fable.TypeHelpers.makeType com Map.empty field.FieldType + |> typeAnnotation com ctx + ObjectTypeProperty(id, ta)) + |> Seq.toArray + let baseExt = + match baseExpr with + | Some expr -> + match expr with + | :? Identifier as id -> + let typeParamInst = + FSharp2Fable.Helpers.tryEntityBase ent + |> Option.bind (getEntityGenParams >> makeTypeParamInst) + InterfaceExtends(id, ?typeParameters=typeParamInst) |> Seq.singleton + | _ -> Seq.empty + | _ -> Seq.empty + let interfaceExt = makeInterfaceExtends com ctx ent + let allExt = Seq.append baseExt interfaceExt |> Seq.toArray + let extends = if Array.isEmpty allExt then None else Some allExt + let id = Identifier(name) + let body = ObjectTypeAnnotation(properties) + let typeParamDecl = getEntityGenParams ent |> makeTypeParamDecl + InterfaceDeclaration(id, body, ?extends_=extends, ?typeParameters=typeParamDecl, ?loc=r) + + let declareObjectType (com: IBabelCompiler) ctx r isPublic (ent: FSharpEntity) name (consArgs: Pattern[]) (consBody: BlockStatement) baseExpr = let displayName = ent.TryGetFullDisplayName() |> Option.map (Naming.unsafeReplaceIdentForbiddenChars '_') |> Option.defaultValue name - let consFunction = makeFunctionExpression (Some displayName) consArgs (U2.Case1 consBody) + let returnType = None + let typeParamDecl = makeEntityTypeParamDecl com ctx ent + let consFunction = makeFunctionExpression (Some displayName) (consArgs, U2.Case1 consBody, returnType, typeParamDecl) + match baseExpr with + | Some e -> [|consFunction; e|] + | None -> [|consFunction|] + |> coreLibCall com ctx None "Types" "declare" + |> declareModuleMember r isPublic name false + + let declareClassType (com: IBabelCompiler) ctx r isPublic (ent: FSharpEntity) name (consArgs: Pattern[]) (consBody: BlockStatement) baseExpr = + let key = Identifier "constructor" + let returnType = None + let typeParamDecl = makeEntityTypeParamDecl com ctx ent + let classMethod = ClassMethod(ClassImplicitConstructor, key, consArgs, consBody, false, false, ?returnType=returnType, ?typeParameters=typeParamDecl, ?loc=r) + let classBody = ClassBody([| U2.Case1 classMethod |], ?loc=r) + let classExpr = ClassExpression(classBody, ?superClass=baseExpr, ?loc=r) + classExpr |> declareModuleMember r isPublic name false + + let declareType (com: IBabelCompiler) ctx r isPublic (ent: FSharpEntity) name (consArgs: Pattern[]) (consBody: BlockStatement) baseExpr: U2 list = let typeDeclaration = - match baseExpr with - | Some e -> [|consFunction; e|] - | None -> [|consFunction|] - |> coreLibCall com ctx None "Types" "declare" - |> declareModuleMember r isPublic name false + // if com.Options.typeDecls && (ent.IsFSharpUnion || ent.IsFSharpRecord || ent.IsValueType || ent.IsFSharpExceptionDeclaration) + // then declareClassType com ctx r isPublic ent name consArgs consBody baseExpr + // else + declareObjectType com ctx r isPublic ent name consArgs consBody baseExpr let reflectionDeclaration = - let genArgs = Array.init ent.GenericParameters.Count (fun _ -> makeIdentUnique com "gen" |> ident) + let genArgs = Array.init ent.GenericParameters.Count (fun _ -> makeIdentUnique com "gen" |> typedIdent com ctx) let body = transformReflectionInfo com ctx r ent (Array.map (fun x -> x :> _) genArgs) - makeFunctionExpression None (Array.map (fun x -> U2.Case2(upcast x)) genArgs) (U2.Case2 body) + let returnType = + if com.Options.typeDecls then + makeImportTypeAnnotation com ctx [] "Reflection" "TypeInfo" + |> TypeAnnotation |> Some + else None + makeFunctionExpression None (Array.map (fun x -> U2.Case2(upcast x)) genArgs, U2.Case2 body, returnType, None) |> declareModuleMember None isPublic (Naming.appendSuffix name Naming.reflectionSuffix) false - [typeDeclaration; reflectionDeclaration] + if com.Options.typeDecls then + let interfaceExpr = makeInterfaceExpression com ctx r ent name baseExpr + let interfaceDeclaration = + ExportNamedDeclaration(interfaceExpr) :> ModuleDeclaration |> U2.Case2 + [interfaceDeclaration; typeDeclaration; reflectionDeclaration] + else + [typeDeclaration; reflectionDeclaration] let transformModuleFunction (com: IBabelCompiler) ctx (info: Fable.ValueDeclarationInfo) args body = - let args, body = getMemberArgsAndBody com ctx (Some info.Name) None args info.HasSpread body + let args, body, returnType, typeParamDecl = getMemberArgsAndBody com ctx (Some info.Name) None args info.HasSpread body // Don't lexically bind `this` (with arrow function) or it will fail with extension members - let expr: Expression = upcast FunctionExpression(args, body) + let expr: Expression = upcast FunctionExpression(args, body, ?returnType=returnType, ?typeParameters=typeParamDecl) if info.IsEntryPoint then declareEntryPoint com ctx expr |> U2.Case1 else @@ -1421,8 +1764,8 @@ module Util = let transformOverrideProperty (com: IBabelCompiler) ctx (info: Fable.AttachedMemberDeclarationInfo) getter setter = let funcExpr (args, body) = let boundThis, args = prepareBoundThis "this" args - let args, body = getMemberArgsAndBody com ctx None boundThis args false body - FunctionExpression(args, body) + let args, body, returnType, typeParamDecl = getMemberArgsAndBody com ctx None boundThis args false body + makeFunctionExpression None (args, U2.Case1 body, returnType, typeParamDecl) let getterFuncExpr = Option.map funcExpr getter let setterFuncExpr = Option.map funcExpr setter let funcCons = Identifier info.EntityName :> Expression @@ -1449,8 +1792,8 @@ module Util = | Fable.ObjectMethod hasSpread -> info.Name, hasSpread, body | _ -> info.Name, false, body let boundThis, args = prepareBoundThis "this" args - let args, body = getMemberArgsAndBody com ctx None boundThis args hasSpread body - let funcExpr = FunctionExpression(args, body) + let args, body, returnType, typeParamDecl = getMemberArgsAndBody com ctx None boundThis args hasSpread body + let funcExpr = makeFunctionExpression None (args, U2.Case1 body, returnType, typeParamDecl) let protoMember = match memberName with | Naming.StartsWith "Symbol." symbolName -> get None (Identifier "Symbol") symbolName @@ -1469,25 +1812,28 @@ module Util = Identifier "name" |> toPattern Identifier "fields" |> restElement|] let body = - [Identifier "tag" :> Expression; Identifier "name" :> _; SpreadElement(Identifier "fields") :> _] - |> callFunctionWithThisContext None baseRef thisExpr |> ExpressionStatement - declareType com ctx r info.IsPublic info.Entity info.EntityName args (BlockStatement [|body|]) (Some baseRef) + [Identifier "tag" :> Expression + Identifier "name" :> Expression + SpreadElement(Identifier "fields") :> Expression] + |> callFunctionWithThisContext None baseRef thisExpr + |> ExpressionStatement :> Statement |> Array.singleton |> BlockStatement + declareType com ctx r info.IsPublic info.Entity info.EntityName args body (Some baseRef) let transformCompilerGeneratedConstructor (com: IBabelCompiler) ctx r (info: Fable.CompilerGeneratedConstructorInfo) = let args = [| for i = 1 to info.Entity.FSharpFields.Count do yield Identifier("arg" + string i) |] - let setters = + let body = info.Entity.FSharpFields - |> Seq.mapi (fun i fi -> - let left = get None (ThisExpression()) fi.Name + |> Seq.mapi (fun i field -> + let left = get None (ThisExpression()) field.Name let right = /// Shortcut instead of using wrapIntExpression - if FSharp2Fable.TypeHelpers.isSignedIntType fi.FieldType + if FSharp2Fable.TypeHelpers.isSignedIntType field.FieldType then BinaryExpression(BinaryOrBitwise, args.[i], NumericLiteral(0.)) :> Expression else args.[i] :> _ assign None left right |> ExpressionStatement :> Statement) - |> Seq.toArray + |> Seq.toArray |> BlockStatement let baseExpr = if info.Entity.IsFSharpExceptionDeclaration then coreValue com ctx "Types" "FSharpException" |> Some @@ -1495,31 +1841,49 @@ module Util = then coreValue com ctx "Types" "Record" |> Some else None let args = [|for arg in args do yield arg |> toPattern|] - declareType com ctx r info.IsPublic info.Entity info.EntityName args (BlockStatement setters) baseExpr + declareType com ctx r info.IsPublic info.Entity info.EntityName args body baseExpr let transformImplicitConstructor (com: IBabelCompiler) ctx r (info: Fable.ClassImplicitConstructorInfo) = let boundThis = Some("this", info.BoundConstructorThis) - let consIdent = Identifier info.EntityName :> Expression - let args, body = getMemberArgsAndBody com ctx None boundThis info.Arguments info.HasSpread info.Body + let consIdent = Identifier(info.EntityName) :> Expression + let args, body, returnType, typeParamDecl = getMemberArgsAndBody com ctx None boundThis info.Arguments info.HasSpread info.Body + let returnType, typeParamDecl = + // change constructor's return type from void to entity type + if com.Options.typeDecls then + let id = Identifier(info.EntityName) + let genParams = getEntityGenParams info.Entity + let typeParamInst = makeTypeParamInst genParams + let returnType = + GenericTypeAnnotation(id, ?typeParameters=typeParamInst) :> TypeAnnotationInfo + |> TypeAnnotation |> Some + let typeParamDecl = makeTypeParamDecl genParams |> mergeTypeParamDecls typeParamDecl + returnType, typeParamDecl + else + returnType, typeParamDecl + let typedPattern = typedIdent com ctx >> toPattern let argIdents, argExprs: Pattern list * Expression list = match info.Arguments with | [] -> [], [] | [unitArg] when unitArg.Type = Fable.Unit -> [], [] | args when info.HasSpread -> let args = List.rev args - (restElement(ident args.Head)) :: (List.map identAsPattern args.Tail) |> List.rev, + (restElement(typedIdent com ctx args.Head)) :: (List.map typedPattern args.Tail) |> List.rev, (SpreadElement(ident args.Head) :> Expression) :: (List.map identAsExpr args.Tail) |> List.rev - | args -> List.map identAsPattern args, List.map identAsExpr args + | args -> + args |> List.map typedPattern, + args |> List.map identAsExpr let exposedCons = - FunctionExpression(List.toArray argIdents, + let body = BlockStatement [| ReturnStatement( ConditionalExpression( // Don't do a null check here, some environments can assign a value to `this`, see #1757 BinaryExpression(BinaryInstanceOf, ThisExpression(), consIdent), callFunctionWithThisContext None consIdent thisExpr argExprs, - NewExpression(consIdent,List.toArray argExprs)) - ) |]) + NewExpression(consIdent, List.toArray argExprs)) + ) |] + let consArgs = List.toArray argIdents + makeFunctionExpression None (consArgs, U2.Case1 body, returnType, typeParamDecl) let baseExpr = match info.Base with | Some(TransformExpr com ctx baseRef) -> Some baseRef @@ -1688,7 +2052,7 @@ module Compiler = member __.AddLog(msg, severity, ?range, ?fileName:string, ?tag: string) = com.AddLog(msg, severity, ?range=range, ?fileName=fileName, ?tag=tag) - let makeCompiler com = new BabelCompiler(com) + let makeCompiler com = BabelCompiler(com) let createFacade (sourceFiles: string[]) (facadeFile: string) = // Remove signature files so fable-splitter doesn't try to compile them @@ -1698,7 +2062,7 @@ module Compiler = let importFile = Array.last sourceFiles StringLiteral(Path.getRelativeFileOrDirPath false facadeFile false importFile) |> ExportAllDeclaration :> ModuleDeclaration |> U2.Case2 |> Array.singleton - Program(facadeFile, decls, sourceFiles_=sourceFiles) + Program(facadeFile, decls, sourceFiles_ = sourceFiles) let transformFile (com: ICompiler) (file: Fable.File) = try @@ -1709,12 +2073,14 @@ module Compiler = DecisionTargets = [] HoistVars = fun _ -> false TailCallOpportunity = None - OptimizeTailCall = fun () -> () } + OptimizeTailCall = fun () -> () + ScopedTypeParams = Set.empty } let rootDecls = transformDeclarations com ctx file.Declarations [] let importDecls = com.GetAllImports() |> transformImports - Program(file.SourcePath, importDecls@rootDecls |> List.toArray, - // We don't add imports as dependencies because those will be handled by Webpack - // TODO: Do it for other clients, like fable-splitter? - dependencies_ = Array.ofSeq file.InlineDependencies) + let body = importDecls @ rootDecls |> List.toArray + // We don't add imports as dependencies because those will be handled by Webpack + // TODO: Do it for other clients, like fable-splitter? + let dependencies = Array.ofSeq file.InlineDependencies + Program(file.SourcePath, body, dependencies_ = dependencies) with | ex -> exn (sprintf "%s (%s)" ex.Message file.SourcePath, ex) |> raise diff --git a/src/Fable.Transforms/Global/Compiler.fs b/src/Fable.Transforms/Global/Compiler.fs index 3c75df7553..98976dc600 100644 --- a/src/Fable.Transforms/Global/Compiler.fs +++ b/src/Fable.Transforms/Global/Compiler.fs @@ -9,6 +9,7 @@ type Verbosity = type CompilerOptions = { typedArrays: bool clampByteArrays: bool + typeDecls: bool debugMode: bool verbosity: Verbosity /// Meant for precompiled libraries (like the Repl Lib) diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 35bcd94142..28a4fa27f3 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -2812,7 +2812,7 @@ let types (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr optio let fsharpType methName (r: SourceLocation option) t (i: CallInfo) (args: Expr list) = match methName with | "MakeTupleType" -> - Helper.CoreCall("Reflection", "tuple", t, args, i.SignatureArgTypes, hasSpread=true, ?loc=r) |> Some + Helper.CoreCall("Reflection", "tuple_type", t, args, i.SignatureArgTypes, hasSpread=true, ?loc=r) |> Some // Prevent name clash with FSharpValue.GetRecordFields | "GetRecordFields" -> Helper.CoreCall("Reflection", "getRecordElements", t, args, i.SignatureArgTypes, ?loc=r) |> Some diff --git a/src/fable-compiler-js/src/Platform.fs b/src/fable-compiler-js/src/Platform.fs index 4f40d7ab29..a3dba2fc34 100644 --- a/src/fable-compiler-js/src/Platform.fs +++ b/src/fable-compiler-js/src/Platform.fs @@ -5,8 +5,9 @@ open Fable.Core.JsInterop type CmdLineOptions = { commonjs: bool optimize: bool - watchMode: bool sourceMaps: bool + typeDecls: bool + watchMode: bool } module JS = diff --git a/src/fable-compiler-js/src/app.fs b/src/fable-compiler-js/src/app.fs index 7067618e2e..cce2a95b5a 100644 --- a/src/fable-compiler-js/src/app.fs +++ b/src/fable-compiler-js/src/app.fs @@ -24,6 +24,12 @@ let printErrors showWarnings (errors: Fable.Standalone.Error[]) = errors |> Array.iter printError failwith "Too many errors." +let toFableCompilerConfig (options: CmdLineOptions): Fable.Standalone.CompilerConfig = + { typedArrays = not (options.typeDecls) + clampByteArrays = false + typeDecls = options.typeDecls + precompiledLib = None } + let parseFiles projectFileName outDir options = // parse project let (dllRefs, fileNames, otherOptions) = parseProject projectFileName @@ -64,7 +70,8 @@ let parseFiles projectFileName outDir options = // Fable (F# to Babel) let fableLibraryDir = "fable-library" - let parseFable (res, fileName) = fable.CompileToBabelAst(fableLibraryDir, res, fileName) + let fableConfig = options |> toFableCompilerConfig + let parseFable (res, fileName) = fable.CompileToBabelAst(fableLibraryDir, res, fileName, fableConfig) let trimPath (path: string) = path.Replace("../", "").Replace("./", "").Replace(":", "") let projDir = projectFileName |> normalizeFullPath |> Path.GetDirectoryName let libDir = getFableLibDir() |> normalizeFullPath @@ -89,8 +96,9 @@ let run opts projectFileName outDir = let options = { commonjs = Option.isSome commandToRun || opts |> Array.contains "--commonjs" optimize = opts |> Array.contains "--optimize-fcs" - watchMode = opts |> Array.contains "--watch" sourceMaps = opts |> Array.contains "--sourceMaps" + typeDecls = opts |> Array.contains "--typescript" + watchMode = opts |> Array.contains "--watch" } parseFiles projectFileName outDir options commandToRun |> Option.iter runCmdAndExitIfFails diff --git a/src/fable-compiler-js/src/util.js b/src/fable-compiler-js/src/util.js index 2321a8e6cf..b381b472f8 100644 --- a/src/fable-compiler-js/src/util.js +++ b/src/fable-compiler-js/src/util.js @@ -137,7 +137,7 @@ export function transformAndSaveBabelAst(babelAst, filePath, projDir, outDir, li // this solves a weird commonjs issue where some imports are not properly qualified babelAst = JSON.parse(serializeToJson(babelAst)); // somehow this helps with that const sourcePath = babelAst.fileName; - const jsPath = filePath.replace(FSHARP_EXT, ".js"); + const jsPath = filePath.replace(FSHARP_EXT, options.typeDecls ? ".ts" : ".js"); let outPath = Path.resolve(outDir, jsPath); outPath = ensureUniquePath(sourcePath, outPath); ensureDirExists(Path.dirname(outPath)); diff --git a/src/fable-library/FSharp.Core.fs b/src/fable-library/FSharp.Core.fs index 2ffe2b8d26..0ce8dcf2c0 100644 --- a/src/fable-library/FSharp.Core.fs +++ b/src/fable-library/FSharp.Core.fs @@ -29,7 +29,7 @@ module Operators = let nullArg x = raise(System.ArgumentNullException(x)) [] - let using (resource : 'T when 'T :> System.IDisposable) (action: 'T -> 'a) = + let using (resource: System.IDisposable) action = try action(resource) finally match (box resource) with null -> () | _ -> resource.Dispose() diff --git a/src/fable-library/Map.d.ts b/src/fable-library/Map.d.ts deleted file mode 100644 index d1f97fbaa2..0000000000 --- a/src/fable-library/Map.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { IEqualityComparer } from "./Util"; - -declare module "./Map" { - export function createMutable( - source: ReadonlyArray | null, - comparer: IEqualityComparer): Map; -} diff --git a/src/fable-library/Reflection.ts b/src/fable-library/Reflection.ts index edf6d18d22..c481cd5557 100644 --- a/src/fable-library/Reflection.ts +++ b/src/fable-library/Reflection.ts @@ -63,11 +63,11 @@ export function compare(t1: TypeInfo, t2: TypeInfo): number { } } -export function type(fullname: string, generics?: TypeInfo[]): TypeInfo { +export function generic_type(fullname: string, generics?: TypeInfo[]): TypeInfo { return new TypeInfo(fullname, generics); } -export function record( +export function record_type( fullname: string, generics: TypeInfo[], constructor: Constructor, @@ -75,13 +75,13 @@ export function record( return new TypeInfo(fullname, generics, constructor, fields); } -export function anonRecord(...fields: FieldInfo[]): TypeInfo { +export function anonRecord_type(...fields: FieldInfo[]): TypeInfo { return new TypeInfo("", undefined, undefined, () => fields); } export type CaseInfoInput = string | [string, FieldInfo[]]; -export function union( +export function union_type( fullname: string, generics: TypeInfo[], constructor: Constructor, @@ -93,48 +93,48 @@ export function union( return t; } -export function tuple(...generics: TypeInfo[]): TypeInfo { +export function tuple_type(...generics: TypeInfo[]): TypeInfo { return new TypeInfo("System.Tuple`" + generics.length, generics); } -export function delegate(...generics: TypeInfo[]): TypeInfo { +export function delegate_type(...generics: TypeInfo[]): TypeInfo { return new TypeInfo("System.Func`" + generics.length, generics); } -export function lambda(argType: TypeInfo, returnType: TypeInfo): TypeInfo { +export function lambda_type(argType: TypeInfo, returnType: TypeInfo): TypeInfo { return new TypeInfo("Microsoft.FSharp.Core.FSharpFunc`2", [argType, returnType]); } -export function option(generic: TypeInfo): TypeInfo { +export function option_type(generic: TypeInfo): TypeInfo { return new TypeInfo("Microsoft.FSharp.Core.FSharpOption`1", [generic]); } -export function list(generic: TypeInfo): TypeInfo { +export function list_type(generic: TypeInfo): TypeInfo { return new TypeInfo("Microsoft.FSharp.Collections.FSharpList`1", [generic]); } -export function array(generic: TypeInfo): TypeInfo { +export function array_type(generic: TypeInfo): TypeInfo { return new TypeInfo(generic.fullname + "[]", [generic]); } -export function enumType(fullname: string, underlyingType: TypeInfo, enumCases: EnumCase[]): TypeInfo { +export function enum_type(fullname: string, underlyingType: TypeInfo, enumCases: EnumCase[]): TypeInfo { return new TypeInfo(fullname, [underlyingType], undefined, undefined, undefined, enumCases); } -export const obj: TypeInfo = new TypeInfo("System.Object"); -export const unit: TypeInfo = new TypeInfo("Microsoft.FSharp.Core.Unit"); -export const char: TypeInfo = new TypeInfo("System.Char"); -export const string: TypeInfo = new TypeInfo("System.String"); -export const bool: TypeInfo = new TypeInfo("System.Boolean"); -export const int8: TypeInfo = new TypeInfo("System.SByte"); -export const uint8: TypeInfo = new TypeInfo("System.Byte"); -export const int16: TypeInfo = new TypeInfo("System.Int16"); -export const uint16: TypeInfo = new TypeInfo("System.UInt16"); -export const int32: TypeInfo = new TypeInfo("System.Int32"); -export const uint32: TypeInfo = new TypeInfo("System.UInt32"); -export const float32: TypeInfo = new TypeInfo("System.Single"); -export const float64: TypeInfo = new TypeInfo("System.Double"); -export const decimal: TypeInfo = new TypeInfo("System.Decimal"); +export const obj_type: TypeInfo = new TypeInfo("System.Object"); +export const unit_type: TypeInfo = new TypeInfo("Microsoft.FSharp.Core.Unit"); +export const char_type: TypeInfo = new TypeInfo("System.Char"); +export const string_type: TypeInfo = new TypeInfo("System.String"); +export const bool_type: TypeInfo = new TypeInfo("System.Boolean"); +export const int8_type: TypeInfo = new TypeInfo("System.SByte"); +export const uint8_type: TypeInfo = new TypeInfo("System.Byte"); +export const int16_type: TypeInfo = new TypeInfo("System.Int16"); +export const uint16_type: TypeInfo = new TypeInfo("System.UInt16"); +export const int32_type: TypeInfo = new TypeInfo("System.Int32"); +export const uint32_type: TypeInfo = new TypeInfo("System.UInt32"); +export const float32_type: TypeInfo = new TypeInfo("System.Single"); +export const float64_type: TypeInfo = new TypeInfo("System.Double"); +export const decimal_type: TypeInfo = new TypeInfo("System.Decimal"); export function name(info: FieldInfo | CaseInfo | TypeInfo): string { if (Array.isArray(info)) { @@ -182,7 +182,7 @@ export function isEnum(t: TypeInfo) { * but it should be enough for type comparison purposes */ export function getGenericTypeDefinition(t: TypeInfo) { - return t.generics == null ? t : new TypeInfo(t.fullname, t.generics.map(() => obj)); + return t.generics == null ? t : new TypeInfo(t.fullname, t.generics.map(() => obj_type)); } export function getEnumUnderlyingType(t: TypeInfo) { diff --git a/src/fable-library/Set.d.ts b/src/fable-library/Set.d.ts deleted file mode 100644 index 2ce1858f69..0000000000 --- a/src/fable-library/Set.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { IEqualityComparer } from "./Util"; - -declare module "./Set" { - export function createMutable( - source: ReadonlyArray | null, - comparer: IEqualityComparer): Set; -} diff --git a/src/fable-standalone/src/Interfaces.fs b/src/fable-standalone/src/Interfaces.fs index bdcdcc77e8..675b4f7287 100644 --- a/src/fable-standalone/src/Interfaces.fs +++ b/src/fable-standalone/src/Interfaces.fs @@ -47,6 +47,13 @@ type IBabelResult = abstract BabelAst: obj abstract FableErrors: Error[] +type CompilerConfig = + { typedArrays: bool + clampByteArrays: bool + typeDecls: bool + precompiledLib: (string -> (string * string) option) option + } + type IFableManager = abstract CreateChecker: references: string[] * readAllBytes: (string -> byte[]) * otherOptions: string[] -> IChecker abstract ClearParseCaches: checker: IChecker -> unit @@ -57,5 +64,5 @@ type IFableManager = abstract GetDeclarationLocation: parseResults: IParseResults * line: int * col: int * lineText: string -> Async abstract GetToolTipText: parseResults: IParseResults * line: int * col: int * lineText: string -> Async abstract GetCompletionsAtLocation: parseResults: IParseResults * line: int * col: int * lineText: string -> Async - abstract CompileToBabelAst: fableLibrary: string * parseResults: IParseResults * fileName: string * ?precompiledLib: (string->(string*string) option) -> IBabelResult + abstract CompileToBabelAst: fableLibrary: string * parseResults: IParseResults * fileName: string * ?config: CompilerConfig -> IBabelResult abstract FSharpAstToString: parseResults: IParseResults * fileName: string -> string diff --git a/src/fable-standalone/src/Main.fs b/src/fable-standalone/src/Main.fs index 83c7c71f20..b76d695bf6 100644 --- a/src/fable-standalone/src/Main.fs +++ b/src/fable-standalone/src/Main.fs @@ -213,16 +213,22 @@ let getCompletionsAtLocation (parseResults: ParseResults) (line: int) (col: int) return [||] } -let makeCompiler fableLibrary fileName (project: Project) precompiledLib (otherFSharpOptions: string[]) = - let options: Fable.CompilerOptions = - { typedArrays = true - clampByteArrays = false - debugMode = otherFSharpOptions |> Array.exists (fun x -> x = "--define:DEBUG" || x = "-d:DEBUG") - verbosity = Fable.Verbosity.Normal - outputPublicInlinedFunctions = false - precompiledLib = precompiledLib } - let com = Compiler(fileName, project, options, fableLibrary) - com +let defaultCompilerConfig: CompilerConfig = + { typedArrays = true + clampByteArrays = false + typeDecls = false + precompiledLib = None } + +let makeCompilerOptions (config: CompilerConfig option) (otherFSharpOptions: string[]): Fable.CompilerOptions = + let config = defaultArg config defaultCompilerConfig + let isDebug = otherFSharpOptions |> Array.exists (fun x -> x = "--define:DEBUG" || x = "-d:DEBUG") + { typedArrays = config.typedArrays + clampByteArrays = config.clampByteArrays + typeDecls = config.typeDecls + debugMode = isDebug + verbosity = Fable.Verbosity.Normal + outputPublicInlinedFunctions = false + precompiledLib = config.precompiledLib } let compileAst (com: Compiler) (project: Project) = FSharp2Fable.Compiler.transformFile com project.ImplementationFiles @@ -270,10 +276,11 @@ let init () = let res = parseResults :?> ParseResults getCompletionsAtLocation res line col lineText - member __.CompileToBabelAst(fableLibrary:string, parseResults:IParseResults, fileName:string, ?precompiledLib) = + member __.CompileToBabelAst(fableLibrary:string, parseResults:IParseResults, fileName:string, ?config) = let res = parseResults :?> ParseResults let project = res.GetProject() - let com = makeCompiler fableLibrary fileName project precompiledLib parseResults.OtherFSharpOptions + let options = makeCompilerOptions config parseResults.OtherFSharpOptions + let com = Compiler(fileName, project, options, fableLibrary) let ast = compileAst com project let errors = com.GetLogs() diff --git a/src/fable-standalone/src/Worker/Worker.fs b/src/fable-standalone/src/Worker/Worker.fs index d3bff5eb3b..ffa5651a32 100644 --- a/src/fable-standalone/src/Worker/Worker.fs +++ b/src/fable-standalone/src/Worker/Worker.fs @@ -111,11 +111,19 @@ let rec loop (box: MailboxProcessor) (state: State) = async { | Some fable, CompileCode(fsharpCode, otherFSharpOptions) -> try + // detect (and remove) a passed non-F# compiler option to avoid changing msg contract + let useTypeDeclarations = otherFSharpOptions |> Array.contains "--typescript" + let otherFSharpOptions = otherFSharpOptions |> Array.filter ((<>) "--typescript") // Check if we need to recreate the FableState because otherFSharpOptions have changed let! fable = makeFableState (Initialized fable) otherFSharpOptions let (parseResults, parsingTime) = measureTime (fun () -> fable.Manager.ParseFSharpScript(fable.Checker, FILE_NAME, fsharpCode, otherFSharpOptions)) () let (res, fableTransformTime) = measureTime (fun () -> - fable.Manager.CompileToBabelAst("fable-library", parseResults, FILE_NAME, fun x -> resolveLibCall(fable.LibMap, x))) () + let fableConfig = + { typedArrays = true + clampByteArrays = false + typeDecls = useTypeDeclarations + precompiledLib = Some (fun x -> resolveLibCall(fable.LibMap, x)) } + fable.Manager.CompileToBabelAst("fable-library", parseResults, FILE_NAME, fableConfig)) () let (jsCode, babelTime, babelErrors) = try let code, t = measureTime fable.BabelAstCompiler res.BabelAst diff --git a/src/fable-standalone/test/bench-compiler/Platform.fs b/src/fable-standalone/test/bench-compiler/Platform.fs index e31d2e4f7c..d0139976aa 100644 --- a/src/fable-standalone/test/bench-compiler/Platform.fs +++ b/src/fable-standalone/test/bench-compiler/Platform.fs @@ -1,5 +1,13 @@ module Fable.Compiler.Platform +type CmdLineOptions = { + commonjs: bool + optimize: bool + sourceMaps: bool + typeDecls: bool + watchMode: bool +} + #if DOTNET_FILE_SYSTEM && !FABLE_COMPILER open System.IO diff --git a/src/fable-standalone/test/bench-compiler/app.fs b/src/fable-standalone/test/bench-compiler/app.fs index 7a910f63a1..3c7632ff91 100644 --- a/src/fable-standalone/test/bench-compiler/app.fs +++ b/src/fable-standalone/test/bench-compiler/app.fs @@ -18,12 +18,11 @@ let printErrors showWarnings (errors: Fable.Standalone.Error[]) = errors |> Array.iter printError failwith "Too many errors." -type CmdLineOptions = { - commonjs: bool - optimize: bool - watchMode: bool - sourceMaps: bool -} +let toFableCompilerConfig (options: CmdLineOptions): Fable.Standalone.CompilerConfig = + { typedArrays = not (options.typeDecls) + clampByteArrays = false + typeDecls = options.typeDecls + precompiledLib = None } let parseFiles projectFileName outDir options = // parse project @@ -64,7 +63,8 @@ let parseFiles projectFileName outDir options = // Fable (F# to Babel) let fableLibraryDir = "fable-library" - let parseFable (res, fileName) = fable.CompileToBabelAst(fableLibraryDir, res, fileName) + let fableConfig = options |> toFableCompilerConfig + let parseFable (res, fileName) = fable.CompileToBabelAst(fableLibraryDir, res, fileName, fableConfig) let trimPath (path: string) = path.Replace("../", "").Replace("./", "").Replace(":", "") let projDir = projectFileName |> normalizeFullPath |> Path.GetDirectoryName @@ -98,8 +98,9 @@ let parseArguments (argv: string[]) = let options = { commonjs = opts |> Array.contains "--commonjs" optimize = opts |> Array.contains "--optimize-fcs" - watchMode = opts |> Array.contains "--watch" sourceMaps = opts |> Array.contains "--sourceMaps" + typeDecls = opts |> Array.contains "--typescript" + watchMode = opts |> Array.contains "--watch" } parseFiles projectFileName outDir options | _ -> printfn "%s" usage diff --git a/src/fable-standalone/test/bench-compiler/package.json b/src/fable-standalone/test/bench-compiler/package.json index a8ee380288..08730f9d5c 100644 --- a/src/fable-standalone/test/bench-compiler/package.json +++ b/src/fable-standalone/test/bench-compiler/package.json @@ -20,15 +20,19 @@ "rollup-bundle": "npm run rollup -- -i out-node/app.js -o dist/bundle.js --format esm", "terser-bundle": "npm run terser -- dist/bundle.js -o dist/bundle.min.js --mangle --compress", "webpack-bundle": "npm run webpack -- -p --entry ./out-node/app.js --output ./dist/bundle.min.js --target node", - "build-test-node": "node out-node/app.js ../../../../../fable-test/fable-test.fsproj out-test && npm run build-test-transform", - "build-test-dotnet": "dotnet run -c Release ../../../../../fable-test/fable-test.fsproj out-test && npm run build-test-transform", + "build-lib-dotnet": "dotnet run -c Release ../../../fable-library/Fable.Library.fsproj out-lib --typescript && npm run build-lib-transform", + "build-lib-transform": "node transform ../../../fable-library/Fable.Library.fsproj out-lib ../../../../build/fable-library --typescript", + "postbuild-lib-transform": "rmdir /s /q out-lib\\fable-library && xcopy /s /y ..\\..\\..\\fable-library\\*.ts out-lib\\ && cd out-lib && npx typescript --init", + "tsc-lib": "npx typescript -p out-lib --outDir out-lib-js", + "build-test-node": "node out-node/app.js ../../../../../fable-test/fable-test.fsproj out-test --typescript && npm run build-test-transform", + "build-test-dotnet": "dotnet run -c Release ../../../../../fable-test/fable-test.fsproj out-test --typescript && npm run build-test-transform", "build-test-dotnet-opt": "dotnet run -c Release ../../../../../fable-test/fable-test.fsproj out-test --optimize-fcs && npm run build-test-transform", - "build-test-transform": "node transform ../../../../../fable-test/fable-test.fsproj out-test ../../../../build/fable-library --sourceMaps", + "build-test-transform": "node transform ../../../../../fable-test/fable-test.fsproj out-test ../../../../build/fable-library --sourceMaps --typescript", "test-node": "node ./out-test/src/test.js", - "build-tests-node": "node out-node/app.js ../../../../tests/Main/Fable.Tests.fsproj out-tests && npm run build-tests-transform", - "build-tests-dotnet": "dotnet run -c Release ../../../../tests/Main/Fable.Tests.fsproj out-tests && npm run build-tests-transform", + "build-tests-node": "node out-node/app.js ../../../../tests/Main/Fable.Tests.fsproj out-tests --typescript && npm run build-tests-transform", + "build-tests-dotnet": "dotnet run -c Release ../../../../tests/Main/Fable.Tests.fsproj out-tests --typescript && npm run build-tests-transform", "build-tests-dotnet-opt": "dotnet run -c Release ../../../../tests/Main/Fable.Tests.fsproj out-tests --optimize-fcs && npm run build-tests-transform", - "build-tests-transform": "node transform ../../../../tests/Main/Fable.Tests.fsproj out-tests ../../../../build/fable-library --sourceMaps", + "build-tests-transform": "node transform ../../../../tests/Main/Fable.Tests.fsproj out-tests ../../../../build/fable-library --typescript", "tsc": "node ../../../../node_modules/typescript/bin/tsc", "babel": "node ../../../../node_modules/@babel/cli/bin/babel", "mocha": "node ../../../../node_modules/mocha/bin/mocha --colors", diff --git a/src/fable-standalone/test/bench-compiler/transform.js b/src/fable-standalone/test/bench-compiler/transform.js index 4db1e78ffd..91c05e2849 100644 --- a/src/fable-standalone/test/bench-compiler/transform.js +++ b/src/fable-standalone/test/bench-compiler/transform.js @@ -5,7 +5,8 @@ import BabelPlugins from "fable-babel-plugins"; const customPlugins = [ BabelPlugins.getRemoveUnneededNulls(), - BabelPlugins.getTransformMacroExpressions(Babel.template) + BabelPlugins.getTransformMacroExpressions(Babel.template), + // "babel-plugin-closure-elimination" ]; const FSHARP_EXT = /\.(fs|fsx)$/i; @@ -109,6 +110,7 @@ function main() { const libDir = Path.resolve(process.argv[4]); const commonjs = process.argv.find(v => v === "--commonjs"); const sourceMaps = process.argv.find(v => v === "--sourceMaps"); + const typeDecls = process.argv.find(v => v === "--typescript"); const babelOptions = commonjs ? { plugins: customPlugins.concat("@babel/plugin-transform-modules-commonjs") } : @@ -123,7 +125,7 @@ function main() { const babelJson = fs.readFileSync(filePath, "utf8"); const babelAst = JSON.parse(babelJson); const sourcePath = babelAst.fileName; - const outPath = filePath.replace(/\.json$/, ".js"); + const outPath = filePath.replace(/\.json$/, typeDecls ? ".ts" : ".js"); fixImportPaths(babelAst, sourcePath, outPath, projDir, outDir, libDir, babelOptions); const babelOpts = Object.assign({}, babelOptions); if (babelOpts.sourceMaps) { @@ -139,6 +141,18 @@ function main() { } } } + + // fix import paths from /fable-library/ to / + if (projPath.endsWith("Fable.Library.fsproj")) { + const filePaths = getFilePaths(outDir); + for (const filePath of filePaths) { + if (filePath.endsWith(".ts")) { + const code = fs.readFileSync(filePath, "utf8"); + const replaced = code.replace(/(?<=import .* from "\.+)\/fable-library\//g, "/"); + fs.writeFileSync(filePath, replaced); + } + } + } } main()