simontonsoftware / s-libs

A collection of libraries for any of javascript, rxjs, or angular.
MIT License
43 stars 5 forks source link

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

Open eric-simonton-sama opened 2 years ago

ersimont commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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>();
      }