Skip to content

Commit 8d209a3

Browse files
Merge pull request #20467 from Kovensky/array-from-union-fix
Accept Iterable|ArrayLike union in Array.from, add tests
2 parents 5365706 + c894eeb commit 8d209a3

12 files changed

+487
-18
lines changed

src/lib/es2015.iterable.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ interface ArrayConstructor {
5252
* Creates an array from an iterable object.
5353
* @param iterable An iterable object to convert to an array.
5454
*/
55-
from<T>(iterable: Iterable<T>): T[];
55+
from<T>(iterable: Iterable<T> | ArrayLike<T>): T[];
5656

5757
/**
5858
* Creates an array from an iterable object.
5959
* @param iterable An iterable object to convert to an array.
6060
* @param mapfn A mapping function to call on every element of the array.
6161
* @param thisArg Value of 'this' used to invoke the mapfn.
6262
*/
63-
from<T, U>(iterable: Iterable<T>, mapfn: (v: T, k: number) => U, thisArg?: any): U[];
63+
from<T, U>(iterable: Iterable<T> | ArrayLike<T>, mapfn: (v: T, k: number) => U, thisArg?: any): U[];
6464
}
6565

6666
interface ReadonlyArray<T> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
tests/cases/compiler/arrayFrom.ts(19,7): error TS2322: Type 'A[]' is not assignable to type 'B[]'.
2+
Type 'A' is not assignable to type 'B'.
3+
Property 'b' is missing in type 'A'.
4+
tests/cases/compiler/arrayFrom.ts(22,7): error TS2322: Type 'A[]' is not assignable to type 'B[]'.
5+
6+
7+
==== tests/cases/compiler/arrayFrom.ts (2 errors) ====
8+
// Tests fix for #20432, ensures Array.from accepts all valid inputs
9+
// Also tests for #19682
10+
11+
interface A {
12+
a: string;
13+
}
14+
15+
interface B {
16+
b: string;
17+
}
18+
19+
const inputA: A[] = [];
20+
const inputB: B[] = [];
21+
const inputALike: ArrayLike<A> = { length: 0 };
22+
const inputARand = getEither(inputA, inputALike);
23+
24+
const result1: A[] = Array.from(inputA);
25+
const result2: A[] = Array.from(inputA.values());
26+
const result3: B[] = Array.from(inputA.values()); // expect error
27+
~~~~~~~
28+
!!! error TS2322: Type 'A[]' is not assignable to type 'B[]'.
29+
!!! error TS2322: Type 'A' is not assignable to type 'B'.
30+
!!! error TS2322: Property 'b' is missing in type 'A'.
31+
const result4: A[] = Array.from(inputB, ({ b }): A => ({ a: b }));
32+
const result5: A[] = Array.from(inputALike);
33+
const result6: B[] = Array.from(inputALike); // expect error
34+
~~~~~~~
35+
!!! error TS2322: Type 'A[]' is not assignable to type 'B[]'.
36+
const result7: B[] = Array.from(inputALike, ({ a }): B => ({ b: a }));
37+
const result8: A[] = Array.from(inputARand);
38+
const result9: B[] = Array.from(inputARand, ({ a }): B => ({ b: a }));
39+
40+
// if this is written inline, the compiler seems to infer
41+
// the ?: as always taking the false branch, narrowing to ArrayLike<T>,
42+
// even when the type is written as : Iterable<T>|ArrayLike<T>
43+
function getEither<T> (in1: Iterable<T>, in2: ArrayLike<T>) {
44+
return Math.random() > 0.5 ? in1 : in2;
45+
}
46+
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//// [arrayFrom.ts]
2+
// Tests fix for #20432, ensures Array.from accepts all valid inputs
3+
// Also tests for #19682
4+
5+
interface A {
6+
a: string;
7+
}
8+
9+
interface B {
10+
b: string;
11+
}
12+
13+
const inputA: A[] = [];
14+
const inputB: B[] = [];
15+
const inputALike: ArrayLike<A> = { length: 0 };
16+
const inputARand = getEither(inputA, inputALike);
17+
18+
const result1: A[] = Array.from(inputA);
19+
const result2: A[] = Array.from(inputA.values());
20+
const result3: B[] = Array.from(inputA.values()); // expect error
21+
const result4: A[] = Array.from(inputB, ({ b }): A => ({ a: b }));
22+
const result5: A[] = Array.from(inputALike);
23+
const result6: B[] = Array.from(inputALike); // expect error
24+
const result7: B[] = Array.from(inputALike, ({ a }): B => ({ b: a }));
25+
const result8: A[] = Array.from(inputARand);
26+
const result9: B[] = Array.from(inputARand, ({ a }): B => ({ b: a }));
27+
28+
// if this is written inline, the compiler seems to infer
29+
// the ?: as always taking the false branch, narrowing to ArrayLike<T>,
30+
// even when the type is written as : Iterable<T>|ArrayLike<T>
31+
function getEither<T> (in1: Iterable<T>, in2: ArrayLike<T>) {
32+
return Math.random() > 0.5 ? in1 : in2;
33+
}
34+
35+
36+
//// [arrayFrom.js]
37+
// Tests fix for #20432, ensures Array.from accepts all valid inputs
38+
// Also tests for #19682
39+
var inputA = [];
40+
var inputB = [];
41+
var inputALike = { length: 0 };
42+
var inputARand = getEither(inputA, inputALike);
43+
var result1 = Array.from(inputA);
44+
var result2 = Array.from(inputA.values());
45+
var result3 = Array.from(inputA.values()); // expect error
46+
var result4 = Array.from(inputB, function (_a) {
47+
var b = _a.b;
48+
return ({ a: b });
49+
});
50+
var result5 = Array.from(inputALike);
51+
var result6 = Array.from(inputALike); // expect error
52+
var result7 = Array.from(inputALike, function (_a) {
53+
var a = _a.a;
54+
return ({ b: a });
55+
});
56+
var result8 = Array.from(inputARand);
57+
var result9 = Array.from(inputARand, function (_a) {
58+
var a = _a.a;
59+
return ({ b: a });
60+
});
61+
// if this is written inline, the compiler seems to infer
62+
// the ?: as always taking the false branch, narrowing to ArrayLike<T>,
63+
// even when the type is written as : Iterable<T>|ArrayLike<T>
64+
function getEither(in1, in2) {
65+
return Math.random() > 0.5 ? in1 : in2;
66+
}
+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
=== tests/cases/compiler/arrayFrom.ts ===
2+
// Tests fix for #20432, ensures Array.from accepts all valid inputs
3+
// Also tests for #19682
4+
5+
interface A {
6+
>A : Symbol(A, Decl(arrayFrom.ts, 0, 0))
7+
8+
a: string;
9+
>a : Symbol(A.a, Decl(arrayFrom.ts, 3, 13))
10+
}
11+
12+
interface B {
13+
>B : Symbol(B, Decl(arrayFrom.ts, 5, 1))
14+
15+
b: string;
16+
>b : Symbol(B.b, Decl(arrayFrom.ts, 7, 13))
17+
}
18+
19+
const inputA: A[] = [];
20+
>inputA : Symbol(inputA, Decl(arrayFrom.ts, 11, 5))
21+
>A : Symbol(A, Decl(arrayFrom.ts, 0, 0))
22+
23+
const inputB: B[] = [];
24+
>inputB : Symbol(inputB, Decl(arrayFrom.ts, 12, 5))
25+
>B : Symbol(B, Decl(arrayFrom.ts, 5, 1))
26+
27+
const inputALike: ArrayLike<A> = { length: 0 };
28+
>inputALike : Symbol(inputALike, Decl(arrayFrom.ts, 13, 5))
29+
>ArrayLike : Symbol(ArrayLike, Decl(lib.es5.d.ts, --, --))
30+
>A : Symbol(A, Decl(arrayFrom.ts, 0, 0))
31+
>length : Symbol(length, Decl(arrayFrom.ts, 13, 34))
32+
33+
const inputARand = getEither(inputA, inputALike);
34+
>inputARand : Symbol(inputARand, Decl(arrayFrom.ts, 14, 5))
35+
>getEither : Symbol(getEither, Decl(arrayFrom.ts, 24, 70))
36+
>inputA : Symbol(inputA, Decl(arrayFrom.ts, 11, 5))
37+
>inputALike : Symbol(inputALike, Decl(arrayFrom.ts, 13, 5))
38+
39+
const result1: A[] = Array.from(inputA);
40+
>result1 : Symbol(result1, Decl(arrayFrom.ts, 16, 5))
41+
>A : Symbol(A, Decl(arrayFrom.ts, 0, 0))
42+
>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
43+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
44+
>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
45+
>inputA : Symbol(inputA, Decl(arrayFrom.ts, 11, 5))
46+
47+
const result2: A[] = Array.from(inputA.values());
48+
>result2 : Symbol(result2, Decl(arrayFrom.ts, 17, 5))
49+
>A : Symbol(A, Decl(arrayFrom.ts, 0, 0))
50+
>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
51+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
52+
>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
53+
>inputA.values : Symbol(Array.values, Decl(lib.es2015.iterable.d.ts, --, --))
54+
>inputA : Symbol(inputA, Decl(arrayFrom.ts, 11, 5))
55+
>values : Symbol(Array.values, Decl(lib.es2015.iterable.d.ts, --, --))
56+
57+
const result3: B[] = Array.from(inputA.values()); // expect error
58+
>result3 : Symbol(result3, Decl(arrayFrom.ts, 18, 5))
59+
>B : Symbol(B, Decl(arrayFrom.ts, 5, 1))
60+
>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
61+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
62+
>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
63+
>inputA.values : Symbol(Array.values, Decl(lib.es2015.iterable.d.ts, --, --))
64+
>inputA : Symbol(inputA, Decl(arrayFrom.ts, 11, 5))
65+
>values : Symbol(Array.values, Decl(lib.es2015.iterable.d.ts, --, --))
66+
67+
const result4: A[] = Array.from(inputB, ({ b }): A => ({ a: b }));
68+
>result4 : Symbol(result4, Decl(arrayFrom.ts, 19, 5))
69+
>A : Symbol(A, Decl(arrayFrom.ts, 0, 0))
70+
>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
71+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
72+
>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
73+
>inputB : Symbol(inputB, Decl(arrayFrom.ts, 12, 5))
74+
>b : Symbol(b, Decl(arrayFrom.ts, 19, 42))
75+
>A : Symbol(A, Decl(arrayFrom.ts, 0, 0))
76+
>a : Symbol(a, Decl(arrayFrom.ts, 19, 56))
77+
>b : Symbol(b, Decl(arrayFrom.ts, 19, 42))
78+
79+
const result5: A[] = Array.from(inputALike);
80+
>result5 : Symbol(result5, Decl(arrayFrom.ts, 20, 5))
81+
>A : Symbol(A, Decl(arrayFrom.ts, 0, 0))
82+
>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
83+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
84+
>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
85+
>inputALike : Symbol(inputALike, Decl(arrayFrom.ts, 13, 5))
86+
87+
const result6: B[] = Array.from(inputALike); // expect error
88+
>result6 : Symbol(result6, Decl(arrayFrom.ts, 21, 5))
89+
>B : Symbol(B, Decl(arrayFrom.ts, 5, 1))
90+
>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
91+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
92+
>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
93+
>inputALike : Symbol(inputALike, Decl(arrayFrom.ts, 13, 5))
94+
95+
const result7: B[] = Array.from(inputALike, ({ a }): B => ({ b: a }));
96+
>result7 : Symbol(result7, Decl(arrayFrom.ts, 22, 5))
97+
>B : Symbol(B, Decl(arrayFrom.ts, 5, 1))
98+
>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
99+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
100+
>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
101+
>inputALike : Symbol(inputALike, Decl(arrayFrom.ts, 13, 5))
102+
>a : Symbol(a, Decl(arrayFrom.ts, 22, 46))
103+
>B : Symbol(B, Decl(arrayFrom.ts, 5, 1))
104+
>b : Symbol(b, Decl(arrayFrom.ts, 22, 60))
105+
>a : Symbol(a, Decl(arrayFrom.ts, 22, 46))
106+
107+
const result8: A[] = Array.from(inputARand);
108+
>result8 : Symbol(result8, Decl(arrayFrom.ts, 23, 5))
109+
>A : Symbol(A, Decl(arrayFrom.ts, 0, 0))
110+
>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
111+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
112+
>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
113+
>inputARand : Symbol(inputARand, Decl(arrayFrom.ts, 14, 5))
114+
115+
const result9: B[] = Array.from(inputARand, ({ a }): B => ({ b: a }));
116+
>result9 : Symbol(result9, Decl(arrayFrom.ts, 24, 5))
117+
>B : Symbol(B, Decl(arrayFrom.ts, 5, 1))
118+
>Array.from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
119+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
120+
>from : Symbol(ArrayConstructor.from, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
121+
>inputARand : Symbol(inputARand, Decl(arrayFrom.ts, 14, 5))
122+
>a : Symbol(a, Decl(arrayFrom.ts, 24, 46))
123+
>B : Symbol(B, Decl(arrayFrom.ts, 5, 1))
124+
>b : Symbol(b, Decl(arrayFrom.ts, 24, 60))
125+
>a : Symbol(a, Decl(arrayFrom.ts, 24, 46))
126+
127+
// if this is written inline, the compiler seems to infer
128+
// the ?: as always taking the false branch, narrowing to ArrayLike<T>,
129+
// even when the type is written as : Iterable<T>|ArrayLike<T>
130+
function getEither<T> (in1: Iterable<T>, in2: ArrayLike<T>) {
131+
>getEither : Symbol(getEither, Decl(arrayFrom.ts, 24, 70))
132+
>T : Symbol(T, Decl(arrayFrom.ts, 29, 19))
133+
>in1 : Symbol(in1, Decl(arrayFrom.ts, 29, 23))
134+
>Iterable : Symbol(Iterable, Decl(lib.es2015.iterable.d.ts, --, --))
135+
>T : Symbol(T, Decl(arrayFrom.ts, 29, 19))
136+
>in2 : Symbol(in2, Decl(arrayFrom.ts, 29, 40))
137+
>ArrayLike : Symbol(ArrayLike, Decl(lib.es5.d.ts, --, --))
138+
>T : Symbol(T, Decl(arrayFrom.ts, 29, 19))
139+
140+
return Math.random() > 0.5 ? in1 : in2;
141+
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
142+
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
143+
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
144+
>in1 : Symbol(in1, Decl(arrayFrom.ts, 29, 23))
145+
>in2 : Symbol(in2, Decl(arrayFrom.ts, 29, 40))
146+
}
147+

0 commit comments

Comments
 (0)