Skip to content

Commit ec1c04b

Browse files
authored
Add non-spread overloads for Option.all and Option.any (#247)
Add array parameter overloads as the preferred API for Option.all and Option.any, matching the pattern already established for Result.all and Result.any in [1]. The spread overloads (Option.all(a, b, c)) are now deprecated in favor of passing an array (Option.all([a, b, c])). This avoids potential stack overflow issues when working with large arrays and provides a more consistent API. The spread overloads will be removed in a future version. [1] 2e03308 ("Add non-spread overload for Result.all (#125)")
1 parent 4bf5a52 commit ec1c04b

4 files changed

Lines changed: 152 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ Backwards incompatible:
66
- Removed the `Some.safeUnwrap` and `Ok.safeUnwrap` methods, use the
77
`value` property instead.
88

9+
Added:
10+
11+
- Added array parameter overloads for `Option.all` and `Option.any`,
12+
allowing `Option.all([a, b, c])` instead of `Option.all(a, b, c)`.
13+
14+
Deprecated:
15+
16+
- The parameter spread variants of `Option.all` and `Option.any` are now
17+
deprecated. Use the new array parameter overloads instead.
18+
919
# 6.0.0
1020

1121
Backwards incompatible:

docs/reference/option.rst

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ Construction:
2525

2626
.. code-block:: typescript
2727
28+
// Preferred: pass an array
29+
static all<T extends Option<any>[]>(options: T): Option<OptionSomeTypes<T>>
30+
31+
// Deprecated: spread arguments
2832
static all<T extends Option<any>[]>(...options: T): Option<OptionSomeTypes<T>>
2933
3034
Parse a set of ``Option``'s, returning an array of all ``Some`` values.
@@ -35,17 +39,21 @@ Example:
3539
.. code-block:: typescript
3640
3741
let options: Option<number>[] = [Some(1), Some(2), Some(3)];
38-
Option.all(...options); // Some([1, 2, 3]), type: Option<number[]>
42+
Option.all(options); // Some([1, 2, 3]), type: Option<number[]>
3943
4044
// Short-circuits on first None
4145
let optionsWithNone: Option<number>[] = [Some(1), None, Some(3)];
42-
Option.all(...optionsWithNone); // None, type: Option<number[]>
46+
Option.all(optionsWithNone); // None, type: Option<number[]>
4347
4448
``any()``
4549
---------
4650
4751
.. code-block:: typescript
4852
53+
// Preferred: pass an array
54+
static any<T extends Option<any>[]>(options: T): Option<OptionSomeTypes<T>[number]>
55+
56+
// Deprecated: spread arguments
4957
static any<T extends Option<any>[]>(...options: T): Option<OptionSomeTypes<T>[number]>
5058
5159
Parse a set of ``Option``'s, short-circuits when an input value is ``Some``.
@@ -55,9 +63,11 @@ Example:
5563
5664
.. code-block:: typescript
5765
58-
Option.any(None, Some(1), Some(2)); // Some(1), type: Option<number>
59-
Option.any(None, None, Some(3)); // Some(3), type: Option<number>
60-
Option.any(None, None, None); // None, type: Option<never>
66+
let options: Option<number>[] = [None, Some(1), Some(2)];
67+
Option.any(options); // Some(1), type: Option<number>
68+
69+
Option.any([None, None, Some(3)]); // Some(3), type: Option<number>
70+
Option.any([None, None, None]); // None, type: Option<never>
6171
6272
``Some.EMPTY``
6373
--------------

src/option.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,23 @@ export type OptionSomeTypes<T extends Option<any>[]> = {
300300
export namespace Option {
301301
/**
302302
* Parse a set of `Option`s, returning an array of all `Some` values.
303-
* Short circuits with the first `None` found, if any
303+
* Short circuits with the first `None` found, if any.
304304
*/
305-
export function all<T extends Option<any>[]>(...options: T): Option<OptionSomeTypes<T>> {
305+
export function all<const T extends Option<any>[]>(options: T): Option<OptionSomeTypes<T>>;
306+
/**
307+
* Parse a set of `Option`s, returning an array of all `Some` values.
308+
* Short circuits with the first `None` found, if any.
309+
*
310+
* @deprecated Pass an array instead of using spread arguments. This overload
311+
* will be removed in a future version.
312+
*/
313+
export function all<T extends Option<any>[]>(...options: T): Option<OptionSomeTypes<T>>;
314+
export function all<T extends Option<any>[]>(
315+
first?: T | T[number],
316+
...rest: Option<any>[]
317+
): Option<OptionSomeTypes<T>> {
318+
const options: Option<any>[] = first === undefined ? [] : Array.isArray(first) ? first : [first, ...rest];
319+
306320
const someOption = [];
307321
for (let option of options) {
308322
if (option.isSome()) {
@@ -319,7 +333,21 @@ export namespace Option {
319333
* Parse a set of `Option`s, short-circuits when an input value is `Some`.
320334
* If no `Some` is found, returns `None`.
321335
*/
322-
export function any<T extends Option<any>[]>(...options: T): Option<OptionSomeTypes<T>[number]> {
336+
export function any<const T extends Option<any>[]>(options: T): Option<OptionSomeTypes<T>[number]>;
337+
/**
338+
* Parse a set of `Option`s, short-circuits when an input value is `Some`.
339+
* If no `Some` is found, returns `None`.
340+
*
341+
* @deprecated Pass an array instead of using spread arguments. This overload
342+
* will be removed in a future version.
343+
*/
344+
export function any<T extends Option<any>[]>(...options: T): Option<OptionSomeTypes<T>[number]>;
345+
export function any<T extends Option<any>[]>(
346+
first?: T | T[number],
347+
...rest: Option<any>[]
348+
): Option<OptionSomeTypes<T>[number]> {
349+
const options: Option<any>[] = first === undefined ? [] : Array.isArray(first) ? first : [first, ...rest];
350+
323351
// short-circuits
324352
for (const option of options) {
325353
if (option.isSome()) {

test/option.test.ts

Lines changed: 96 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -106,22 +106,102 @@ test('mapOr / mapOrElse', () => {
106106
).toEqual(22);
107107
});
108108

109-
test('all / any', () => {
110-
const strings = ['foo', 'bar', 'baz'] as const;
111-
const options = [Some('foo' as const), Some('bar' as const), Some('baz' as const)] as const;
112-
113-
const all = Option.all(...options);
114-
eq<typeof all, Option<['foo', 'bar', 'baz']>>(true);
115-
116-
expect(Option.all(...options)).toEqual(Some(strings));
117-
expect(Option.all()).toEqual(Some([]));
118-
expect(Option.all(...options, None)).toEqual(None);
119-
120-
expect(Option.any(...options)).toEqual(Some('foo'));
121-
expect(Option.any(...options, None)).toEqual(Some('foo'));
122-
expect(Option.any(None, None)).toEqual(None);
123-
expect(Option.any(None, Some('foo'))).toEqual(Some('foo'));
124-
expect(Option.any()).toEqual(None);
109+
test('Option.all', () => {
110+
const some0: Option<number> = Some(3);
111+
const some1: Option<boolean> = Some(true);
112+
const some2: Option<string> = Some('hello');
113+
114+
// Empty cases
115+
const all0 = Option.all([]);
116+
expect(all0).toEqual(Some([]));
117+
eq<typeof all0, Option<[]>>(true);
118+
119+
const all0Spread = Option.all();
120+
expect(all0Spread).toEqual(Some([]));
121+
eq<typeof all0Spread, Option<[]>>(true);
122+
123+
// All Some
124+
const all1 = Option.all([some0, some1]);
125+
expect(all1).toEqual(Some([3, true]));
126+
eq<typeof all1, Option<[number, boolean]>>(true);
127+
128+
const all1Spread = Option.all(some0, some1);
129+
expect(all1Spread).toEqual(Some([3, true]));
130+
eq<typeof all1Spread, Option<[number, boolean]>>(true);
131+
132+
// With None
133+
const all2 = Option.all([some0, None]);
134+
expect(all2).toEqual(None);
135+
eq<typeof all2, Option<[number, never]>>(true);
136+
137+
const all2Spread = Option.all(some0, None);
138+
expect(all2Spread).toEqual(None);
139+
eq<typeof all2Spread, Option<[number, never]>>(true);
140+
141+
// Dynamic array
142+
const all3 = Option.all([] as Option<string>[]);
143+
eq<typeof all3, Option<string[]>>(true);
144+
145+
const all3Spread = Option.all(...([] as Option<string>[]));
146+
eq<typeof all3Spread, Option<string[]>>(true);
147+
148+
// Multiple with None in middle
149+
const all4 = Option.all([some0, some1, some2, None]);
150+
expect(all4).toEqual(None);
151+
eq<typeof all4, Option<[number, boolean, string, never]>>(true);
152+
153+
const all4Spread = Option.all(some0, some1, some2, None);
154+
expect(all4Spread).toEqual(None);
155+
eq<typeof all4Spread, Option<[number, boolean, string, never]>>(true);
156+
});
157+
158+
test('Option.any', () => {
159+
const some0: Option<number> = Some(3);
160+
const some1: Option<boolean> = Some(true);
161+
const some2: Option<string> = Some('hello');
162+
163+
// Empty cases
164+
const any0 = Option.any([]);
165+
expect(any0).toEqual(None);
166+
eq<typeof any0, Option<never>>(true);
167+
168+
const any0Spread = Option.any();
169+
expect(any0Spread).toEqual(None);
170+
eq<typeof any0Spread, Option<never>>(true);
171+
172+
// All Some - returns first
173+
const any1 = Option.any([some0, some1]);
174+
expect(any1).toEqual(Some(3));
175+
eq<typeof any1, Option<number | boolean>>(true);
176+
177+
const any1Spread = Option.any(some0, some1);
178+
expect(any1Spread).toEqual(Some(3));
179+
eq<typeof any1Spread, Option<number | boolean>>(true);
180+
181+
// All None
182+
const any2 = Option.any([None, None]);
183+
expect(any2).toEqual(None);
184+
eq<typeof any2, Option<never>>(true);
185+
186+
const any2Spread = Option.any(None, None);
187+
expect(any2Spread).toEqual(None);
188+
eq<typeof any2Spread, Option<never>>(true);
189+
190+
// Dynamic array
191+
const any3 = Option.any([] as Option<string>[]);
192+
eq<typeof any3, Option<string>>(true);
193+
194+
const any3Spread = Option.any(...([] as Option<string>[]));
195+
eq<typeof any3Spread, Option<string>>(true);
196+
197+
// None then Some
198+
const any4 = Option.any([None, None, some2, some0]);
199+
expect(any4).toEqual(Some('hello'));
200+
eq<typeof any4, Option<string | number>>(true);
201+
202+
const any4Spread = Option.any(None, None, some2, some0);
203+
expect(any4Spread).toEqual(Some('hello'));
204+
eq<typeof any4Spread, Option<string | number>>(true);
125205
});
126206

127207
test('Type Helpers', () => {

0 commit comments

Comments
 (0)