Repository navigation
Cannot infer generic argument type from passed callback #31146
Description
Activity
Another thing I noticed is that this works for functions declared on the same scope but fails for function expressions passed in to the HOF
const inferArguments = <T>(callback: T) => callback; function noop(){} function takesNoop({ a = noop}) {} const OK = inferArguments(takesNoop); OK({a: noop}); // OK! OK({b: noop}); // Expected error - Got one! OK({a: false}); // Expected error - Got one! const BAD = inferArguments(function takesNoop({ a = noop}) {}); BAD({a: noop}); // OK! BAD({b: noop}); // Expected error - No Error! BAD({a: false}); // Expected error - No Error!
Set
noImplicitAnytotrueand you'll see the issue is really that we issue anoImplicitAnyerror onimplicit(though it seems like we infer all the types forinferArgumentsjust fine, so it's odd).- addedDomain: check: Type InferenceRelated to type inference performed during signature resolution or `infer` type resolutionRelated to type inference performed during signature resolution or `infer` type resolutionNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on May 8, 2019 I have a maybe related issue:
function f(a) { // Would emit an error with noImplicitAny return () => a } const x = f(1) // I would have expected `x` to be `() => number`, but it is `() => any` const y: string = x() // Passes
function f<T>(a: T) { return () => a } const x = f(1) // `() => number` const y: string = x() // Fails
- addedBugA bug in TypeScriptA bug in TypeScriptand removedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Aug 22, 2019 RyanCavanaugh commented
on Aug 22, 2019 MemberMore actionsLooks like a bug to me - the default in the binding pattern should have made a candidate for
TAny update on this? I'm running into an issue that is perhaps related. In the following function,
Resnever gets inferred.export const execGrpc = <Req, Res extends {}>( call: ( request: Req, callback: (error: ServiceError | null, response: Res) => void ) => ClientUnaryCall, req: Req ): Promise<Either<ServiceError, Res>>Happy to provide more details and/or help debug if it makes sense.
I've also run into this same issue. It would be nice to see it fixed.
A common issue is having to manually specify a type for the Promise constructor. (tested in Visual Studio Code with TypeScript 3.7.2)
const p = new Promise((resolve) => resolve(1))
pis of typePromise<unknown>, so in order to use it later, one has to specify the type explicitly to have p be of typePromise<number>:const p = new Promise<number>((resolve) => resolve(1))
Pradyuman Vig (@pradyuman) I've also run into the same issue with making my own wrapper for gRPC calls. I found that it is due to having multiple overloads for
call(the auto-generated TypeScript declarations have multiple overloads for generated methods in the client), which prevents inference from working.Here's a reproducible example (tested in Visual Studio Code with TypeScript 3.7.2), using number instead of gRPC request/response protobuf types:
function f<T, U> ( call: ( request: U, innerCallback: (error: Error | null, value?: T) => void ) => void, request: U): Promise<T> { return new Promise((resolve, reject) => call(request, (error, response) => { if (error) { reject(error) } else { resolve(response) } })) } declare function p (request: number, call: (error: null, a: number) => void) // declare function p (request: number, a2: boolean, call: (error: null, a: number) => void) async function g () { const v = await f(p, 1) // the type of v is inferred as number // but if the other declaration above is uncommented, it is inferred as unknown }
My workaround is to specify the type explicitly (
const v: number = await f(p, 1)), which still catches type errors (likeconst v: boolean = await f(p, 1))Reacted by Robert HurstRyan Cavanaugh (@RyanCavanaugh) this affects event react typings for typescript:
DefinitelyTyped/DefinitelyTyped#30057 (comment)Because now any usage useCallback leads to silent any inference
Any update on this?
C# can't infer the types either:
using System; namespace ConsoleApp1 { static class Program { static void f<T>(Action<Action<T>> c) { c(x => Console.WriteLine(x)); } static void Main(string[] a) { f(x => x("test")); // error } } }
Java can though:
import java.util.function.Consumer; public class Main { static <T> T f(Consumer<Consumer<T>> c) { c.accept(x -> System.out.println(x)); return null; } public static void main(String[] args) { String s = f(x -> x.accept("a")); // output: a String s2 = f(Main::g); // output: a // Integer i = f(x -> x.accept("a")); // does not compile, good } static void g(Consumer<String> c) { c.accept("a"); } }
Ran into the same issue today! Any plans to get it fixed?
Ryan Cavanaugh (@RyanCavanaugh) this affects event react typings for typescript:
DefinitelyTyped/DefinitelyTyped#30057 (comment)Because now any usage useCallback leads to silent any inference
Any update on this?
That's because react typings don't provide typings for >TS 3.0.
For example, we use the following
useCallbackin our codebase instead of using the "official" react typings:export const useCallback: <T extends (...args: never[]) => unknown>( callback: T, deps: Array<unknown>, ) => T;Anıl Anar (@anilanar) there's a better way:
extends Function
https://www.typescriptlang.org/v2/en/play?#code/GYVwdgxgLglg9mABCAzgUwMIEMA2OBGWEA1ogDwAqiaAHlGmACYqIAUAdJ1gE4DmKALkRg0ANzTcA2gF0AlIgC8APmRhiYOAHcwS1gChEiCLgJFiQigBoDiRmgAOgxAEFu3LAE8y4dVp3XZC0QAbxtuNCgQbiRjPEISAG49AF89PVBIWARkdGw4swAmSmo6BmY2TnYefiEsMA8ZeWVEOo9dWNMSC0tbBydXdy8fDW0lQMQqUMNwyOijE3jiJNT08Gh4JFRMBbMAZjI8ztJaeiYWADE1rLAegCVdYCFDxZ7iNA8UAH5a+sannZIITCESiSGAyzSq0yG0QvAiAGU4ABbNCsapCNFCMAgJH4CQ9fBCFBQbgwMC8JoqURwGCMeRTRAzUEtPisACMPQA5AVObIIXoAPQClqIGAsETibgE0XisQSPQQBDEoyKHLbfIkVhogmUoEpHqNJKC4VYGUterSsXmjwKpVQWyqrbPQparA6xQqUL6xCGtJCkVWmBI+w4GAQGD21qWlhBkNhiPW21gZVoR25AHEXau93NL3JA1yI3+igeeypq32bhwfA4NBIxAAWkQUAAFlh7VbeHA4Iw9HCoIiUawIHzjRNS+WY7wNOFGI3RcHQ+HI-U+wjkai6UlixOzZo4NxiGTeIgy9xgGhoGuBxvWGg+UAReacted by Anıl AnarReacted by Raine RevereIn TypeScript, this is the current issue (affecting
new Promise):type Action<T> = (t: T) => void // the original example in the original post seems to be OK now, as there is an error for // const implicit = inferArguments(({a = noop}) => {}); function f<T>(_: Action<T>): T { return null as any; } const x = f((_: string) => {}); // y has type string, OK // current issue in TypeScript 3.7 to 4.0 function f2<T>(_: Action<Action<T>>): T { return null as any; } const y = f2(x => x("1")); // y has type unknown, BAD <------------------------------ const y2 = f2((x: Action<string>) => x("1")); // y2 has type string, OK const y3 = f2<string>(x => x("1")); // y3 has type string, OK
Also, it actually turns out that C# can infer the type when it is explicitly written and not a method group:
using System; void f<T>(Action<Action<T>> c) { } void g(Action<string> f) { } f((Action<string> f) => f("a")); // ok // f(f => f("a")); // error // f(g); // error void f2<T>(Action<T> c) { } void g2(string s) { } f2((string _) => {}); // ok // f2(g2); // error
- addedCursed?It's likely this is extremely difficult to fix without making something else much, much worseIt's likely this is extremely difficult to fix without making something else much, much worse
on Jul 24, 2025 Ran into this exact pattern in a real codebase today (React lazy-loading helper wrapping
React.lazywith apickoption to support named exports). Confirms this is still present as of TypeScript 5.9.3 (also reproduced identically on the new Go-based compiler, 7.0.2) — 6+ years after this was filed and confirmed as a bug.Minimal repro (isolated, no React):
declare function lazyLoad<T>( factory: () => Promise<any>, opts: { pick: (mod: any) => { default: T } } ): T; declare const NamedComponent: (props: { foo: string }) => any; // Extracted + explicitly annotated pick: infers T correctly const pickFn = (m: any): { default: typeof NamedComponent } => ({ default: m.NamedComponent }); const A = lazyLoad(() => Promise.resolve({}), { pick: pickFn }); const a: string = A; // correctly errors: (props) => any is not string // Inline pick, no annotation: T silently becomes unknown, no error below const B = lazyLoad(() => Promise.resolve({}), { pick: (m) => ({ default: m.NamedComponent }) }); const b: string = B; // BUG: should also error, but does not
In our case this means
React.lazy-based components loaded via a named-exportpickoption silently lose all prop type-checking (IntrinsicAttributeserrors, or worse, no errors at all where there should be). The workaround (extractingpickinto a separately-typedconstat every call site) works but is significant boilerplate for what's otherwise a clean inline pattern — real impact for anyone building this kind of "lazy + adapter" helper.Just want to add a data point that this is still very much alive and biting real-world code, in case it helps with prioritization.

TypeScript Version: 3.4.0-dev.201xxxxx
Search Terms:
infer, parameter, argument, callback, function
Code
Expected behavior:
Both function calls with
({a: false})should cause a type errorActual behavior:
Only the function call with the explicit typing causes a type error
Playground Link
Related Issues: #30975 Looks similar but seems different since there should be a clear way for inference to work in this case
Note that
Parametersis correctly able to extract the correct types for such a construct as seen in this bit of code:This appears to be an error with inline callback functions used in this fashion