-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
infer function generic signature from actual function's return type #49618
Comments
It's not that TS doesn't infer the generics, in general, but just that it's inferring it as test(() => ({ a: 1 })); // T is inferred as "{ a: number }" as you might expect It looks like the issue here is the unannotated In this very simple case (where |
Two cases to consider here The first case is where the function expression uses the parameter but the return type of the function provably doesn't depend on the parameter type. In practice, it's very rare for these kinds of function expressions to exist - they're by definition impure, and given control flow effects it's very difficult to even construct such a function. The second case is where the function expression doesn't use the parameter, as is the case here. They can be removed WLOG, and after doing so the parameter is inferred successfully as expected. So on balance there's not really much gain to be had here - the inference is still sound, and detecting if a used parameter has no effect on the return type of a function is a very difficult calculation to always get right |
I had to google this acronym. First time I've ever encountered it. To me it feels like TS should be treating |
Yeah, I'll reopen #47599 since it's not fully covered |
That said, I'm not really sure what the endgoal is. Any "improvement" we make here is just going to sow a bunch of "TypeScript is inconsistent, therefore has bug" reports because people will wonder why function test<T>(fn: (prev: T) => T) { }
test((prev) => {
return 0;
}); works but not function test<T>(fn: (prev: T) => T) { }
test((prev) => {
return prev ? 0 : 1;
}); The current behavior is at least very explainable and easy to reason about; moving the needle into the grey zone just raises more questions than it does solve problems. |
Yeah, I understand. What bothers me is mostly theoretical - the fact that the current behavior basically amounts to:
It feels like a bug, even if the "correct" behavior isn't really qualitatively "better" in practice. |
To be clear, my problem isn’t the |
I believe what actually happens is that we collect candidates for It's weird since if you had written |
I think a big part of what makes this case so tricky is that |
@weswigham pointed out this example type Box<T> = { contents: T };
function test<T>(fn: (prev: Box<T>) => T) { }
test((prev) => ({ a: 1 })); It's not clear how we'd turn this into an error -- somehow it's (speculatively) a " function test<T>(x?: T): T;
test(); So there seems to be some difficulty in establishing what rule exactly would turn the OP example into an error without either breaking something that shouldn't be broken (benign zero-candidate inference), failing to break something equally suspect ( |
Does TypeScript have a Hindley-Milner type system? Because I'm pretty sure it would work with that kind of type inference. TypeScript would see that the return type and first parameter have to be the same, so it could check for return type and see "oh it's an object literal with type It seems like it doesn't work like that now, right? |
No, TS's type system is not H-M. Type inference is done locally. See #30134. Your example of inferring function test<T>(fn: (prev: T) => T) { }
test((prev) => {
return prev ? 0 : 1;
}); would infer |
Interesting points above. I can describe a real-world use case. Suppose we have a const fname = reactiveVar('John')
const lname = reactiveVar('Doe')
const fullname = memo((prev) => {
return fname.get() + ' ' + lname.get()
})
fullname() // string, combination of fname + lname In this first case, Any time that The second way to use the const fname = reactiveVar('John')
const lname = reactiveVar('Doe')
const fullname = memo((prev) => {
return fname.get() + ' ' + lname.get()
}, "Godzilla Kong") // <--------------------------- HERE, new movie idea
fullname() // string, combination of fname + lname In this case, This works totally fine in plain JavaScript, but the main issue is that in TypeScript, it currently requires too much superfluous type annotation. |
@trusktr did you mean to use |
I have a real-world use case related to a PR in nanostores. I'm adding a Here is a contrived example to keep it simple. The real use case involves async, so I'll demonstrate with a let firstName = atom('')
let lastName = atom('')
let $userId = atom(0)
let fullNameUserId = computed(async cx => {
await sleep(100)
let userId = cx(() => $userId()) // Should infer as a number but infers as any
return cx(() => `${firstName()}-${lastName()}-${userId}`) // Should infer as a string but infers as any
})
function sleep(ms: number) {
return new Promise(res => {
setTimeout(() => res(null), ms)
})
}
declare function computed<Value extends any, Cx extends ComputedCx<any>>(cx: Cx): ReadableAtom<Value>
type ComputedCx<R> = (cb: () => R) => R
declare function atom<Value>(initialValue: Value): WritableAtom<Value = any>
interface ReadableAtom<Value = any> {
// ...
}
interface WritableAtom<Value = any> extends ReadableAtom<Value> {
// ...
} |
This issue has been marked as "Too Complex" and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
Suggestion
🔍 Search Terms
Maybe an issue exists, but I wasn't sure what to search for. "Infer generic function type from return value" didn't really help.
✅ Viability Checklist
My suggestion meets these guidelines:
⭐ Suggestion
T
should be inferred as the type of the returned object,{a: number}
📃 Motivating Example
https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABFApgZygHgCoD4AUwYAXIvgA4BOKAbqdgJSIC8uijiA3ogL4BQqDPgrUaTVmW4BDUgEZeDBgG4+QA
💻 Use Cases
make life easier
The text was updated successfully, but these errors were encountered: