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
Meta-issue: Use Full Unification for Generic Inference? #30134
Today, TypeScript cannot retain or synthesize free type parameters during generic inference. This means code like this doesn't typecheck, when it should:
This approach would work only for really simple cases. If for example there is any constraint on the type parameter of identity we will get an error, and there really is no way to forward the generic type constraint. Also if the number of type parameters is variable we again have an issue, but this could be solved with a number of overloads.
Ryan Cavanaugh (@RyanCavanaugh)
Not sure if this is in scope here, but I have seen people often struggle with generic react components and HOCs. There really is no good way to write a HOC that forwards generic type parameters, maintains generic type constraints and removes some keys from props. Not sure if this will ever be possible but one can dream :). A simple example of what I am talking about:
typeOmit<T,KextendskeyofT>=Pick<T,Exclude<keyofT,K>>functionHOC<Pextends{hocProp: string}>(component: (p: P)=>void): (p: Omit<P,'hocProp'>)=>void{returnnull!;}// Works great for regulart components// const Component: (props: { prop: string }) => voidconstComponent=HOC(function(props: {prop: string,hocProp: string}){});// Curenly an error, ideally we could write HOC to get // const GenericComponent: <T extends number | string>(p: { prop: T }) => voidconstGenericComponent=HOC(function<Textendsnumber|string>(p: {prop: T,hocProp: string}){})
Reacted by Ryan Cavanaugh, Harlan, Lee Powell, Chayim Refael Friedman, Yue Liu, Kyle Herock, Ghabriel Nunes, SlurpTheo and Isaac Donoghue
Ryan Cavanaugh (@RyanCavanaugh) is #29791 another example of something that could be addressed by this? You mention the absence full unification in your comment there.
I'm currently running into this issue, or at least what I think is this issue. I have a class that extends an interface that uses generics. I have to explicitly type the parameters on methods in my class even though the type is already explicitly stated on the interface. If I don't, it complains that I didn't type it and calls it an any. If I type it incorrectly, it tells me the type is wrong, so clearly it knows what type it's supposed to be.
In FolderImplementation if I don't explicitly type folder it complains that Parameter 'folder' implicitly has an 'any' type. If I purposefully type it incorrectly (for example, as string) it complains that Type '(folder: string) => Promise<string>' is not assignable to type '(folder: WindowsFolder) => Promise<string>'.
Would this be fixed by this? It would be a lot less verbose if I didn't have to re-explicitly-type everything.
classPair<L,R>{constructor(readonlyleft: L,readonlyright: R){}}classPairBuilder<L,R>{constructor(readonlyr: R){}build(l: L){returnnewPair(l,this.r);}map<R2>(f: ((r1: R)=>R2)): PairBuilder<L,R2>{returnnewPairBuilder(f(this.r));}}constmakePairBuilder=<L>(r: string)=>newPairBuilder<L,string>(r);functionf0(): PairBuilder<string,string>{returnmakePairBuilder('hello');// resolves as expected// const makePairBuilder: <string>(r: string) => PairBuilder<string, string>}constbadPair=f0().build({});// type error as expected// Argument of type '{}' is not assignable to parameter of type 'string'.constpair=f0().build('world');// resolves as expected// const pair: Pair<string, string>functionf1(): PairBuilder<string,string>{returnmakePairBuilder('hello').map(s=>s+' world');}// unexpected type error:// Type 'PairBuilder<unknown, string>' is not assignable to type 'PairBuilder<string, string>'.
in f0(), the compiler correctly narrows the type for makePairBuilder to match the declared return value. However, when that call is "hidden" behind an extra call to map as in f1(), the inference fails, even with an explicit declaration.
This case may be a more specific and easier to analyze issue; the left type is unchanged by PairBuilder.map which makes it easier to conclude that we can carry it forward through the map() call.
Specifically, I got here because I'm trying to correctly type a call to React.forwardRef, where the component I'm forwarding a ref to has a generic props type. Is this possible? I've struggled to figure out a way to do this without losing the type safety of the generic props type.
Reacted by Sean Heffernan, so/ふかねこ and Kristóf Poduszló
Ryan Cavanaugh (@RyanCavanaugh) I'm not 100% sure, but I think this issue describes what I'm trying to do with the below example:
declareconstvalues: number[];declarefunctionpassFirst<Textends(...args: any[])=>void>(fn: T): (...args: Parameters<T>)=>Parameters<T>[0];constmap=values.reduce(passFirst((res,v)=>res.set(v,v*2)),newMap<number,number>());// map, res and v are all type `any`
I found in a project that I was often converting an array of values to a Map, and wanted a utility function that called the provided function with it's arguments, but returned the first parameter. Unfortunately I could not get it to work, as the generic typing always became too general (even if I tried multiple generic parameters).
I was thinking about creating a new issue for this, until I finnally found this one 😀
Ryan Cavanaugh (@RyanCavanaugh), I have some ideas. At first you should make function signature checker work with more complicated cases. For example, if we'll try to use wrapping type checkers, it will not work as expected.
To show it we need to define some infrastructure for advanced function check:
typeTuple=Array<any>&{0: any}|[]typeFixedTuple<TextendsTuple>=T&{length: T['length']}typeFixedParams<Fextends(...args: unknown[])=>unknown>=Fextends(...args: infer P)=>unknown ? PextendsTuple ? FixedTuple<P> : never : nevertypeStrictFunction<Fextends(...args: unknown[])=>unknown>=F&((...args: FixedParams<F>)=>unknown)// Type to show matching errorstypeMatches<F1extendsStrictFunction<F2>,F2extends(...args: unknown[])=>unknown>=[F1,F2]
And the example itself:
typeLiteral<Textendsstring>=stringextendsT ? never : TtypeF=<Textendsstring>(str: Literal<T>)=>TtypeValid=(str: 'https://xroom.app')=>'https://xroom.app'typeInvalid=(str: string)=>string// No errors, works as expectedtypeMatchesValid=Matches<F,Valid>// Still no errors, but there should be, because// type 'string' is not assignable to type 'never'typeMatchesInvalid=Matches<F,Invalid>// Don't know why, but it works right, after adding an extra param.typeOneMoreParam=(str1: string,str2: string)=>string// Type 'A' does not satisfy the constraint 'StrictFunction<OneMoreParam>'.// Type 'A' is not assignable to type '(...args: FixedTuple<[string, string]>) => unknown'.// Types of parameters 'str' and 'args' are incompatible.// Type 'FixedTuple<[string, string]>' is not assignable to type '[never]'.// Types of property '0' are incompatible.// Type 'string' is not assignable to type 'never'.ts(2344)typeMagicCheck=Matches<F,OneMoreParam>
I hope, it will be helpful for you. Other ideas will be published later.
functiontest<T>(f: (a: number[])=>T){returnf([1,2,3]);}functioncallbackProvider<TextendsR[],R>(): (a: T)=>R{return(a: T)=>a[0];}constresult=test(callbackProvider());// T is number[], but R is unknown, so result type is unknown
I could get it to work though, using conditional Type with a circular reference:
typeItemType<T>=TextendsIterable<infer R> ? R : never;functiontest<T>(f: (a: number[])=>T){returnf([1,2,3]);}functioncallbackProvider<TextendsItemType<T>[]>(): (a: T)=>ItemType<T>{return(a: T)=>a[0];}constresult=test(callbackProvider());// T is number[] and result type is number
add(value: T): void {
const key: string = value.hash()
if (!this.hashTable[key]) {
this.hashTable[key] = value
} else {
throw new RangeError('Key ' + key + ' already exists.')
}
}
typescript says value.hash() is not a function when I modeled it properly with an interface and a class that implements the interface in fact it had been working great as hashable
TypeError: value.hash is not a function
at eval (eval at add (http://localhost:4200/main.js:1:1), <anonymous>:1:7)
at HashSet.add (http://localhost:4200/main.js:12607:27)
at HashSet.populate (http://localhost:4200/main.js:12573:18)
Im peeling a TypeORM entity off the back end successfully in its own class that implements hash()
functiontest<TItem,TValue>(data: {item: TItem;getValue: (item: TItem)=>TValue;onChange: (value: TValue)=>void;}){}test({item: {value: 1},getValue: (item)=>item.value,onChange: (value)=>{},// value is unknown});test({item: {value: 1},getValue: (item)=>1,onChange: (value)=>{},// value is unknown});test({item: {value: 1},getValue: ()=>1,onChange: (value)=>{},// value is number});
Similar (/identical?) in structure to what was reported in #25092.
Have additional utility types or syntax—i.e. some way of helping the TS compiler determine where to infer a generic, and where to just enforce it—been considered?
In the example above, the TS compiler currently seems to want to infer B from the parameter of useB(). As the code author, one could (at least in this case) tell the TS compiler to explicitly infer B from the return type of aToB().
Some examples, extending the example above:
// Explicitly specify from where to infer generic `B`.interfaceExample<A,B>{aToB: (a: A)=> infer B// oraToB: (a: A)=>assignB// oraToB: (a: A)=>determineB// oraToB: (a: A)=>Infer<B>}// Or explicitly specify from where to _not_ infer (i.e. just enforce) generic `B`.interfaceExample<A,B>{useB: (b: deriveB)=>void// oruseB: (b: Derived<B>)=>void// oruseB: (b: Weak<B>)=>void}
Understandably this would just be a duct tape solution to the bigger shortcoming; ideally TypeScript would be able to infer these generics correctly. Given that a revamp of the generic inference algorithm (full unification or some multi-pass attempt) may be too complex at this point, maybe something like this could serve as an "intermediary" solution? No idea if this would be trivial to implement, or equally too complex.
One minor added benefit of explicitly telling the compiler from where to infer a given generic might be that type collisions could then be reported in places where such errors may be more expected. In the example above, a mismatch would be detected and reported in the useB() parameter, as opposed to the aToB() return statement.
Obvious downsides to this approach include additional syntax/utility type, and extra onus on code authors to comply with and work around the compiler.
EDIT (2024-02-08):
TypeScript 5.4 will introduce a NoInfer<T> type, which I believe addresses the issue in this particular post.
Is this issue the most appropriate one for the inference failure in the following?
declarefunctiong<T>(x: T): T;declarefunctionh<U>(f: (x: U)=>string): voidh(g)// error, inference of U fails and falls back to unknown//~ <-- Argument of type '<T>(x: T) => T' is not assignable to parameter of type '(x: unknown) => string'.h<string>(g)// okay, U manually specified as string, T is inferred as stringh(g<string>)// okay TS4.7+, T is manually specified as string, U is inferred as string
Ryan Cavanaugh (@RyanCavanaugh) is there a possibility of taking a look at this issue for the next version? I've been running into this issue a lot lately within the react ecosystem.
Ryan Cavanaugh (@RyanCavanaugh) is there a possibility of taking a look at this issue for the next version? I've been running into this issue a lot lately within the react ecosystem.
is there currently a way to overcome this shortcoming without specifying the generic?
Thanks for this playground–I thought I was losing my mind seeing onClick being inferred correctly according to the hover UI, but the args being implicitly any according to the type checker
[...] TypeScript's type argument inference algorithm [...] differs from the unification based type inference implemented by some functional programming languages, but it has the distinct advantage of being able to make partial inferences in incomplete code which is hugely beneficial to statement completion in IDEs.
Activity
essenmitsosse commentedon Feb 27, 2019
Wouldn't that be a solution:
Which wouldn't work with non generic functions, but this could be fixed like that:
It's definitely not pretty, but it seems to do the job.
RyanCavanaugh commentedon Feb 27, 2019
Correct.
memoizeis possible to write a workaround for, but in more complex cases it isn't possible to fix in user code.dragomirtitian commentedon Feb 27, 2019
Marcus Blättermann (@essenmitsosse)
This approach would work only for really simple cases. If for example there is any constraint on the type parameter of
identitywe will get an error, and there really is no way to forward the generic type constraint. Also if the number of type parameters is variable we again have an issue, but this could be solved with a number of overloads.Ryan Cavanaugh (@RyanCavanaugh)
Not sure if this is in scope here, but I have seen people often struggle with generic react components and HOCs. There really is no good way to write a HOC that forwards generic type parameters, maintains generic type constraints and removes some keys from props. Not sure if this will ever be possible but one can dream :). A simple example of what I am talking about:
yortus commentedon Feb 28, 2019
Ryan Cavanaugh (@RyanCavanaugh) is #29791 another example of something that could be addressed by this? You mention the absence full unification in your comment there.
airlaser commentedon Aug 14, 2019
I'm currently running into this issue, or at least what I think is this issue. I have a class that extends an interface that uses generics. I have to explicitly type the parameters on methods in my class even though the type is already explicitly stated on the interface. If I don't, it complains that I didn't type it and calls it an
any. If I type it incorrectly, it tells me the type is wrong, so clearly it knows what type it's supposed to be.An example:
In
FolderImplementationif I don't explicitly typefolderit complains thatParameter 'folder' implicitly has an 'any' type.If I purposefully type it incorrectly (for example, asstring) it complains thatType '(folder: string) => Promise<string>' is not assignable to type '(folder: WindowsFolder) => Promise<string>'.Would this be fixed by this? It would be a lot less verbose if I didn't have to re-explicitly-type everything.
johnsonwj commentedon Sep 7, 2019
I think I also have an example for this issue:
in
f0(), the compiler correctly narrows the type formakePairBuilderto match the declared return value. However, when that call is "hidden" behind an extra call tomapas inf1(), the inference fails, even with an explicit declaration.This case may be a more specific and easier to analyze issue; the left type is unchanged by
PairBuilder.mapwhich makes it easier to conclude that we can carry it forward through themap()call.(edit1: fix some wording)
(edit2: remove the unnecessary
Pair.mapfunction)bogdanq commentedon Dec 3, 2019
link
RyanCavanaugh commentedon Dec 12, 2019
Ford04 good point - the issue in the OP was tactically fixed by allowing some transpositions of type parameters. I need to write up a new example
ekilah commentedon Jan 30, 2020
Is this SO post relevant for your need for a new example Ryan Cavanaugh (@RyanCavanaugh) ? https://stackoverflow.com/questions/58469229/react-with-typescript-generics-while-using-react-forwardref
Specifically, I got here because I'm trying to correctly type a call to
React.forwardRef, where the component I'm forwarding a ref to has a generic props type. Is this possible? I've struggled to figure out a way to do this without losing the type safety of the generic props type.csvn commentedon Mar 10, 2020
Ryan Cavanaugh (@RyanCavanaugh) I'm not 100% sure, but I think this issue describes what I'm trying to do with the below example:
I found in a project that I was often converting an array of values to a Map, and wanted a utility function that called the provided function with it's arguments, but returned the first parameter. Unfortunately I could not get it to work, as the generic typing always became too general (even if I tried multiple generic parameters).
I was thinking about creating a new issue for this, until I finnally found this one 😀
awerlogus commentedon Mar 25, 2020
Ryan Cavanaugh (@RyanCavanaugh), I have some ideas. At first you should make function signature checker work with more complicated cases. For example, if we'll try to use wrapping type checkers, it will not work as expected.
To show it we need to define some infrastructure for advanced function check:
And the example itself:
I hope, it will be helpful for you. Other ideas will be published later.
millsp commentedon Feb 2, 2021
Here's another example, extracted from a discussion:
Play it
Here we would like
NofinterpolatePathto be inferred the size of the tuple items.Farenheith commentedon Feb 22, 2021
Is this an example for this issue?
Playground Link
I could get it to work though, using conditional Type with a circular reference:
Playground Link
3 remaining items
nhhockeyplayer commentedon Nov 12, 2021
My HashSet wont work anymore
TypeError: value.hash is not a functionIUserDTO-> hash()
UserEntity implements IUserDTO-> hash(){}
typescript says value.hash() is not a function when I modeled it properly with an interface and a class that implements the interface in fact it had been working great as hashable
but now my generic wont work anymore
whats going on?
Only diff is Im pulling entities off the back end http and collecting them as typeorm entities
are generics toast now? This would take out the entire abstraction layer across the industry.
nhhockeyplayer commentedon Nov 12, 2021
I use
"noImplicitAny": false,to accomodate previously stealth working deep-spread constructs
nhhockeyplayer commentedon Nov 12, 2021
well my issue might be this
declaring IHashable interface to be a class Hashable
instead of interface
maybe this is why Im not getting anything under the hood
strange how angular and typescript will let one get away with and run with until it finally shows up
I cant imagine generics not working
nhhockeyplayer commentedon Nov 12, 2021
still fails
Im peeling a TypeORM entity off the back end successfully in its own class that implements hash()
can anyone answer if generics are broken?
juanrgm commentedon Nov 19, 2021
Another example:
Andarist commentedon Feb 23, 2022
Based on the comment here: #44999 (comment) the "full unification" algorithm would solve the issue outlined in that issue.
echocrow commentedon Mar 5, 2022
Adding to the list of examples:
Excerpt:
Similar (/identical?) in structure to what was reported in #25092.
Have additional utility types or syntax—i.e. some way of helping the TS compiler determine where to infer a generic, and where to just enforce it—been considered?
In the example above, the TS compiler currently seems to want to infer
Bfrom the parameter ofuseB(). As the code author, one could (at least in this case) tell the TS compiler to explicitly inferBfrom the return type ofaToB().Some examples, extending the example above:
Understandably this would just be a duct tape solution to the bigger shortcoming; ideally TypeScript would be able to infer these generics correctly. Given that a revamp of the generic inference algorithm (full unification or some multi-pass attempt) may be too complex at this point, maybe something like this could serve as an "intermediary" solution? No idea if this would be trivial to implement, or equally too complex.
One minor added benefit of explicitly telling the compiler from where to infer a given generic might be that type collisions could then be reported in places where such errors may be more expected. In the example above, a mismatch would be detected and reported in the
useB()parameter, as opposed to theaToB()return statement.Obvious downsides to this approach include additional syntax/utility type, and extra onus on code authors to comply with and work around the compiler.
EDIT (2024-02-08):
TypeScript 5.4 will introduce a
NoInfer<T>type, which I believe addresses the issue in this particular post.jcalz commentedon Apr 10, 2022
Is this issue the most appropriate one for the inference failure in the following?
Playground link
Or does there exist another more specific GitHub issue somewhere for this? I can't find one if there is.
lifeiscontent commentedon Aug 24, 2022
Ryan Cavanaugh (@RyanCavanaugh) is there a possibility of taking a look at this issue for the next version? I've been running into this issue a lot lately within the react ecosystem.
Example
is there currently a way to overcome this shortcoming without specifying the generic?
huangyingwen commentedon Aug 29, 2023
I can't restrict the last parameter to be RequestParams, I don't know if it's related to this problem
tpict commentedon Jan 31, 2024
Thanks for this playground–I thought I was losing my mind seeing
onClickbeing inferred correctly according to the hover UI, but the args being implicitlyanyaccording to the type checkerjcalz commentedon Oct 22, 2024
Linking to Anders Hejlsberg (@ahejlsberg)'s comment #17520 (comment) which I always look for when coming here: