type-fest icon indicating copy to clipboard operation
type-fest copied to clipboard

Bug: ConditionalKeys<Base, Condition> fails when used on Arrays

Open KilianKilmister opened this issue 4 years ago • 1 comments

There is a bug in Typescript related to keyof Array that affects ConditionalKeys<Base, Condition> and the Types using it. (reporting the bug itself to typescript, aswell) It can be fixed adHoc without affecting any non.bugged existing uses

case:

Playground Link

type Arr = readonly [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

type WhoopMyArray_Faulty<Base extends ArrayLike<any>, Condition> = {  
		[Key in keyof Base]:
			Base[Key] extends Condition
				? 'Whoop it'
				: Base[Key] extends Base[number] ?  Base[Key] : never
	}

// action is performed on array elements but includes all values of Array.prototype
type WhoopedArrayKeys = WhoopMyArray_Faulty<Arr, 7 | 5 | 2>[keyof Arr]

So using ConditionalKeys<Base, Condition>

Playground Link

import { ConditionalKeys } from 'type-fest'

type WhoopedArray = readonly [0, 1, 'Whoop it', 3, 'Whoop it', 5, 'Whoop it', 7, 8, 9]
type WhoopedArrayKeys = ConditionalKeys<WhoopedArray, 'Whoop it'>

results in "2" | "4" | "6" and all Array.prototype-values

this can be fixed by adding a temporary dummy key to the UtilityType:

Playground Link

type WhoopedArray = [0, 1, 'Whoop it', 3, 'Whoop it', 5, 'Whoop it', 7, 8, 9]

export type ConditionalKeys<Base, Condition> = NonNullable< // TODO: report bug in ts as type-fest
	// Wrap in `NonNullable` to strip away the `undefined` type from the produced union.
	{
		// Map through all the keys of the given base type. NOTE: dummy-prop is needed for Arrays
		[Key in keyof Base | '__im-a-dummy-prop__']:
			// Pick only keys with types extending the given `Condition` type.
			Base[Exclude<Key, '__im-a-dummy-prop__'>] extends Condition
				// Retain this key since the condition passes.
				? Key
				// Discard this key since the condition fails.
				: never
	// Convert the produced object into a union type of the keys which passed the conditional test.
	}[keyof Base]
>;

type WhoopedArrayKeys = ConditionalKeys<WhoopedArray, 'Whoop it'>
// -> '2' | '4'  |  '6'

Took me ages to find out why my stuff isn't working

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • The funding will be given to active contributors.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

KilianKilmister avatar Aug 15 '20 14:08 KilianKilmister

This fix does not involve an arbitrary dummy key.

export type ConditionalKeys<Base, Condition> = {
	// Map through all the keys of the given base type.
	[Key in Exclude<keyof Base, never>]:
	// Pick only keys with types extending the given `Condition` type.
	Base[Key] extends Condition
	// Retain this key since the condition passes.
		? Key
	// Discard this key since the condition fails.
		: never;

	// Convert the produced object into a union type of the keys which passed the conditional test.
}[keyof Base];

0f-0b avatar Jun 01 '23 00:06 0f-0b