Skip to content

Commit

Permalink
Erase unions and records
Browse files Browse the repository at this point in the history
  • Loading branch information
ncave committed Mar 6, 2021
1 parent 5968307 commit 7ec3698
Show file tree
Hide file tree
Showing 15 changed files with 137 additions and 26 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"name": "Run bench-compiler (Node)",
"program": "${workspaceRoot}/src/fable-standalone/test/bench-compiler/out-node/app.js",
// "args": ["${workspaceRoot}/tests/Main/Fable.Tests.fsproj", "out-tests"],
"args": ["${workspaceRoot}/../fable-test/fable-test.fsproj", "out-test"],
"args": ["${workspaceRoot}/../fable-test/fable-test.fsproj", "out-test", "--eraseTypes"],
"cwd": "${workspaceRoot}/src/fable-standalone/test/bench-compiler"
},
{
Expand Down
1 change: 1 addition & 0 deletions src/Fable.AST/Plugins.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type Verbosity =
| Silent

type CompilerOptions =
abstract EraseTypes: bool
abstract TypedArrays: bool
abstract ClampByteArrays: bool
abstract Typescript: bool
Expand Down
3 changes: 2 additions & 1 deletion src/Fable.Cli/Entry.fs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ type Runner =
argValue "--extension" args |> Option.defaultValue (defaultFileExt typescript args)

let compilerOptions =
CompilerOptionsHelper.Make(typescript = typescript,
CompilerOptionsHelper.Make(eraseTypes = flagEnabled "--eraseTypes" args,
typescript = typescript,
typedArrays = typedArrays,
fileExtension = fileExt,
define = define,
Expand Down
16 changes: 16 additions & 0 deletions src/Fable.Transforms/FSharp2Fable.Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,16 @@ module Helpers =
let makeRangeFrom (fsExpr: FSharpExpr) =
Some (makeRange fsExpr.Range)

let isErasedTypeDef (com: Compiler) (tdef: FSharpEntity) =
com.Options.EraseTypes && tdef.IsFSharp
&& (tdef.IsFSharpUnion || tdef.IsFSharpRecord || tdef.IsValueType || tdef.IsByRef)
&& not (tdef.TryFullName = Some Types.reference) // no F# refs
&& not (hasAttribute Atts.customEquality tdef.Attributes)
&& not (hasAttribute Atts.customComparison tdef.Attributes)

let isErasedType (com: Compiler) (t: FSharpType) =
t.HasTypeDefinition && (isErasedTypeDef com t.TypeDefinition)

let unionCaseTag (com: IFableCompiler) (ent: FSharpEntity) (unionCase: FSharpUnionCase) =
try
// If the order of cases changes in the declaration, the tag has to change too.
Expand Down Expand Up @@ -1153,6 +1163,12 @@ module Util =
makeImportUserGenerated None Fable.Any selector path |> Some
| _ -> None

let isErasedEntity (com: Compiler) (ent: Fable.Entity) =
match ent with
| :? FsEnt as fsEnt ->
Helpers.isErasedTypeDef com fsEnt.FSharpEntity
| _ -> false

let isErasedOrStringEnumEntity (ent: Fable.Entity) =
ent.Attributes |> Seq.exists (fun att ->
match att.Entity.FullName with
Expand Down
77 changes: 57 additions & 20 deletions src/Fable.Transforms/Fable2Babel.fs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,12 @@ module Reflection =
let ent = com.GetEntity(ent)
if ent.IsInterface then
warnAndEvalToFalse "interfaces"
elif FSharp2Fable.Util.isErasedEntity com ent then
let expr = com.TransformAsExpr(ctx, expr)
let idx = if ent.IsFSharpUnion then 1 else 0
let actual = Util.getExpr None expr (Util.ofInt idx)
let expected = Util.ofString ent.FullName
Expression.binaryExpression(BinaryEqualStrict, actual, expected, ?loc=range)
else
match tryJsConstructor com ctx ent with
| Some cons ->
Expand Down Expand Up @@ -383,6 +389,7 @@ module Annotation =
| Fable.LambdaType _ -> Util.uncurryLambdaType typ ||> makeFunctionTypeAnnotation com ctx typ
| Fable.DelegateType(argTypes, returnType) -> makeFunctionTypeAnnotation com ctx typ argTypes returnType
| Fable.GenericParam name -> makeSimpleTypeAnnotation com ctx name
| Replacements.ErasedType com (_, _, genArgs) -> makeTupleTypeAnnotation com ctx genArgs
| Fable.DeclaredType(ent, genArgs) ->
makeEntityTypeAnnotation com ctx ent genArgs
| Fable.AnonymousRecordType(fieldNames, genArgs) ->
Expand Down Expand Up @@ -814,7 +821,11 @@ module Util =

let getUnionExprTag (com: IBabelCompiler) ctx r (fableExpr: Fable.Expr) =
let expr = com.TransformAsExpr(ctx, fableExpr)
getExpr r expr (Expression.stringLiteral("tag"))
match fableExpr.Type with
| Replacements.ErasedType com _ ->
getExpr r expr (ofInt 0)
| _ ->
getExpr r expr (Expression.stringLiteral("tag"))

/// Wrap int expressions with `| 0` to help optimization of JS VMs
let wrapIntExpression typ (e: Expression) =
Expand Down Expand Up @@ -960,27 +971,41 @@ module Util =
com.TransformAsExpr(ctx, x)
| Fable.NewRecord(values, ent, genArgs) ->
let ent = com.GetEntity(ent)
let values = List.mapToArray (fun x -> com.TransformAsExpr(ctx, x)) values
let consRef = ent |> jsConstructor com ctx
let typeParamInst =
if com.Options.Typescript && (ent.FullName = Types.reference)
then makeGenTypeParamInst com ctx genArgs
else None
Expression.newExpression(consRef, values, ?typeArguments=typeParamInst, ?loc=r)
let values = List.map (fun x -> com.TransformAsExpr(ctx, x)) values
if FSharp2Fable.Util.isErasedEntity com ent then
let recordName = ent.FullName |> ofString
recordName::values |> List.toArray |> Expression.arrayExpression
else
let consRef = ent |> jsConstructor com ctx
let values = values |> List.toArray
let typeParamInst =
if com.Options.Typescript && (ent.FullName = Types.reference)
then makeGenTypeParamInst com ctx genArgs
else None
Expression.newExpression(consRef, values, ?typeArguments=typeParamInst, ?loc=r)
| Fable.NewAnonymousRecord(values, fieldNames, _genArgs) ->
let values = List.mapToArray (fun x -> com.TransformAsExpr(ctx, x)) values
Array.zip fieldNames values |> makeJsObject
if com.Options.EraseTypes then
values |> Expression.arrayExpression
else
Array.zip fieldNames values |> makeJsObject
| Fable.NewUnion(values, tag, ent, genArgs) ->
let ent = com.GetEntity(ent)
let values = List.map (fun x -> com.TransformAsExpr(ctx, x)) values
let consRef = ent |> jsConstructor com ctx
let typeParamInst =
if com.Options.Typescript
then makeGenTypeParamInst com ctx genArgs
else None
// let caseName = ent.UnionCases |> List.item tag |> getUnionCaseName |> ofString
let values = (ofInt tag)::values |> List.toArray
Expression.newExpression(consRef, values, ?typeArguments=typeParamInst, ?loc=r)
if FSharp2Fable.Util.isErasedEntity com ent then
let unionCase = ent.UnionCases |> List.item tag
let caseTag = tag |> ofInt
let caseName = unionCase.FullName |> ofString
caseTag::caseName::values |> List.toArray |> Expression.arrayExpression
else
let consRef = ent |> jsConstructor com ctx
let typeParamInst =
if com.Options.Typescript
then makeGenTypeParamInst com ctx genArgs
else None
// let caseName = ent.UnionCases |> List.item tag |> getUnionCaseName |> ofString
let values = (ofInt tag)::values |> List.toArray
Expression.newExpression(consRef, values, ?typeArguments=typeParamInst, ?loc=r)

let enumerator2iterator com ctx =
let enumerator = Expression.callExpression(get None (Expression.identifier("this")) "GetEnumerator", [||])
Expand Down Expand Up @@ -1203,7 +1228,10 @@ module Util =

| Fable.FieldGet (field, index) ->
let expr = com.TransformAsExpr(ctx, fableExpr)
get range expr field.Name
match fableExpr.Type with
| Replacements.ErasedType com (offset, _, _) ->
getExpr range expr (ofInt (index + offset))
| _ -> get range expr field.Name

| Fable.ListHead ->
// get range (com.TransformAsExpr(ctx, fableExpr)) "head"
Expand Down Expand Up @@ -1231,7 +1259,11 @@ module Util =

| Fable.UnionField(index, _) ->
let expr = com.TransformAsExpr(ctx, fableExpr)
getExpr range (getExpr None expr (Expression.stringLiteral("fields"))) (ofInt index)
match fableExpr.Type with
| Replacements.ErasedType com (offset, _, _) ->
getExpr range expr (ofInt (index + offset))
| _ ->
getExpr range (getExpr None expr (Expression.stringLiteral("fields"))) (ofInt index)

let transformSet (com: IBabelCompiler) ctx range fableExpr (value: Fable.Expr) kind =
let expr = com.TransformAsExpr(ctx, fableExpr)
Expand All @@ -1241,7 +1273,12 @@ module Util =
| Fable.ValueSet -> expr
| Fable.ByKeySet(Fable.FieldKey fi) -> get None expr fi.Name
| Fable.ByKeySet(Fable.ExprKey(TransformExpr com ctx e)) -> getExpr None expr e
| Fable.FieldSet (field, index) -> get None expr field.Name
| Fable.FieldSet (field, index) ->
match fableExpr.Type with
| Replacements.ErasedType com (offset, _, _) ->
getExpr None expr (ofInt (index + offset))
| _ ->
get None expr field.Name
assign range ret value

let transformBindingExprBody (com: IBabelCompiler) (ctx: Context) (var: Fable.Ident) (value: Fable.Expr) =
Expand Down
4 changes: 3 additions & 1 deletion src/Fable.Transforms/Global/Compiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ module Literals =

type CompilerOptionsHelper =
static member DefaultExtension = ".fs.js"
static member Make(?typedArrays,
static member Make(?eraseTypes,
?typedArrays,
?typescript,
?define,
?configuration,
Expand All @@ -19,6 +20,7 @@ type CompilerOptionsHelper =
member _.Define = define
member _.Configuration = defaultArg configuration "Debug"
member _.DebugMode = isDebug
member _.EraseTypes = defaultArg eraseTypes false
member _.Typescript = defaultArg typescript false
member _.TypedArrays = defaultArg typedArrays true
member _.OptimizeFSharpAst = defaultArg optimizeFSharpAst false
Expand Down
20 changes: 20 additions & 0 deletions src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,17 @@ let (|NewAnonymousRecord|_|) = function
Some([], exprs, fieldNames, genArgs, r)
| _ -> None

let (|ErasedType|_|) (com: Compiler) = function
| Fable.AnonymousRecordType (fieldNames, genArgs) when com.Options.EraseTypes ->
Some (0, false, genArgs)
| Fable.DeclaredType (ent, genArgs) ->
let ent = com.GetEntity(ent)
if FSharp2Fable.Util.isErasedEntity com ent then
let offset = if ent.IsFSharpUnion then 2 else 1
Some (offset, ent.IsFSharpUnion, genArgs)
else None
| _ -> None

let coreModFor = function
| BclGuid -> "Guid"
| BclDateTime -> "Date"
Expand Down Expand Up @@ -414,6 +425,9 @@ let toString com (ctx: Context) r (args: Expr list) =
| Number _ -> Helper.InstanceCall(head, "toString", String, tail)
| Array _ | List _ ->
Helper.LibCall(com, "Types", "seqToString", String, [head], ?loc=r)
| ErasedType com (offset, isUnion, _) ->
let args = [makeIntConst offset; makeBoolConst isUnion; head]
Helper.LibCall(com, "Types", "erasedTypeToString", String, args, ?loc=r)
// | DeclaredType(ent, _) when ent.IsFSharpUnion || ent.IsFSharpRecord || ent.IsValueType ->
// Helper.InstanceCall(head, "toString", String, [], ?loc=r)
// | DeclaredType(ent, _) ->
Expand Down Expand Up @@ -710,6 +724,7 @@ let identityHash com r (arg: Expr) =
// | Array _ -> "arrayHash"
// | Builtin (BclDateTime|BclDateTimeOffset) -> "dateHash"
// | Builtin (BclInt64|BclUInt64|BclDecimal) -> "fastStructuralHash"
| ErasedType com _ -> "structuralHash"
| DeclaredType _ -> "safeHash"
| _ -> "identityHash"
Helper.LibCall(com, "Util", methodName, Number Int32, [arg], ?loc=r)
Expand All @@ -726,6 +741,7 @@ let structuralHash (com: ICompiler) r (arg: Expr) =
| Array _ -> "arrayHash"
| Builtin (BclDateTime|BclDateTimeOffset) -> "dateHash"
| Builtin (BclInt64|BclUInt64|BclDecimal) -> "fastStructuralHash"
| ErasedType com _ -> "structuralHash"
| DeclaredType(ent, _) ->
let ent = com.GetEntity(ent)
if not ent.IsInterface then "safeHash"
Expand All @@ -748,6 +764,8 @@ let rec equals (com: ICompiler) ctx r equal (left: Expr) (right: Expr) =
Helper.InstanceCall(left, "Equals", Boolean, [right]) |> is equal
| Builtin (BclInt64|BclUInt64|BclDecimal|BclBigInt as bt) ->
Helper.LibCall(com, coreModFor bt, "equals", Boolean, [left; right], ?loc=r) |> is equal
| ErasedType com _ ->
Helper.LibCall(com, "Util", "equalArrays", Boolean, [left; right], ?loc=r) |> is equal
| DeclaredType _ ->
Helper.LibCall(com, "Util", "equals", Boolean, [left; right], ?loc=r) |> is equal
| Array t ->
Expand All @@ -772,6 +790,8 @@ and compare (com: ICompiler) ctx r (left: Expr) (right: Expr) =
Helper.LibCall(com, "Date", "compare", Number Int32, [left; right], ?loc=r)
| Builtin (BclInt64|BclUInt64|BclDecimal|BclBigInt as bt) ->
Helper.LibCall(com, coreModFor bt, "compare", Number Int32, [left; right], ?loc=r)
| ErasedType com _ ->
Helper.LibCall(com, "Util", "compareArrays", Number Int32, [left; right], ?loc=r)
| DeclaredType _ ->
Helper.LibCall(com, "Util", "compare", Number Int32, [left; right], ?loc=r)
| Array t ->
Expand Down
1 change: 1 addition & 0 deletions src/fable-compiler-js/src/Platform.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type CmdLineOptions = {
benchmark: bool
optimize: bool
// sourceMaps: bool
eraseTypes: bool
typedArrays: bool
typescript: bool
printAst: bool
Expand Down
2 changes: 2 additions & 0 deletions src/fable-compiler-js/src/app.fs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ let parseFiles projectFileName options =

let parseFable (res, fileName) =
fable.CompileToBabelAst(libDir, res, fileName,
eraseTypes = options.eraseTypes,
typedArrays = options.typedArrays,
typescript = options.typescript)

Expand Down Expand Up @@ -239,6 +240,7 @@ let run opts projectFileName outDir =
benchmark = opts |> hasFlag "--benchmark"
optimize = opts |> hasFlag "--optimize"
// sourceMaps = opts |> hasFlag "--sourceMaps"
eraseTypes = opts |> hasFlag "--eraseTypes"
typedArrays = opts |> tryFlag "--typedArrays"
|> Option.defaultValue (opts |> hasFlag "--typescript" |> not)
typescript = opts |> hasFlag "--typescript"
Expand Down
14 changes: 14 additions & 0 deletions src/fable-library/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ export function seqToString<T>(self: Iterable<T>): string {
return str + "]";
}

export function erasedTypeToString(offset: number, isUnion: boolean, fields: any[]) {
if (Array.isArray(fields) && offset > 0) {
const name = toString(fields[offset - 1]);
if (isUnion) {
const caseName = name.substring(name.lastIndexOf(".") + 1);
return unionToString(caseName, fields.slice(offset));
} else {
return name; // records and value types
}
} else {
return toString(fields);
}
}

export function toString(x: any, callStack = 0): string {
if (x != null && typeof x === "object") {
if (typeof x.toString === "function") {
Expand Down
4 changes: 3 additions & 1 deletion src/fable-standalone/src/Interfaces.fs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ type IFableManager =
abstract GetToolTipText: parseResults: IParseResults * line: int * col: int * lineText: string -> string[]
abstract GetCompletionsAtLocation: parseResults: IParseResults * line: int * col: int * lineText: string -> Completion[]
abstract CompileToBabelAst: fableLibrary: string * parseResults: IParseResults * fileName: string
* ?eraseTypes: bool
* ?typedArrays: bool
* ?typescript: bool -> IBabelResult
* ?typescript: bool
-> IBabelResult
abstract PrintBabelAst: babelResult: IBabelResult * IWriter -> Async<unit>
abstract FSharpAstToString: parseResults: IParseResults * fileName: string -> string
6 changes: 4 additions & 2 deletions src/fable-standalone/src/Main.fs
Original file line number Diff line number Diff line change
Expand Up @@ -258,14 +258,16 @@ let init () =
getCompletionsAtLocation res line col lineText

member __.CompileToBabelAst(fableLibrary:string, parseResults:IParseResults, fileName:string,
?typedArrays, ?typescript) =
?eraseTypes, ?typedArrays, ?typescript) =
let res = parseResults :?> ParseResults
let project = res.GetProject()
let define = parseResults.OtherFSharpOptions |> Array.choose (fun x ->
if x.StartsWith("--define:") || x.StartsWith("-d:")
then x.[(x.IndexOf(':') + 1)..] |> Some
else None) |> Array.toList
let options = Fable.CompilerOptionsHelper.Make(define=define, ?typedArrays=typedArrays, ?typescript=typescript)
let options =
Fable.CompilerOptionsHelper.Make(define=define,
?eraseTypes=eraseTypes, ?typedArrays=typedArrays, ?typescript=typescript)
let com = CompilerImpl(fileName, project, options, fableLibrary)
let ast =
FSharp2Fable.Compiler.transformFile com
Expand Down
1 change: 1 addition & 0 deletions src/fable-standalone/test/bench-compiler/Platform.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type CmdLineOptions = {
benchmark: bool
optimize: bool
// sourceMaps: bool
eraseTypes: bool
typedArrays: bool
typescript: bool
printAst: bool
Expand Down
2 changes: 2 additions & 0 deletions src/fable-standalone/test/bench-compiler/app.fs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ let parseFiles projectFileName options =

let parseFable (res, fileName) =
fable.CompileToBabelAst(libDir, res, fileName,
eraseTypes = options.eraseTypes,
typedArrays = options.typedArrays,
typescript = options.typescript)

Expand Down Expand Up @@ -229,6 +230,7 @@ let run opts projectFileName outDir =
benchmark = opts |> hasFlag "--benchmark"
optimize = opts |> hasFlag "--optimize"
// sourceMaps = opts |> hasFlag "--sourceMaps"
eraseTypes = opts |> hasFlag "--eraseTypes"
typedArrays = opts |> tryFlag "--typedArrays"
|> Option.defaultValue (opts |> hasFlag "--typescript" |> not)
typescript = opts |> hasFlag "--typescript"
Expand Down
10 changes: 10 additions & 0 deletions src/fable-standalone/test/bench-compiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
"build-opt": "npm run build --optimize",
"postbuild": "npm run rollup-bundle",

"prebuild-dotnet-erased": "git clean -fdX && npm run build-lib -- --eraseTypes",
"build-dotnet-erased": "dotnet run -c Release bench-compiler.fsproj out-node --fableLib out-lib --eraseTypes",
"postbuild-dotnet-erased": "npm run rollup-bundle",

"build-node": "node dist/bundle.js bench-compiler.fsproj out-node2",
"build-node-es": "node out-node/app.js bench-compiler.fsproj out-node2",
"benchmark": "node dist/bundle.js bench-compiler.fsproj out-node2 --benchmark",
Expand Down Expand Up @@ -41,6 +45,12 @@
"build-test-node-ts": "npm run build-test-node --typescript",
"test": "node ./out-test/src/test.js",

"prebuild-test-erased": "git clean -fdX && npm run build-lib -- --eraseTypes",
"build-test-erased": "dotnet run -c Release ../../../../../fable-test/fable-test.fsproj out-test --fableLib out-lib --eraseTypes",

"prebuild-tests-erased": "git clean -fdX && npm run build-lib -- --eraseTypes",
"build-tests-erased": "dotnet run -c Release ../../../../tests/Main/Fable.Tests.fsproj out-tests --fableLib out-lib --eraseTypes",

"prebuild-tests": "git clean -fdX && npm run build-lib",
"prebuild-tests-ts": "git clean -fdX && npm run build-lib-ts",
"build-tests": "dotnet run -c Release ../../../../tests/Main/Fable.Tests.fsproj out-tests --fableLib out-lib",
Expand Down

0 comments on commit 7ec3698

Please sign in to comment.