Skip to content

Commit c191f31

Browse files
ncavealfonsogarciacaro
authored andcommitted
Fixed List type
1 parent 9b5d2f2 commit c191f31

5 files changed

Lines changed: 88 additions & 65 deletions

File tree

src/Fable.Transforms/Fable2Babel.fs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -808,12 +808,13 @@ module Util =
808808
| Fable.NewTuple vals -> makeTypedArray com ctx Fable.Any (Fable.ArrayValues vals)
809809
// Optimization for bundle size: compile list literals as List.ofArray
810810
| Replacements.ListLiteral(exprs, t) ->
811-
[|List.rev exprs |> makeArray com ctx|] |> coreLibConstructorCall com ctx "Types" "List"
811+
[|List.rev exprs |> makeArray com ctx|]
812+
|> coreLibCall com ctx r "Types" "newList"
812813
| Fable.NewList (headAndTail, _) ->
813814
match headAndTail with
814-
| None -> coreLibConstructorCall com ctx "Types" "List" [||]
815+
| None -> coreLibCall com ctx r "Types" "newList" [||]
815816
| Some(TransformExpr com ctx head, TransformExpr com ctx tail) ->
816-
callFunction r (get None tail "add") [head]
817+
coreLibCall com ctx r "Types" "cons" [|head; tail|]
817818
| Fable.NewOption (value, t) ->
818819
match value with
819820
| Some (TransformExpr com ctx e) ->
@@ -1053,8 +1054,8 @@ module Util =
10531054
let expr = com.TransformAsExpr(ctx, fableExpr)
10541055
match getKind with
10551056
| Fable.ExprGet(TransformExpr com ctx prop) -> getExpr range expr prop
1056-
| Fable.ListHead -> get range expr "head"
1057-
| Fable.ListTail -> get range expr "tail"
1057+
| Fable.ListHead -> get range expr "Head"
1058+
| Fable.ListTail -> get range expr "Tail"
10581059
| Fable.FieldGet(fieldName,_,_) ->
10591060
let expr =
10601061
match fableExpr with
@@ -1211,7 +1212,7 @@ module Util =
12111212
let op = if nonEmpty then BinaryUnequal else BinaryEqual
12121213
upcast BinaryExpression(op, com.TransformAsExpr(ctx, expr), NullLiteral(), ?loc=range)
12131214
| Fable.ListTest nonEmpty ->
1214-
let expr = get range (com.TransformAsExpr(ctx, expr)) "isEmpty"
1215+
let expr = get range (com.TransformAsExpr(ctx, expr)) "IsEmpty"
12151216
if nonEmpty then upcast UnaryExpression(UnaryNot, expr, ?loc=range)
12161217
else expr
12171218
| Fable.UnionCaseTest(uci, ent) ->

src/Fable.Transforms/Replacements.fs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1840,15 +1840,16 @@ let arrayModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Ex
18401840
let lists (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) =
18411841
match i.CompiledName, thisArg, args with
18421842
// Use methods for Head and Tail (instead of Get(ListHead) for example) to check for empty lists
1843-
| ReplaceName
1844-
[ "get_Head", "head"
1845-
"get_Tail", "tail"
1846-
"get_Item", "item"
1847-
"get_Length", "length"
1848-
"GetSlice", "slice" ] methName, Some x, _ ->
1849-
let args = match args with [ExprType Unit] -> [x] | args -> args @ [x]
1850-
Helper.CoreCall("List", methName, t, args, i.SignatureArgTypes, ?loc=r) |> Some
1851-
| "get_IsEmpty", Some x, _ -> Test(x, ListTest false, r) |> Some
1843+
| ("get_Head" | "get_Tail" | "get_Length" | "get_IsEmpty"), Some callee, _ ->
1844+
let meth = Naming.removeGetSetPrefix i.CompiledName
1845+
get r t callee meth |> Some
1846+
| "get_Item", Some callee, _ ->
1847+
let meth = Naming.removeGetSetPrefix i.CompiledName
1848+
Helper.InstanceCall(callee, meth, t, args, i.SignatureArgTypes, ?loc=r) |> Some
1849+
| "GetSlice", Some x, _ ->
1850+
let meth = Naming.lowerFirst i.CompiledName
1851+
let args = match args with [ExprType Unit] -> [x] | args -> args @ [x]
1852+
Helper.CoreCall("List", meth, t, args, i.SignatureArgTypes, ?loc=r) |> Some
18521853
| "get_Empty", None, _ -> NewList(None, (genArg com ctx r 0 i.GenericArgs)) |> makeValue r |> Some
18531854
| "Cons", None, [h;t] -> NewList(Some(h,t), (genArg com ctx r 0 i.GenericArgs)) |> makeValue r |> Some
18541855
| ("GetHashCode" | "Equals" | "CompareTo"), Some callee, _ ->

src/fable-library/List.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ let splitAt i xs =
433433

434434
let outOfRange() = failwith "Index out of range"
435435

436-
let slice (lower: int option) (upper: int option) (xs: 'T list) =
436+
let getSlice (lower: int option) (upper: int option) (xs: 'T list) =
437437
let lower = defaultArg lower 0
438438
let hasUpper = Option.isSome upper
439439
if lower < 0 then outOfRange()
@@ -503,7 +503,7 @@ let windowed (windowSize: int) (source: 'T list): 'T list list =
503503
failwith "windowSize must be positive"
504504
let mutable res = []
505505
for i = length source downto windowSize do
506-
res <- (slice (Some(i-windowSize)) (Some(i-1)) source) :: res
506+
res <- (getSlice (Some(i-windowSize)) (Some(i-1)) source) :: res
507507
res
508508

509509
let splitInto (chunks: int) (source: 'T list): 'T list list =

src/fable-library/Types.ts

Lines changed: 53 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,32 @@ function compareList<T>(self: List<T>, other: List<T>) {
5454
if (other == null) {
5555
return -1;
5656
}
57-
const selfLen = self.length;
58-
const otherLen = other.length;
57+
const selfLen = self.Length;
58+
const otherLen = other.Length;
5959
const minLen = Math.min(selfLen, otherLen);
6060
for (let i = 0; i < minLen; i++) {
61-
const res = compare(self.item(i), other.item(i));
61+
const res = compare(self.Item(i), other.Item(i));
6262
if (res !== 0) { return res; }
6363
}
6464
return selfLen > otherLen ? 1 : (selfLen < otherLen ? -1 : 0);
6565
}
6666
}
6767

68+
export function newList<T>(vals: T[]): List<T> {
69+
return new List(vals);
70+
}
71+
72+
export function cons<T>(head: T, tail: List<T>): List<T> {
73+
// If this points to the last index of the stack, push the new value into it.
74+
// Otherwise, this becomes an "actual" tail.
75+
if (tail.vals.length === tail.idx + 1) {
76+
tail.vals.push(head);
77+
return new List(tail.vals, tail.tail);
78+
} else {
79+
return new List([head], tail);
80+
}
81+
}
82+
6883
/**
6984
* F# list is represented in runtime by an optimized type that uses a stack (a reverted JS array)
7085
* to store the values, so we can a have a big list represented by a single object (plus the stack).
@@ -73,57 +88,52 @@ function compareList<T>(self: List<T>, other: List<T>) {
7388
export class List<T> implements IEquatable<List<T>>, IComparable<List<T>>, Iterable<T> {
7489
public vals: T[];
7590
public idx: number;
76-
public _tail: List<T> | undefined;
91+
public tail?: List<T>;
7792

78-
constructor(vals?: T[], idx?: number) {
93+
constructor(vals?: T[], tail?: List<T>, idx?: number) {
7994
this.vals = vals ?? [];
8095
this.idx = idx ?? this.vals.length - 1;
96+
this.tail = tail;
8197
}
8298

83-
add(item: T): List<T> {
84-
// If this points to the last index of the stack, push the new value into it.
85-
// Otherwise, this becomes an "actual" tail.
86-
if (this.vals.length === this.idx + 1) {
87-
this.vals.push(item);
88-
return new List(this.vals);
99+
public Item(i: number): T {
100+
if (i < 0) {
101+
throw new Error("Index out of range");
102+
} else if (i <= this.idx) {
103+
return this.vals[this.idx - i];
104+
} else if (this.tail) {
105+
return this.tail.Item(i - this.idx - 1);
89106
} else {
90-
const li = new List([item]);
91-
li._tail = this;
92-
return li;
93-
}
94-
}
95-
96-
/** Unsafe, check length before calling it */
97-
public item(i: number): T | undefined {
98-
let rev_i = this.idx - i;
99-
if (rev_i >= 0) {
100-
return this.vals[rev_i];
101-
} else if (this._tail) {
102-
return this._tail.item(rev_i * -1 - 1);
107+
throw new Error("Index out of range");
103108
}
104-
return undefined;
105109
}
106110

107-
/** Unsafe, check isEmpty before calling it */
108-
public get head(): T | undefined {
109-
return this.vals[this.idx];
111+
public get Head(): T {
112+
if (this.idx >= 0) {
113+
return this.vals[this.idx];
114+
} else if (this.idx < 0 && this.tail) {
115+
return this.tail.Head;
116+
} else {
117+
throw new Error("List was empty");
118+
}
110119
}
111120

112-
public get tail(): List<T> | undefined {
113-
if (this.idx === 0 && this._tail) {
114-
return this._tail;
121+
public get Tail(): List<T> | undefined {
122+
if (this.idx === 0 && this.tail) {
123+
return this.tail;
115124
} else if (this.idx >= 0) {
116-
return new List(this.vals, this.idx - 1);
125+
return new List(this.vals, this.tail, this.idx - 1);
126+
} else {
127+
return this.tail?.Tail;
117128
}
118-
return undefined;
119129
}
120130

121-
public get isEmpty() {
122-
return this.idx < 0;
131+
public get IsEmpty(): boolean {
132+
return this.idx < 0 && (this.tail?.IsEmpty ?? true);
123133
}
124134

125-
public get length(): number {
126-
return this.idx + 1 + (this._tail?.length ?? 0);
135+
public get Length(): number {
136+
return this.idx + 1 + (this.tail?.Length ?? 0);
127137
}
128138

129139
public toString() {
@@ -139,15 +149,13 @@ export class List<T> implements IEquatable<List<T>>, IComparable<List<T>>, Itera
139149
let li: List<T> = this;
140150
return {
141151
next: (): IteratorResult<T> => {
142-
if (curIdx < 0) {
143-
if (li._tail) {
144-
li = li._tail;
145-
curIdx = li.idx;
146-
} else {
147-
return { done: true, value: undefined };
148-
}
152+
while (curIdx < 0 && li.tail) {
153+
li = li.tail;
154+
curIdx = li.idx;
149155
}
150-
return { done: false, value: li.vals[curIdx--] };
156+
return (curIdx < 0)
157+
? { done: true, value: undefined }
158+
: { done: false, value: li.vals[curIdx--] };
151159
}
152160
};
153161
}

tests/Main/ListTests.fs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,26 @@ module List =
4242

4343
let tests =
4444
testList "Lists" [
45-
// TODO: Empty lists may be represented as null, make sure they don't conflict with None
4645
testCase "Some [] works" <| fun () ->
4746
let xs: int list option = Some []
4847
let ys: int list option = None
4948
Option.isSome xs |> equal true
5049
Option.isNone ys |> equal true
5150

51+
testCase "List equality works" <| fun () ->
52+
let xs = [1;2;3]
53+
let ys = [1;2;3]
54+
let zs = [1;4;3]
55+
xs = ys |> equal true
56+
xs = zs |> equal false
57+
58+
testCase "List comparison works" <| fun () ->
59+
let xs = [1;2;3]
60+
let ys = [1;2;3]
61+
let zs = [1;4;3]
62+
xs < ys |> equal false
63+
xs < zs |> equal true
64+
5265
testCase "Pattern matching with lists works" <| fun () ->
5366
match [] with [] -> true | _ -> false
5467
|> equal true
@@ -302,9 +315,9 @@ let tests =
302315
|> List.sum |> equal 9
303316

304317
testCase "List.rev works" <| fun () ->
305-
let xs = [1; 2]
318+
let xs = [1; 2; 3]
306319
let ys = xs |> List.rev
307-
equal 2 ys.Head
320+
equal 3 ys.Head
308321

309322
testCase "List.scan works" <| fun () ->
310323
let xs = [1; 2; 3; 4]

0 commit comments

Comments
 (0)