Trying to reach peak TypeScript performance I'm currently getting into some of the niche areas of the language, and there's something I don't get.
With strict-null-checks and strict mode and that stuff enabled, I don't understand why this code gives me an error:
function filterNullable<T>(arr: T[]): NonNullable<T>[] {
const ret: NonNullable<T>[] = [];
arr.forEach(el => {
if (el !== null && el !== undefined) {
ret.push(el); // TS2345: Argument of type 'T' is not assignable to parameter of type 'NonNullable '.
}
})
return ret;
}
Clearly, even if T is a type like string | number | undefined, by the time I'm trying to push the filtered value into the array, it's pretty damn clear that it can't be undefined. Is TypeScript not smart enough to follow me here (I don't think so, because similar things work just fine), am I missing a crucial detail, or is the NonNullable type simply not as powerful as one might expect?
(I'm aware that there are more concise methods of doing what I'm doing here, however I need code with a similar logic to do more complicated things, so the Array.prototype.filter style method is not quite applicable)
-------Edit:
I kind of get the problem now, however, instead of using type assertions in the push method, I'd then rather rewrite the whole thing to something like
function filterNullable2<T>(arr: Array<T | undefined | null>): T[] {
const ret: T[] = [];
arr.forEach(el => {
if (el !== null && el !== undefined) {
ret.push(el);
}
})
return ret;
}
which seems to do a better job. But it needs more writing.
What I'm really trying to do is find a better way to write a utility for NGRX, as this is really annoying (thank god there isn't a third nullish type)
import {MemoizedSelector, Store} from "@ngrx/store";
import {filterNullable} from "../shared/operators";
import {Observable} from "rxjs";
export function selectDefined<T, S> (store: Store<T>, selector: MemoizedSelector<T, NonNullable<S>>): Observable<S>;
export function selectDefined<T, S> (store: Store<T>, selector: MemoizedSelector<T, NonNullable<S> | null>): Observable<S>;
export function selectDefined<T, S> (store: Store<T>, selector: MemoizedSelector<T, NonNullable<S> | undefined>): Observable<S>;
export function selectDefined<T, S> (store: Store<T>, selector: MemoizedSelector<T, NonNullable<S> | undefined | null>): Observable<S> {
return store.select(selector).pipe(filterNullable());
}
I'd hope to find a way to write this like
export function selectDefined<T, S> (store: Store<T>, selector: MemoizedSelector<T, S>): Observable<NonNullable<S>> {
return store.select(selector).pipe(filterNullable());
// TS2322: Type 'Observable<S>' is not assignable to type 'Observable<NonNullable<S>>'. Type 'S' is not assignable to type 'NonNullable<S>'.
}
which, unfortunately, does not work