From b7e7be487334d3eeb6dc36e3eaf2fa482664ade3 Mon Sep 17 00:00:00 2001 From: bowzee Date: Sun, 30 Apr 2023 22:21:14 +0300 Subject: [PATCH 01/12] fix(expand): correct function signature Also removed hacky type-casting within the function body by fixing the high-level signature --- src/internal/operators/expand.ts | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/internal/operators/expand.ts b/src/internal/operators/expand.ts index 0b0e43d243..1ab16aa8c7 100644 --- a/src/internal/operators/expand.ts +++ b/src/internal/operators/expand.ts @@ -3,21 +3,21 @@ import { Observable } from '../Observable'; import { mergeInternals } from './mergeInternals'; /* tslint:disable:max-line-length */ -export function expand>( - project: (value: T, index: number) => O, +export function expand( + project: (value: T | O, index: number) => ObservableInput, concurrent?: number, scheduler?: SchedulerLike -): OperatorFunction>; +): OperatorFunction; /** * @deprecated The `scheduler` parameter will be removed in v8. If you need to schedule the inner subscription, * use `subscribeOn` within the projection function: `expand((value) => fn(value).pipe(subscribeOn(scheduler)))`. * Details: Details: https://rxjs.dev/deprecations/scheduler-argument */ -export function expand>( - project: (value: T, index: number) => O, +export function expand( + project: (value: T | O, index: number) => ObservableInput, concurrent: number | undefined, scheduler: SchedulerLike -): OperatorFunction>; +): OperatorFunction; /* tslint:enable:max-line-length */ /** @@ -70,11 +70,11 @@ export function expand>( * the output Observable and merging the results of the Observables obtained * from this transformation. */ -export function expand>( - project: (value: T, index: number) => O, +export function expand( + project: (value: T | O, index: number) => ObservableInput, concurrent = Infinity, scheduler?: SchedulerLike -): OperatorFunction> { +): OperatorFunction { concurrent = (concurrent || 0) < 1 ? Infinity : concurrent; return (source) => new Observable((subscriber) => @@ -83,8 +83,7 @@ export function expand>( source, subscriber, - // HACK: Cast because TypeScript seems to get confused here. - project as (value: T, index: number) => ObservableInput>, + project, concurrent, // onBeforeNext From 562ad55a12306e89ef347e986c9c5f2d38f08cda Mon Sep 17 00:00:00 2001 From: bowzee Date: Fri, 12 May 2023 02:01:30 +0300 Subject: [PATCH 02/12] fix(expand): remove unused `ObservedValueOf` declaration --- src/internal/operators/expand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal/operators/expand.ts b/src/internal/operators/expand.ts index 1ab16aa8c7..41cb48d42b 100644 --- a/src/internal/operators/expand.ts +++ b/src/internal/operators/expand.ts @@ -1,4 +1,4 @@ -import { OperatorFunction, ObservableInput, ObservedValueOf, SchedulerLike } from '../types'; +import { OperatorFunction, ObservableInput, SchedulerLike } from '../types'; import { Observable } from '../Observable'; import { mergeInternals } from './mergeInternals'; From 8929a29d02340a5b732cb28d674f6489cb43be39 Mon Sep 17 00:00:00 2001 From: bowzee Date: Mon, 15 May 2023 02:29:55 +0300 Subject: [PATCH 03/12] fix(expand): one more attempt --- src/internal/operators/expand.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/internal/operators/expand.ts b/src/internal/operators/expand.ts index 41cb48d42b..1dc78e0856 100644 --- a/src/internal/operators/expand.ts +++ b/src/internal/operators/expand.ts @@ -2,19 +2,17 @@ import { OperatorFunction, ObservableInput, SchedulerLike } from '../types'; import { Observable } from '../Observable'; import { mergeInternals } from './mergeInternals'; +export type ExpandProject = (value: T | (unknown extends O ? never : O), index: number) => ObservableInput; + /* tslint:disable:max-line-length */ -export function expand( - project: (value: T | O, index: number) => ObservableInput, - concurrent?: number, - scheduler?: SchedulerLike -): OperatorFunction; +export function expand(project: ExpandProject, concurrent?: number, scheduler?: SchedulerLike): OperatorFunction; /** * @deprecated The `scheduler` parameter will be removed in v8. If you need to schedule the inner subscription, * use `subscribeOn` within the projection function: `expand((value) => fn(value).pipe(subscribeOn(scheduler)))`. * Details: Details: https://rxjs.dev/deprecations/scheduler-argument */ export function expand( - project: (value: T | O, index: number) => ObservableInput, + project: ExpandProject, concurrent: number | undefined, scheduler: SchedulerLike ): OperatorFunction; @@ -70,11 +68,7 @@ export function expand( * the output Observable and merging the results of the Observables obtained * from this transformation. */ -export function expand( - project: (value: T | O, index: number) => ObservableInput, - concurrent = Infinity, - scheduler?: SchedulerLike -): OperatorFunction { +export function expand(project: ExpandProject, concurrent = Infinity, scheduler?: SchedulerLike): OperatorFunction { concurrent = (concurrent || 0) < 1 ? Infinity : concurrent; return (source) => new Observable((subscriber) => From 5aa5e57029256f4b0207f6c2dd8a42cb4f3a8db1 Mon Sep 17 00:00:00 2001 From: bowzee Date: Sat, 20 May 2023 07:14:09 +0300 Subject: [PATCH 04/12] fix(expand): enforce the project function return type to be the same as its input type --- src/internal/operators/expand.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/internal/operators/expand.ts b/src/internal/operators/expand.ts index 1dc78e0856..7e6f771d86 100644 --- a/src/internal/operators/expand.ts +++ b/src/internal/operators/expand.ts @@ -2,20 +2,22 @@ import { OperatorFunction, ObservableInput, SchedulerLike } from '../types'; import { Observable } from '../Observable'; import { mergeInternals } from './mergeInternals'; -export type ExpandProject = (value: T | (unknown extends O ? never : O), index: number) => ObservableInput; - /* tslint:disable:max-line-length */ -export function expand(project: ExpandProject, concurrent?: number, scheduler?: SchedulerLike): OperatorFunction; +export function expand( + project: (value: T, index: number) => ObservableInput, + concurrent?: number, + scheduler?: SchedulerLike +): OperatorFunction; /** * @deprecated The `scheduler` parameter will be removed in v8. If you need to schedule the inner subscription, * use `subscribeOn` within the projection function: `expand((value) => fn(value).pipe(subscribeOn(scheduler)))`. * Details: Details: https://rxjs.dev/deprecations/scheduler-argument */ -export function expand( - project: ExpandProject, +export function expand( + project: (value: T, index: number) => ObservableInput, concurrent: number | undefined, scheduler: SchedulerLike -): OperatorFunction; +): OperatorFunction; /* tslint:enable:max-line-length */ /** @@ -68,7 +70,11 @@ export function expand( * the output Observable and merging the results of the Observables obtained * from this transformation. */ -export function expand(project: ExpandProject, concurrent = Infinity, scheduler?: SchedulerLike): OperatorFunction { +export function expand( + project: (value: T, index: number) => ObservableInput, + concurrent = Infinity, + scheduler?: SchedulerLike +): OperatorFunction { concurrent = (concurrent || 0) < 1 ? Infinity : concurrent; return (source) => new Observable((subscriber) => From 886152dd5bf1f6681c03df562cf1a69f03c00722 Mon Sep 17 00:00:00 2001 From: bowzee Date: Sat, 20 May 2023 07:15:45 +0300 Subject: [PATCH 05/12] fix(expand/spec): correct signature of project function --- spec/operators/expand-spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/operators/expand-spec.ts b/spec/operators/expand-spec.ts index fa5b8bd6ce..2593457e67 100644 --- a/spec/operators/expand-spec.ts +++ b/spec/operators/expand-spec.ts @@ -511,7 +511,7 @@ describe('expand', () => { synchronousObservable .pipe( - expand(() => EMPTY), + expand((_) => EMPTY), take(3) ) .subscribe(() => { From f4eac7f65b5c249fb507c0e5a2418e869465ee37 Mon Sep 17 00:00:00 2001 From: bowzee Date: Sat, 20 May 2023 07:16:34 +0300 Subject: [PATCH 06/12] fix(expand/spec-dtslint): remove testing of type inferring with different i/o types --- spec-dtslint/operators/expand-spec.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/spec-dtslint/operators/expand-spec.ts b/spec-dtslint/operators/expand-spec.ts index edba34e0a0..1dc7b62f44 100644 --- a/spec-dtslint/operators/expand-spec.ts +++ b/spec-dtslint/operators/expand-spec.ts @@ -7,12 +7,6 @@ it('should infer correctly', () => { const q = of(1, 2, 3).pipe(expand(value => Promise.resolve(value))); // $ExpectType Observable }); -it('should infer correctly with a different type as the source', () => { - const o = of(1, 2, 3).pipe(expand(value => of('foo'))); // $ExpectType Observable - const p = of(1, 2, 3).pipe(expand(value => ['foo'])); // $ExpectType Observable - const q = of(1, 2, 3).pipe(expand(value => Promise.resolve('foo'))); // $ExpectType Observable -}); - it('should support a project function with index', () => { const o = of(1, 2, 3).pipe(expand((value, index) => of(index))); // $ExpectType Observable }); @@ -47,5 +41,5 @@ it('should enforce scheduler type', () => { }); it('should support union types', () => { - const o = of(1).pipe(expand(x => typeof x === 'string' ? of(123) : of('test'))); // $ExpectType Observable + const o = of(1).pipe(expand(x => typeof x === 'string' ? of(123) : of('test'))); // $ExpectType Observable }); From 2f6cbca4556bbcf221077a1b39b1a7de8352dd14 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 23 May 2023 19:24:49 +0300 Subject: [PATCH 07/12] fix(expand): simplify return type from OperatorFunction to MonotypeOperatorFunction Co-authored-by: Lucas Garcia <58547290+LcsGa@users.noreply.github.com> --- src/internal/operators/expand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal/operators/expand.ts b/src/internal/operators/expand.ts index 7e6f771d86..37306e94e8 100644 --- a/src/internal/operators/expand.ts +++ b/src/internal/operators/expand.ts @@ -7,7 +7,7 @@ export function expand( project: (value: T, index: number) => ObservableInput, concurrent?: number, scheduler?: SchedulerLike -): OperatorFunction; +): MonotypeOperatorFunction; /** * @deprecated The `scheduler` parameter will be removed in v8. If you need to schedule the inner subscription, * use `subscribeOn` within the projection function: `expand((value) => fn(value).pipe(subscribeOn(scheduler)))`. From a02b3601a60ffa84695713e3bafa955abd47f199 Mon Sep 17 00:00:00 2001 From: bowzee Date: Tue, 23 May 2023 19:36:06 +0300 Subject: [PATCH 08/12] fix(expand): complete OperatorFunction -> MonoTypeOperatorFunction refactoring --- src/internal/operators/expand.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/internal/operators/expand.ts b/src/internal/operators/expand.ts index 37306e94e8..01741a68dc 100644 --- a/src/internal/operators/expand.ts +++ b/src/internal/operators/expand.ts @@ -1,4 +1,4 @@ -import { OperatorFunction, ObservableInput, SchedulerLike } from '../types'; +import { ObservableInput, SchedulerLike, MonoTypeOperatorFunction } from '../types'; import { Observable } from '../Observable'; import { mergeInternals } from './mergeInternals'; @@ -7,7 +7,7 @@ export function expand( project: (value: T, index: number) => ObservableInput, concurrent?: number, scheduler?: SchedulerLike -): MonotypeOperatorFunction; +): MonoTypeOperatorFunction; /** * @deprecated The `scheduler` parameter will be removed in v8. If you need to schedule the inner subscription, * use `subscribeOn` within the projection function: `expand((value) => fn(value).pipe(subscribeOn(scheduler)))`. @@ -17,7 +17,7 @@ export function expand( project: (value: T, index: number) => ObservableInput, concurrent: number | undefined, scheduler: SchedulerLike -): OperatorFunction; +): MonoTypeOperatorFunction; /* tslint:enable:max-line-length */ /** @@ -74,7 +74,7 @@ export function expand( project: (value: T, index: number) => ObservableInput, concurrent = Infinity, scheduler?: SchedulerLike -): OperatorFunction { +): MonoTypeOperatorFunction { concurrent = (concurrent || 0) < 1 ? Infinity : concurrent; return (source) => new Observable((subscriber) => From e065b426b54140f92a2f76e12d1b0107c224b2c0 Mon Sep 17 00:00:00 2001 From: bowzee Date: Wed, 31 May 2023 20:38:38 +0300 Subject: [PATCH 09/12] fix(expand): correct signature with the output type extension --- src/internal/operators/expand.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/internal/operators/expand.ts b/src/internal/operators/expand.ts index 01741a68dc..a4b8901e48 100644 --- a/src/internal/operators/expand.ts +++ b/src/internal/operators/expand.ts @@ -1,23 +1,23 @@ -import { ObservableInput, SchedulerLike, MonoTypeOperatorFunction } from '../types'; +import { ObservableInput, SchedulerLike, OperatorFunction } from '../types'; import { Observable } from '../Observable'; import { mergeInternals } from './mergeInternals'; /* tslint:disable:max-line-length */ -export function expand( - project: (value: T, index: number) => ObservableInput, +export function expand( + project: (value: I | O, index: number) => ObservableInput, concurrent?: number, scheduler?: SchedulerLike -): MonoTypeOperatorFunction; +): OperatorFunction; /** * @deprecated The `scheduler` parameter will be removed in v8. If you need to schedule the inner subscription, * use `subscribeOn` within the projection function: `expand((value) => fn(value).pipe(subscribeOn(scheduler)))`. * Details: Details: https://rxjs.dev/deprecations/scheduler-argument */ -export function expand( - project: (value: T, index: number) => ObservableInput, +export function expand( + project: (value: I | O, index: number) => ObservableInput, concurrent: number | undefined, scheduler: SchedulerLike -): MonoTypeOperatorFunction; +): OperatorFunction; /* tslint:enable:max-line-length */ /** @@ -70,11 +70,11 @@ export function expand( * the output Observable and merging the results of the Observables obtained * from this transformation. */ -export function expand( - project: (value: T, index: number) => ObservableInput, +export function expand( + project: (value: I | O, index: number) => ObservableInput, concurrent = Infinity, scheduler?: SchedulerLike -): MonoTypeOperatorFunction { +): OperatorFunction { concurrent = (concurrent || 0) < 1 ? Infinity : concurrent; return (source) => new Observable((subscriber) => From 8fd3e54d51ad541fa4ad61809c770e0546a0cfe4 Mon Sep 17 00:00:00 2001 From: bowzee Date: Wed, 31 May 2023 20:51:52 +0300 Subject: [PATCH 10/12] test(expand): should infer correctly with output type extending input type --- spec-dtslint/operators/expand-spec.ts | 94 ++++++++++++++------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/spec-dtslint/operators/expand-spec.ts b/spec-dtslint/operators/expand-spec.ts index 1dc7b62f44..47521ac93a 100644 --- a/spec-dtslint/operators/expand-spec.ts +++ b/spec-dtslint/operators/expand-spec.ts @@ -1,45 +1,49 @@ -import { of, asyncScheduler } from 'rxjs'; -import { expand } from 'rxjs/operators'; - -it('should infer correctly', () => { - const o = of(1, 2, 3).pipe(expand(value => of(value))); // $ExpectType Observable - const p = of(1, 2, 3).pipe(expand(value => [value])); // $ExpectType Observable - const q = of(1, 2, 3).pipe(expand(value => Promise.resolve(value))); // $ExpectType Observable -}); - -it('should support a project function with index', () => { - const o = of(1, 2, 3).pipe(expand((value, index) => of(index))); // $ExpectType Observable -}); - -it('should support concurrent parameter', () => { - const o = of(1, 2, 3).pipe(expand(value => of(1), 47)); // $ExpectType Observable -}); - -it('should support a scheduler', () => { - const o = of(1, 2, 3).pipe(expand(value => of(1), 47, asyncScheduler)); // $ExpectType Observable -}); - -it('should enforce types', () => { - const o = of(1, 2, 3).pipe(expand()); // $ExpectError -}); - -it('should enforce project types', () => { - const o = of(1, 2, 3).pipe(expand((value: string, index) => of(1))); // $ExpectError - const p = of(1, 2, 3).pipe(expand((value, index: string) => of(1))); // $ExpectError -}); - -it('should enforce project return type', () => { - const o = of(1, 2, 3).pipe(expand(value => 1)); // $ExpectError -}); - -it('should enforce concurrent type', () => { - const o = of(1, 2, 3).pipe(expand(value => of(1), 'foo')); // $ExpectError -}); - -it('should enforce scheduler type', () => { - const o = of(1, 2, 3).pipe(expand(value => of(1), 47, 'foo')); // $ExpectError -}); - -it('should support union types', () => { - const o = of(1).pipe(expand(x => typeof x === 'string' ? of(123) : of('test'))); // $ExpectType Observable -}); +import { of, asyncScheduler } from 'rxjs'; +import { expand } from 'rxjs/operators'; + +it('should infer correctly', () => { + const o = of(1, 2, 3).pipe(expand(value => of(value))); // $ExpectType Observable + const p = of(1, 2, 3).pipe(expand(value => [value])); // $ExpectType Observable + const q = of(1, 2, 3).pipe(expand(value => Promise.resolve(value))); // $ExpectType Observable +}); + +it('should infer correctly with output type extending input type', () => { + const o = of(1).pipe(expand((value: number | string) => of(value.toString()))); // $ExpectType Observable +}); + +it('should support a project function with index', () => { + const o = of(1, 2, 3).pipe(expand((value, index) => of(index))); // $ExpectType Observable +}); + +it('should support concurrent parameter', () => { + const o = of(1, 2, 3).pipe(expand(value => of(1), 47)); // $ExpectType Observable +}); + +it('should support a scheduler', () => { + const o = of(1, 2, 3).pipe(expand(value => of(1), 47, asyncScheduler)); // $ExpectType Observable +}); + +it('should enforce types', () => { + const o = of(1, 2, 3).pipe(expand()); // $ExpectError +}); + +it('should enforce project types', () => { + const o = of(1, 2, 3).pipe(expand((value: string, index) => of(1))); // $ExpectError + const p = of(1, 2, 3).pipe(expand((value, index: string) => of(1))); // $ExpectError +}); + +it('should enforce project return type', () => { + const o = of(1, 2, 3).pipe(expand(value => 1)); // $ExpectError +}); + +it('should enforce concurrent type', () => { + const o = of(1, 2, 3).pipe(expand(value => of(1), 'foo')); // $ExpectError +}); + +it('should enforce scheduler type', () => { + const o = of(1, 2, 3).pipe(expand(value => of(1), 47, 'foo')); // $ExpectError +}); + +it('should support union types', () => { + const o = of(1).pipe(expand(x => typeof x === 'string' ? of(123) : of('test'))); // $ExpectType Observable +}); From 2ecb23414bece7f5c472dd960a8864045ed063c9 Mon Sep 17 00:00:00 2001 From: bowzee Date: Thu, 1 Jun 2023 01:31:21 +0300 Subject: [PATCH 11/12] fix(expand): justify the project output generic type --- src/internal/operators/expand.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/internal/operators/expand.ts b/src/internal/operators/expand.ts index a4b8901e48..7f9ae8751f 100644 --- a/src/internal/operators/expand.ts +++ b/src/internal/operators/expand.ts @@ -3,7 +3,7 @@ import { Observable } from '../Observable'; import { mergeInternals } from './mergeInternals'; /* tslint:disable:max-line-length */ -export function expand( +export function expand( project: (value: I | O, index: number) => ObservableInput, concurrent?: number, scheduler?: SchedulerLike @@ -13,7 +13,7 @@ export function expand( * use `subscribeOn` within the projection function: `expand((value) => fn(value).pipe(subscribeOn(scheduler)))`. * Details: Details: https://rxjs.dev/deprecations/scheduler-argument */ -export function expand( +export function expand( project: (value: I | O, index: number) => ObservableInput, concurrent: number | undefined, scheduler: SchedulerLike @@ -70,7 +70,7 @@ export function expand( * the output Observable and merging the results of the Observables obtained * from this transformation. */ -export function expand( +export function expand( project: (value: I | O, index: number) => ObservableInput, concurrent = Infinity, scheduler?: SchedulerLike From ac471b46109a8034c44e20d43261e983b9934c37 Mon Sep 17 00:00:00 2001 From: bowzee Date: Thu, 1 Jun 2023 01:39:30 +0300 Subject: [PATCH 12/12] test(expand): more dtslint cases for input/output compatibility --- spec-dtslint/operators/expand-spec.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/spec-dtslint/operators/expand-spec.ts b/spec-dtslint/operators/expand-spec.ts index 47521ac93a..18b34fbd11 100644 --- a/spec-dtslint/operators/expand-spec.ts +++ b/spec-dtslint/operators/expand-spec.ts @@ -7,10 +7,26 @@ it('should infer correctly', () => { const q = of(1, 2, 3).pipe(expand(value => Promise.resolve(value))); // $ExpectType Observable }); -it('should infer correctly with output type extending input type', () => { +it('should infer correctly specifying value argument type', () => { const o = of(1).pipe(expand((value: number | string) => of(value.toString()))); // $ExpectType Observable }); +it('should infer correctly with specifying different input/output types', () => { + const o = of(1).pipe(expand((value) => of(value.toString()))); // $ExpectType Observable +}); + +it('should enforce project output type to be assignable with its generic', () => { + const o = of(1).pipe(expand((value) => of(value))); // $ExpectError +}); + +it('should enforce project input type to be assignable with upstream', () => { + const o = of(1).pipe(expand((value: string) => of(value))); // $ExpectError +}); + +it('should enforce project input/output types compatibility by default', () => { + const o = of(1).pipe(expand((value) => of(value.toString()))); // $ExpectError +}); + it('should support a project function with index', () => { const o = of(1, 2, 3).pipe(expand((value, index) => of(index))); // $ExpectType Observable });