Repository navigation
Can't correctly infer generic interface type when it's behind a function #25092
Description
Activity
donaldpipowitch commented
on Jul 2, 2018 ContributorMore actionsIs this the same issue? Return type is casted to
any, if generic is used:declare function $eval<T extends Element = HTMLElement, A = any, R = any>( selector: string, pageFunction: (element: T, ...args: A[]) => R, ...args: A[] ): Promise<ReturnType<typeof pageFunction>>; $eval<HTMLButtonElement>('.btn', (el) => el.disabled).then(value => value); // ^-- boolean ^-- any, SHOULD be boolean $eval('.btn', (el) => el.spellcheck).then(value => value); // ^-- boolean ^-- boolean
Tested on PlayGround.
I wonder if I got the same issue for interfaces + classes. Have a look at this playground.
If you hover overcreateHandler(line 15) you will see{}as inferred type instead ofstringAndy (Andrewkraft) (@Andy-MS) I got a similar issue and found a solution/workaround:
- declare function f<T>(obj: { get: (p: number) => T, set: (v: T) => void }): T; + declare function f<T>(obj: { get: (p: number) => T, set: <U extends T>(v: U) => void }): T;
declare function f<T>(obj: { get: (p: number) => T, set: <U extends T>(v: U) => void }): T; const res0 = f({ get: p => 0, set: v => {} }); const res0N: number = res0; // Works! const res1 = f({ get: () => 0, set: v => {} }); const res1N: number = res1; // Works const res2 = f({ get: p => 0, set: () => {} }); const res2N: number = res2; // Works
Hope it helps :) .
Benjamin Otto (@otbe) This works.
Reacted by Josh Minzner, Alec Larson and Joe_Sky14 (@YiSiWang) thanks! Never thought swapping the position would change something :)
I wonder if this is bug or works as intended?- addedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Jul 23, 2018 Inside the compiler, we have this concepts of something called a contextual type and a context-sensitive function. A contextual type is simply the type a position must have based on what it is being assigned to. A context-sensitive function is a function with untyped function parameters. When a context-sensitive function exists, its type must be derived from the contextual type at that location.
In
const myGeneric = inferTypeFn({ retrieveGeneric: parameter => 5, operateWithGeneric: generic => generic.toFixed() });
both functions in the object literal are context-sensitive. This means we try to solve their types during inference independently. We see
parameterunannotated first, lock in{}as the inference (since we decide thatparametermust have a type before we can check the body), and then that's what it's stuck with.whereas
const myWorkingGeneric = inferTypeFn({ retrieveGeneric: (parameter: string) => 5, operateWithGeneric: generic => generic.toFixed() });
only
operateWithGenericis context-sensitive. This means we create an inference ofT -> stringbefore we even look at the context sensitive function (since we do context-free inferences and then context-sensitive ones) and everything's good.We're conservative here because we assume that a parameter, if present, may affect the return type of a function. Based on what we've talked about in related issues before, it's unlikely we'll specifically improve this without moving to a full unification-based solve for the arguments. the recommendation right now is to not list parameters you don't use (or if you must list them, manually assign them types). ❤️
Reacted by csr632, Yuji Tomita and ragamufinReacted by Lynn, Alec Larson, maxwellium, csr632, Yuji Tomita and Andreas KrutReacted by yossarian, Yuji Tomita and Jared Poole- addedDesign LimitationConstraints of the existing architecture prevent this from being fixedConstraints of the existing architecture prevent this from being fixedand removedBugA bug in TypeScriptA bug in TypeScriptNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Sep 13, 2018 Wesley Wigham (@weswigham) I wonder if following piece of code is related to this issue:
declare function test<T>(a: T, b: T): void; declare const a: { cb: (arg: number) => number } test(a, { cb: arg => arg }) // [ts] Parameter 'arg' implicitly has an 'any' type.
We have type checking here (
(arg: string) => argwon't fit), but don't have type inference.
Is it the same problem or something different?Reacted by Kirill Agalakov and Vladimir KatushenokMight be related #23429, #22715 #15005
I've added a compete example of Anton Sutarmin (@sutarmin) 's problem here
Wesley Wigham (@weswigham) I should add that this affects JSX as well, with generic components:
interface MyInterface<T> { retrieveGeneric: (parameter: string) => T, operateWithGeneric: (generic: T) => string } export declare function Component<T>(props: MyInterface<T>): JSX.Element const element = ( <Component operateWithGeneric={ generic => generic.toFixed() } // <- error here with generic being `{}` retrieveGeneric={ parameter => 5 } /> )
Reacted by marcellino ornelasRyanCavanaugh commented
on Mar 13, 2019 MemberMore actionsTracking at #30134
declare function f<T>(obj: { get: (p: number) => T, set: <U extends T>(v: U) => void }): T; const res0 = f({ get: p => 0, set: v => {} }); const res0N: number = res0; // Works! const res1 = f({ get: () => 0, set: v => {} }); const res1N: number = res1; // Works const res2 = f({ get: p => 0, set: () => {} }); const res2N: number = res2; // Works
It will report error when you do this:
declare function f<T>(obj: { get: (p: number) => T, set: <U extends T>(v: U) => void }): T; const res0 = f({ get: p => 0, set: v => { // error! return v.toFixed() } });
This is so wierd.
- addedFix AvailableA PR has been opened for this issueA PR has been opened for this issue
on Apr 3, 2022
TypeScript Version: 2.9
Search Terms:
function parameter inference
Code
Expected behavior:
myGenerichas every type correctly inferred,parameteris a string,genericis a number.Actual behavior:
it doesn't infer the correct type for
genericparameter unless you manually specify the type ofparameter(which it already had the right type)Playground Link:
https://www.typescriptlang.org/play/#src=interface%20MyInterface%3CT%3E%20%7B%0D%0A%20%20%20%20retrieveGeneric%3A%20(parameter%3A%20string)%20%3D%3E%20T%2C%0D%0A%20%20%20%20operateWithGeneric%3A%20(generic%3A%20T)%20%3D%3E%20string%0D%0A%7D%0D%0A%0D%0Aconst%20inferTypeFn%20%3D%20%3CT%3E(generic%3A%20MyInterface%3CT%3E)%20%3D%3E%20generic%3B%0D%0A%0D%0A%2F%2F%20inferred%20type%20for%20myGeneric%20%3D%20MyInterface%3C%7B%7D%3E%2C%20%60generic.toFixed()%60%20marked%20as%20error%20(as%20%7B%7D%20doesn't%20have%20.toFixed())%0D%0Aconst%20myGeneric%20%3D%20inferTypeFn(%7B%0D%0A%20%20%20%20retrieveGeneric%3A%20parameter%20%3D%3E%205%2C%0D%0A%20%20%20%20operateWithGeneric%3A%20generic%20%3D%3E%20generic.toFixed()%0D%0A%7D)%3B%0D%0A%0D%0A%2F%2F%20inferred%20type%20for%20myGeneric%20%3D%20MyInterface%3Cnumber%3E%2C%20everything%20OK%0D%0Aconst%20myWorkingGeneric%20%3D%20inferTypeFn(%7B%0D%0A%20%20%20%20retrieveGeneric%3A%20(parameter%3A%20string)%20%3D%3E%205%2C%0D%0A%20%20%20%20operateWithGeneric%3A%20generic%20%3D%3E%20generic.toFixed()%0D%0A%7D)%3B%0D%0A%0D%0A