s-libs icon indicating copy to clipboard operation
s-libs copied to clipboard

[micro-dash] Feature request: make `isEmpty()` a type guard

Open eric-simonton-sama opened this issue 4 years ago • 3 comments

eric-simonton-sama avatar Nov 01 '21 14:11 eric-simonton-sama

While many cases could be covered nicely with a simple implementation, it would be complicated to make correct in all cases. Take this for example:

type EmptyObject = Record<any, never>;
declare function isEmpty(
  value: any,
): value is Nil | Primitive | readonly [] | EmptyObject;

declare const numOrArray: number | number[];
if (isEmpty(numOrArray)) {
  // should narrow to `1 | []`
  // but is `number`
}

ersimont avatar Nov 03 '21 01:11 ersimont

The test I started to write before deciding other things have priority:

  it('has fancy typing', () => {
    interface O {
      a: Date;
    }
    const o = {} as O;

    const oOrN = o as O | null;
    if (isEmpty(oOrN)) {
      expectTypeOf(oOrN).toEqualTypeOf<null>();
    } else {
      expectTypeOf(oOrN).toEqualTypeOf<O>();
    }

    const oOrE = o as O | {};
    if (isEmpty(oOrE)) {
      expectTypeOf(oOrE).toEqualTypeOf<{}>();
    } else {
      expectTypeOf(oOrE).toEqualTypeOf<O>();
    }

    const numOrArray = 1 as number | number[];
    if (isEmpty(numOrArray)) {
      expectTypeOf(numOrArray).toEqualTypeOf<number | []>();
    } else {
      expectTypeOf(numOrArray).toEqualTypeOf<number[]>();
    }
  });

ersimont avatar Nov 03 '21 23:11 ersimont

Some more tests. These are easy to get passing, but that last one above is not!

      const obj = {} as {} | Date;
      if (isEmpty(obj)) {
        expectTypeOf(obj).toEqualTypeOf<{}>();
      } else {
        expectTypeOf(obj).toEqualTypeOf<Date>();
      }

      const eobj = {} as EmptyObject | Date;
      if (isEmpty(eobj)) {
        expectTypeOf(eobj).toEqualTypeOf<EmptyObject>();
      } else {
        expectTypeOf(eobj).toEqualTypeOf<Date>();
      }

      const robj = {} as Readonly<EmptyObject> | Date;
      if (isEmpty(robj)) {
        expectTypeOf(robj).toEqualTypeOf<Readonly<EmptyObject>>();
      } else {
        expectTypeOf(robj).toEqualTypeOf<Date>();
      }

      const ary = [] as [] | Date;
      if (isEmpty(ary)) {
        expectTypeOf(ary).toEqualTypeOf<[]>();
      } else {
        expectTypeOf(ary).toEqualTypeOf<Date>();
      }

      const rary = [] as readonly [] | Date;
      if (isEmpty(rary)) {
        expectTypeOf(rary).toEqualTypeOf<readonly []>();
      } else {
        expectTypeOf(rary).toEqualTypeOf<Date>();
      }

ersimont avatar Aug 27 '22 18:08 ersimont