From fc176b6a0e0309fe9284e6e8a26ce3367f94b50e Mon Sep 17 00:00:00 2001 From: Alfonso Garcia-Caro Date: Fri, 30 Nov 2018 15:50:04 +0100 Subject: [PATCH] Fix #1655: Result reflection --- src/dotnet/Fable.Compiler/Global/Prelude.fs | 2 + .../Transforms/FSharp2Fable.Util.fs | 8 - .../Fable.Compiler/Transforms/Fable2Babel.fs | 140 ++++++++++++------ .../Fable.Compiler/Transforms/Replacements.fs | 48 +++--- src/js/fable-compiler/src/index.ts | 2 +- 5 files changed, 129 insertions(+), 71 deletions(-) diff --git a/src/dotnet/Fable.Compiler/Global/Prelude.fs b/src/dotnet/Fable.Compiler/Global/Prelude.fs index 12c690277d..59c3a97227 100644 --- a/src/dotnet/Fable.Compiler/Global/Prelude.fs +++ b/src/dotnet/Fable.Compiler/Global/Prelude.fs @@ -229,6 +229,8 @@ module Naming = then baseName else baseName + "$" + suffix + let reflectionSuffix = "reflection" + let private printPart sanitize separator part overloadSuffix = (if part = "" then "" else separator + (sanitize part)) + (if overloadSuffix = "" then "" else "$$" + overloadSuffix) diff --git a/src/dotnet/Fable.Compiler/Transforms/FSharp2Fable.Util.fs b/src/dotnet/Fable.Compiler/Transforms/FSharp2Fable.Util.fs index 794738808b..3256876892 100644 --- a/src/dotnet/Fable.Compiler/Transforms/FSharp2Fable.Util.fs +++ b/src/dotnet/Fable.Compiler/Transforms/FSharp2Fable.Util.fs @@ -796,14 +796,6 @@ module Util = | None, Some entityFullName -> entityFullName.StartsWith("Fable.Core.") | None, None -> false - /// Check if an entity is NOT actually declared in JS code - /// Currently used to check if there's reflection info available at runtime - let isNonDeclaredEntity (ent: FSharpEntity) = - ent.IsInterface - || isErasedUnion ent - || isGlobalOrImportedEntity ent - || isReplacementCandidate ent - /// We can add a suffix to the entity name for special methods, like reflection declaration let entityRefWithSuffix (com: ICompiler) (ent: FSharpEntity) suffix = if ent.IsInterface then diff --git a/src/dotnet/Fable.Compiler/Transforms/Fable2Babel.fs b/src/dotnet/Fable.Compiler/Transforms/Fable2Babel.fs index da1592912d..78f271ad2b 100644 --- a/src/dotnet/Fable.Compiler/Transforms/Fable2Babel.fs +++ b/src/dotnet/Fable.Compiler/Transforms/Fable2Babel.fs @@ -378,7 +378,50 @@ module Util = | Fable.AsPojo(expr, caseRule) -> com.TransformAsExpr(ctx, Replacements.makePojo com r caseRule expr) | Fable.Curry(expr, arity) -> com.TransformAsExpr(ctx, Replacements.curryExprAtRuntime arity expr) - let rec transformTypeInfo (com: IBabelCompiler) ctx r (genMap: Map) t: Expression = + let rec transformRecordReflectionInfo com ctx (ent: FSharpEntity) generics = + // TODO: Refactor these three bindings to reuse in transformUnionReflectionInfo + let fullname = defaultArg ent.TryFullName Naming.unknown + let fullnameExpr = StringLiteral fullname :> Expression + let genMap = + 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 -> + let typeInfo = + FSharp2Fable.TypeHelpers.makeType com Map.empty x.FieldType + |> transformTypeInfo com ctx None genMap + (ArrayExpression [|StringLiteral x.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 "Reflection" "record" + + and transformUnionReflectionInfo com ctx (ent: FSharpEntity) generics = + let fullname = defaultArg ent.TryFullName Naming.unknown + let fullnameExpr = StringLiteral fullname :> Expression + let genMap = + let genParamNames = ent.GenericParameters |> Seq.map (fun x -> x.Name) |> Seq.toArray + Array.zip genParamNames generics |> Map + let cases = + ent.UnionCases |> Seq.map (fun uci -> + let fieldTypes = + uci.UnionCaseFields |> Seq.map (fun fi -> + FSharp2Fable.TypeHelpers.makeType com Map.empty fi.FieldType + |> transformTypeInfo com ctx None genMap) |> Seq.toArray + let caseInfo = + if fieldTypes.Length = 0 then + getUnionCaseName uci |> StringLiteral :> Expression + else + ArrayExpression [| + getUnionCaseName uci |> StringLiteral :> Expression + ArrayExpression fieldTypes :> Expression + |] :> Expression + caseInfo) |> Seq.toArray + let cases = ArrowFunctionExpression([||], ArrayExpression cases :> Expression |> U2.Case2) :> Expression + [|fullnameExpr; upcast ArrayExpression generics; jsConstructor com ctx ent; cases|] + |> coreLibCall com ctx "Reflection" "union" + + and transformTypeInfo (com: IBabelCompiler) ctx r (genMap: Map) t: Expression = let error msg = addErrorAndReturnNull com r msg let primitiveTypeInfo name = @@ -391,6 +434,11 @@ module Util = let genericTypeInfo name genArgs = let resolved = resolveGenerics genArgs coreLibCall com ctx "Reflection" 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 "Reflection" "type" args match t with // TODO: Type info forErasedUnion? | Fable.ErasedUnion _ | Fable.Any -> primitiveTypeInfo "obj" @@ -430,53 +478,55 @@ module Util = | Fable.Regex -> nonGenericTypeInfo Types.regex | Fable.MetaType -> nonGenericTypeInfo Types.type_ | Fable.DeclaredType(ent, generics) -> - let generics = generics |> List.map (transformTypeInfo com ctx r genMap) |> List.toArray - if FSharp2Fable.Util.isNonDeclaredEntity ent then - 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 "Reflection" "type" args - else - let reflectionMethodExpr = FSharp2Fable.Util.entityRefWithSuffix com ent "reflection" - CallExpression(com.TransformAsExpr(ctx, reflectionMethodExpr), generics) :> Expression + match ent, generics with + | Replacements.BuiltinEntity kind -> + match kind with + | Replacements.BclGuid -> primitiveTypeInfo "string" + | Replacements.BclTimeSpan -> primitiveTypeInfo "float64" + | Replacements.BclDateTime + | Replacements.BclDateTimeOffset + | Replacements.BclTimer + | Replacements.BclInt64 + | Replacements.BclUInt64 + | Replacements.BclDecimal + | Replacements.BclBigInt -> genericEntity ent [||] + | Replacements.BclHashSet gen + | Replacements.FSharpSet gen -> + genericEntity ent [|transformTypeInfo com ctx r genMap gen|] + | Replacements.BclDictionary(key, value) + | Replacements.FSharpMap(key, value) -> + genericEntity ent [| + transformTypeInfo com ctx r genMap key + transformTypeInfo com ctx r genMap value + |] + | Replacements.FSharpResult(ok, err) -> + transformUnionReflectionInfo com ctx ent [| + transformTypeInfo com ctx r genMap ok + transformTypeInfo com ctx r genMap err + |] + | Replacements.FSharpReference gen -> + transformRecordReflectionInfo com ctx ent [|transformTypeInfo com ctx r genMap gen|] + | _ -> + let generics = generics |> List.map (transformTypeInfo com ctx r genMap) |> List.toArray + /// Check if the entity is actually declared in JS code + if ent.IsInterface + || FSharp2Fable.Util.isErasedUnion ent + || FSharp2Fable.Util.isGlobalOrImportedEntity ent + // TODO!!! Get reflection info from types in precompiled libs + || FSharp2Fable.Util.isReplacementCandidate ent then + genericEntity ent generics + else + let reflectionMethodExpr = FSharp2Fable.Util.entityRefWithSuffix com ent Naming.reflectionSuffix + CallExpression(com.TransformAsExpr(ctx, reflectionMethodExpr), generics) :> Expression let transformReflectionInfo com ctx (ent: FSharpEntity) generics = - let fullname = defaultArg ent.TryFullName Naming.unknown - let fullnameExpr = StringLiteral fullname :> Expression - let genMap = - let genParamNames = ent.GenericParameters |> Seq.map (fun x -> x.Name) |> Seq.toArray - Array.zip genParamNames generics |> Map if ent.IsFSharpRecord then - let fields = - ent.FSharpFields |> Seq.map (fun x -> - let typeInfo = - FSharp2Fable.TypeHelpers.makeType com Map.empty x.FieldType - |> transformTypeInfo com ctx None genMap - (ArrayExpression [|StringLiteral x.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 "Reflection" "record" + transformRecordReflectionInfo com ctx ent generics elif ent.IsFSharpUnion then - let cases = - ent.UnionCases |> Seq.map (fun uci -> - let fieldTypes = - uci.UnionCaseFields |> Seq.map (fun fi -> - FSharp2Fable.TypeHelpers.makeType com Map.empty fi.FieldType - |> transformTypeInfo com ctx None genMap) |> Seq.toArray - let caseInfo = - if fieldTypes.Length = 0 then - getUnionCaseName uci |> StringLiteral :> Expression - else - ArrayExpression [| - getUnionCaseName uci |> StringLiteral :> Expression - ArrayExpression fieldTypes :> Expression - |] :> Expression - caseInfo) |> Seq.toArray - let cases = ArrowFunctionExpression([||], ArrayExpression cases :> Expression |> U2.Case2) :> Expression - [|fullnameExpr; upcast ArrayExpression generics; jsConstructor com ctx ent; cases|] - |> coreLibCall com ctx "Reflection" "union" + transformUnionReflectionInfo com ctx ent generics else + 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 "Reflection" "type" args @@ -824,6 +874,8 @@ module Util = | Replacements.BclDictionary _ | Replacements.FSharpSet _ | Replacements.FSharpMap _ -> fail "set/maps" + | Replacements.FSharpResult _ + | Replacements.FSharpReference _ -> fail "result/reference" | Fable.DeclaredType (ent, genArgs) -> match ent.TryFullName with | Some Types.idisposable -> @@ -1261,7 +1313,7 @@ module Util = let genArgs = Array.init ent.GenericParameters.Count (fun _ -> makeIdentUnique com "gen" |> ident) let body = transformReflectionInfo com ctx ent (Array.map (fun x -> x :> _) genArgs) makeFunctionExpression None (Array.map (fun x -> U2.Case2(upcast x)) genArgs) (U2.Case2 body) - |> declareModuleMember isPublic (Naming.appendSuffix name "reflection") false + |> declareModuleMember isPublic (Naming.appendSuffix name Naming.reflectionSuffix) false [typeDeclaration; reflectionDeclaration] let transformModuleFunction (com: IBabelCompiler) ctx (info: Fable.ValueDeclarationInfo) args body = diff --git a/src/dotnet/Fable.Compiler/Transforms/Replacements.fs b/src/dotnet/Fable.Compiler/Transforms/Replacements.fs index 7655a65209..48a6db3d73 100644 --- a/src/dotnet/Fable.Compiler/Transforms/Replacements.fs +++ b/src/dotnet/Fable.Compiler/Transforms/Replacements.fs @@ -131,26 +131,36 @@ type BuiltinType = | BclDictionary of key:Type * value:Type | FSharpSet of Type | FSharpMap of key:Type * value:Type + // TODO: Add Choice for reflection support? + | FSharpResult of Type * Type + | FSharpReference of Type + +let (|BuiltinEntity|_|) (ent: FSharpEntity, genArgs) = + // TODO: Convert this to dictionary + match ent.TryFullName, genArgs with + | Some Types.guid, _ -> Some BclGuid + | Some Types.timespan, _ -> Some BclTimeSpan + | Some Types.datetime, _ -> Some BclDateTime + | Some Types.datetimeOffset, _ -> Some BclDateTimeOffset + | Some "System.Timers.Timer", _ -> Some BclTimer + | Some Types.int64, _ -> Some BclInt64 + | Some Types.uint64, _ -> Some BclUInt64 + | Some "Microsoft.FSharp.Core.int64`1", _ -> Some BclInt64 + | Some Types.decimal, _ + | Some "Microsoft.FSharp.Core.decimal`1", _ -> Some BclDecimal + | Some Types.bigint, _ -> Some BclBigInt + | Some Types.fsharpSet, [t] -> Some(FSharpSet(t)) + | Some Types.fsharpMap, [k;v] -> Some(FSharpMap(k,v)) + | Some Types.hashset, [t] -> Some(BclHashSet(t)) + | Some Types.dictionary, [k;v] -> Some(BclDictionary(k,v)) + | Some Types.result, [k;v] -> Some(FSharpResult(k,v)) + | Some Types.reference, [v] -> Some(FSharpReference(v)) + | _ -> None let (|Builtin|_|) = function | DeclaredType(ent, genArgs) -> - // TODO: Convert this to dictionary - match ent.TryFullName, genArgs with - | Some Types.guid, _ -> Some BclGuid - | Some Types.timespan, _ -> Some BclTimeSpan - | Some Types.datetime, _ -> Some BclDateTime - | Some Types.datetimeOffset, _ -> Some BclDateTimeOffset - | Some "System.Timers.Timer", _ -> Some BclTimer - | Some Types.int64, _ -> Some BclInt64 - | Some Types.uint64, _ -> Some BclUInt64 - | Some "Microsoft.FSharp.Core.int64`1", _ -> Some BclInt64 - | Some Types.decimal, _ - | Some "Microsoft.FSharp.Core.decimal`1", _ -> Some BclDecimal - | Some Types.bigint, _ -> Some BclBigInt - | Some Types.fsharpSet, [t] -> Some(FSharpSet(t)) - | Some Types.fsharpMap, [k;v] -> Some(FSharpMap(k,v)) - | Some Types.hashset, [t] -> Some(BclHashSet(t)) - | Some Types.dictionary, [k;v] -> Some(BclDictionary(k,v)) + match ent, genArgs with + | BuiltinEntity x -> Some x | _ -> None | _ -> None @@ -236,6 +246,8 @@ let coreModFor = function | BclTimeSpan -> "Int32" | FSharpSet _ -> "Set" | FSharpMap _ -> "Map" + | FSharpResult _ -> "Option" + | FSharpReference _ -> "Types" | BclHashSet _ | BclDictionary _ -> failwith "Cannot decide core module" @@ -933,7 +945,7 @@ let injectArg com (ctx: Context) r moduleName methName (genArgs: (string * Type) | None -> args | Some injections -> args @ injections -// TODO: Add other entities (see Fable 1 Replacements.tryReplaceEntity) +// TODO!!! How to add other entities? let tryEntityRef (com: Fable.ICompiler) (ent: FSharpEntity) = match ent.FullName with | Types.reference -> makeCoreRef Any "FSharpRef" "Types" |> Some diff --git a/src/js/fable-compiler/src/index.ts b/src/js/fable-compiler/src/index.ts index 49995ca333..4535290280 100644 --- a/src/js/fable-compiler/src/index.ts +++ b/src/js/fable-compiler/src/index.ts @@ -59,7 +59,7 @@ export default function start(cliArgs?: {}): ICompilerProxy { // Error handling child.on("error", (err) => { - console.error(err); + console.error("Cannot spawn dotnet", err.message); }); // child.stderr.on("data", (data) => { // console.error(`child proccess errored: ${data}`);