diff --git a/src/dotnet/Fable.Compiler/AST/AST.Babel.fs b/src/dotnet/Fable.Compiler/AST/AST.Babel.fs index 1f66ad9d50..9ed3230c47 100644 --- a/src/dotnet/Fable.Compiler/AST/AST.Babel.fs +++ b/src/dotnet/Fable.Compiler/AST/AST.Babel.fs @@ -42,10 +42,11 @@ type TypeAnnotation(typeAnnotation) = member __.Type = "TypeAnnotation" member __.TypeAnnotation: TypeAnnotationInfo = typeAnnotation -// TODO: TypeParameter can also have `variance` and `bound` properties -type TypeParameter(name) = +type TypeParameter(name, ?bound, ?``default``) = member __.Type = "TypeParameter" member __.Name: string = name + member __.Bound: TypeAnnotation option = bound + member __.Default: TypeAnnotationInfo option = ``default`` type TypeParameterDeclaration(``params``) = member __.Type = "TypeParameterDeclaration" @@ -284,7 +285,7 @@ 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(id, ``params``, body, ?generator_, ?async_, ?returnType, ?typeParameters, ?loc) = inherit Declaration("FunctionDeclaration", ?loc = loc) let generator = defaultArg generator_ false let async = defaultArg async_ false @@ -293,6 +294,8 @@ type FunctionDeclaration(id, ``params``, body, ?generator_, ?async_, ?loc) = member __.Body: BlockStatement = body member __.Generator = generator member __.Async = async + member __.ReturnType: TypeAnnotation option = returnType + member __.TypeParameters: TypeParameterDeclaration option = typeParameters // Expressions @@ -304,7 +307,7 @@ 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_, ?returnType, ?typeParameters, ?loc) = inherit Expression("ArrowFunctionExpression", ?loc = loc) let async = defaultArg async_ false let expression = match body with U2.Case1 _ -> false | U2.Case2 _ -> true @@ -312,8 +315,10 @@ type ArrowFunctionExpression(``params``, body, ?async_, ?loc) = member __.Params: Pattern array = ``params`` member __.Body: U2 = body member __.Async: bool = async + 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 @@ -322,6 +327,8 @@ type FunctionExpression(``params``, body, ?generator_, ?async_, ?id, ?loc) = member __.Body: BlockStatement = body member __.Generator: bool = generator member __.Async: bool = async + 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 @@ -366,7 +373,7 @@ type ObjectMember(typ, key, ?value, ?computed_, ?loc) = member __.Key: Expression = key member __.Value: Expression option = value member __.Computed: bool = computed - // member __.decorators: Decorator array = defaultArg decorators [] + // member __.Decorators: Decorator array = defaultArg decorators [] type ObjectProperty(key, value, ?shorthand_, ?computed_, ?loc) = inherit ObjectMember("ObjectProperty", key, value, ?computed_=computed_, ?loc=loc) @@ -375,7 +382,7 @@ type ObjectProperty(key, value, ?shorthand_, ?computed_, ?loc) = type ObjectMethodKind = ObjectGetter | ObjectSetter | ObjectMeth -type ObjectMethod(kind_, key, ``params``, body, ?computed_, ?generator_, ?async_, ?loc) = +type ObjectMethod(kind_, key, ``params``, body, ?computed_, ?generator_, ?async_, ?returnType, ?typeParameters, ?loc) = inherit ObjectMember("ObjectMethod", key, ?computed_=computed_, ?loc=loc) let generator = defaultArg generator_ false let async = defaultArg async_ false @@ -389,6 +396,8 @@ type ObjectMethod(kind_, key, ``params``, body, ?computed_, ?generator_, ?async_ member __.Body: BlockStatement = body member __.Generator: bool = generator member __.Async: bool = async + member __.ReturnType: TypeAnnotation option = returnType + member __.TypeParameters: TypeParameterDeclaration option = typeParameters /// 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. @@ -523,27 +532,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 __.Decorators: Decorator array = defaultArg decorators [] + member __.TypeAnnotation: TypeAnnotation option = typeAnnotation -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``, ?returnType, ?typeParameters, ?loc) = inherit Node("ClassMethod", ?loc = loc) let kind = match kind_ with @@ -557,7 +570,9 @@ type ClassMethod(kind_, key, ``params``, body, computed, ``static``, ?loc) = member __.Body: BlockStatement = body member __.Computed: bool = computed member __.Static: bool = ``static`` - // member __.decorators: Decorator array = defaultArg decorators [] + member __.ReturnType: TypeAnnotation option = returnType + member __.TypeParameters: TypeParameterDeclaration option = typeParameters + // member __.Decorators: Decorator array = defaultArg decorators [] // This appears in astexplorer.net but it's not documented // member __.expression: bool = false @@ -580,7 +595,7 @@ type ClassDeclaration(body, id, ?superClass, ?typeParameters, ?loc) = member __.Id: Identifier = id member __.SuperClass: Expression option = superClass member __.TypeParameters: TypeParameterDeclaration option = typeParameters - // member __.decorators: Decorator array = defaultArg decorators [] + // member __.Decorators: Decorator array = defaultArg decorators [] /// Anonymous class: e.g., var myClass = class { } type ClassExpression(body, ?id, ?superClass, ?typeParameters, ?loc) = @@ -589,7 +604,7 @@ 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 = defaultArg decorators [] // type MetaProperty(meta, property, ?loc) = // inherit Expression("MetaProperty", ?loc = loc) @@ -654,3 +669,70 @@ 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 list = types + +type FunctionTypeParam(name, typeInfo, ?optional) = + member __.Type = "FunctionTypeParam" + member __.Name: Identifier = name + member __.TypeAnnotation: TypeAnnotationInfo = typeInfo + member __.Optional = defaultArg optional false + +type FunctionTypeAnnotation(``params``, returnType, ?rest) = + inherit TypeAnnotationInfo("FunctionTypeAnnotation") + member __.Params: FunctionTypeParam list = ``params`` + member __.Rest: FunctionTypeParam option = rest + member __.ReturnType: TypeAnnotationInfo = returnType + +type NullableTypeAnnotation(typ) = + inherit TypeAnnotationInfo("NullableTypeAnnotation") + member __.TypeAnnotation: TypeAnnotationInfo = typ + +type GenericTypeAnnotation(id, ?typeParams) = + inherit TypeAnnotationInfo("GenericTypeAnnotation") + member __.Id: Identifier = id + member __.TypeParameters: TypeParameterInstantiation option = typeParams + +type ObjectTypeProperty(key, value, ?isStatic, ?isOptional) = + inherit Node("ObjectTypeProperty") + member __.Key: Identifier = key + member __.Value: TypeAnnotationInfo = value + member __.Static: bool = defaultArg isStatic false + member __.Optional: bool = defaultArg isOptional false + +type ObjectTypeAnnotation(properties) = + inherit TypeAnnotationInfo("ObjectTypeAnnotation") + member __.Properties: obj list = [] + member __.CallProperties: obj list = [] + member __.Indexers: obj list = [] + +type InterfaceExtends(id, ?typeParameters) = + member __.Type = "InterfaceExtends" + member __.Id: Identifier = id + member __.TypeParameters: TypeParameterInstantiation option = typeParameters + +type InterfaceDeclaration(body, id, extends, ?typeParameters, ?loc) = + inherit Declaration("InterfaceDeclaration", ?loc = loc) + member __.Id: Identifier = id + member __.Body: ObjectTypeAnnotation = body + member __.Extends: InterfaceExtends list = extends + member __.TypeParameters: TypeParameterDeclaration option = typeParameters diff --git a/src/dotnet/Fable.Compiler/CLI/Parser.fs b/src/dotnet/Fable.Compiler/CLI/Parser.fs index bf91ae3aab..256d3645c5 100644 --- a/src/dotnet/Fable.Compiler/CLI/Parser.fs +++ b/src/dotnet/Fable.Compiler/CLI/Parser.fs @@ -54,6 +54,7 @@ let toCompilerOptions (msg: Message): CompilerOptions = { typedArrays = msg.typedArrays clampByteArrays = msg.clampByteArrays verbose = GlobalParams.Singleton.Verbose + typedDeclarations = false outputPublicInlinedFunctions = Array.contains "FABLE_REPL_LIB" msg.define precompiledLib = None } diff --git a/src/dotnet/Fable.Compiler/Global/Compiler.fs b/src/dotnet/Fable.Compiler/Global/Compiler.fs index 3f0d958c76..10bb501db9 100644 --- a/src/dotnet/Fable.Compiler/Global/Compiler.fs +++ b/src/dotnet/Fable.Compiler/Global/Compiler.fs @@ -4,6 +4,7 @@ type CompilerOptions = { typedArrays: bool clampByteArrays: bool verbose: bool + typedDeclarations: bool /// Meant for precompiled libraries (like the Repl Lib) /// to make public inlined functions part of the JS outputPublicInlinedFunctions: bool diff --git a/src/dotnet/Fable.Compiler/Transforms/Fable2Babel.fs b/src/dotnet/Fable.Compiler/Transforms/Fable2Babel.fs index 90c9a52780..4f34477771 100644 --- a/src/dotnet/Fable.Compiler/Transforms/Fable2Babel.fs +++ b/src/dotnet/Fable.Compiler/Transforms/Fable2Babel.fs @@ -264,6 +264,84 @@ module Util = let macroExpression range (txt: string) args = MacroExpression(txt, List.toArray args, ?loc=range) :> Expression + let getTypeParams (args: Fable.Ident list) (body: Fable.Expr) = + let rec getGenParams = function + | Fable.GenericParam name -> [name] + | t -> t.Generics |> List.collect getGenParams + let types = (args |> List.map (fun i -> i.Type)) @ [body.Type] + ([], types) ||> List.fold (fun acc t -> acc @ (getGenParams t)) + |> List.distinct + + 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 -> + upcast NumberTypeAnnotation() + | Fable.EnumType(_kind, _fullName) -> + upcast NumberTypeAnnotation() + | Fable.Option genArg -> + upcast NullableTypeAnnotation(typeAnnotation com ctx genArg) + | Fable.Tuple genArgs -> + List.map (typeAnnotation com ctx) genArgs + |> TupleTypeAnnotation + :> TypeAnnotationInfo + | Fable.Array genArg -> + // TODO: Typed arrays? + upcast GenericTypeAnnotation( + Identifier("Array"), + TypeParameterInstantiation([| typeAnnotation com ctx genArg |])) + | Fable.List genArg -> + // TODO: + upcast AnyTypeAnnotation() + | Fable.FunctionType(kind, returnType) -> + let argTypes = + match kind with + | Fable.LambdaType argType -> [argType] + | Fable.DelegateType argTypes -> argTypes + argTypes + |> List.mapi (fun i argType -> + FunctionTypeParam( + Identifier("arg" + (string i)), + typeAnnotation com ctx argType)) + |> fun argTypes -> + FunctionTypeAnnotation( + argTypes, typeAnnotation com ctx returnType) + :> TypeAnnotationInfo + | Fable.GenericParam name -> + upcast GenericTypeAnnotation(Identifier(name)) + | Fable.ErasedUnion genArg -> + upcast AnyTypeAnnotation() + | Fable.DeclaredType(ent, [genArg]) when ent.TryFullName = Some Types.ienumerableGeneric -> + upcast GenericTypeAnnotation( + Identifier("Iterable"), + TypeParameterInstantiation([| typeAnnotation com ctx genArg |])) + | Fable.DeclaredType(ent, genArgs) -> + try + let entRef = entityRefMaybeImported com ctx ent + match entRef with + | :? StringLiteral as str -> + match str.Value with + | "number" -> upcast NumberTypeAnnotation() + | "boolean" -> upcast BooleanTypeAnnotation() + | "string" -> upcast StringTypeAnnotation() + | _ -> upcast AnyTypeAnnotation() + | :? Identifier as id -> + let typeParams = + match List.map (typeAnnotation com ctx) genArgs with + | [] -> None + | xs -> TypeParameterInstantiation(xs |> List.toArray) |> Some + upcast GenericTypeAnnotation(id, ?typeParams=typeParams) + // TODO: Resolve references to types in nested modules + | _ -> upcast AnyTypeAnnotation() + with + | _ -> upcast AnyTypeAnnotation() + let getMemberArgsAndBody (com: IBabelCompiler) ctx name boundThis args hasSpread (body: Fable.Expr) = let args, body = match boundThis with @@ -284,7 +362,30 @@ module Util = else FableTransforms.replaceValues (Map [thisArg.Name, boundThisExpr]) body args, body | None -> args, body - let args, body = com.TransformFunction(ctx, name, args, body) + let args', body' = com.TransformFunction(ctx, name, args, body) + let args, returnType, typeParameters = + if com.Options.typedDeclarations then + let args'' = args' |> Array.mapi (fun i arg -> + match arg with + | U2.Case2 e -> + match e with + | :? Identifier as id -> + Identifier(id.Name, TypeAnnotation(typeAnnotation com ctx args.[i].Type)) + :> PatternExpression + | arg -> arg + |> U2.Case2 + | arg -> arg) + let returnType = TypeAnnotation(typeAnnotation com ctx body.Type) |> Some + let typeParameters = + match getTypeParams args body with + | [] -> None + | typeParams -> + typeParams + |> List.toArray |> Array.map TypeParameter + |> TypeParameterDeclaration |> Some + args'', returnType, typeParameters + else + args', None, None let args = if not hasSpread then args @@ -292,9 +393,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|] + let body = + match body' with + | U2.Case1 e -> e + | U2.Case2 e -> BlockStatement [|ReturnStatement e|] + args, body, returnType, typeParameters let getUnionCaseName uci = FSharp2Fable.Helpers.unionCaseCompiledName uci @@ -586,16 +689,18 @@ 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, typeParameters = getMemberArgsAndBody com ctx None boundThis args hasSpread body + ObjectMethod(kind, prop, args, body, computed_=computed, + ?returnType=returnType, ?typeParameters=typeParameters) |> 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, typeParameters = 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=typeParameters) |> U3.Case2 |> Some | Fable.ObjectValue, TransformExpr com ctx value -> let prop, computed = memberFromExpr com ctx key ObjectProperty(prop, value, computed_=computed) |> U3.Case1 |> Some @@ -1290,9 +1395,10 @@ module Util = match expr with | :? ClassExpression as e -> upcast ClassDeclaration(e.Body, privateIdent, - ?superClass=e.SuperClass, ?typeParameters=e.TypeParameters) + ?superClass=e.SuperClass, ?typeParameters=e.TypeParameters, ?loc=e.Loc) | :? FunctionExpression as e -> - upcast FunctionDeclaration(privateIdent, e.Params, e.Body) + upcast FunctionDeclaration(privateIdent, e.Params, e.Body, + ?returnType=e.ReturnType, ?typeParameters=e.TypeParameters, ?loc=e.Loc) | _ -> upcast varDeclaration privateIdent isMutable expr if not isPublic then U2.Case1 (decl :> Statement) @@ -1320,9 +1426,9 @@ module Util = [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, typeParameters = 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=typeParameters) if info.IsEntryPoint then declareEntryPoint com ctx expr |> U2.Case1 else @@ -1342,8 +1448,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, typeParameters = getMemberArgsAndBody com ctx None boundThis args false body + FunctionExpression(args, body, ?returnType=returnType, ?typeParameters=typeParameters) let getterFuncExpr = Option.map funcExpr getter let setterFuncExpr = Option.map funcExpr setter let funcCons = Identifier info.EntityName :> Expression @@ -1370,8 +1476,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, typeParameters = getMemberArgsAndBody com ctx None boundThis args hasSpread body + let funcExpr = FunctionExpression(args, body, ?returnType=returnType, ?typeParameters=typeParameters) let protoMember = match memberName with | Naming.StartsWith "Symbol." symbolName -> get None (Identifier "Symbol") symbolName @@ -1420,7 +1526,7 @@ module Util = let transformImplicitConstructor (com: IBabelCompiler) ctx (info: Fable.ClassImplicitConstructorInfo) = let boundThis = Some("this", info.BoundConstructorThis) - let args, body = getMemberArgsAndBody com ctx None boundThis info.Arguments info.HasSpread info.Body + let args, body, returnType, typeParameters = getMemberArgsAndBody com ctx None boundThis info.Arguments info.HasSpread info.Body let argIdents, argExprs: Pattern list * Expression list = match info.Arguments with | [] -> [], [] @@ -1432,14 +1538,15 @@ module Util = | args -> List.map identAsPattern args, List.map identAsExpr args let consIdent = Identifier info.EntityName :> Expression let exposedCons = - FunctionExpression(List.toArray argIdents, + let body = BlockStatement [| ReturnStatement( ConditionalExpression( BinaryExpression(BinaryUnequal, ThisExpression(), NullLiteral()), callFunctionWithThisContext None consIdent thisExpr argExprs, NewExpression(consIdent,List.toArray argExprs)) - ) |]) + ) |] + FunctionExpression(List.toArray argIdents, body, ?returnType=returnType, ?typeParameters=typeParameters) let baseExpr = match info.Base with | Some(TransformExpr com ctx baseRef) -> Some baseRef diff --git a/src/dotnet/Fable.Repl/Main.fs b/src/dotnet/Fable.Repl/Main.fs index 791f8f856b..d2745b5646 100644 --- a/src/dotnet/Fable.Repl/Main.fs +++ b/src/dotnet/Fable.Repl/Main.fs @@ -244,6 +244,7 @@ let makeCompiler fableLibrary fileName (project: Project) precompiledLib = { typedArrays = true clampByteArrays = false verbose = false + typedDeclarations = false outputPublicInlinedFunctions = false precompiledLib = precompiledLib } let com = Compiler(fileName, project, options, fableLibrary)