is icon indicating copy to clipboard operation
is copied to clipboard

Improve type guard for `.all`

Open sindresorhus opened this issue 6 years ago • 12 comments

Issuehunt badges

It would be nice if this worked:

const a = foo();
const b = bar();

// Both `a` and `b` is an `object`, but TS only knows they're `any`.

if (is.all(is.object, a, b)) {
	// `a` and `b` is now known by TS as `object`
}

There is a $30.00 open bounty on this issue. Add more on Issuehunt.

sindresorhus avatar Feb 12 '19 07:02 sindresorhus

That’s possible I guess if there’s a way to know if the predicate is a type guard predicate

gioragutt avatar Feb 12 '19 11:02 gioragutt

We don’t need to know it’s a type guard. We can just pass on what TS infers, maybe by using the infer keyword: https://dev.to/miracleblue/how-2-typescript-serious-business-with-typescripts-infer-keyword-40i5

sindresorhus avatar Feb 12 '19 12:02 sindresorhus

Someone mentions the "built in ReturnType<fn>" type, this might help...

altho, following this issue:

interface Array<T> {
    filter<U extends T>(pred: (a: T) => a is U): U[];
}
type Predicate<T> = (value: unknown) => value is T;

is.all = <T>(predicate: Predicate<T>, ...values: unknown[]): values is T[] =>
  predicateOnArray(Array.prototype.every, predicate, values);

does this look right?

gioragutt avatar Feb 12 '19 21:02 gioragutt

Looks good. Can you do a PR? :)

sindresorhus avatar Feb 18 '19 02:02 sindresorhus

@issuehunt has funded $30.00 to this issue.


IssueHuntBot avatar Mar 20 '19 08:03 IssueHuntBot

I ran into this recently and tried the suggested fix, it looks like this isn't possible generically.

export type Predicate<T = unknown> = (value: unknown) => value is T;

is.all = <T>(predicate: Predicate<T>, ...values: unknown[]): values is T[] =>
	predicateOnArray(Array.prototype.every, predicate, values);

Gives the error A type predicate cannot reference a rest parameter.ts (1229).

Apparently it also isn't possible to do this manually for a few arguments. This is a compile error (and all the other variants I tried).

function test(a: unknown, b: unknown): (a is true) & (b is true) {
    return true;
}

Gerrit0 avatar May 09 '19 22:05 Gerrit0

@Gerrit0 Thanks for looking into it!

to what line does this error refer to? is it thrown from predicateOnArray or all? Did you try to update predicateOnArray to have the second parameter be of type Predicate<T> as well?

gioragutt avatar May 10 '19 11:05 gioragutt

The error is caused by values is T[] on all. Updating Predicate in the predicateOnArray function unfortunately has no effect since the type guard is lost (changed to just be boolean) when it is used in all (or any).

It is possible to achieve the goal in the OP with a different signature. This works as expected (no changes necessary for predicateOnArray).

is.allArray = <T>(predicate: Predicate<T>, values: unknown[]): values is T[] =>
	predicateOnArray(Array.prototype.every, predicate, values)

Gerrit0 avatar May 10 '19 16:05 Gerrit0

Any update on this issue ?

nicosayer avatar Feb 11 '22 16:02 nicosayer

@nicosayer I think you can use is.array(value, predicate) for that, does it help you?

gioragutt avatar Feb 14 '22 16:02 gioragutt

@nicosayer I think you can use is.array(value, predicate) for that, does it help you?

I am not sure how this helps, can you elaborate ?

To make sure everything is clear, this is how I would summarize the issue:

What is currently happening:

let a: string | number
let b: string | number

if (is.all(is.string, a, b)) {
  // `a` and `b` are of type string | number
}

What is actually expected:

let a: string | number
let b: string | number

if (is.all(is.string, a, b)) {
  // `a` and `b` are of type string
}

nicosayer avatar Feb 15 '22 08:02 nicosayer

@nicosayer yeah my bad, I've been out of context for a while and thought it would work, but it doesn't type-guard the specific array items

gioragutt avatar Feb 15 '22 09:02 gioragutt