TypeScript Version: 3.4.0-dev.201xxxxx
Search Terms:
infer, parameter, argument, callback, function
Code
function inferArguments<T>(callback: ((t: T) => void)) {
return callback;
}
function noop(){}
const explicit = inferArguments(({a = noop}: {a: Function}) => {});
explicit({a: noop}); // OK!
explicit({a: false}); // Expected error - Got one!
const implicit = inferArguments(({a = noop}) => {});
implicit({a: noop}); // OK!
implicit({a: false}); // Expected error - No Error!
Expected behavior:
Both function calls with ({a: false}) should cause a type error
Actual behavior:
Only the function call with the explicit typing causes a type error
Related Issues: #30975 Looks similar but seems different since there should be a clear way for inference to work in this case
Note that Parameters is correctly able to extract the correct types for such a construct as seen in this bit of code:
function noop() { }
function callback({ a = noop }) { }
let args: Parameters<typeof callback>;
args[0].a as Function
This appears to be an error with inline callback functions used in this fashion
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 noImplicitAny to true and you'll see the issue is really that we issue a noImplicitAny error on implicit (though it seems like we infer all the types for inferArguments just fine, so it's odd).
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
Looks like a bug to me - the default in the binding pattern should have made a candidate for T
Any update on this? I'm running into an issue that is perhaps related. In the following function, Res never 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))
p is of type Promise<unknown>, so in order to use it later, one has to specify the type explicitly to have p be of type Promise<number>:
const p = new Promise<number>((resolve) => resolve(1))
@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 (like const v: boolean = await f(p, 1))
@RyanCavanaugh this affects event react typings for typescript:
https://github.com/DefinitelyTyped/DefinitelyTyped/pull/30057#discussion_r232123748
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?
@Aetet
@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 useCallback in our codebase instead of using the "official" react typings:
export const useCallback: <T extends (...args: never[]) => unknown>(
callback: T,
deps: Array<unknown>,
) => T;
@anilanar there's a better way: extends Function
https://www.typescriptlang.org/v2/en/play?#code/GYVwdgxgLglg9mABCAzgUwMIEMA2OBGWEA1ogDwAqiaAHlGmACYqIAUAdJ1gE4DmKALkRg0ANzTcA2gF0AlIgC8APmRhiYOAHcwS1gChEiCLgJFiQigBoDiRmgAOgxAEFu3LAE8y4dVp3XZC0QAbxtuNCgQbiRjPEISAG49AF89PVBIWARkdGw4swAmSmo6BmY2TnYefiEsMA8ZeWVEOo9dWNMSC0tbBydXdy8fDW0lQMQqUMNwyOijE3jiJNT08Gh4JFRMBbMAZjI8ztJaeiYWADE1rLAegCVdYCFDxZ7iNA8UAH5a+sannZIITCESiSGAyzSq0yG0QvAiAGU4ABbNCsapCNFCMAgJH4CQ9fBCFBQbgwMC8JoqURwGCMeRTRAzUEtPisACMPQA5AVObIIXoAPQClqIGAsETibgE0XisQSPQQBDEoyKHLbfIkVhogmUoEpHqNJKC4VYGUterSsXmjwKpVQWyqrbPQparA6xQqUL6xCGtJCkVWmBI+w4GAQGD21qWlhBkNhiPW21gZVoR25AHEXau93NL3JA1yI3+igeeypq32bhwfA4NBIxAAWkQUAAFlh7VbeHA4Iw9HCoIiUawIHzjRNS+WY7wNOFGI3RcHQ+HI-U+wjkai6UlixOzZo4NxiGTeIgy9xgGhoGuBxvWGg+UA

Might be related. This requires T to be defined explicitly when using test.
In 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