Skip to content

callbacks should select the right overload #35501

Description

@millsp

TypeScript Version: 3.8.0-dev.20191121

Search Terms:

callback overload mismatch

Code

declare function fn(x: string): string;
declare function fn(x: string[]): string;

declare function map<A, R>(fn: (item: A) => R, list: A[]): R[];

const mapped = map(fn, ['1']) // error

Expected behavior:

TypeScript does infer A as string and could choose the right overload fn(x: string), decline the ones that don't fit.

Actual behavior:

Checks the last overload only, so it shows an error.

Playground Link: http://www.typescriptlang.org/play/?ssl=7&ssc=1&pln=1&pc=1#code/CYUwxgNghgTiAEAzArgOzAFwJYHtVNQAoAPALngGcMYtUBzASnKpvoG4AoUSWBFdbHgIlm1WnQDaAXSaUx7Dl3DQ4SNJlz4AtlAAOAHgCCAGngAlAHyFEqcoSwYQW8oYbwAvBfOmIWKi+lZM2lODjA8KngdXV0QYA8ovWtUUwkAcgBGNJkOIA

Related Issues:

Activity

  1. millsp commented on Dec 4, 2019

    @millsp
    ContributorAuthor

    Another example, where both overloads would match

    declare function fn(x: string): string;
    declare function fn(x: string): number;
    
    declare function map<A, R>(fn: (item: A) => R, list: A[]): R[];
    
    const mapped = map(fn, ['1'])

    mapped should be of type number[] | string[]

  2. millsp commented on Dec 4, 2019

    @millsp
    ContributorAuthor

    Here's my current workaround

    declare function fn(x: string): string;
    declare function fn(x: string[]): string;
    
    declare function map<A, R>(fn: (item: A) => R, list: A[]): R[];
    
    const mapped = map((x) => fn(x), ['1'])

    Wrapping fn does get TS to choose the right overload

  3. RyanCavanaugh commented on Dec 5, 2019

    @RyanCavanaugh
    Member

    Generic inference is not able to do overload resolution, though your examples all don't need overloads in the first place.

  4. millsp commented on Dec 5, 2019

    @millsp
    ContributorAuthor

    The examples are silly yes, but they do show how overloads could be improved. I get these scenarios quite often (on ramda especially). So for now, I wrap with () => which does the trick, so I thought this could have been solved easily.

  5. jamiebuilds commented on Dec 17, 2019

    @jamiebuilds

    Here is the simplest possible replication of this I could come up with: [Playground]

    interface Callback {
      (value: number): any
      (value: string): any
    }
    
    function fn(callback: Callback) {
      // ...
    }
    
    fn(value => {
      // ^ Parameter 'value' implicitly has an 'any' type.
    })
    Here is a real world example: [Playground]
    interface Callback<T> {
      (error: null, result: T): unknown
      (error: Error, result: null): unknown
    }
    
    interface Task<T> {
      (callback: Callback<T>): unknown
    }
    
    export function series<T>(tasks: Task<T>[], callback: Callback<T[]>): void {
      let index = 0
      let results: T[] = []
    
      function next() {
        let task = tasks[index]
        if (!task) {
          callback(null, results)
        } else {
          task((error, result) => {
            //  ^ Parameter 'error' implicitly has an 'any' type.
            //         ^ Parameter 'results' implicitly has an 'any' type.
            if (error) {
              callback(error, null)
            } else {
              results.push(result)
              next()
            }
          })
        }
      }
      next()
    }
    
    series([
      cb => setTimeout(() => cb(null, 1), 300),
      cb => setTimeout(() => cb(null, 2), 200),
      cb => setTimeout(() => cb(null, 3), 100),
    ], (error, results) => {
      // ^ Parameter 'error' implicitly has an 'any' type.
      //       ^ Parameter 'results' implicitly has an 'any' type.
      if (error) {
        console.error(error)
      } else {
        console.log(results)
      }
    })
  6. jcalz commented on Nov 17, 2021

    @jcalz
    Contributor

    Just to be clear: Note that even though #42620 is listed above as closing this issue, Wesley Wigham (@weswigham) says it only fixed the comment before this one, not the whole issue. So this issue is still a design limitation and is not fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions