-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Can't correctly infer generic interface type when it's behind a function #25092
Comments
The error doesn't happen if at least one of the parameters is not declared in the callback: declare function f<T>(obj: { get: (p: number) => T, set: (v: T) => void }): T;
const res0 = f({ get: p => 0, set: v => {} });
const res0N: number = res0; // Error
const res1 = f({ get: () => 0, set: v => {} });
const res1N: number = res1; // Works
const res2 = f({ get: p => 0, set: () => {} });
const res2N: number = res2; // Works |
Is this the same issue? Return type is casted to 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. |
@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 :) . |
@YiSiWang thanks! Never thought swapping the position would change something :) |
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 whereas const myWorkingGeneric = inferTypeFn({
retrieveGeneric: (parameter: string) => 5,
operateWithGeneric: generic => generic.toFixed()
}); only 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). ❤️ |
@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 ( |
@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 }
/>
) |
Tracking at #30134 |
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. |
TypeScript Version: 2.9
Search Terms:
function parameter inference
Code
Expected behavior:
myGeneric
has every type correctly inferred,parameter
is a string,generic
is a number.Actual behavior:
it doesn't infer the correct type for
generic
parameter 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
The text was updated successfully, but these errors were encountered: