-
Notifications
You must be signed in to change notification settings - Fork 13.1k
fix: [Symbol.iterator]() lost on union with never
#62661
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
ba03419
20bd02d
93fb716
a0a3bb4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45830,19 +45830,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
| * the `[Symbol.asyncIterator]()` method first, and then the `[Symbol.iterator]()` method. | ||
| */ | ||
| function getIterationTypesOfIterable(type: Type, use: IterationUse, errorNode: Node | undefined) { | ||
| if (type === silentNeverType) { | ||
| const reducedType = getReducedType(type); | ||
| if (reducedType === silentNeverType) { | ||
| return silentNeverIterationTypes; | ||
| } | ||
| if (isTypeAny(type)) { | ||
| if (isTypeAny(reducedType)) { | ||
| return anyIterationTypes; | ||
| } | ||
|
|
||
| if (!(type.flags & TypeFlags.Union)) { | ||
| if (!(reducedType.flags & TypeFlags.Union)) { | ||
| const errorOutputContainer: ErrorOutputContainer | undefined = errorNode ? { errors: undefined, skipLogging: true } : undefined; | ||
| const iterationTypes = getIterationTypesOfIterableWorker(type, use, errorNode, errorOutputContainer); | ||
| const iterationTypes = getIterationTypesOfIterableWorker(reducedType, use, errorNode, errorOutputContainer); | ||
| if (iterationTypes === noIterationTypes) { | ||
| if (errorNode) { | ||
| const rootDiag = reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); | ||
| const rootDiag = reportTypeNotIterableError(errorNode, reducedType, !!(use & IterationUse.AllowsAsyncIterablesFlag)); | ||
| if (errorOutputContainer?.errors) { | ||
| addRelatedInfo(rootDiag, ...errorOutputContainer.errors); | ||
| } | ||
|
|
@@ -45858,21 +45859,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
| } | ||
|
|
||
| const cacheKey = use & IterationUse.AllowsAsyncIterablesFlag ? "iterationTypesOfAsyncIterable" : "iterationTypesOfIterable"; | ||
| const cachedTypes = getCachedIterationTypes(type, cacheKey); | ||
| const cachedTypes = getCachedIterationTypes(reducedType, cacheKey); | ||
| if (cachedTypes) return cachedTypes === noIterationTypes ? undefined : cachedTypes; | ||
|
|
||
| let allIterationTypes: IterationTypes[] | undefined; | ||
| for (const constituent of (type as UnionType).types) { | ||
| for (const constituent of (reducedType as UnionType).types) { | ||
|
||
| const errorOutputContainer: ErrorOutputContainer | undefined = errorNode ? { errors: undefined } : undefined; | ||
| const iterationTypes = getIterationTypesOfIterableWorker(constituent, use, errorNode, errorOutputContainer); | ||
| if (iterationTypes === noIterationTypes) { | ||
| if (errorNode) { | ||
| const rootDiag = reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); | ||
| const rootDiag = reportTypeNotIterableError(errorNode, reducedType, !!(use & IterationUse.AllowsAsyncIterablesFlag)); | ||
| if (errorOutputContainer?.errors) { | ||
| addRelatedInfo(rootDiag, ...errorOutputContainer.errors); | ||
| } | ||
| } | ||
| setCachedIterationTypes(type, cacheKey, noIterationTypes); | ||
| setCachedIterationTypes(reducedType, cacheKey, noIterationTypes); | ||
| return undefined; | ||
| } | ||
| else if (errorOutputContainer?.errors?.length) { | ||
|
|
@@ -45885,7 +45886,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
| } | ||
|
|
||
| const iterationTypes = allIterationTypes ? combineIterationTypes(allIterationTypes) : noIterationTypes; | ||
| setCachedIterationTypes(type, cacheKey, iterationTypes); | ||
| setCachedIterationTypes(reducedType, cacheKey, iterationTypes); | ||
| return iterationTypes === noIterationTypes ? undefined : iterationTypes; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| //// [tests/cases/compiler/iterableWithNeverAsUnionMember.ts] //// | ||
|
|
||
| === iterableWithNeverAsUnionMember.ts === | ||
| declare const o1: { a: "foo" } & { a: "bar" }; | ||
| >o1 : Symbol(o1, Decl(iterableWithNeverAsUnionMember.ts, 0, 13)) | ||
| >a : Symbol(a, Decl(iterableWithNeverAsUnionMember.ts, 0, 19)) | ||
| >a : Symbol(a, Decl(iterableWithNeverAsUnionMember.ts, 0, 34)) | ||
|
|
||
| const [el1] = o1; // error | ||
| >el1 : Symbol(el1, Decl(iterableWithNeverAsUnionMember.ts, 1, 7)) | ||
| >o1 : Symbol(o1, Decl(iterableWithNeverAsUnionMember.ts, 0, 13)) | ||
|
|
||
| // https://github.com/microsoft/TypeScript/issues/62462 | ||
| declare var x: number[] | ({ t: "a" } & { t: "b" }); | ||
| >x : Symbol(x, Decl(iterableWithNeverAsUnionMember.ts, 4, 11)) | ||
| >t : Symbol(t, Decl(iterableWithNeverAsUnionMember.ts, 4, 28)) | ||
| >t : Symbol(t, Decl(iterableWithNeverAsUnionMember.ts, 4, 41)) | ||
|
|
||
| let [el2] = x; // ok | ||
| >el2 : Symbol(el2, Decl(iterableWithNeverAsUnionMember.ts, 5, 5)) | ||
| >x : Symbol(x, Decl(iterableWithNeverAsUnionMember.ts, 4, 11)) | ||
|
|
||
| for (const elem of x) { // ok | ||
| >elem : Symbol(elem, Decl(iterableWithNeverAsUnionMember.ts, 7, 10)) | ||
| >x : Symbol(x, Decl(iterableWithNeverAsUnionMember.ts, 4, 11)) | ||
|
|
||
| elem.toFixed(); | ||
| >elem.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) | ||
| >elem : Symbol(elem, Decl(iterableWithNeverAsUnionMember.ts, 7, 10)) | ||
| >toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) | ||
| } | ||
|
|
||
| type Shape = | ||
| >Shape : Symbol(Shape, Decl(iterableWithNeverAsUnionMember.ts, 9, 1)) | ||
|
|
||
| | { kind: "circle"; radius: number } | ||
| >kind : Symbol(kind, Decl(iterableWithNeverAsUnionMember.ts, 12, 5)) | ||
| >radius : Symbol(radius, Decl(iterableWithNeverAsUnionMember.ts, 12, 21)) | ||
|
|
||
| | { kind: "rectangle"; width: number; height: number }; | ||
| >kind : Symbol(kind, Decl(iterableWithNeverAsUnionMember.ts, 13, 5)) | ||
| >width : Symbol(width, Decl(iterableWithNeverAsUnionMember.ts, 13, 24)) | ||
| >height : Symbol(height, Decl(iterableWithNeverAsUnionMember.ts, 13, 39)) | ||
|
|
||
| type Circle = Shape & { kind: "circle" }; | ||
| >Circle : Symbol(Circle, Decl(iterableWithNeverAsUnionMember.ts, 13, 57)) | ||
| >Shape : Symbol(Shape, Decl(iterableWithNeverAsUnionMember.ts, 9, 1)) | ||
| >kind : Symbol(kind, Decl(iterableWithNeverAsUnionMember.ts, 15, 23)) | ||
|
|
||
| function doStuffWithCircle(arg: Circle | [Circle, (newValue: Circle) => void]) { | ||
| >doStuffWithCircle : Symbol(doStuffWithCircle, Decl(iterableWithNeverAsUnionMember.ts, 15, 41)) | ||
| >arg : Symbol(arg, Decl(iterableWithNeverAsUnionMember.ts, 17, 27)) | ||
| >Circle : Symbol(Circle, Decl(iterableWithNeverAsUnionMember.ts, 13, 57)) | ||
| >Circle : Symbol(Circle, Decl(iterableWithNeverAsUnionMember.ts, 13, 57)) | ||
| >newValue : Symbol(newValue, Decl(iterableWithNeverAsUnionMember.ts, 17, 51)) | ||
| >Circle : Symbol(Circle, Decl(iterableWithNeverAsUnionMember.ts, 13, 57)) | ||
|
|
||
| if (Array.isArray(arg)) { | ||
| >Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) | ||
| >Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) | ||
| >isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) | ||
| >arg : Symbol(arg, Decl(iterableWithNeverAsUnionMember.ts, 17, 27)) | ||
|
|
||
| let [value, setValue] = arg; // ok | ||
| >value : Symbol(value, Decl(iterableWithNeverAsUnionMember.ts, 19, 9)) | ||
| >setValue : Symbol(setValue, Decl(iterableWithNeverAsUnionMember.ts, 19, 15)) | ||
| >arg : Symbol(arg, Decl(iterableWithNeverAsUnionMember.ts, 17, 27)) | ||
| } | ||
| } | ||
|
|
||
| function f1<T extends { a: "foo" } & { a: "bar" }>(x: T) { | ||
| >f1 : Symbol(f1, Decl(iterableWithNeverAsUnionMember.ts, 21, 1)) | ||
| >T : Symbol(T, Decl(iterableWithNeverAsUnionMember.ts, 23, 12)) | ||
| >a : Symbol(a, Decl(iterableWithNeverAsUnionMember.ts, 23, 23)) | ||
| >a : Symbol(a, Decl(iterableWithNeverAsUnionMember.ts, 23, 38)) | ||
| >x : Symbol(x, Decl(iterableWithNeverAsUnionMember.ts, 23, 51)) | ||
| >T : Symbol(T, Decl(iterableWithNeverAsUnionMember.ts, 23, 12)) | ||
|
|
||
| let [y] = x; // error | ||
| >y : Symbol(y, Decl(iterableWithNeverAsUnionMember.ts, 24, 7)) | ||
| >x : Symbol(x, Decl(iterableWithNeverAsUnionMember.ts, 23, 51)) | ||
| } | ||
|
|
||
| declare const o2: ({ a: "foo" } & { a: "bar" }) | ({ b: "qwe" } & { b: "rty" }); | ||
| >o2 : Symbol(o2, Decl(iterableWithNeverAsUnionMember.ts, 27, 13)) | ||
| >a : Symbol(a, Decl(iterableWithNeverAsUnionMember.ts, 27, 20)) | ||
| >a : Symbol(a, Decl(iterableWithNeverAsUnionMember.ts, 27, 35)) | ||
| >b : Symbol(b, Decl(iterableWithNeverAsUnionMember.ts, 27, 52)) | ||
| >b : Symbol(b, Decl(iterableWithNeverAsUnionMember.ts, 27, 67)) | ||
|
|
||
| const [el3] = o2; // error | ||
| >el3 : Symbol(el3, Decl(iterableWithNeverAsUnionMember.ts, 28, 7)) | ||
| >o2 : Symbol(o2, Decl(iterableWithNeverAsUnionMember.ts, 27, 13)) | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| //// [tests/cases/compiler/iterableWithNeverAsUnionMember.ts] //// | ||
|
|
||
| === iterableWithNeverAsUnionMember.ts === | ||
| declare const o1: { a: "foo" } & { a: "bar" }; | ||
| >o1 : never | ||
| > : ^^^^^ | ||
| >a : "foo" | ||
| > : ^^^^^ | ||
| >a : "bar" | ||
| > : ^^^^^ | ||
|
|
||
| const [el1] = o1; // error | ||
| >el1 : never | ||
| > : ^^^^^ | ||
| >o1 : never | ||
| > : ^^^^^ | ||
|
|
||
| // https://github.com/microsoft/TypeScript/issues/62462 | ||
| declare var x: number[] | ({ t: "a" } & { t: "b" }); | ||
| >x : number[] | ||
| > : ^^^^^^^^ | ||
| >t : "a" | ||
| > : ^^^ | ||
| >t : "b" | ||
| > : ^^^ | ||
|
|
||
| let [el2] = x; // ok | ||
| >el2 : number | ||
| > : ^^^^^^ | ||
| >x : number[] | ||
| > : ^^^^^^^^ | ||
|
|
||
| for (const elem of x) { // ok | ||
| >elem : number | ||
| > : ^^^^^^ | ||
| >x : number[] | ||
| > : ^^^^^^^^ | ||
|
|
||
| elem.toFixed(); | ||
| >elem.toFixed() : string | ||
| > : ^^^^^^ | ||
| >elem.toFixed : (fractionDigits?: number) => string | ||
| > : ^ ^^^ ^^^^^ | ||
| >elem : number | ||
| > : ^^^^^^ | ||
| >toFixed : (fractionDigits?: number) => string | ||
| > : ^ ^^^ ^^^^^ | ||
| } | ||
|
|
||
| type Shape = | ||
| >Shape : Shape | ||
| > : ^^^^^ | ||
|
|
||
| | { kind: "circle"; radius: number } | ||
| >kind : "circle" | ||
| > : ^^^^^^^^ | ||
| >radius : number | ||
| > : ^^^^^^ | ||
|
|
||
| | { kind: "rectangle"; width: number; height: number }; | ||
| >kind : "rectangle" | ||
| > : ^^^^^^^^^^^ | ||
| >width : number | ||
| > : ^^^^^^ | ||
| >height : number | ||
| > : ^^^^^^ | ||
|
|
||
| type Circle = Shape & { kind: "circle" }; | ||
| >Circle : { kind: "circle"; radius: number; } & { kind: "circle"; } | ||
| > : ^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^ | ||
| >kind : "circle" | ||
| > : ^^^^^^^^ | ||
|
|
||
| function doStuffWithCircle(arg: Circle | [Circle, (newValue: Circle) => void]) { | ||
| >doStuffWithCircle : (arg: Circle | [Circle, (newValue: Circle) => void]) => void | ||
| > : ^ ^^ ^^^^^^^^^ | ||
| >arg : ({ kind: "circle"; radius: number; } & { kind: "circle"; }) | [{ kind: "circle"; radius: number; } & { kind: "circle"; }, (newValue: Circle) => void] | ||
| > : ^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^ ^^ ^^^^^ ^ | ||
| >newValue : { kind: "circle"; radius: number; } & { kind: "circle"; } | ||
| > : ^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^ | ||
|
|
||
| if (Array.isArray(arg)) { | ||
| >Array.isArray(arg) : boolean | ||
| > : ^^^^^^^ | ||
| >Array.isArray : (arg: any) => arg is any[] | ||
| > : ^ ^^ ^^^^^ | ||
| >Array : ArrayConstructor | ||
| > : ^^^^^^^^^^^^^^^^ | ||
| >isArray : (arg: any) => arg is any[] | ||
| > : ^ ^^ ^^^^^ | ||
| >arg : ({ kind: "circle"; radius: number; } & { kind: "circle"; }) | [{ kind: "circle"; radius: number; } & { kind: "circle"; }, (newValue: Circle) => void] | ||
| > : ^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^ ^^ ^^^^^ ^ | ||
|
|
||
| let [value, setValue] = arg; // ok | ||
| >value : { kind: "circle"; radius: number; } & { kind: "circle"; } | ||
| > : ^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^ | ||
| >setValue : (newValue: Circle) => void | ||
| > : ^ ^^ ^^^^^ | ||
| >arg : [{ kind: "circle"; radius: number; } & { kind: "circle"; }, (newValue: Circle) => void] | ||
| > : ^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^ ^^ ^^^^^ ^ | ||
| } | ||
| } | ||
|
|
||
| function f1<T extends { a: "foo" } & { a: "bar" }>(x: T) { | ||
| >f1 : <T extends { a: "foo"; } & { a: "bar"; }>(x: T) => void | ||
| > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^ | ||
| >a : "foo" | ||
| > : ^^^^^ | ||
| >a : "bar" | ||
| > : ^^^^^ | ||
| >x : T | ||
| > : ^ | ||
|
|
||
| let [y] = x; // error | ||
| >y : never | ||
| > : ^^^^^ | ||
| >x : T | ||
| > : ^ | ||
| } | ||
|
|
||
| declare const o2: ({ a: "foo" } & { a: "bar" }) | ({ b: "qwe" } & { b: "rty" }); | ||
| >o2 : never | ||
| > : ^^^^^ | ||
| >a : "foo" | ||
| > : ^^^^^ | ||
| >a : "bar" | ||
| > : ^^^^^ | ||
| >b : "qwe" | ||
| > : ^^^^^ | ||
| >b : "rty" | ||
| > : ^^^^^ | ||
|
|
||
| const [el3] = o2; // error | ||
| >el3 : never | ||
| > : ^^^^^ | ||
| >o2 : never | ||
| > : ^^^^^ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| iterableWithNeverAsUnionMember.ts(2,7): error TS2488: Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator. | ||
| iterableWithNeverAsUnionMember.ts(25,7): error TS2488: Type 'T' must have a '[Symbol.iterator]()' method that returns an iterator. | ||
| iterableWithNeverAsUnionMember.ts(29,7): error TS2488: Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator. | ||
|
|
||
|
|
||
| ==== iterableWithNeverAsUnionMember.ts (3 errors) ==== | ||
| declare const o1: { a: "foo" } & { a: "bar" }; | ||
| const [el1] = o1; // error | ||
| ~~~~~ | ||
| !!! error TS2488: Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator. | ||
|
|
||
| // https://github.com/microsoft/TypeScript/issues/62462 | ||
| declare var x: number[] | ({ t: "a" } & { t: "b" }); | ||
| let [el2] = x; // ok | ||
|
|
||
| for (const elem of x) { // ok | ||
| elem.toFixed(); | ||
| } | ||
|
|
||
| type Shape = | ||
| | { kind: "circle"; radius: number } | ||
| | { kind: "rectangle"; width: number; height: number }; | ||
|
|
||
| type Circle = Shape & { kind: "circle" }; | ||
|
|
||
| function doStuffWithCircle(arg: Circle | [Circle, (newValue: Circle) => void]) { | ||
| if (Array.isArray(arg)) { | ||
| let [value, setValue] = arg; // ok | ||
| } | ||
| } | ||
|
|
||
| function f1<T extends { a: "foo" } & { a: "bar" }>(x: T) { | ||
| let [y] = x; // error | ||
| ~~~ | ||
| !!! error TS2488: Type 'T' must have a '[Symbol.iterator]()' method that returns an iterator. | ||
| } | ||
|
|
||
| declare const o2: ({ a: "foo" } & { a: "bar" }) | ({ b: "qwe" } & { b: "rty" }); | ||
| const [el3] = o2; // error | ||
| ~~~~~ | ||
| !!! error TS2488: Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just
type = getReducedType(type)?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done:
a0a3bb4(#62661)