Skip to content

Commit 083129f

Browse files
authored
A union including non-iterable types is not iterable (#40350)
* WIP * If method type derives solely from the global iterator or generator type, use its type arguments * Add test for problem fixed as side effect
1 parent 4a3b195 commit 083129f

12 files changed

+155
-7
lines changed

src/compiler/checker.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33881,8 +33881,9 @@ namespace ts {
3388133881
if (iterationTypes === noIterationTypes) {
3388233882
if (errorNode) {
3388333883
reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag));
33884-
errorNode = undefined;
3388533884
}
33885+
setCachedIterationTypes(type, cacheKey, noIterationTypes);
33886+
return undefined;
3388633887
}
3388733888
else {
3388833889
allIterationTypes = append(allIterationTypes, iterationTypes);
@@ -34224,6 +34225,28 @@ namespace ts {
3422434225
return methodName === "next" ? anyIterationTypes : undefined;
3422534226
}
3422634227

34228+
// If the method signature comes exclusively from the global iterator or generator type,
34229+
// create iteration types from its type arguments like `getIterationTypesOfIteratorFast`
34230+
// does (so as to remove `undefined` from the next and return types). We arrive here when
34231+
// a contextual type for a generator was not a direct reference to one of those global types,
34232+
// but looking up `methodType` referred to one of them (and nothing else). E.g., in
34233+
// `interface SpecialIterator extends Iterator<number> {}`, `SpecialIterator` is not a
34234+
// reference to `Iterator`, but its `next` member derives exclusively from `Iterator`.
34235+
if (methodType?.symbol && methodSignatures.length === 1) {
34236+
const globalGeneratorType = resolver.getGlobalGeneratorType(/*reportErrors*/ false);
34237+
const globalIteratorType = resolver.getGlobalIteratorType(/*reportErrors*/ false);
34238+
const isGeneratorMethod = globalGeneratorType.symbol?.members?.get(methodName as __String) === methodType.symbol;
34239+
const isIteratorMethod = !isGeneratorMethod && globalIteratorType.symbol?.members?.get(methodName as __String) === methodType.symbol;
34240+
if (isGeneratorMethod || isIteratorMethod) {
34241+
const globalType = isGeneratorMethod ? globalGeneratorType : globalIteratorType;
34242+
const { mapper } = methodType as AnonymousType;
34243+
return createIterationTypes(
34244+
getMappedType(globalType.typeParameters![0], mapper!),
34245+
getMappedType(globalType.typeParameters![1], mapper!),
34246+
methodName === "next" ? getMappedType(globalType.typeParameters![2], mapper!) : undefined);
34247+
}
34248+
}
34249+
3422734250
// Extract the first parameter and return type of each signature.
3422834251
let methodParameterTypes: Type[] | undefined;
3422934252
let methodReturnTypes: Type[] | undefined;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment4.ts(5,7): error TS2548: Type 'number[] | null' is not an array type or does not have a '[Symbol.iterator]()' method that returns an iterator.
2+
3+
4+
==== tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment4.ts (1 errors) ====
5+
// #35497
6+
7+
8+
declare const data: number[] | null;
9+
const [value] = data; // Error
10+
~~~~~~~
11+
!!! error TS2548: Type 'number[] | null' is not an array type or does not have a '[Symbol.iterator]()' method that returns an iterator.
12+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//// [destructuringArrayBindingPatternAndAssignment4.ts]
2+
// #35497
3+
4+
5+
declare const data: number[] | null;
6+
const [value] = data; // Error
7+
8+
9+
//// [destructuringArrayBindingPatternAndAssignment4.js]
10+
"use strict";
11+
// #35497
12+
var __read = (this && this.__read) || function (o, n) {
13+
var m = typeof Symbol === "function" && o[Symbol.iterator];
14+
if (!m) return o;
15+
var i = m.call(o), r, ar = [], e;
16+
try {
17+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
18+
}
19+
catch (error) { e = { error: error }; }
20+
finally {
21+
try {
22+
if (r && !r.done && (m = i["return"])) m.call(i);
23+
}
24+
finally { if (e) throw e.error; }
25+
}
26+
return ar;
27+
};
28+
var _a = __read(data, 1), value = _a[0]; // Error
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
=== tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment4.ts ===
2+
// #35497
3+
4+
5+
declare const data: number[] | null;
6+
>data : Symbol(data, Decl(destructuringArrayBindingPatternAndAssignment4.ts, 3, 13))
7+
8+
const [value] = data; // Error
9+
>value : Symbol(value, Decl(destructuringArrayBindingPatternAndAssignment4.ts, 4, 7))
10+
>data : Symbol(data, Decl(destructuringArrayBindingPatternAndAssignment4.ts, 3, 13))
11+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
=== tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment4.ts ===
2+
// #35497
3+
4+
5+
declare const data: number[] | null;
6+
>data : number[] | null
7+
>null : null
8+
9+
const [value] = data; // Error
10+
>value : any
11+
>data : number[] | null
12+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//// [generatorReturnTypeIndirectReferenceToGlobalType.ts]
2+
interface I1 extends Iterator<0, 1, 2> {}
3+
4+
function* f1(): I1 {
5+
const a = yield 0;
6+
return 1;
7+
}
8+
9+
10+
//// [generatorReturnTypeIndirectReferenceToGlobalType.js]
11+
"use strict";
12+
function* f1() {
13+
const a = yield 0;
14+
return 1;
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
=== tests/cases/conformance/generators/generatorReturnTypeIndirectReferenceToGlobalType.ts ===
2+
interface I1 extends Iterator<0, 1, 2> {}
3+
>I1 : Symbol(I1, Decl(generatorReturnTypeIndirectReferenceToGlobalType.ts, 0, 0))
4+
>Iterator : Symbol(Iterator, Decl(lib.es2015.iterable.d.ts, --, --))
5+
6+
function* f1(): I1 {
7+
>f1 : Symbol(f1, Decl(generatorReturnTypeIndirectReferenceToGlobalType.ts, 0, 41))
8+
>I1 : Symbol(I1, Decl(generatorReturnTypeIndirectReferenceToGlobalType.ts, 0, 0))
9+
10+
const a = yield 0;
11+
>a : Symbol(a, Decl(generatorReturnTypeIndirectReferenceToGlobalType.ts, 3, 7))
12+
13+
return 1;
14+
}
15+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
=== tests/cases/conformance/generators/generatorReturnTypeIndirectReferenceToGlobalType.ts ===
2+
interface I1 extends Iterator<0, 1, 2> {}
3+
4+
function* f1(): I1 {
5+
>f1 : () => I1
6+
7+
const a = yield 0;
8+
>a : 2
9+
>yield 0 : 2
10+
>0 : 0
11+
12+
return 1;
13+
>1 : 1
14+
}
15+

tests/baselines/reference/generatorYieldContextualType.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ declare function f2<T, R, S>(gen: () => Generator<R, T, S> | AsyncGenerator<R, T
2525
f2<0, 0, 1>(async function* () {
2626
>f2<0, 0, 1>(async function* () { const a = yield 0; return 0;}) : void
2727
>f2 : <T, R, S>(gen: () => Generator<R, T, S> | AsyncGenerator<R, T, S>) => void
28-
>async function* () { const a = yield 0; return 0;} : () => AsyncGenerator<0, 0, 1>
28+
>async function* () { const a = yield 0; return 0;} : () => AsyncGenerator<0, 0, 1 | undefined>
2929

3030
const a = yield 0;
31-
>a : 1
32-
>yield 0 : 1
31+
>a : 1 | undefined
32+
>yield 0 : 1 | undefined
3333
>0 : 0
3434

3535
return 0;

tests/baselines/reference/types.asyncGenerators.es2018.2.errors.txt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ tests/cases/conformance/types/asyncGenerators/types.asyncGenerators.es2018.2.ts(
4444
tests/cases/conformance/types/asyncGenerators/types.asyncGenerators.es2018.2.ts(70,42): error TS2322: Type 'AsyncGenerator<number, any, undefined>' is not assignable to type 'Iterator<number, any, undefined>'.
4545
The types returned by 'next(...)' are incompatible between these types.
4646
Type 'Promise<IteratorResult<number, any>>' is not assignable to type 'IteratorResult<number, any>'.
47-
Property 'value' is missing in type 'Promise<IteratorResult<number, any>>' but required in type 'IteratorYieldResult<number>'.
47+
Type 'Promise<IteratorResult<number, any>>' is missing the following properties from type 'IteratorReturnResult<any>': done, value
4848
tests/cases/conformance/types/asyncGenerators/types.asyncGenerators.es2018.2.ts(74,12): error TS2504: Type '{}' must have a '[Symbol.asyncIterator]()' method that returns an async iterator.
4949

5050

@@ -191,8 +191,7 @@ tests/cases/conformance/types/asyncGenerators/types.asyncGenerators.es2018.2.ts(
191191
!!! error TS2322: Type 'AsyncGenerator<number, any, undefined>' is not assignable to type 'Iterator<number, any, undefined>'.
192192
!!! error TS2322: The types returned by 'next(...)' are incompatible between these types.
193193
!!! error TS2322: Type 'Promise<IteratorResult<number, any>>' is not assignable to type 'IteratorResult<number, any>'.
194-
!!! error TS2322: Property 'value' is missing in type 'Promise<IteratorResult<number, any>>' but required in type 'IteratorYieldResult<number>'.
195-
!!! related TS2728 /.ts/lib.es2015.iterable.d.ts:33:5: 'value' is declared here.
194+
!!! error TS2322: Type 'Promise<IteratorResult<number, any>>' is missing the following properties from type 'IteratorReturnResult<any>': done, value
196195
yield 1;
197196
}
198197
async function * yieldStar() {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// #35497
2+
3+
// @target: es5
4+
// @downlevelIteration: true
5+
// @lib: es6
6+
// @strict: true
7+
8+
declare const data: number[] | null;
9+
const [value] = data; // Error
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// @strict: true
2+
// @target: esnext
3+
4+
interface I1 extends Iterator<0, 1, 2> {}
5+
6+
function* f1(): I1 {
7+
const a = yield 0;
8+
return 1;
9+
}

0 commit comments

Comments
 (0)