Skip to content

Commit

Permalink
Fix #1655: Result reflection
Browse files Browse the repository at this point in the history
  • Loading branch information
alfonsogarciacaro committed Nov 30, 2018
1 parent 7c1513c commit fc176b6
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 71 deletions.
2 changes: 2 additions & 0 deletions src/dotnet/Fable.Compiler/Global/Prelude.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 0 additions & 8 deletions src/dotnet/Fable.Compiler/Transforms/FSharp2Fable.Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
140 changes: 96 additions & 44 deletions src/dotnet/Fable.Compiler/Transforms/Fable2Babel.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Expression>) 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<string, Expression>) t: Expression =
let error msg =
addErrorAndReturnNull com r msg
let primitiveTypeInfo name =
Expand All @@ -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"
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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 ->
Expand Down Expand Up @@ -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 =
Expand Down
48 changes: 30 additions & 18 deletions src/dotnet/Fable.Compiler/Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -236,6 +246,8 @@ let coreModFor = function
| BclTimeSpan -> "Int32"
| FSharpSet _ -> "Set"
| FSharpMap _ -> "Map"
| FSharpResult _ -> "Option"
| FSharpReference _ -> "Types"
| BclHashSet _
| BclDictionary _ -> failwith "Cannot decide core module"

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/js/fable-compiler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down

0 comments on commit fc176b6

Please sign in to comment.