Skip to content

Drop TS 4 support and add const P extends for getIn method types #2072

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

Merged
merged 1 commit into from
Mar 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
- run: npm ci
- run: npm run build
- run: npm run test:unit
- run: npm run test:types -- --target 4.5,5.0,current
- run: npm run test:types -- --target 5.0,current
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! To draw your attention, tstyche@3.1.1 and above makes it possible to test agains a range of versions:

Suggested change
- run: npm run test:types -- --target 5.0,current
- run: npm run test:types -- --target '>=5.0'

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice, but doing that the tests ran 5 times slower.
I do not know if it's worth it for now (but I do keep that in mind)

- run: npx size-limit
- run: npm run check-git-clean

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ collections in your [Flowtype](https://flowtype.org/) or [TypeScript](https://ty
advantage of type generics, error detection, and auto-complete in your IDE.

Installing `immutable` via npm brings with it type definitions for Flow (v0.55.0 or higher)
and TypeScript (v4.5 or higher), so you shouldn't need to do anything at all!
and TypeScript (v5.0 or higher), so you shouldn't need to do anything at all!

#### Using TypeScript with Immutable.js v4+

Expand Down
19 changes: 8 additions & 11 deletions type-definitions/immutable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -877,10 +877,7 @@ declare namespace Immutable {
get<K extends keyof R>(key: K, notSetValue?: unknown): R[K];
get<NSV>(key: unknown, notSetValue: NSV): NSV;

// TODO `<const P extends ...>` can be used after dropping support for TypeScript 4.x
// reference: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#const-type-parameters
// after this change, `as const` assertions can be remove from the type tests
getIn<P extends ReadonlyArray<PropertyKey>>(
getIn<const P extends ReadonlyArray<PropertyKey>>(
searchKeyPath: [...P],
notSetValue?: unknown
): RetrievePath<R, P>;
Expand Down Expand Up @@ -5925,9 +5922,6 @@ declare namespace Immutable {
updater: (value: V | NSV) => V
): { [key: string]: V };

// TODO `<const P extends ...>` can be used after dropping support for TypeScript 4.x
// reference: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#const-type-parameters
// after this change, `as const` assertions can be remove from the type tests
/**
* Returns the value at the provided key path starting at the provided
* collection, or notSetValue if the key path is not defined.
Expand All @@ -5942,17 +5936,20 @@ declare namespace Immutable {
* getIn({ x: { y: { z: 123 }}}, ['x', 'q', 'p'], 'ifNotSet') // 'ifNotSet'
* ```
*/
function getIn<C, P extends ReadonlyArray<PropertyKey>>(
function getIn<C, const P extends ReadonlyArray<PropertyKey>>(
object: C,
keyPath: [...P]
): RetrievePath<C, P>;
function getIn<C, P extends KeyPath<unknown>>(object: C, keyPath: P): unknown;
function getIn<C, P extends ReadonlyArray<PropertyKey>, NSV>(
function getIn<C, const P extends KeyPath<unknown>>(
object: C,
keyPath: P
): unknown;
function getIn<C, const P extends ReadonlyArray<PropertyKey>, NSV>(
collection: C,
keyPath: [...P],
notSetValue: NSV
): RetrievePath<C, P> extends never ? NSV : RetrievePath<C, P>;
function getIn<C, P extends KeyPath<unknown>, NSV>(
function getIn<C, const P extends KeyPath<unknown>, NSV>(
object: C,
keyPath: P,
notSetValue: NSV
Expand Down
40 changes: 15 additions & 25 deletions type-definitions/ts-tests/functional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ test('get', () => {
});

test('getIn', () => {
expect(getIn('a', ['length' as const])).type.toBe<never>();
expect(getIn('a', ['length'])).type.toBe<never>();

expect(getIn([1, 2, 3], [0])).type.toBe<number>();

Expand All @@ -32,50 +32,40 @@ test('getIn', () => {
// We do not handle List in getIn TS type yet (hard to convert to a tuple)
expect(getIn([1, 2, 3], List([0]))).type.toBe<unknown>();

expect(getIn([1, 2, 3], [0], 'a' as const)).type.toBe<number>();
expect(getIn([1, 2, 3], [0], 'a')).type.toBe<number>();

expect(getIn(List([1, 2, 3]), [0])).type.toBe<number>();

// first parameter type is Array<number> so we can not detect that the number will be invalid
expect(getIn(List([1, 2, 3]), [99])).type.toBe<number>();

expect(getIn(List([1, 2, 3]), ['a' as const])).type.toBe<never>();
expect(getIn(List([1, 2, 3]), ['a'])).type.toBe<never>();

expect(
getIn(List([1, 2, 3]), ['a' as const], 'missing')
).type.toBe<'missing'>();
expect(getIn(List([1, 2, 3]), ['a'], 'missing')).type.toBe<'missing'>();

expect(getIn({ x: 10, y: 20 }, ['x' as const])).type.toBe<number>();
expect(getIn({ x: 10, y: 20 }, ['x'])).type.toBe<number>();

expect(
getIn({ x: 10, y: 20 }, ['z' as const], 'missing')
).type.toBe<'missing'>();
expect(getIn({ x: 10, y: 20 }, ['z'], 'missing')).type.toBe<'missing'>();

expect(getIn({ x: { y: 20 } }, ['x' as const])).type.toBe<{ y: number }>();
expect(getIn({ x: { y: 20 } }, ['x'])).type.toBe<{ y: number }>();

expect(getIn({ x: { y: 20 } }, ['z' as const])).type.toBe<never>();
expect(getIn({ x: { y: 20 } }, ['z'])).type.toBe<never>();

expect(
getIn({ x: { y: 20 } }, ['x' as const, 'y' as const])
).type.toBe<number>();
expect(getIn({ x: { y: 20 } }, ['x', 'y'])).type.toBe<number>();

expect(
getIn({ x: Map({ y: 20 }) }, ['x' as const, 'y' as const])
).type.toBe<number>();
expect(getIn({ x: Map({ y: 20 }) }, ['x', 'y'])).type.toBe<number>();

expect(
getIn(Map({ x: Map({ y: 20 }) }), ['x' as const, 'y' as const])
).type.toBe<number>();
expect(getIn(Map({ x: Map({ y: 20 }) }), ['x', 'y'])).type.toBe<number>();

const o = Map({ x: List([Map({ y: 20 })]) });

expect(getIn(o, ['x' as const, 'y' as const])).type.toBe<never>();
expect(getIn(o, ['x', 'y'])).type.toBe<never>();

expect(getIn(o, ['x' as const])).type.toBe<List<MapOf<{ y: number }>>>();
expect(getIn(o, ['x'])).type.toBe<List<MapOf<{ y: number }>>>();

expect(getIn(o, ['x' as const, 0])).type.toBe<MapOf<{ y: number }>>();
expect(getIn(o, ['x', 0])).type.toBe<MapOf<{ y: number }>>();

expect(getIn(o, ['x' as const, 0, 'y' as const])).type.toBe<number>();
expect(getIn(o, ['x', 0, 'y'])).type.toBe<number>();
});

test('has', () => {
Expand Down
14 changes: 7 additions & 7 deletions type-definitions/ts-tests/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,20 @@ test('#getIn', () => {

expect(result).type.toBeNumber();

expect(Map({ a: 4, b: true }).getIn(['a' as const])).type.toBeNumber();
expect(Map({ a: 4, b: true }).getIn(['a'])).type.toBeNumber();

expect(
Map({ a: Map({ b: Map({ c: Map({ d: 4 }) }) }) }).getIn([
'a' as const,
'b' as const,
'c' as const,
'd' as const,
'a',
'b',
'c',
'd',
])
).type.toBeNumber();

expect(Map({ a: [1] }).getIn(['a' as const, 0])).type.toBeNumber();
expect(Map({ a: [1] }).getIn(['a', 0])).type.toBeNumber();

expect(Map({ a: List([1]) }).getIn(['a' as const, 0])).type.toBeNumber();
expect(Map({ a: List([1]) }).getIn(['a', 0])).type.toBeNumber();
});

test('#set', () => {
Expand Down