镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Consider re-ordering Array#reduce overloads in lib.d.ts #7014

Description

@yortus

Example code:

type UnaryFunction = (arg1) => any;
type BinaryFunction = (arg1, arg2) => any;

let binaryFuncs: BinaryFunction[] = [];
let unaryFunc = arg1 => {};
let reduced = binaryFuncs.reduce((prev, next) => prev, unaryFunc);

// ACTUAL:
let f: UnaryFunction = reduced;     // ERROR binary not assignable to unary

// EXPECTED:
let f: UnaryFunction = reduced;     // OK - both lhs and rhs really are unary

The call to Array#reduce in the above example definitely returns a unary function, but the type system erroneously infers the return type as a binary function.

This seems to be caused by the declaration order of the two overloads of Array#reduce in lib.d.ts. If the declaration order is reversed, the problem is solved.

The two overloaded declarations in lib.d.ts are as follows:

reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue?: T): T;
reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;

The first overload matches the example with T=BinaryFunction, since it satisfies the compiler's assignability checks. So the second overload is never considered, even though it is a strictly better match, with T=BinaryFunction and U=UnaryFunction.

Would it be possible to swap the overload order for Array#reduce (and Array#reduceRight) in lib.d.ts to resolve this issue?

Activity

  1. yortus commented on Feb 11, 2016

    @yortus
    ContributorAuthor

    Simpler example:

    let arrayOfObjs: {}[] = [/*...*/];
    let countOfProps = arrayOfObjs.reduce((sum, obj) => sum + Object.keys(obj).length, 0);

    This doesn't compile, tsc reports "Operator '+' cannot be applied to types '{}' and 'number'".

    In fact the code is fine and runs fine.

    tsc took the first overload since it works with T={}, so the second (correct) overload is not considered.

    In general, the second overload will always be skipped if U is assignable to T (in this example, number is assignable to {}).

    Swapping the order of the two Array#reduce overloads in lib.d.ts fixes this.

  2. changed the title [-]Type inference problem with Array.reduce (lib.d.ts)[/-] [+]Consider re-ordering Array#reduce overloads in lib.d.ts[/+] on Feb 11, 2016
  3. RyanCavanaugh commented on Feb 12, 2016

    @RyanCavanaugh
    Member

    I believe it's safe to do this now -- the order we had was dependent on behavior that is no longer the case (ancient history, see http://typescript.codeplex.com/workitem/2352).

  4. yortus commented on Feb 12, 2016

    @yortus
    ContributorAuthor

    Wow, nice archaeological find 😄 Those were the days...

  5. aluanhaddad commented on Mar 11, 2016

    @aluanhaddad
    Contributor

    Glad this can now be resolved, and I really appreciate the effort the team takes in polishing these corner cases.

    As a side not, I really dislike the argument ordering that TC39 chose for Array.prototype.reduce and Array.prototype.reduceRight. The seed should come first, not just because it makes type inference easier, but because it makes things more readable. Anyway, that decision is ancient history, but I wanted to rant about it.

  6. johnnyreilly commented on Apr 4, 2016

    @johnnyreilly

    Just stumbled on this - look forward to this being resolved! For anyone else wondering what to do in the interim just supply the type parameter. So to take Troy Gerwien (@yortus) example, this:

    let arrayOfObjs: {}[] = [/*...*/];
    let countOfProps = arrayOfObjs.reduce((sum, obj) => sum + Object.keys(obj).length, 0);

    Becomes:

    let arrayOfObjs: {}[] = [/*...*/];
    let countOfProps = arrayOfObjs.reduce<number>((sum, obj) => sum + Object.keys(obj).length, 0);
  7. added
    Domain: lib.d.tsThe issue relates to the different libraries shipped with TypeScript
    and removed on Jun 9, 2016
  8. RyanCavanaugh commented on Jun 9, 2016

    @RyanCavanaugh
    Member

    Taking PRs on this with the caveat we're still not 100% sure this is going to be an overall improvement, so reserving the right to reject it if it turns out to be bad on net.

  9. added this to the milestone on Jun 9, 2016
  10. vilicvane commented on Sep 25, 2016

    @vilicvane

    I am on this PR.

  11. 1 remaining item

  12. vilicvane commented on Oct 2, 2018

    @vilicvane

    Bilal Durrani (@bdurrani) Just noticed that I closed the PR myself. Might had encountered some problem but forgot to update the comment here.

  13. chriskrycho commented on Dec 19, 2019

    @chriskrycho

    This appears to still be an issue. Anyone know why #11356 ended up closed? vilic?

  14. starpit commented on Dec 20, 2019

    @starpit

    agreed, we encounter this with typescript 3.7.3

    const A = [1, '2', 3]
    const str: string = A.reduce((str, a) => `${str} ${a.toString()}`, '')

    which results in this compilation error:

    Type 'string | number' is not assignable to type 'string'.
      Type 'number' is not assignable to type 'string'.
    
    2     const str: string = A.reduce((str, a) => `${str} ${a.toString}`, '')
                ~~~

    there is an easy enough workaround for now, i.e. A.map(_ => _.toString()).reduce(...), but this seems worth fixing?

  15. Meowu commented on Feb 21, 2020

    @Meowu

    agreed, we encounter this with typescript 3.7.3

    const A = [1, '2', 3]
    const str: string = A.reduce((str, a) => `${str} ${a.toString()}`, '')

    which results in this compilation error:

    Type 'string | number' is not assignable to type 'string'.
      Type 'number' is not assignable to type 'string'.
    
    2     const str: string = A.reduce((str, a) => `${str} ${a.toString}`, '')
                ~~~

    there is an easy enough workaround for now, i.e. A.map(_ => _.toString()).reduce(...), but this seems worth fixing?

    An easier workaround.We provide the generic paramater here, so it matches the second overload.

    const A = [1, '2', 3]
    const str: string = A.reduce<string>((str, a) => `${str} ${a.toString()}`, '')
  16. okmttdhr commented on Mar 31, 2020

    @okmttdhr
  17. SCWR commented on Sep 2, 2021

    @SCWR

    may be fix this with typescript 4.2.2?

  18. added 2 commits that reference this issue on Jan 29, 2026
    9f60978
    3efe7e1
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

    Domain: lib.d.tsThe issue relates to the different libraries shipped with TypeScriptGood First IssueWell scoped, documented and has the green lightHelp WantedYou can do thisSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions