You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Infer type guard => array.filter(x => !!x) should refine Array<T|null> to Array<T> #16069
For filter specifically, #7657 would be something we can do here. but that still requires you to write an explicit type guard annotation somewhere, (x: number| null): x is number somewhere.
the other part is the compiler figuring out that type guard automatically from the function body, but that is not a trivial, #5101 tracks some aspects of that.
Reacted by Oleh Dutchenko, Bell King and Gavin Golden
Reacted by Oleh Dutchenko and Bell King
Reacted by Aleksandr Chernyavenko, Oleh Dutchenko and Bell King
Reacted by Oleh Dutchenko, Bell King and Simon Schick
Reopening to track this since both other issues now target lib.d.ts changes
Reacted by Aluan Haddad, JasonKleban, Kerem Kat, Sean Poulter, Jacob Massey, Moritz Mahringer, Ian MacLeod, Reyad Attiyat, Josh Manning, Bell King and 8 more
Reacted by Stéphane Trebel, Aluan Haddad, David Rodrigue, Oliver Eisenhut, Lazar Ljubenović, Kerem Kat, Minh, Aleksey Levenstein, Sean Poulter, Jacob Massey and 8 more
Subscribing since this is a common pattern I use after a map
Reacted by Tomáš Hübelbauer, Jacob Massey, Jim Schneidereit, Mad Mirrajabi, Tony Gentilcore, Carson McKinstry, Andrei F, Marvin Heilemann, Breck Yunits, Willem and 16 more
// I wish I could just do the following:
let result = ["", 3,null, undefined].filter(x => x != null);
Instead, you can do this:
// Create this helper function
function isNotNullOrUndefined<T extends Object>(input: null | undefined | T): input is T {
return input != null;
}
// This actually determines that result is of type (string|number)[]
let result = ["", 3,null, undefined].filter(isNotNullOrUndefined);
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 input is T then it allows filter to utilize the control flow features of the type checking to discriminate the union.
That being said, I hope that we can get this type guard inference into the language. :)
Reacted by Thomas Boyt, Eugene Lubarsky, Esa-Matti Suuronen, Lokdei, Leandro Aguiar, Mike Hays, An Phi, Micah Zoltu, Glavin Wiechert, daftcoder and 21 more
Reacted by Glavin Wiechert, James H. and Lukas von Dollen
Reacted by Glavin Wiechert, John Datserakis and Laurence Lord
changed the title [-]array.filter(x => !!x) should refine Array<T|null> to Array<T>[/-][+]Infer type guard => array.filter(x => !!x) should refine Array<T|null> to Array<T>[/+]on May 2, 2018
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).
Martin Johns (@MartinJohns) All variables are well typed (maybe some in context in the example above). Do you mean casting or use not-null assertion? Then I would either break the chain and move the first result in a new not-null variable. Otherwise I have to do it (cast, assert) everywhere again and again. I just wanted another example for this topic here (Infer type guard from filter). But I understand the problem. Not sure if TypeScript can fix that. Because the callback must explicitly set the return type like a type-guard does. And TS does not know that's inside the callback. But the TS lang devs know it better for sure. Idk. Spontaneous ideas (not tested): Wrap filter fn in a type-guard fn or use an own custom function to filter array with condition fn which returns type guarded type (input is T from T | undefined | null or unknown). Something like arr.filter(notNull((value) => doStuff())) or notNull as standalone filter. ...
typeMyObj={data?: string};typeMyArray={list?: MyObj[]}[];constmyArray: MyArray=[];constresult=myArray.map((arr)=>arr.list).filter((arr): arr is MyObj[]=>!!(arr&&arr.length)).map((arr)=>arr.filter((obj): obj is Required<MyObj>=>!!(obj&&obj.data)).map(obj=>JSON.parse(obj.data)));
Second example
array.filter((item): item is WhateverTheTypeOfArrayItemIs[]=>item?.length)
infacto Actually we can make first example work with current state of things using some more complex typeguards Playground
typeMyObj={data?: string};typeMyArray={list?: MyObj[]}[];constmyArray: MyArray=[];constisNotNullish=<T>(input: T|null|undefined): input is T=>input!=nullconstisNotEmpty=<T>(input: T[])=>input.length!==0;functionboth<I,O1extendsI>(predicate1: (input: I)=>boolean,predicate2: (input: I)=>input is O1,): (input: I)=>input is O1;functionboth<I,O1extendsI>(predicate1: (input: I)=>input is O1,predicate2: (input: O1)=>boolean,): (input: I)=>input is O1;functionboth<I,O1extendsI,O2extendsO1>(predicate1: (input: I)=>input is O1,predicate2: (input: O1)=>input is O2,): (input: I)=>input is O2;functionboth<I>(predicate1: (input: I)=>boolean,predicate2: (input: I)=>boolean,){return(input: I)=>predicate1(input)&&predicate2(input)}typeHasNonNullishPredicate<Pextendsstring>=<Textends{[KinP]?: unknown}>(obj: T)=>obj is T&{[KinP]: Exclude<T[K],undefined|null>};functionhasNonNullish<Pextendsstring>(prop: P): HasNonNullishPredicate<P>;functionhasNonNullish(prop: string){return(input: object)=>propininput&&(inputasany).prop!=null;// this `as any` cast will be unnecessary in TSv4.9}constresult=myArray.map((arr)=>arr.list).filter(both(isNotNullish,isNotEmpty)).map((arr)=>arr.filter(hasNonNullish('data')).map(obj=>JSON.parse(obj.data)));
Artem Baranov (@temoncher) Thanks for your input. Helpful stuff. Somehow I hadn't thought of the return value of the arrow function in the filter method. 🤦♂️ Only indirect. Yes, makes sense. ^^ But anyway, it would be great if TypeScript could just infer that for less and cleaner code.
Reacted by Artem Baranov, Alexander Lichter, Daniel Lamando, Samuel Fernández and Dan Findley
Sorry if I disturb you. I'm facing this problem again, where infer type guard for filter function would be awesome. Just another contribution post or thinking out loud.
To the example above:
context.filter((obj): obj is Required<MyObj>=>!!obj?.data)
This guards the objects to have all properties required. In this example, it's ok. But for more complex object with several (optional) properties should rather do that.
context.filter((obj): obj is MyObj&Required<Pick<MyObj,'data'>>=>!!obj?.data)
Pick only the tested properties. Otherwise you may obfuscate nullish properties. It's a really long johnny. Infer type would simply look like:
context.filter((obj)=>!!obj?.data)
Now I craft many type guards to avoid such long complex type defs. I mean this is just a simple example. The real world code type def would be 2 miles of code horizontal scoll. I thinking of a chain guard-type function like rxjs pipes. But it's either exhausting or unsafe (e.g. simple cast from unknown by assuming types). I don't want to rule out that I'm thinking too complicated and that there is a much simpler way. I just want to fix strict type issues. In best case without blind casting.
i hope anyone advocating for using is knows that's still inherently type unsafe
functionisPresent<T>(input: null|undefined|T): input is T{return!input;}constarr=["string",8,null,undefined].filter(isPresent);// expects ["string", 8]// but is instead [null, undefined]console.log(arr)
is is no different from as, so i still like using flatMap, especially with the benchmark in #16069 (comment) revealing that (the JIT compiler is allowing that) the performance cost is smaller than linear
Reacted by Inga, Michael Schaufelberger, Justin Haaheim, Stephen Pittman, JDP, Tobi and Lavrans Bjerkestrand
Reacted by Marc Gray, Spencer Kaiser, Sepehr Sobhani and Jean-Luc Toutant
When will TypeScript's type inference be fixed to handle this simple example ?
// Types as `(o: string | undefined) => o is string`constmyGuard=(o: string|undefined): o is string=>!o;// Types as `(o: string | undefined) => boolean` but should type as `(o: string | undefined) => o is string`constmySecondGuard=(o: string|undefined)=>myGuard(o);
Reacted by Damian Senn, Deer404 and Dmytro
Reacted by Martin Johns, Hyunbin Seo, Mitchell Grice, Eugene Zaretskiy, MartinDavi, Mikko Porkola, Ethan Resnick, Yudai Nakata and erictheswift
Reacted by Anton Bessonov, Lonli-Lokli, Leonardo Gavaudan, Steve Weiss, Lizzi Lindboe, NicoVIII, Fabian Meyertöns, Matthias Vonend, ExE Boss, Adonis Kakoulidis and 12 more
Reacted by Nick Breaton, Paul Colleoni, ExE Boss, Adonis Kakoulidis, Maciej Holyszko, ヤコブ, Robin Goupil, Morgan Touverey Quilling, Géry Ogam, Andreas Källberg and 3 more
constmyGuard=(o: string|undefined): o is string=>!o;constemptyString='';if(!myGuard(emptyString)){// according to the "if and only if" nature of type predicates,// the fact that the predicate failed means that `emptyString` // _must not_ be a string, but actually it is.}
diff --git a/node_modules/typescript/lib/lib.es5.d.ts b/node_modules/typescript/lib/lib.es5.d.ts
index a88d3b6..f091ecb 100644
--- a/node_modules/typescript/lib/lib.es5.d.ts+++ b/node_modules/typescript/lib/lib.es5.d.ts@@ -1268,6 +1268,7 @@ interface ReadonlyArray<T> {
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
*/
filter<S extends T>(predicate: (value: T, index: number, array: readonly T[]) => value is S, thisArg?: any): S[];
+ filter<F extends BooleanConstructor>(predicate: F, thisArg?: any): Exclude<T, false | null | undefined | '' | 0>[];
/**
* Returns the elements of an array that meet the condition specified in a callback function.
* @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.
@@ -1459,6 +1460,7 @@ interface Array<T> {
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
*/
filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
+ filter<F extends BooleanConstructor>(predicate: F, thisArg?: any): Exclude<T, false | null | undefined | '' | 0>[];
/**
* Returns the elements of an array that meet the condition specified in a callback function.
* @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.
Activity
mhegazy commentedon May 24, 2017
For filter specifically, #7657 would be something we can do here. but that still requires you to write an explicit type guard annotation somewhere,
(x: number| null): x is numbersomewhere.the other part is the compiler figuring out that type guard automatically from the function body, but that is not a trivial, #5101 tracks some aspects of that.
RyanCavanaugh commentedon Jun 29, 2017
Reopening to track this since both other issues now target lib.d.ts changes
dgreene1 commentedon Oct 27, 2017
Subscribing since this is a common pattern I use after a map
AlexGalays commentedon Nov 24, 2017
Just chiming in to say that this shouldn't be fixed just for Arrays.
This is very useful for all custom (container or not) types too.
dgreene1 commentedon Feb 28, 2018
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
input is Tthen it allows filter to utilize the control flow features of the type checking to discriminate the union.###################################################
That being said, I hope that we can get this type guard inference into the language. :)
[-]array.filter(x => !!x) should refine Array<T|null> to Array<T>[/-][+]Infer type guard => array.filter(x => !!x) should refine Array<T|null> to Array<T>[/+]benschulz commentedon May 2, 2018
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).
scott-ho commentedon May 25, 2018
Dan Greene (@dgreene1) I trid to write a simple operator to make the guard simpler to use, but failed. Do you have any suggestion?
Andy (Andrewkraft) (@Andy-MS) Mohamed Hegazy (@mhegazy) could you help to improve the types above?
Hotell commentedon May 25, 2018
Scott Ho (@scott-ho) https://twitter.com/martin_hotell/status/999707821980647424
you're welcome :) 💃
41 remaining items
MartinJohns commentedon Nov 25, 2022
infacto You could also just add a type annotation.
infacto commentedon Nov 28, 2022
Martin Johns (@MartinJohns) All variables are well typed (maybe some in context in the example above). Do you mean casting or use not-null assertion? Then I would either break the chain and move the first result in a new not-null variable. Otherwise I have to do it (cast, assert) everywhere again and again. I just wanted another example for this topic here (Infer type guard from filter). But I understand the problem. Not sure if TypeScript can fix that. Because the callback must explicitly set the return type like a type-guard does. And TS does not know that's inside the callback. But the TS lang devs know it better for sure. Idk. Spontaneous ideas (not tested): Wrap filter fn in a type-guard fn or use an own custom function to filter array with condition fn which returns type guarded type (input is T from T | undefined | null or unknown). Something like
arr.filter(notNull((value) => doStuff()))ornotNullas standalone filter. ...temoncher commentedon Nov 28, 2022
infacto I think maybe Martin Johns (@MartinJohns) meant typing filter predicate as a typeguard like this
First example playground
Second example
But I want to admit I don't like this option
temoncher commentedon Nov 28, 2022
infacto Actually we can make first example work with current state of things using some more complex typeguards Playground
infacto commentedon Nov 29, 2022
Artem Baranov (@temoncher) Thanks for your input. Helpful stuff. Somehow I hadn't thought of the return value of the arrow function in the filter method. 🤦♂️ Only indirect. Yes, makes sense. ^^ But anyway, it would be great if TypeScript could just infer that for less and cleaner code.
infacto commentedon Dec 2, 2022
Sorry if I disturb you. I'm facing this problem again, where infer type guard for filter function would be awesome. Just another contribution post or thinking out loud.
To the example above:
This guards the objects to have all properties required. In this example, it's ok. But for more complex object with several (optional) properties should rather do that.
Pick only the tested properties. Otherwise you may obfuscate nullish properties. It's a really long johnny. Infer type would simply look like:
Now I craft many type guards to avoid such long complex type defs. I mean this is just a simple example. The real world code type def would be 2 miles of code horizontal scoll. I thinking of a chain guard-type function like rxjs pipes. But it's either exhausting or unsafe (e.g. simple cast from unknown by assuming types). I don't want to rule out that I'm thinking too complicated and that there is a much simpler way. I just want to fix strict type issues. In best case without blind casting.
robertmassaioli commentedon Dec 2, 2022
infacto in pretty sure that's what isPresentKey does from ts-is-present
mhmo91 commentedon Apr 20, 2023
This is pretty annoying issue that happens quite often. Can we raise priority on this one?
TheAlexLichter commentedon Apr 20, 2023
Leaving https://github.com/total-typescript/ts-reset/ here as a drop-in "fix" until this lands in TS itself.
btoo commentedon Apr 20, 2023
i hope anyone advocating for using
isknows that's still inherently type unsafeisis no different fromas, so i still like usingflatMap, especially with the benchmark in #16069 (comment) revealing that (the JIT compiler is allowing that) the performance cost is smaller than linearptitjes commentedon Jul 12, 2023
Hi all,
When will TypeScript's type inference be fixed to handle this simple example ?
JustinGrote commentedon Mar 15, 2024
Thank you Dan Vanderkam (@danvk) and Ryan Cavanaugh (@RyanCavanaugh)!
ethanresnick commentedon Jul 1, 2024
Didier Villevalois (@ptitjes) The issue with your example is that the first guard is not correct:
b-fett commentedon Jul 2, 2024
i use such a patch:
with