Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upInfer type guard => array.filter(x => !!x) should refine Array<T|null> to Array<T> #16069
Comments
For filter specifically, #7657 would be something we can do here. but that still requires you to write an explicit type guard annotation somewhere, |
Reopening to track this since both other issues now target lib.d.ts changes |
Subscribing since this is a common pattern I use after a map |
Just chiming in to say that this shouldn't be fixed just for Arrays. |
Here's a workaround:
Instead, you can do this:
I believe that the first snippet doesn't work due to TS not being able to automatically infer that the callback function inside the filter is a type guard. So by explicitly defining the function's return type as ################################################### That being said, I hope that we can get this type guard inference into the language. :) |
As I said in #10734 (comment), I'm very eager to see type guards inferred and have already implemented it prototypically for a subset of arrow expressions. If you get around to specifying the workings of type guard inference (i.e. the hard part), I'd gladly get involved in the implementation (the easier part). |
@dgreene1 I trid to write a simple operator to make the guard simpler to use, but failed. Do you have any suggestion? import { Observable } from 'rxjs';
import { filter } from 'rxjs/operators';
export function isNotNullOrUndefined<T>(input: null | undefined | T): input is T {
return input != null;
}
export function skipEmpty<T>() {
return function skipEmptyOperator(source$: Observable<T>) {
return source$.pipe(filter(isNotNullOrUndefined));
};
} @andy-ms @mhegazy could you help to improve the types above? |
@scott-ho https://twitter.com/martin_hotell/status/999707821980647424 |
@scott-ho, I'd check out the approach @Hotell shared. I wish I could help you more, but I'm not familiar with rxJs yet. So I'm not really sure what |
@Hotell Thanks for your guidance. It seems your solution only works in v2.8 or above. And I finally make it works import { Observable } from 'rxjs';
import { filter } from 'rxjs/operators';
export function isNotNullOrUndefined<T>(input: null | undefined | T): input is T {
return input != null;
}
export function skipEmpty<T>() {
return function skipEmptyOperator(source$: Observable<null | undefined | T>) {
return source$.pipe(filter(isNotNullOrUndefined));
};
} |
I thought I'll share my solution export type Empty = null | undefined;
export function isEmpty(value: any): value is Empty {
return [null, undefined].includes(value);
}
export function removeEmptyElementsFromArray<T>(array: Array<T | Empty>): T[] {
return array.filter((value) => !isEmpty(value)) as T[];
} example: const nums = [2, 3, 4, null] // type is (number | null)[]
const numsWithoutEmptyValues = removeEmptyElementsFromArray(nums); // type is number[] |
@pie6k As far as I can tell that's not a solution, that's merely your assertion ( |
@pie6k That Here's a cleaned up version that has no need for type assertions:
|
@samhh it's narrowing, but it's doing it safely as we're removing empty values before |
@pie6k It's not safe. You can prove this like so: export function removeEmptyElementsFromArray<T>(array: Array<T | Empty>): T[] {
return array as T[];
} This still compiles just as yours did. It's the assertion that's pleasing the compiler; you can remove the I don't think there's any way to generically type guard a negative, which is what your example needs to drop the assertion. |
I believe you're talking past one another. This |
Referencing #18122, because this is what is required for the example code there to work, and I can't comment there because it's |
This is quite a clean solution for this problem, using type predicates: function isNotNull<T>(it: T): it is NonNullable<T> {
return it != null;
}
const nullableNumbers = [1, null];
const nonNullableNumbers = nullableNumbers.filter(isNotNull); |
@kasperpeulen, if you make this mistake, it still type-checks:
Using a callback with a type predicate is not much safer than using a type assertion on the return value. And using an arrow function does not look clean at all: nullableItems.filter((it): it is NonNullable<typeof it> => it !== null) |
This is not a solution to this problem since this problem is all about |
I wrote this for people that wonder how they can filter all nulls from an array in a way that typescript understand. If you use this isNotNull as a utility function, then I think just writing However, I totally agree that it would be awesome if typescript can infer any type predicates itself. |
Wow! @robertmassaioli this is cool! function isPresent<T>(t: T | undefined | null | void): t is T {
return t !== undefined && t !== null;
}
const foo: Array<number | null> = [2,3, null, 4];
const bar = foo.filter(isPresent); // number[] |
Similar to Source: /**
* Builds a function that can be used to filter down objects
* to the ones that have a defined non-null value under a key `k`.
*
* @example
* ```ts
* const filesWithUrl = files.filter(file => file.url);
* files[0].url // In this case, TS might still treat this as undefined/null
*
* const filesWithUrl = files.filter(hasPresentKey("url"));
* files[0].url // TS will know that this is present
* ```
*/
export function hasPresentKey<
V,
K extends string | number | symbol,
T extends {}
>(
k: K
): (a: T & { [k in K]?: V | undefined | null }) => a is T & { [k in K]: V };
export function hasPresentKey<
V,
K extends string | number | symbol,
T extends {}
>(k: K): (a: T & { [k in K]?: V | undefined }) => a is T & { [k in K]: V } {
return (a): a is T & { [k in K]: V } => a[k] !== undefined && a[k] !== null;
} P.S. @robertmassaioli maybe we could add it to |
@jtomaszewski That looks like a good addition to Update from 21/Sep/2020: Okay, it seems that the above functions don't quite typecheck the way that we would like them to. PR in which we are investigating further here: robertmassaioli/ts-is-present#1 |
Anyways, I added the code in ailohq/ts-is-present@1f0457f . Feel free to look it up. Strangely I have some problems with |
@jtomaszewski I have moved the repository to Github. I want you to get credit in the commits so please feel free to try again there: https://github.com/robertmassaioli/ts-is-present |
Is this also the issue tracking that I would expect these two lines to be the same?
I understand the issue is that predicate-like functions aren't inferred as type guards, so any type narrowing that might happen inside it isn't available to the context? In this example, of course I can write |
@graup Seems like the same issue yes. |
TypeScript Version: 2.3
Code
with
strictNullChecks
enabled.Expected behavior:
This should type check. The type of
evenSquares
should benumber[]
.Actual behavior:
The type of
evenSquares
is deduced as(number|null)[]
, despite the null values being removed via the call to.filter
.