Skip to content

Commit

Permalink
Fix: updateDefault inference unnecessary V | D
Browse files Browse the repository at this point in the history
  • Loading branch information
Vovan-VE committed Dec 20, 2023
1 parent c5b1bf7 commit c679fc5
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 2 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 2.0.1 (2023-12-21)

- Fix: `updateDefault()` since `1.2.0` in some cases could inference unnecessary
annoying `V | D` in untyped callback param even when `D` is already a subtype
of `V`. See test cases for details.

## 2.0.0 (2023-12-14)

- **BREAKING**: Drop Node < 18.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cubux/readonly-map",
"version": "2.0.0",
"version": "2.0.1",
"description": "Functions to work with read-only maps",
"keywords": [
"map",
Expand Down
5 changes: 5 additions & 0 deletions src/_internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* @see https://github.com/microsoft/TypeScript/issues/14829#issuecomment-504042546
* TS>=5.4
*/
export type NoInfer<T> = [T][T extends any ? 0 : never];
19 changes: 18 additions & 1 deletion src/updateDefault.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
import { NoInfer } from './_internal';
import set from './set';

interface updateDefaultFn {
<K, V>(
map: ReadonlyMap<K, V>,
key: K,
defaultValue: NoInfer<V>,
updater: (prev: V, key: K, map: ReadonlyMap<K, V>) => V,
): ReadonlyMap<K, V>;

<K, V, D = never>(
map: ReadonlyMap<K, V>,
key: K,
defaultValue: V | D,
updater: (prev: V | D, key: K, map: ReadonlyMap<K, V>) => V,
): ReadonlyMap<K, V>;
}

/**
* Creates new map from input `map` by updating value in the given `key` with
* the given callback `updater()`. The given `defaultValue` will be used as
Expand Down Expand Up @@ -45,4 +62,4 @@ function updateDefault<K, V, D = never>(
);
}

export default updateDefault;
export default updateDefault as updateDefaultFn;
46 changes: 46 additions & 0 deletions test/deep/updateDefaultDeep.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,49 @@ it("does nothing when it's nothing to change", () => {
expect(noop).toHaveBeenNthCalledWith(2, origA);
expect(prev).toEqual(orig);
});

describe('default is subtype', () => {
interface Patch {
n?: number;
s?: string;
}
type M = ReadonlyMap<number, ReadonlyMap<number, Patch>>;

const orig: M = new Map();

it('full', () => {
const next = updateDefaultDeep(orig, [10, 20], { n: 1, s: '' }, (p) => {
const { n, s } = p;
return { n, s };
});
const m: M = next;
expect(m.has(10)).toBeTruthy();
});

it('partial', () => {
const next = updateDefaultDeep(orig, [10, 20], { n: 1 }, (p) => {
const { n, s } = p;
return { n, s };
});
const m: M = next;
expect(m.has(10)).toBeTruthy();
});

it('{}', () => {
const next = updateDefaultDeep(orig, [10, 20], {}, (p) => {
const { n, s } = p;
return { n, s };
});
const m: M = next;
expect(m.has(10)).toBeTruthy();
});

it('alt', () => {
const next = updateDefaultDeep(orig, [10, 20], null, (p) => {
const { n, s } = p || {};
return { n, s };
});
const m: M = next;
expect(m.has(10)).toBeTruthy();
});
});
51 changes: 51 additions & 0 deletions test/updateDefault.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,54 @@ it("alternate 'default' type", () => {
const expected2: ReadonlyMap<number, string> = next2;
expect(expected2.get(42)).toBe('x*');
});

describe('default is subtype', () => {
interface Patch {
n?: number;
s?: string;
}
type M = ReadonlyMap<number, Patch>;

const orig: M = new Map();

it('full', () => {
const next = updateDefault(orig, 10, { n: 1, s: '' }, (p) => {
const { n, s } = p;
return { n, s };
});
const m: M = next;
expect(m.has(10)).toBeTruthy();
});

it('partial', () => {
const next = updateDefault(orig, 10, { n: 1 }, (p) => {
// `p` must be `Patch` here, not `Patch | { n:number }`,
// so `s` is known
const { n, s } = p;
return { n, s };
});
const m: M = next;
expect(m.has(10)).toBeTruthy();
});

it('{}', () => {
const next = updateDefault(orig, 10, {}, (p) => {
// `p` must be `Patch` here, not `Patch | {}`,
// so `n` and `s` are known
const { n, s } = p;
return { n, s };
});
const m: M = next;
expect(m.has(10)).toBeTruthy();
});

it('alt', () => {
const next = updateDefault(orig, 10, null, (p) => {
// `p` must be `Patch | null` here
const { n, s } = p || {};
return { n, s };
});
const m: M = next;
expect(m.has(10)).toBeTruthy();
});
});

0 comments on commit c679fc5

Please sign in to comment.