better-typescript-lib icon indicating copy to clipboard operation
better-typescript-lib copied to clipboard

Issue when using `object` with `Object.entries` and `Object.values`

Open ehoogeveen-medweb opened this issue 1 year ago • 3 comments
trafficstars

TS Playground

As shown above, this overload included in better-typescript-lib cause an issue when applied to an object without a specified key or value type. The overload seems to match too eagerly, returning a type with never instead of falling through to the more generic call signatures.

Here's one way to fix it, although I don't know if it's the best fix: TS Playground

The same fix seems to work for Object.entries as well.

Note: This issue is unrelated to https://github.com/uhyo/better-typescript-lib/pull/31 (but maybe worth mentioning there)

ehoogeveen-medweb avatar May 13 '24 15:05 ehoogeveen-medweb

For posterity and to avoid having to check the playground, the problematic overloads are:

interface ObjectConstructor {
  values<K extends PropertyKey, V>(o: Record<K, V>): V[];
  entries<K extends PropertyKey, V>(o: Record<K, V>): [string, V][];
}

and the proposed fix:

interface ObjectConstructor {
  values<K extends PropertyKey, V>(o: [V] extends [never] ? never : Record<K, V>): V[];
  entries<K extends PropertyKey, V>(o: [V] extends [never] ? never : Record<K, V>): [string, V][];
}

ehoogeveen-medweb avatar May 13 '24 15:05 ehoogeveen-medweb

This might be a better fix, targeting object and wider types specifically:

interface ObjectConstructor {
  values<K extends PropertyKey, V>(o: object extends Record<K, V> ? never : Record<K, V>): V[];
  entries<K extends PropertyKey, V>(o: object extends Record<K, V> ? never : Record<K, V>): [string, V][];
}

ehoogeveen-medweb avatar May 13 '24 15:05 ehoogeveen-medweb

Hmm, it seems that object and any record with never-typed keys mutually extend each other:

type foo = object extends Record<never, unknown> ? true : false; // true
type bar = Record<never, unknown> extends object ? true : false; // true

so to be minimally invasive I guess we want something like

interface ObjectConstructor {
  values<K extends PropertyKey, V>(o: [K, V] extends [never, never] ? never : Record<K, V>): V[];
  entries<K extends PropertyKey, V>(o: [K, V] extends [never, never] ? never : Record<K, V>): [string, V][];
}

so that records with never-type keys or never-type values still yield the expected result.

Putting it together with https://github.com/uhyo/better-typescript-lib/pull/31:

type KeyToString<K extends PropertyKey> = K extends string ? K : K extends number ? `${K}` : never;

interface ObjectConstructor {
  keys<K extends PropertyKey, V>(o: [K, V] extends [never, never] ? never : Record<K, V>): KeyToString<K>[];
  values<K extends PropertyKey, V>(o: [K, V] extends [never, never] ? never : Record<K, V>): V[];
  entries<K extends PropertyKey, V>(o: [K, V] extends [never, never] ? never : Record<K, V>): [KeyToString<K>, V][];
}

TS Playground

ehoogeveen-medweb avatar May 13 '24 19:05 ehoogeveen-medweb

A fix has been released as v2.8.0. Thank you for reporting!

uhyo avatar Jul 16 '24 14:07 uhyo