-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Type guards in Array.prototype.filter #7657
Comments
The required overload is interface Array<T> {
filter<U extends T>(pred: (a: T) => a is U): U[];
} (which you can add to your codebase today to make this work) |
Wow, I didn't realize it would be that easy. It doesn't fix the first case (presumably because the function is not getting recognized as a type guard function), but it does work with a cast: let numbers: number[] =
[1, '2', 3, '4']
.filter(<(x) => x is number>(x => typeof x == 'number')); Would inferring that |
The problem I ran into with adding the overload into |
Looks to be the same as #2835 |
(context: #2835 was long before we had type guards) |
if someone can patch this to lib.d.ts, i could get rid of so many asserts in my code. |
Is there some place to read up about the "is" in @RyanCavanaugh's overloading code above? Couldn't find it anywhere, neither in this issue about reserved keywords nor on the pages linked from there. |
Thank you! EDIT: Okay, my bad - I've mistaken this comment in the "is-keyword" issue for a suggestion rather than an existing solution. |
Documentation for user defined type guards is available in the language handbook at: http://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards |
Yes, now that I know the name of the feature I was already able to find information about it. Thanks anyway. :) |
Wow 👍 to add @RyanCavanaugh 's suggestion in the lib.d.ts. I was having something like:
It is magical. ❤️ |
@calebegg You can just add let numbers: number[] =
[1, '2', 3, '4']
.filter((x):x is number => typeof x == 'number'); In principle this would allow the compiler to check that |
A few more cases (using TS2.0): // setup
class A { type = 'A' }
class B { type = 'B' }
type C = A | B
function isA(_): _ is A { return _.type === 'A' }
const cs: C[] = [new A(), new B(), new B()]
// (1) - OK
let as = cs.filter(isA)
// (2) - Not OK - type is still C[]
let as = cs.filter(_ => isA(_))
// (3) - Not OK - type is still C[]
let bs = cs.filter(_ => !isA(_)) |
Does this work with discriminated unions? My understanding is that, with @RyanCavanaugh's overload, you still need user defined type guards. E.g: const numbers: number[] = [1, '2', 3, '4']
.filter((x):x is number => typeof x == 'number')
.map(y => y) Surely with TypeScript 2's discriminated union types, we can drop the type guard here somehow? I'm not sure how, though! const numbers: number[] = [1, '2', 3, '4']
.filter(x => typeof x == 'number')
.map(y => y) |
@RyanCavanaugh I'm also curious how this will help when you're filtering on properties, e.g.: // https://github.com/Microsoft/TypeScript/issues/8123
// Will be fixed in TS 2.1
declare global {
interface Array<T> {
filter<U extends T>(pred: (a: T) => a is U): U[];
}
}
const numbers: number[] = [1, '1']
.filter((x): x is number => typeof x == 'number')
.map(y => y) // y is number, good!
const numbers2: number[] = [{ foo: 1 }, { foo: '1' }]
.filter(x => typeof x.foo == 'number')
.map(y => y.foo) // y.foo is number | string, not good! |
You want |
Thank you! Any ideas where discriminated unions will/can help here, or do we have to provide user defined type guards? |
I'm trying to get something similar working in // definition 1
export declare function filter<T>(
this: Observable<T>,
predicate: (value: T, index: number) => boolean,
thisArg?: any
): Observable<T>;
// definition 2
export declare function filter<T, S extends T>(
this: Observable<T>,
predicate: (value: T, index: number) => value is S,
thisArg?: any
): Observable<S>; Definition 2 looks like it's trying to implement the Here's a contrived example of using a type guard with interface Action { type: string; }
class FooAction implements Action { type = 'FOO'; name = 'Bob'; }
function isFooAction(action: Action): action is FooAction {
return action && action.type === 'FOO';
}
var bob$: Observable<Action> = someActionObservable
.filter(isFooAction) // I'd like this to cause map to see a FooAction instead of just Action below
.map(foo => foo.name) // foo is FooAction with definition 1 commented-out, Action otherwise
.filter(foo => foo.name === 'Bob'); // fails to compile without definition 1 Is there a magic type definition tweak I can make to avoid cluttering the method chain with unnecessary casts? It seems like there should be some way to flow the type info through method chains like this without a conflict. UPDATE: Oops - the UPDATE 2: Nevermind, I found a solution that only requires updating the type definitions. I'm preparing a pull request for rxjs now. |
I have tested this with TS 2.1.1. const roArr: ReadonlyArray<string | null> = ["foo", null];
const fRoArr = roArr.filter((i): i is string => typeof i === "string");
// -> fRoArr inferred as string[]
const arr: Array<string | null> = ["foo", null];
const fArr = arr.filter((i): i is string => typeof i === "string");
// -> fArr inferred as (string | null)[] |
How can you use type predicates to negate a type? For example: [1, 'foo', true].filter((x): x is not number => typeof x !== 'number') |
Not ideal, but this works for primitives. Although, it might be easier to leave that unguarded. type NotNumber = Object | string | symbol | undefined | null | boolean | Function;
function notNumber(x: any): x is NotNumber {
return typeof x !== 'number';
} |
@mhegazy @RyanCavanaugh Possible for one of you guys to chime in here? It's a lot of friction to have to (a) explicitly parametrize or (b) explicitly type guard every |
I tried the type guard as follows with no luck: type FooBar = Foo | Bar
interface Foo {
type: 'Foo'
}
interface Bar {
type: 'Bar'
}
const fooBars: Array<FooBar> = [
{ type: 'Foo' },
{ type: 'Bar' },
]
const foos: Array<Foo> = fooBars.filter(
(item): item is Foo => item.type === 'Foo'
) |
Is this related to this problem? Make sure to enable strictNullChecks. I would expect the control flow here to know that in the .map, the value is number and not undefined. If this is the same issue, is there any plans to make this scenario work in the near-ish future? |
This issue has been fixed by #16223 and can be closed. You can try it out in |
@maiermic: Are you sure? I still get an error on this code: ['a', undefined].filter((e): e is string => !!e)[0].substring(0, 10);
Playground (be sure to check "strictNullChecks" in options to see the error) |
@calebegg That looks like a bug to me. Your example works if you use a function declaration instead of the lambda: ['a', undefined].filter(isString)[0].substring(0, 10);
function isString(e): e is string {
return !!e
} |
@NaridaL Interesting, thanks for identifying the issue! |
TypeScript Version:
nightly (1.9.0-dev.20160323)
Code
Playground
Expected behavior:
.filter with a type guard returns an array of the specified type. This would be especially useful with
--strictNullChecks
, so we could do something likefoo.map(maybeReturnsNull).filter(x => x != null)....
Actual behavior:
.filter returns an array of the same type, and the two
let
s above are errors.The text was updated successfully, but these errors were encountered: