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

Improve `Includes` type

Open benzaria opened this issue 6 months ago • 10 comments

  • Fully supports optional elements (?) → returns boolean if the match is optional.
  • Full Supports for Arrays (Rest element)
  • Correctly handles required undefined separately from optional (?).
Feature Old New
Optional element support
Required undefined distinction
Rest element support
Result precision true/false true/false/boolean
Edge case coverage limited complete

benzaria avatar Sep 28 '25 18:09 benzaria

After consideration a recursive types is better to cover the rest element cases. if there is any trick that can be used in the first maped object type to handle the rest element, whoud that be better ?

benzaria avatar Sep 29 '25 20:09 benzaria

Suggestions for some more tests:

expectType<Includes<readonly [1, 2?, 3?], 2>>(boolean);

expectType<Includes<[1?, 2?, 3?], 1>>(boolean);
expectType<Includes<[1?, 2?, 3?], 4>>(false);

expectType<Includes<[unknown], unknown>>(true);
expectType<Includes<[unknown], string>>(false);
expectType<Includes<[1, unknown], unknown>>(true);

expectType<Includes<[null], null>>(true);
expectType<Includes<[null], undefined>>(false);

expectType<Includes<[true, false], true>>(true);
expectType<Includes<[true, false], boolean>>(false);
expectType<Includes<[boolean], boolean>>(true);

sindresorhus avatar Oct 10 '25 03:10 sindresorhus

@sindresorhus

expectType<Includes<[true, false], boolean>>(false);

Umm, this should be true, because:

Includes<[true, false], boolean>
=> Includes<[true, false], true> | Includes<[true, false], false>
=> true | true
=> true

expectType<Includes<[boolean], boolean>>(true);

And, this should be boolean, because:

Includes<[boolean], boolean>
=> Includes<[true], true> | Includes<[true], false> | Includes<[false], true> | Includes<[false], false>
=> true | false | false | true
=> boolean

som-sm avatar Oct 13 '25 05:10 som-sm

@som-sm Includes here is exact-equality (IsEqual) based. No assignability, no distribution over Item. So I feel like this is correct:

  • Includes<[true, false], boolean> ⇒ false*
  • Includes<[boolean], boolean> ⇒ true
  • It only returns boolean when uncertainty is introduced by optionals/rest.

sindresorhus avatar Oct 13 '25 05:10 sindresorhus

@som-sm Includes here is exact-equality (IsEqual) based. No assignability, no distribution over Item. So I feel like this is correct:

@sindresorhus Umm...ok gotcha, but in my experience distributing is usually a good idea. I'm not sure of good use cases for this type, but here's a quick, contrived example:

declare function includes<
	const T extends readonly unknown[],
	const Target extends Includes<T, Target> extends false ? never : unknown,
>(array: T, target: Target): void;

includes([1, 2, 3] as [1, 2, 3 | 4], 4); // Errors, but shouldn't
// Argument of type '4' is not assignable to parameter of type 'never'

@benzaria Could you share some practical, real-world examples for this type?

som-sm avatar Oct 13 '25 07:10 som-sm

I intentionally designed Includes to be strict following the original behavior.

But @som-sm had a good point of adding distribution, which can be useful.

I can add an option, that triggers it and make it false by default.

@sindresorhus WDYT

benzaria avatar Oct 13 '25 21:10 benzaria

Yeah, I think a {distributeItem} option could be useful, but it should be off by default.

sindresorhus avatar Oct 14 '25 05:10 sindresorhus

Missed this, it should be distributeItem singular, not plural. We are distributing the "Item" type.

sindresorhus avatar Oct 19 '25 04:10 sindresorhus

Missed this, it should be distributeItem singular, not plural. We are distributing the "Item" type.

I did notice that, but in this case, we’re actually distributing both the Item type and the array element types — that’s why I chose the plural form. Would you still prefer keeping it singular for consistency?

benzaria avatar Oct 19 '25 17:10 benzaria

I still prefer distributeItem (singular). While array elements do get distributed, that's an implementation detail of the comparison logic. The option specifically controls whether to distribute the Item parameter. The name should describe what the user is controlling (distribution of the Item type), not all the internal mechanics.

sindresorhus avatar Oct 19 '25 18:10 sindresorhus