Skip to content
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

Add ReadonlyTuple type #383

Merged
merged 6 commits into from
May 24, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export {
NonNegativeInteger,
} from './source/numeric';
export {StringKeyOf} from './source/string-key-of';
export {ReadonlyTuple} from './source/readonly-tuple';

// Template literal types
export {CamelCase} from './source/camel-case';
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ Click the type names for complete docs.
- [`FixedLengthArray`](source/fixed-length-array.d.ts) - Create a type that represents an array of the given type and length.
- [`MultidimensionalArray`](source/multidimensional-array.d.ts) - Create a type that represents a multidimensional array of the given type and dimensions.
- [`MultidimensionalReadonlyArray`](source/multidimensional-readonly-array.d.ts) - Create a type that represents a multidimensional readonly array of the given type and dimensions.
- [`ReadonlyTuple`](source/readonly-tuple.d.ts) - Create a type that represents a readonly tuple of the given type and length.

### Numeric

Expand Down
2 changes: 2 additions & 0 deletions source/fixed-length-array.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ guestFencingTeam.push('Sam');
//=> error TS2339: Property 'push' does not exist on type 'FencingTeam'
```

@deprecated This type doesn't prevent you to access values outside the array length via index (see issue#284),
Copy link
Owner

Choose a reason for hiding this comment

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

I'm not sure if it makes sense to deprecate this. Someone might want a mutable version. I think we should document how this differs from ReadonlyTuple though.

use ReadonlyTuple instead.
@category Array
*/
export type FixedLengthArray<Element, Length extends number, ArrayPrototype = [Element, ...Element[]]> = Pick<
Expand Down
40 changes: 40 additions & 0 deletions source/readonly-tuple.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Creates a readonly tuple of <Element> and length 'Length'
* @private
* @see ReadonlyTuple which is safer because it tests if `Length` is a specific finite number
*/
type BuildTupleHelper<Element, Length extends number, Rest extends Element[]> =
Rest['length'] extends Length ?
readonly [...Rest] : // Terminate with readonly array (aka tuple)
BuildTupleHelper<Element, Length, [Element, ...Rest]>;

/**
Create a type that represents a tuple of the given type and length.

Use-cases:
- Declaring fixed-length tuples with a large number of items.
- Creating a range union (for example, `0 | 1 | 2 | 3 | 4` from the keys of such a type) without having to resort to recursive types.
- Creating a tuple of coordinates with a static length, for example, length of 3 for a 3D vector.

@example
```
import {ReadonlyTuple} from 'type-fest';

type FencingTeam = ReadonlyTuple<string, 3>;

const guestFencingTeam: FencingTeam = ['Josh', 'Michael', 'Robert'];

const homeFencingTeam: FencingTeam = ['George', 'John'];
//=> error TS2322: Type string[] is not assignable to type 'FencingTeam'

guestFencingTeam.push('Sam');
//=> error TS2339: Property 'push' does not exist on type 'FencingTeam'
```

@category Utilities
*/
export type ReadonlyTuple<Element, Length extends number> =
number extends Length
// Because `Length extends number` and `number extends Length`, then Length is not a specific finite number
? readonly Element[] // It's not fixed length
: BuildTupleHelper<Element, Length, []>; // Otherwise it is fixed length tuple
16 changes: 16 additions & 0 deletions test-d/readonly-tuple.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {expectAssignable, expectError, expectNotAssignable} from 'tsd';
import {ReadonlyTuple} from '../index';

type TupleOfThreeStrings = ReadonlyTuple<string, 3>;

expectAssignable<TupleOfThreeStrings>(['a', 'b', 'c']);

expectNotAssignable<TupleOfThreeStrings>(['a', 'b', 123]);
expectNotAssignable<TupleOfThreeStrings>(['a']);
expectNotAssignable<TupleOfThreeStrings>(['a', 'b']);
expectNotAssignable<TupleOfThreeStrings>(['a', 'b', 'c', 'd']);

declare const test: TupleOfThreeStrings;

expectError(test.push);
expectError(test[2] = 'a');