Skip to content

Commit

Permalink
fix: type guard handing of empty Map/Set
Browse files Browse the repository at this point in the history
Previously an iterable (array,set,map) was considered per default value if empty size, however we checked always for `.length` which isn't available in Set/Map. This commit fixes it and uses `.size` correctly.
  • Loading branch information
marcj committed Apr 7, 2024
1 parent 58042a9 commit da4cf82
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 5 deletions.
23 changes: 22 additions & 1 deletion packages/bson/tests/type-spec.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
Type,
typeOf,
TypePropertySignature,
UUID
UUID,
} from '@deepkit/type';
import { expect, test } from '@jest/globals';
import { deserializeBSON } from '../src/bson-deserializer.js';
Expand Down Expand Up @@ -1047,3 +1047,24 @@ test('array with mongoid', () => {
references: [{ cls: 'User', id: '5f3b9b3b9c6b2b1b1c0b1b1b' }]
});
});

test('Map part of union', () => {
type T1 = null | Map<Date, number>;
type T2 = null | { tags: Map<Date, number> };

expect(roundTrip<T1>(null)).toBe(null);
expect(roundTrip<T2>(null)).toBe(null);

{
const date = new Date;
const map = new Map<Date, number>([[date, 1]]);
expect(roundTrip<T1>(map)).toEqual(map);
expect(roundTrip<T2>({ tags: map })).toEqual({ tags: map });
}

{
const map = new Map<Date, number>();
expect(roundTrip<T1>(map)).toEqual(map);
expect(roundTrip<T2>({ tags: map })).toEqual({ tags: map });
}
});
4 changes: 4 additions & 0 deletions packages/core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,10 @@ export function isIterable(value: any): boolean {
return isArray(value) || value instanceof Set || value instanceof Map;
}

export function iterableSize(value: Array<unknown> | Set<unknown> | Map<unknown, unknown>): number {
return isArray(value) ? value.length : value.size || 0;
}

/**
* Returns __filename, works in both cjs and esm.
*/
Expand Down
27 changes: 25 additions & 2 deletions packages/core/tests/core.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,19 @@ import {
isClassInstance,
isConstructable,
isFunction,
isIterable,
isNumeric,
isObject,
isPlainObject,
isPromise,
isPrototypeOfBase,
isUndefined,
range,
iterableSize,
rangeArray,
setPathValue,
sleep,
stringifyValueWithType, zip
stringifyValueWithType,
zip,
} from '../src/core.js';

class SimpleClass {
Expand Down Expand Up @@ -581,3 +583,24 @@ test('zip', () => {
expect(zip([1, 2], ['a', 'b', 'c'])).toEqual([[1, 'a'], [2, 'b']]);
expect(zip([1, 2, 3], ['a', 'b', 'c'], [true, false, true])).toEqual([[1, 'a', true], [2, 'b', false], [3, 'c', true]]);
});

test('isIterable', () => {
expect(isIterable([])).toBe(true);
expect(isIterable({})).toBe(false);
expect(isIterable(new Map())).toBe(true);
expect(isIterable(new Set())).toBe(true);
expect(isIterable(new WeakMap())).toBe(false);
expect(isIterable(new WeakSet())).toBe(false);
expect(isIterable(new Uint8Array())).toBe(false);
expect(isIterable(new Error())).toBe(false);
});

test('iterableSize', () => {
expect(iterableSize([])).toBe(0);
expect(iterableSize([1, 2, 3])).toBe(3);
expect(iterableSize(new Map())).toBe(0);
expect(iterableSize(new Map([[1, 2], [3, 4]]) as any)).toBe(2);
expect(iterableSize(new Set())).toBe(0);
expect(iterableSize(new Set([1, 2, 3]) as any)).toBe(3);
});

5 changes: 3 additions & 2 deletions packages/type/src/serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
isNumeric,
isObject,
isObjectLiteral,
iterableSize,
stringifyValueWithType,
toFastProperties,
} from '@deepkit/core';
Expand Down Expand Up @@ -1467,7 +1468,7 @@ export function serializeArray(type: TypeArray, state: TemplateState) {
}

export function typeGuardArray(elementType: Type, state: TemplateState) {
state.setContext({ isIterable });
state.setContext({ isIterable, iterableSize });
const v = state.compilerContext.reserveName('v');
const i = state.compilerContext.reserveName('i');
const item = state.compilerContext.reserveName('item');
Expand All @@ -1476,7 +1477,7 @@ export function typeGuardArray(elementType: Type, state: TemplateState) {
let ${v} = false;
let ${i} = 0;
if (isIterable(${state.accessor})) {
${v} = ${state.accessor}.length === 0;
${v} = iterableSize(${state.accessor}) === 0;
for (const ${item} of ${state.accessor}) {
${executeTemplates(state.fork(v, item).extendPath(new RuntimeCode(i)), elementType)}
if (!${v}) break;
Expand Down
24 changes: 24 additions & 0 deletions packages/type/tests/type-spec.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -797,3 +797,27 @@ test('array with mongoid', () => {
references: [{ cls: 'User', id: '5f3b9b3b9c6b2b1b1c0b1b1b' }]
});
});

test('Map part of union', () => {
type T1 = null | Map<Date, number>;
type T2 = null | { tags: Map<Date, number> };

expect(roundTrip<T1>(null)).toBe(null);
expect(roundTrip<T2>(null)).toBe(null);

{
const date = new Date;
const map = new Map<Date, number>([[date, 1]]);
expect(serializeToJson<T1>(map)).toEqual([[date.toJSON(), 1]]);
expect(roundTrip<T1>(map)).toEqual(map);
expect(roundTrip<T2>({ tags: map })).toEqual({ tags: map });
}

// empty map
{
const map = new Map<Date, number>();
expect(serializeToJson<T1>(map)).toEqual([]);
expect(roundTrip<T1>(map)).toEqual(map);
expect(roundTrip<T2>({ tags: map })).toEqual({ tags: map });
}
});

0 comments on commit da4cf82

Please sign in to comment.