Skip to content

Add ArrayReverse type#1266

Merged
sindresorhus merged 7 commits intomainfrom
feat/add-reverse-type
Dec 25, 2025
Merged

Add ArrayReverse type#1266
sindresorhus merged 7 commits intomainfrom
feat/add-reverse-type

Conversation

@som-sm
Copy link
Collaborator

@som-sm som-sm commented Oct 11, 2025

This PR fixes this issue in #1167. I was playing around with the fix and had done most of the work in the process, so I thought I’d open a separate PR.

Credits to @benzaria for suggesting this type.

Reverse works normally when the input array doesn't contain optional elements, but if the array includes an optional element, the result splits into a union of separate tuples, like:

Note: If the tuple contains optional elements, the result will be a union of tuples, refer to the examples below:
@example
```ts
import type {ArrayReverse} from 'type-fest';
type A = ArrayReverse<[string, number, boolean?]>;
//=> [boolean, number, string] | [number, string]
type B = ArrayReverse<[string, number?, boolean?]>;
//=> [boolean, number, string] | [number, string] | [string]
type C = ArrayReverse<[string?, number?, boolean?]>;
//=> [boolean, number, string] | [number, string] | [string] | []
type D = ArrayReverse<[string, number?, ...boolean[]]>;
//=> [...boolean[], number, string] | [string]
type E = ArrayReverse<[string?, number?, ...boolean[]]>;
//=> [...boolean[], number, string] | [number, string] | [string] | []
```


I've kept the name as ArrayReverse instead of just Reverse to match with our existing Array* types (like ArraySlice, ArraySplice).

@claude

This comment was marked as resolved.

@som-sm som-sm force-pushed the feat/add-reverse-type branch from 0e9cfea to af7ed2a Compare October 11, 2025 15:49
@claude

This comment was marked as outdated.

@claude

This comment was marked as outdated.

@som-sm som-sm requested a review from sindresorhus October 11, 2025 15:56
@som-sm
Copy link
Collaborator Author

som-sm commented Oct 11, 2025

For inputs containing only optional elements, I originally wasn't splitting the result, for example, ArrayReverse<[1?, 2?, 3?]> was returning [3?, 2?, 1?]. While this type of output is accurate, it's not precise, because [3?, 2?, 1?] could be [3] | [3, 2] but reversing [1?, 2?, 3?] would never yield [3] or [3, 2].

@som-sm
Copy link
Collaborator Author

som-sm commented Oct 11, 2025

@benzaria Would appreciate if you could share your thoughts on this!

som-sm and others added 2 commits October 12, 2025 00:26
Co-authored-by: benz <benzaria@users.noreply.github.com>
@som-sm som-sm force-pushed the feat/add-reverse-type branch from 7e59b1b to 5aaf11d Compare October 11, 2025 18:59
@claude

This comment was marked as outdated.

@som-sm som-sm mentioned this pull request Oct 12, 2025
@som-sm
Copy link
Collaborator Author

som-sm commented Oct 12, 2025

@sindresorhus While adding test for long tuples, I realised my implementation was not tail recursive because I had simplified a conditional using the If type. While this mistake is obvious, it might get missed. Refer #6cc3e53.

So, does it make sense to add a note like the following to the If type or is it too trivial?

Note: Sometimes using the If type can make an implementation non tail recursive, which would affect the performance of the implementation. In such cases, it's better to use a conditional directly. Refer the following example:

import type {If, IsEqual, StringRepeat} from 'type-fest';

// The following implementation is not tail recursive
type Includes<S extends string, Char extends string> =
	S extends `${infer First}${infer Rest}`
		? If<IsEqual<First, Char>,
			'found',
			Includes<Rest, Char>>
		: 'not found';

// Hence, instantiations with long strings will fail
type HundredZeroes = StringRepeat<'0', 100>;

// @ts-expect-error
type Fails = Includes<HundredZeroes, '1'>;
//           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Error: Type instantiation is excessively deep and possibly infinite.

// However, if we use a simple conditional instead of `If`, the implementation becomes tail-recursive
type IncludesWithoutIf<S extends string, Char extends string> =
	S extends `${infer First}${infer Rest}`
		? IsEqual<First, Char> extends true
			? 'found'
			: IncludesWithoutIf<Rest, Char>
		: 'not found';

// Now, instantiations with long strings will work
type Works = IncludesWithoutIf<HundredZeroes, '1'>;
//=> 'not found'

@sindresorhus
Copy link
Owner

So, does it make sense to add a note like the following to the If type or is it too trivial?

Definitely makes sense 👍

Repository owner deleted a comment from claude bot Dec 2, 2025
@som-sm som-sm force-pushed the feat/add-reverse-type branch from 011c5e5 to 34b24e3 Compare December 2, 2025 18:51
Repository owner deleted a comment from claude bot Dec 2, 2025
@som-sm som-sm force-pushed the feat/add-reverse-type branch from 34b24e3 to b1ff308 Compare December 2, 2025 19:01
Repository owner deleted a comment from claude bot Dec 2, 2025
@som-sm som-sm force-pushed the feat/add-reverse-type branch 3 times, most recently from c6c8946 to fd8a66b Compare December 3, 2025 06:35
Repository owner deleted a comment from claude bot Dec 3, 2025
@som-sm som-sm force-pushed the feat/add-reverse-type branch from fd8a66b to f105a3a Compare December 3, 2025 06:42
Comment on lines +28 to +30
"test:tsc": "node --max-old-space-size=6144 ./node_modules/.bin/tsc",
"test:tsd": "node --max-old-space-size=6144 ./node_modules/.bin/tsd",
"test:xo": "node --max-old-space-size=6144 ./node_modules/.bin/xo",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the test:tsd script now requires some bump in memory. test:xo works fine without a bump when run independently, but when run in parallel through run-p, apparently it also needs some extra memory.

@som-sm som-sm force-pushed the feat/add-reverse-type branch from f105a3a to bc49cd6 Compare December 20, 2025 18:39
Repository owner deleted a comment from claude bot Dec 20, 2025
@sindresorhus
Copy link
Owner

Is this ready?

@som-sm
Copy link
Collaborator Author

som-sm commented Dec 23, 2025

Is this ready?

@sindresorhus Yeah, it's ready!

I think we can proceed with this PR and close out #1167, since it still has some unresolved issues. This PR already credits it, so it should be fine to move forward.

@sindresorhus sindresorhus merged commit dfbefad into main Dec 25, 2025
6 checks passed
@sindresorhus sindresorhus deleted the feat/add-reverse-type branch December 25, 2025 02:48
som-sm added a commit that referenced this pull request Dec 25, 2025
Co-authored-by: benz <benzaria@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants