Skip to content

Cannot infer generic argument type from passed callback #31146

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

Open
kolodny opened this issue Apr 28, 2019 · 14 comments
Open

Cannot infer generic argument type from passed callback #31146

kolodny opened this issue Apr 28, 2019 · 14 comments
Labels
Bug A bug in TypeScript Domain: Type Inference Related to type inference performed during signature resolution or `infer` type resolution
Milestone

Comments

@kolodny
Copy link

kolodny commented Apr 28, 2019

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

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 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

@kolodny
Copy link
Author

kolodny commented Apr 29, 2019

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!

@weswigham
Copy link
Member

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).

@weswigham weswigham added Domain: Type Inference Related to type inference performed during signature resolution or `infer` type resolution Needs Investigation This issue needs a team member to investigate its status. labels May 8, 2019
@adrianheine
Copy link

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

@RyanCavanaugh RyanCavanaugh added Bug A bug in TypeScript and removed Needs Investigation This issue needs a team member to investigate its status. labels Aug 22, 2019
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Aug 22, 2019
@RyanCavanaugh
Copy link
Member

Looks like a bug to me - the default in the binding pattern should have made a candidate for T

@pradyuman
Copy link

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.

@RobertWHurst
Copy link

I've also run into this same issue. It would be nice to see it fixed.

@theonlypwner
Copy link

theonlypwner commented Nov 19, 2019

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))

@Aetet
Copy link

Aetet commented Nov 24, 2019

@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?

@theonlypwner
Copy link

theonlypwner commented Nov 26, 2019

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"); }
}

@xpl
Copy link

xpl commented Mar 30, 2020

Ran into the same issue today! Any plans to get it fixed?

@anilanar
Copy link
Contributor

anilanar commented Apr 14, 2020

@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;

@lxsmnsyc
Copy link

image

Might be related. This requires T to be defined explicitly when using test.

@theonlypwner
Copy link

theonlypwner commented Sep 21, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Domain: Type Inference Related to type inference performed during signature resolution or `infer` type resolution
Projects
None yet
Development

No branches or pull requests