Skip to content

Commit fe28b2f

Browse files
committed
Erase unions and records
1 parent 155bd5e commit fe28b2f

File tree

15 files changed

+149
-27
lines changed

15 files changed

+149
-27
lines changed

.vscode/launch.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"name": "Run bench-compiler (Node)",
7474
"program": "${workspaceRoot}/src/fable-standalone/test/bench-compiler/out-node/app.js",
7575
// "args": ["${workspaceRoot}/tests/Main/Fable.Tests.fsproj", "out-tests", "--fableLib", "out-lib"],
76-
"args": ["${workspaceRoot}/../fable-test/fable-test.fsproj", "out-test", "--fableLib", "out-lib"],
76+
"args": ["${workspaceRoot}/../fable-test/fable-test.fsproj", "out-test", "--fableLib", "out-lib", "--eraseTypes"],
7777
"cwd": "${workspaceRoot}/src/fable-standalone/test/bench-compiler"
7878
},
7979
{
@@ -82,7 +82,7 @@
8282
"name": "Run bench-compiler (.NET)",
8383
"program": "${workspaceFolder}/src/fable-standalone/test/bench-compiler/bin/Debug/net5.0/bench-compiler.dll",
8484
// "args": ["${workspaceRoot}/tests/Main/Fable.Tests.fsproj", "out-tests", "--fableLib", "out-lib"],
85-
"args": ["${workspaceRoot}/../fable-test/fable-test.fsproj", "out-test", "--fableLib", "out-lib"],
85+
"args": ["${workspaceRoot}/../fable-test/fable-test.fsproj", "out-test", "--fableLib", "out-lib", "--eraseTypes"],
8686
"cwd": "${workspaceFolder}/src/fable-standalone/test/bench-compiler"
8787
},
8888
{

src/Fable.AST/Plugins.fs

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type Language =
1414
| TypeScript
1515

1616
type CompilerOptions =
17+
abstract EraseTypes: bool
1718
abstract TypedArrays: bool
1819
abstract ClampByteArrays: bool
1920
abstract Language: Language

src/Fable.Cli/Entry.fs

+1
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ type Runner =
172172

173173
let compilerOptions =
174174
CompilerOptionsHelper.Make(language=language,
175+
eraseTypes = flagEnabled "--eraseTypes" args,
175176
typedArrays = typedArrays,
176177
fileExtension = fileExt,
177178
define = define,

src/Fable.Transforms/FSharp2Fable.Util.fs

+16
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,16 @@ module Helpers =
448448
let makeRangeFrom (fsExpr: FSharpExpr) =
449449
Some (makeRange fsExpr.Range)
450450

451+
let isErasedTypeDef (com: Compiler) (tdef: FSharpEntity) =
452+
com.Options.EraseTypes && tdef.IsFSharp
453+
&& (tdef.IsFSharpUnion || tdef.IsFSharpRecord || tdef.IsValueType || tdef.IsByRef)
454+
&& not (tdef.TryFullName = Some Types.reference) // no F# refs
455+
&& not (hasAttribute Atts.customEquality tdef.Attributes)
456+
&& not (hasAttribute Atts.customComparison tdef.Attributes)
457+
458+
let isErasedType (com: Compiler) (t: FSharpType) =
459+
t.HasTypeDefinition && (isErasedTypeDef com t.TypeDefinition)
460+
451461
let unionCaseTag (com: IFableCompiler) (ent: FSharpEntity) (unionCase: FSharpUnionCase) =
452462
try
453463
// If the order of cases changes in the declaration, the tag has to change too.
@@ -1180,6 +1190,12 @@ module Util =
11801190
makeImportUserGenerated None Fable.Any selector path |> Some
11811191
| _ -> None
11821192

1193+
let isErasedEntity (com: Compiler) (ent: Fable.Entity) =
1194+
match ent with
1195+
| :? FsEnt as fsEnt ->
1196+
Helpers.isErasedTypeDef com fsEnt.FSharpEntity
1197+
| _ -> false
1198+
11831199
let isErasedOrStringEnumEntity (ent: Fable.Entity) =
11841200
ent.Attributes |> Seq.exists (fun att ->
11851201
match att.Entity.FullName with

src/Fable.Transforms/Fable2Babel.fs

+66-20
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,12 @@ module Reflection =
310310
let ent = com.GetEntity(ent)
311311
if ent.IsInterface then
312312
warnAndEvalToFalse "interfaces"
313+
elif FSharp2Fable.Util.isErasedEntity com ent then
314+
let expr = com.TransformAsExpr(ctx, expr)
315+
let idx = if ent.IsFSharpUnion then 1 else 0
316+
let actual = Util.getExpr None expr (Util.ofInt idx)
317+
let expected = Util.ofString ent.FullName
318+
Expression.binaryExpression(BinaryEqualStrict, actual, expected, ?loc=range)
313319
else
314320
match tryJsConstructor com ctx ent with
315321
| Some cons ->
@@ -382,6 +388,7 @@ module Annotation =
382388
| Fable.LambdaType _ -> Util.uncurryLambdaType typ ||> makeFunctionTypeAnnotation com ctx typ
383389
| Fable.DelegateType(argTypes, returnType) -> makeFunctionTypeAnnotation com ctx typ argTypes returnType
384390
| Fable.GenericParam name -> makeSimpleTypeAnnotation com ctx name
391+
| Replacements.ErasedType com (_, _, _, genArgs) -> makeTupleTypeAnnotation com ctx genArgs
385392
| Fable.DeclaredType(ent, genArgs) ->
386393
makeEntityTypeAnnotation com ctx ent genArgs
387394
| Fable.AnonymousRecordType(fieldNames, genArgs) ->
@@ -813,9 +820,18 @@ module Util =
813820
let getUnionCaseName (uci: Fable.UnionCase) =
814821
match uci.CompiledName with Some cname -> cname | None -> uci.Name
815822

823+
// let getUnionCaseFullName (uci: Fable.UnionCase) =
824+
// uci.XmlDocSig
825+
// |> Naming.replacePrefix "T:Microsoft.FSharp." "FSharp."
826+
// |> Naming.replacePrefix "T:" ""
827+
816828
let getUnionExprTag (com: IBabelCompiler) ctx r (fableExpr: Fable.Expr) =
817829
let expr = com.TransformAsExpr(ctx, fableExpr)
818-
getExpr r expr (Expression.stringLiteral("tag"))
830+
match fableExpr.Type with
831+
| Replacements.ErasedType com _ ->
832+
getExpr r expr (ofInt 0)
833+
| _ ->
834+
getExpr r expr (Expression.stringLiteral("tag"))
819835

820836
/// Wrap int expressions with `| 0` to help optimization of JS VMs
821837
let wrapIntExpression typ (e: Expression) =
@@ -961,27 +977,39 @@ module Util =
961977
com.TransformAsExpr(ctx, x)
962978
| Fable.NewRecord(values, ent, genArgs) ->
963979
let ent = com.GetEntity(ent)
964-
let values = List.mapToArray (fun x -> com.TransformAsExpr(ctx, x)) values
965-
let consRef = ent |> jsConstructor com ctx
966-
let typeParamInst =
967-
if com.Options.Language = TypeScript && (ent.FullName = Types.reference)
968-
then makeGenTypeParamInst com ctx genArgs
969-
else None
970-
Expression.newExpression(consRef, values, ?typeArguments=typeParamInst, ?loc=r)
980+
let values = List.map (fun x -> com.TransformAsExpr(ctx, x)) values
981+
if FSharp2Fable.Util.isErasedEntity com ent then
982+
let recordName = ent.FullName |> ofString
983+
recordName::values |> List.toArray |> Expression.arrayExpression
984+
else
985+
let consRef = ent |> jsConstructor com ctx
986+
let typeParamInst =
987+
if com.Options.Language = TypeScript && (ent.FullName = Types.reference)
988+
then makeGenTypeParamInst com ctx genArgs
989+
else None
990+
Expression.newExpression(consRef, values |> List.toArray, ?typeArguments=typeParamInst, ?loc=r)
971991
| Fable.NewAnonymousRecord(values, fieldNames, _genArgs) ->
972992
let values = List.mapToArray (fun x -> com.TransformAsExpr(ctx, x)) values
973-
Array.zip fieldNames values |> makeJsObject
993+
if com.Options.EraseTypes then
994+
values |> Expression.arrayExpression
995+
else
996+
Array.zip fieldNames values |> makeJsObject
974997
| Fable.NewUnion(values, tag, ent, genArgs) ->
975998
let ent = com.GetEntity(ent)
976999
let values = List.map (fun x -> com.TransformAsExpr(ctx, x)) values
977-
let consRef = ent |> jsConstructor com ctx
978-
let typeParamInst =
979-
if com.Options.Language = TypeScript
980-
then makeGenTypeParamInst com ctx genArgs
981-
else None
982-
// let caseName = ent.UnionCases |> List.item tag |> getUnionCaseName |> ofString
983-
let values = (ofInt tag)::values |> List.toArray
984-
Expression.newExpression(consRef, values, ?typeArguments=typeParamInst, ?loc=r)
1000+
if FSharp2Fable.Util.isErasedEntity com ent then
1001+
let caseTag = tag |> ofInt
1002+
let caseName = ent.UnionCases |> List.item tag |> getUnionCaseName |> ofString
1003+
caseTag::caseName::values |> List.toArray |> Expression.arrayExpression
1004+
else
1005+
let consRef = ent |> jsConstructor com ctx
1006+
let typeParamInst =
1007+
if com.Options.Language = TypeScript
1008+
then makeGenTypeParamInst com ctx genArgs
1009+
else None
1010+
// let caseName = ent.UnionCases |> List.item tag |> getUnionCaseName |> ofString
1011+
let values = (ofInt tag)::values |> List.toArray
1012+
Expression.newExpression(consRef, values, ?typeArguments=typeParamInst, ?loc=r)
9851013

9861014
let enumerator2iterator com ctx =
9871015
let enumerator = Expression.callExpression(get None (Expression.identifier("this")) "GetEnumerator", [||])
@@ -1200,7 +1228,14 @@ module Util =
12001228
let expr = com.TransformAsExpr(ctx, fableExpr)
12011229
match key with
12021230
| Fable.ExprKey(TransformExpr com ctx prop) -> getExpr range expr prop
1203-
| Fable.FieldKey field -> get range expr field.Name
1231+
| Fable.FieldKey field ->
1232+
match fableExpr.Type with
1233+
| Replacements.ErasedType com (fieldNames, offset, _, _) ->
1234+
let indexOpt = fieldNames |> Array.tryFindIndex (fun name -> name = field.Name)
1235+
match indexOpt with
1236+
| Some index -> getExpr range expr (ofInt (offset + index))
1237+
| _ -> get range expr field.Name
1238+
| _ -> get range expr field.Name
12041239

12051240
| Fable.ListHead ->
12061241
// get range (com.TransformAsExpr(ctx, fableExpr)) "head"
@@ -1228,15 +1263,26 @@ module Util =
12281263

12291264
| Fable.UnionField(index, _) ->
12301265
let expr = com.TransformAsExpr(ctx, fableExpr)
1231-
getExpr range (getExpr None expr (Expression.stringLiteral("fields"))) (ofInt index)
1266+
match fableExpr.Type with
1267+
| Replacements.ErasedType com (_, offset, _, _) ->
1268+
getExpr range expr (ofInt (offset + index))
1269+
| _ ->
1270+
getExpr range (getExpr None expr (Expression.stringLiteral("fields"))) (ofInt index)
12321271

12331272
let transformSet (com: IBabelCompiler) ctx range fableExpr (value: Fable.Expr) kind =
12341273
let expr = com.TransformAsExpr(ctx, fableExpr)
12351274
let value = com.TransformAsExpr(ctx, value) |> wrapIntExpression value.Type
12361275
let ret =
12371276
match kind with
12381277
| None -> expr
1239-
| Some(Fable.FieldKey fi) -> get None expr fi.Name
1278+
| Some(Fable.FieldKey field) ->
1279+
match fableExpr.Type with
1280+
| Replacements.ErasedType com (fieldNames, offset, _, _) ->
1281+
let indexOpt = fieldNames |> Array.tryFindIndex (fun name -> name = field.Name)
1282+
match indexOpt with
1283+
| Some index -> getExpr None expr (ofInt (offset + index))
1284+
| _ -> get None expr field.Name
1285+
| _ -> get None expr field.Name
12401286
| Some(Fable.ExprKey(TransformExpr com ctx e)) -> getExpr None expr e
12411287
assign range ret value
12421288

src/Fable.Transforms/Global/Compiler.fs

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module Literals =
66
type CompilerOptionsHelper =
77
static member DefaultExtension = ".fs.js"
88
static member Make(?language,
9+
?eraseTypes,
910
?typedArrays,
1011
?define,
1112
?optimizeFSharpAst,
@@ -18,6 +19,7 @@ type CompilerOptionsHelper =
1819
member _.Define = define
1920
member _.DebugMode = isDebug
2021
member _.Language = defaultArg language JavaScript
22+
member _.EraseTypes = defaultArg eraseTypes false
2123
member _.TypedArrays = defaultArg typedArrays true
2224
member _.OptimizeFSharpAst = defaultArg optimizeFSharpAst false
2325
member _.Verbosity = defaultArg verbosity Verbosity.Normal

src/Fable.Transforms/Replacements.fs

+23
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,20 @@ let (|NewAnonymousRecord|_|) = function
278278
Some([], exprs, fieldNames, genArgs, r)
279279
| _ -> None
280280

281+
let (|ErasedType|_|) (com: Compiler) = function
282+
| Fable.AnonymousRecordType (fieldNames, genArgs) when com.Options.EraseTypes ->
283+
Some (fieldNames, 0, false, genArgs)
284+
| Fable.DeclaredType (ent, genArgs) ->
285+
let ent = com.GetEntity(ent)
286+
if FSharp2Fable.Util.isErasedEntity com ent then
287+
let offset = if ent.IsFSharpUnion then 2 else 1
288+
let fieldNames =
289+
if ent.IsFSharpUnion then [||] // not used for unions
290+
else ent.FSharpFields |> List.map (fun x -> x.Name) |> List.toArray
291+
Some (fieldNames, offset, ent.IsFSharpUnion, genArgs)
292+
else None
293+
| _ -> None
294+
281295
let coreModFor = function
282296
| BclGuid -> "Guid"
283297
| BclDateTime -> "Date"
@@ -436,6 +450,9 @@ let toString com (ctx: Context) r (args: Expr list) =
436450
| Number _ -> Helper.InstanceCall(head, "toString", String, tail)
437451
| Array _ | List _ ->
438452
Helper.LibCall(com, "Types", "seqToString", String, [head], ?loc=r)
453+
| ErasedType com (_, offset, isUnion, _) ->
454+
let args = [makeIntConst offset; makeBoolConst isUnion; head]
455+
Helper.LibCall(com, "Types", "erasedTypeToString", String, args, ?loc=r)
439456
// | DeclaredType(ent, _) when ent.IsFSharpUnion || ent.IsFSharpRecord || ent.IsValueType ->
440457
// Helper.InstanceCall(head, "toString", String, [], ?loc=r)
441458
// | DeclaredType(ent, _) ->
@@ -732,6 +749,7 @@ let identityHash com r (arg: Expr) =
732749
// | Array _ -> "arrayHash"
733750
// | Builtin (BclDateTime|BclDateTimeOffset) -> "dateHash"
734751
// | Builtin (BclInt64|BclUInt64|BclDecimal) -> "fastStructuralHash"
752+
| ErasedType com _ -> "structuralHash"
735753
| DeclaredType _ -> "safeHash"
736754
| _ -> "identityHash"
737755
Helper.LibCall(com, "Util", methodName, Number Int32, [arg], ?loc=r)
@@ -748,6 +766,7 @@ let structuralHash (com: ICompiler) r (arg: Expr) =
748766
| Array _ -> "arrayHash"
749767
| Builtin (BclDateTime|BclDateTimeOffset) -> "dateHash"
750768
| Builtin (BclInt64|BclUInt64|BclDecimal) -> "fastStructuralHash"
769+
| ErasedType com _ -> "structuralHash"
751770
| DeclaredType(ent, _) ->
752771
let ent = com.GetEntity(ent)
753772
if not ent.IsInterface then "safeHash"
@@ -770,6 +789,8 @@ let rec equals (com: ICompiler) ctx r equal (left: Expr) (right: Expr) =
770789
Helper.InstanceCall(left, "Equals", Boolean, [right]) |> is equal
771790
| Builtin (BclInt64|BclUInt64|BclDecimal|BclBigInt as bt) ->
772791
Helper.LibCall(com, coreModFor bt, "equals", Boolean, [left; right], ?loc=r) |> is equal
792+
| ErasedType com _ ->
793+
Helper.LibCall(com, "Util", "equalArrays", Boolean, [left; right], ?loc=r) |> is equal
773794
| DeclaredType _ ->
774795
Helper.LibCall(com, "Util", "equals", Boolean, [left; right], ?loc=r) |> is equal
775796
| Array t ->
@@ -794,6 +815,8 @@ and compare (com: ICompiler) ctx r (left: Expr) (right: Expr) =
794815
Helper.LibCall(com, "Date", "compare", Number Int32, [left; right], ?loc=r)
795816
| Builtin (BclInt64|BclUInt64|BclDecimal|BclBigInt as bt) ->
796817
Helper.LibCall(com, coreModFor bt, "compare", Number Int32, [left; right], ?loc=r)
818+
| ErasedType com _ ->
819+
Helper.LibCall(com, "Util", "compareArrays", Number Int32, [left; right], ?loc=r)
797820
| DeclaredType _ ->
798821
Helper.LibCall(com, "Util", "compare", Number Int32, [left; right], ?loc=r)
799822
| Array t ->

src/fable-compiler-js/src/Platform.fs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type CmdLineOptions = {
1010
sourceMaps: bool
1111
typedArrays: bool
1212
typescript: bool
13+
eraseTypes: bool
1314
printAst: bool
1415
// watch: bool
1516
}

src/fable-compiler-js/src/app.fs

+2
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ let parseFiles projectFileName options =
162162

163163
let parseFable (res, fileName) =
164164
fable.CompileToBabelAst(libDir, res, fileName,
165+
eraseTypes = options.eraseTypes,
165166
typedArrays = options.typedArrays,
166167
typescript = options.typescript)
167168

@@ -258,6 +259,7 @@ let run opts projectFileName outDir =
258259
typedArrays = opts |> tryFlag "--typedArrays"
259260
|> Option.defaultValue (opts |> hasFlag "--typescript" |> not)
260261
typescript = opts |> hasFlag "--typescript"
262+
eraseTypes = opts |> hasFlag "--eraseTypes"
261263
printAst = opts |> hasFlag "--printAst"
262264
// watch = opts |> hasFlag "--watch"
263265
}

src/fable-library/Types.ts

+14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@ export function seqToString<T>(self: Iterable<T>): string {
1717
return str + "]";
1818
}
1919

20+
export function erasedTypeToString(offset: number, isUnion: boolean, fields: any[]) {
21+
if (Array.isArray(fields) && offset > 0) {
22+
const name = toString(fields[offset - 1]);
23+
if (isUnion) {
24+
const caseName = name.substring(name.lastIndexOf(".") + 1);
25+
return unionToString(caseName, fields.slice(offset));
26+
} else {
27+
return name; // records and value types
28+
}
29+
} else {
30+
return toString(fields);
31+
}
32+
}
33+
2034
export function toString(x: any, callStack = 0): string {
2135
if (x != null && typeof x === "object") {
2236
if (typeof x.toString === "function") {

src/fable-standalone/src/Interfaces.fs

+3-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ type IFableManager =
6767
abstract GetToolTipText: parseResults: IParseResults * line: int * col: int * lineText: string -> string[]
6868
abstract GetCompletionsAtLocation: parseResults: IParseResults * line: int * col: int * lineText: string -> Completion[]
6969
abstract CompileToBabelAst: fableLibrary: string * parseResults: IParseResults * fileName: string
70+
* ?eraseTypes: bool
7071
* ?typedArrays: bool
71-
* ?typescript: bool -> IBabelResult
72+
* ?typescript: bool
73+
-> IBabelResult
7274
abstract PrintBabelAst: babelResult: IBabelResult * IWriter -> Async<unit>
7375
abstract FSharpAstToString: parseResults: IParseResults * fileName: string -> string

src/fable-standalone/src/Main.fs

+5-4
Original file line numberDiff line numberDiff line change
@@ -212,14 +212,15 @@ let getCompletionsAtLocation (parseResults: ParseResults) (line: int) (col: int)
212212
| None ->
213213
[||]
214214

215-
let compileToFableAst (parseResults: IParseResults) fileName fableLibrary typedArrays language =
215+
let compileToFableAst (parseResults: IParseResults) fileName fableLibrary typedArrays language eraseTypes =
216216
let res = parseResults :?> ParseResults
217217
let project = res.GetProject()
218218
let define = parseResults.OtherFSharpOptions |> Array.choose (fun x ->
219219
if x.StartsWith("--define:") || x.StartsWith("-d:")
220220
then x.[(x.IndexOf(':') + 1)..] |> Some
221221
else None) |> Array.toList
222-
let options = Fable.CompilerOptionsHelper.Make(language=language, define=define, ?typedArrays=typedArrays)
222+
let options = Fable.CompilerOptionsHelper.Make(language=language,
223+
define=define, ?typedArrays=typedArrays, ?eraseTypes=eraseTypes)
223224
let com = CompilerImpl(fileName, project, options, fableLibrary)
224225
let fableAst =
225226
FSharp2Fable.Compiler.transformFile com
@@ -288,10 +289,10 @@ let init () =
288289
getCompletionsAtLocation res line col lineText
289290

290291
member __.CompileToBabelAst(fableLibrary:string, parseResults:IParseResults, fileName:string,
291-
?typedArrays, ?typescript) =
292+
?typedArrays, ?typescript, ?eraseTypes) =
292293
let language = match typescript with | Some true -> TypeScript | _ -> JavaScript
293294
let com, fableAst, errors =
294-
compileToFableAst parseResults fileName fableLibrary typedArrays language
295+
compileToFableAst parseResults fileName fableLibrary typedArrays language eraseTypes
295296
let babelAst =
296297
fableAst |> Fable2Babel.Compiler.transformFile com
297298
upcast BabelResult(babelAst, errors)

src/fable-standalone/test/bench-compiler/Platform.fs

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type CmdLineOptions = {
88
sourceMaps: bool
99
typedArrays: bool
1010
typescript: bool
11+
eraseTypes: bool
1112
printAst: bool
1213
// watch: bool
1314
}

0 commit comments

Comments
 (0)