From 1d7107a45b369ccb33debe021901cc5814b93464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 26 Oct 2025 10:26:21 +0100 Subject: [PATCH 1/2] Fixed an issue with "slow" sync iteration types spoiling cached value for async ones --- src/compiler/checker.ts | 19 ++- .../forAwaitForIntersection1.symbols | 119 ++++++++++++++++++ .../reference/forAwaitForIntersection1.types | 115 +++++++++++++++++ .../compiler/forAwaitForIntersection1.ts | 45 +++++++ 4 files changed, 286 insertions(+), 12 deletions(-) create mode 100644 tests/baselines/reference/forAwaitForIntersection1.symbols create mode 100644 tests/baselines/reference/forAwaitForIntersection1.types create mode 100644 tests/cases/compiler/forAwaitForIntersection1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1740df24a15c6..be4ab2511a4a7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -45924,7 +45924,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let noCache = false; if (use & IterationUse.AllowsAsyncIterablesFlag) { - const iterationTypes = getIterationTypesOfIterableCached(type, asyncIterationTypesResolver) || + let iterationTypes = getIterationTypesOfIterableCached(type, asyncIterationTypesResolver) || getIterationTypesOfIterableFast(type, asyncIterationTypesResolver); if (iterationTypes) { if (iterationTypes === noIterationTypes && errorNode) { @@ -45937,6 +45937,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { iterationTypes; } } + iterationTypes = getIterationTypesOfIterableSlow(type, asyncIterationTypesResolver, errorNode, errorOutputContainer, noCache); + if (iterationTypes !== noIterationTypes) { + return iterationTypes; + } } if (use & IterationUse.AllowsSyncIterablesFlag) { @@ -45960,17 +45964,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } - } - - if (use & IterationUse.AllowsAsyncIterablesFlag) { - const iterationTypes = getIterationTypesOfIterableSlow(type, asyncIterationTypesResolver, errorNode, errorOutputContainer, noCache); - if (iterationTypes !== noIterationTypes) { - return iterationTypes; - } - } - - if (use & IterationUse.AllowsSyncIterablesFlag) { - let iterationTypes = getIterationTypesOfIterableSlow(type, syncIterationTypesResolver, errorNode, errorOutputContainer, noCache); + + iterationTypes = getIterationTypesOfIterableSlow(type, syncIterationTypesResolver, errorNode, errorOutputContainer, noCache); if (iterationTypes !== noIterationTypes) { if (use & IterationUse.AllowsAsyncIterablesFlag) { iterationTypes = getAsyncFromSyncIterationTypes(iterationTypes, errorNode); diff --git a/tests/baselines/reference/forAwaitForIntersection1.symbols b/tests/baselines/reference/forAwaitForIntersection1.symbols new file mode 100644 index 0000000000000..a4e1068332b30 --- /dev/null +++ b/tests/baselines/reference/forAwaitForIntersection1.symbols @@ -0,0 +1,119 @@ +//// [tests/cases/compiler/forAwaitForIntersection1.ts] //// + +=== forAwaitForIntersection1.ts === +type Stream1 = Iterable & AsyncIterable; +>Stream1 : Symbol(Stream1, Decl(forAwaitForIntersection1.ts, 0, 0)) +>T_Sync : Symbol(T_Sync, Decl(forAwaitForIntersection1.ts, 0, 13)) +>T_Async : Symbol(T_Async, Decl(forAwaitForIntersection1.ts, 0, 20)) +>Iterable : Symbol(Iterable, Decl(lib.es2015.iterable.d.ts, --, --)) +>T_Sync : Symbol(T_Sync, Decl(forAwaitForIntersection1.ts, 0, 13)) +>AsyncIterable : Symbol(AsyncIterable, Decl(lib.es2018.asynciterable.d.ts, --, --)) +>T_Async : Symbol(T_Async, Decl(forAwaitForIntersection1.ts, 0, 20)) + +class A1 {} +>A1 : Symbol(A1, Decl(forAwaitForIntersection1.ts, 0, 74)) + +class B1 {} +>B1 : Symbol(B1, Decl(forAwaitForIntersection1.ts, 2, 11)) + +async function loop1(stream: Stream1) { +>loop1 : Symbol(loop1, Decl(forAwaitForIntersection1.ts, 3, 11)) +>stream : Symbol(stream, Decl(forAwaitForIntersection1.ts, 5, 21)) +>Stream1 : Symbol(Stream1, Decl(forAwaitForIntersection1.ts, 0, 0)) +>A1 : Symbol(A1, Decl(forAwaitForIntersection1.ts, 0, 74)) +>B1 : Symbol(B1, Decl(forAwaitForIntersection1.ts, 2, 11)) + + for await (const b of stream) {} +>b : Symbol(b, Decl(forAwaitForIntersection1.ts, 6, 18)) +>stream : Symbol(stream, Decl(forAwaitForIntersection1.ts, 5, 21)) +} + +type Stream2 = Iterable & AsyncIterable; +>Stream2 : Symbol(Stream2, Decl(forAwaitForIntersection1.ts, 7, 1)) +>T_Sync : Symbol(T_Sync, Decl(forAwaitForIntersection1.ts, 9, 13)) +>T_Async : Symbol(T_Async, Decl(forAwaitForIntersection1.ts, 9, 20)) +>Iterable : Symbol(Iterable, Decl(lib.es2015.iterable.d.ts, --, --)) +>T_Sync : Symbol(T_Sync, Decl(forAwaitForIntersection1.ts, 9, 13)) +>AsyncIterable : Symbol(AsyncIterable, Decl(lib.es2018.asynciterable.d.ts, --, --)) +>T_Async : Symbol(T_Async, Decl(forAwaitForIntersection1.ts, 9, 20)) + +class A2 {} +>A2 : Symbol(A2, Decl(forAwaitForIntersection1.ts, 9, 74)) + +class B2 {} +>B2 : Symbol(B2, Decl(forAwaitForIntersection1.ts, 11, 11)) + +async function loop2(stream: Stream2) { +>loop2 : Symbol(loop2, Decl(forAwaitForIntersection1.ts, 12, 11)) +>stream : Symbol(stream, Decl(forAwaitForIntersection1.ts, 14, 21)) +>Stream2 : Symbol(Stream2, Decl(forAwaitForIntersection1.ts, 7, 1)) +>A2 : Symbol(A2, Decl(forAwaitForIntersection1.ts, 9, 74)) +>B2 : Symbol(B2, Decl(forAwaitForIntersection1.ts, 11, 11)) + + for (const a of stream) {} +>a : Symbol(a, Decl(forAwaitForIntersection1.ts, 15, 12)) +>stream : Symbol(stream, Decl(forAwaitForIntersection1.ts, 14, 21)) +} + +type Stream3 = Iterable & AsyncIterable; +>Stream3 : Symbol(Stream3, Decl(forAwaitForIntersection1.ts, 16, 1)) +>T_Sync : Symbol(T_Sync, Decl(forAwaitForIntersection1.ts, 18, 13)) +>T_Async : Symbol(T_Async, Decl(forAwaitForIntersection1.ts, 18, 20)) +>Iterable : Symbol(Iterable, Decl(lib.es2015.iterable.d.ts, --, --)) +>T_Sync : Symbol(T_Sync, Decl(forAwaitForIntersection1.ts, 18, 13)) +>AsyncIterable : Symbol(AsyncIterable, Decl(lib.es2018.asynciterable.d.ts, --, --)) +>T_Async : Symbol(T_Async, Decl(forAwaitForIntersection1.ts, 18, 20)) + +class A3 {} +>A3 : Symbol(A3, Decl(forAwaitForIntersection1.ts, 18, 74)) + +class B3 {} +>B3 : Symbol(B3, Decl(forAwaitForIntersection1.ts, 20, 11)) + +async function loop3(stream: Stream3) { +>loop3 : Symbol(loop3, Decl(forAwaitForIntersection1.ts, 21, 11)) +>stream : Symbol(stream, Decl(forAwaitForIntersection1.ts, 23, 21)) +>Stream3 : Symbol(Stream3, Decl(forAwaitForIntersection1.ts, 16, 1)) +>A3 : Symbol(A3, Decl(forAwaitForIntersection1.ts, 18, 74)) +>B3 : Symbol(B3, Decl(forAwaitForIntersection1.ts, 20, 11)) + + for await (const b of stream) {} +>b : Symbol(b, Decl(forAwaitForIntersection1.ts, 24, 18)) +>stream : Symbol(stream, Decl(forAwaitForIntersection1.ts, 23, 21)) + + for (const a of stream) {} +>a : Symbol(a, Decl(forAwaitForIntersection1.ts, 26, 12)) +>stream : Symbol(stream, Decl(forAwaitForIntersection1.ts, 23, 21)) +} + +type Stream4 = Iterable & AsyncIterable; +>Stream4 : Symbol(Stream4, Decl(forAwaitForIntersection1.ts, 27, 1)) +>T_Sync : Symbol(T_Sync, Decl(forAwaitForIntersection1.ts, 29, 13)) +>T_Async : Symbol(T_Async, Decl(forAwaitForIntersection1.ts, 29, 20)) +>Iterable : Symbol(Iterable, Decl(lib.es2015.iterable.d.ts, --, --)) +>T_Sync : Symbol(T_Sync, Decl(forAwaitForIntersection1.ts, 29, 13)) +>AsyncIterable : Symbol(AsyncIterable, Decl(lib.es2018.asynciterable.d.ts, --, --)) +>T_Async : Symbol(T_Async, Decl(forAwaitForIntersection1.ts, 29, 20)) + +class A4 {} +>A4 : Symbol(A4, Decl(forAwaitForIntersection1.ts, 29, 74)) + +class B4 {} +>B4 : Symbol(B4, Decl(forAwaitForIntersection1.ts, 31, 11)) + +// verify that resolving sync iteration first doesn't spoil the type for async iteration +async function loop4(stream: Stream4) { +>loop4 : Symbol(loop4, Decl(forAwaitForIntersection1.ts, 32, 11)) +>stream : Symbol(stream, Decl(forAwaitForIntersection1.ts, 35, 21)) +>Stream4 : Symbol(Stream4, Decl(forAwaitForIntersection1.ts, 27, 1)) +>A4 : Symbol(A4, Decl(forAwaitForIntersection1.ts, 29, 74)) +>B4 : Symbol(B4, Decl(forAwaitForIntersection1.ts, 31, 11)) + + for (const a of stream) {} +>a : Symbol(a, Decl(forAwaitForIntersection1.ts, 36, 12)) +>stream : Symbol(stream, Decl(forAwaitForIntersection1.ts, 35, 21)) + + for await (const b of stream) {} +>b : Symbol(b, Decl(forAwaitForIntersection1.ts, 38, 18)) +>stream : Symbol(stream, Decl(forAwaitForIntersection1.ts, 35, 21)) +} diff --git a/tests/baselines/reference/forAwaitForIntersection1.types b/tests/baselines/reference/forAwaitForIntersection1.types new file mode 100644 index 0000000000000..747795ec5b380 --- /dev/null +++ b/tests/baselines/reference/forAwaitForIntersection1.types @@ -0,0 +1,115 @@ +//// [tests/cases/compiler/forAwaitForIntersection1.ts] //// + +=== forAwaitForIntersection1.ts === +type Stream1 = Iterable & AsyncIterable; +>Stream1 : Stream1 +> : ^^^^^^^^^^^^^^^^^^^^^^^^ + +class A1 {} +>A1 : A1 +> : ^^ + +class B1 {} +>B1 : B1 +> : ^^ + +async function loop1(stream: Stream1) { +>loop1 : (stream: Stream1) => Promise +> : ^ ^^ ^^^^^^^^^^^^^^^^^^ +>stream : Stream1 +> : ^^^^^^^^^^^^^^^ + + for await (const b of stream) {} +>b : B1 +> : ^^ +>stream : Stream1 +> : ^^^^^^^^^^^^^^^ +} + +type Stream2 = Iterable & AsyncIterable; +>Stream2 : Stream2 +> : ^^^^^^^^^^^^^^^^^^^^^^^^ + +class A2 {} +>A2 : A2 +> : ^^ + +class B2 {} +>B2 : B2 +> : ^^ + +async function loop2(stream: Stream2) { +>loop2 : (stream: Stream2) => Promise +> : ^ ^^ ^^^^^^^^^^^^^^^^^^ +>stream : Stream2 +> : ^^^^^^^^^^^^^^^ + + for (const a of stream) {} +>a : A2 +> : ^^ +>stream : Stream2 +> : ^^^^^^^^^^^^^^^ +} + +type Stream3 = Iterable & AsyncIterable; +>Stream3 : Stream3 +> : ^^^^^^^^^^^^^^^^^^^^^^^^ + +class A3 {} +>A3 : A3 +> : ^^ + +class B3 {} +>B3 : B3 +> : ^^ + +async function loop3(stream: Stream3) { +>loop3 : (stream: Stream3) => Promise +> : ^ ^^ ^^^^^^^^^^^^^^^^^^ +>stream : Stream3 +> : ^^^^^^^^^^^^^^^ + + for await (const b of stream) {} +>b : B3 +> : ^^ +>stream : Stream3 +> : ^^^^^^^^^^^^^^^ + + for (const a of stream) {} +>a : A3 +> : ^^ +>stream : Stream3 +> : ^^^^^^^^^^^^^^^ +} + +type Stream4 = Iterable & AsyncIterable; +>Stream4 : Stream4 +> : ^^^^^^^^^^^^^^^^^^^^^^^^ + +class A4 {} +>A4 : A4 +> : ^^ + +class B4 {} +>B4 : B4 +> : ^^ + +// verify that resolving sync iteration first doesn't spoil the type for async iteration +async function loop4(stream: Stream4) { +>loop4 : (stream: Stream4) => Promise +> : ^ ^^ ^^^^^^^^^^^^^^^^^^ +>stream : Stream4 +> : ^^^^^^^^^^^^^^^ + + for (const a of stream) {} +>a : A4 +> : ^^ +>stream : Stream4 +> : ^^^^^^^^^^^^^^^ + + for await (const b of stream) {} +>b : B4 +> : ^^ +>stream : Stream4 +> : ^^^^^^^^^^^^^^^ +} diff --git a/tests/cases/compiler/forAwaitForIntersection1.ts b/tests/cases/compiler/forAwaitForIntersection1.ts new file mode 100644 index 0000000000000..67ec4b0debf30 --- /dev/null +++ b/tests/cases/compiler/forAwaitForIntersection1.ts @@ -0,0 +1,45 @@ +// @strict: true +// @target: es2018 +// @lib: esnext +// @noEmit: true + +type Stream1 = Iterable & AsyncIterable; + +class A1 {} +class B1 {} + +async function loop1(stream: Stream1) { + for await (const b of stream) {} +} + +type Stream2 = Iterable & AsyncIterable; + +class A2 {} +class B2 {} + +async function loop2(stream: Stream2) { + for (const a of stream) {} +} + +type Stream3 = Iterable & AsyncIterable; + +class A3 {} +class B3 {} + +async function loop3(stream: Stream3) { + for await (const b of stream) {} + + for (const a of stream) {} +} + +type Stream4 = Iterable & AsyncIterable; + +class A4 {} +class B4 {} + +// verify that resolving sync iteration first doesn't spoil the type for async iteration +async function loop4(stream: Stream4) { + for (const a of stream) {} + + for await (const b of stream) {} +} \ No newline at end of file From 653da455ac568b61f484da7d8fc4f7c566540813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 26 Oct 2025 12:50:23 +0100 Subject: [PATCH 2/2] fmt --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index be4ab2511a4a7..32811dd2d661b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -45964,7 +45964,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } - + iterationTypes = getIterationTypesOfIterableSlow(type, syncIterationTypesResolver, errorNode, errorOutputContainer, noCache); if (iterationTypes !== noIterationTypes) { if (use & IterationUse.AllowsAsyncIterablesFlag) {