Skip to content

Incorrect overload selected for Array.prototype.map(...)Β #47571

Closed

Description

@oleg-slapdash

Bug Report

πŸ”Ž Search Terms

overload, array.prototype.map

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried.

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

declare type Obj = {a: true};
declare const arr: Array<string | Obj>;
declare function func(url: string | Obj): string;
declare function func<T extends Obj | string>(url: T): string | null;
const fails: string[] = arr.map(func);
// ^^^ Type '(string | null)[]' is not assignable to type 'string[]'.
const works: string[] = arr.map((v) => func(v));



interface CustomArray<T> {
    map<U>(callbackfn: (value: T) => U): U[];
}
declare const arr2: CustomArray<string | Obj>;
const fails2: string[] = arr2.map(func);
// ^^^ Type '(string | null)[]' is not assignable to type 'string[]'.
const works2: string[] = arr2.map((v) => func(v));

πŸ™ Actual behavior

It fails to select the correct overloard.

πŸ™‚ Expected behavior

It is supposed to select the correct overload.

Activity

  1. RyanCavanaugh commented on Jan 25, 2022

    @RyanCavanaugh
    Member

    Inference doesn't do normal overload resolution per se, instead it performs structural inference between the signatures to try to line things up.

    Is there a real definition of func ? This problem can almost always be avoided in non-pathological cases.

  2. oleg-slapdash commented on Jan 25, 2022

    @oleg-slapdash
    Author

    Ryan Cavanaugh (@RyanCavanaugh), this is an approximation. Basically, this is a helper function that gets a string property of an object or returns a string if a string is passed instead of an object.

    type ObjWithFoo = {foo: string};
    
    declare function getFoo(foo: string | ObjWithFoo): string;
    declare function getFoo(url: ObjWithFoo | string | null | undefined): string | null | undefined;
    
    declare const arr: Array<string | ObjWithFoo>;
    
    export const works: string[] = arr.map((v) => getFoo(v));
    export const fails: string[] = arr.map(getFoo);
    // Err: ^^^ Type '(string | null | undefined)[]' is not assignable to type 'string[]'
  3. RyanCavanaugh commented on Jan 25, 2022

    @RyanCavanaugh
    Member

    Conditional types will get instantiated, so I'd recommend this:

    type ObjWithFoo = {foo: string};
    
    declare function getFoo<T extends ObjWithFoo | string | null | undefined>(url: T): T extends undefined | null ? T : string;
    
    declare const arr: Array<string | ObjWithFoo>;
    
    export const works: string[] = arr.map((v) => getFoo(v));
    export const alsoWorks: string[] = arr.map(getFoo);
    
    declare const arrU: Array<string | ObjWithFoo | undefined>;
    export const chk = arrU.map(getFoo); // Array<string | undefined>
  4. oleg-slapdash commented on Jan 25, 2022

    @oleg-slapdash
    Author
  5. maple5233 commented on Apr 26, 2022

    @maple5233

    Conditional types will get instantiated, so I'd recommend this:

    type ObjWithFoo = {foo: string};
    
    declare function getFoo<T extends ObjWithFoo | string | null | undefined>(url: T): T extends undefined | null ? T : string;
    
    declare const arr: Array<string | ObjWithFoo>;
    
    export const works: string[] = arr.map((v) => getFoo(v));
    export const alsoWorks: string[] = arr.map(getFoo);
    
    declare const arrU: Array<string | ObjWithFoo | undefined>;
    export const chk = arrU.map(getFoo); // Array<string | undefined>

    Will this bug be fixed? The way using the conditional types looks like a work-around.

  6. ExplodingCabbage commented on Mar 14, 2025

    @ExplodingCabbage

    Summary of others' points from duplicate #61398, for the benefit of anyone else landing here from search:

    • This was never fixed in any way and the "completed" closure reason here should be ignored (as with on many other closed issues)
    • The "Design Limitation" label indicates the actual reason for closure
    • Per https://github.com/Microsoft/TypeScript/wiki/FAQ#this-is-closed-but-should-be-open-or-vice-versa, that label is basically just a "we don't see how to fix this right now" label, not necessarily a "there is a profound design reason this is unfixable" label. PRs to fix it remain welcome despite the closure.
    • It's specifically the case that whenever an overloaded function is passed as an argument to another function, its last overload signature is treated as its signature and all preceding signatures are ignored. There is something of a logic to this in that the last signature will sometimes be a "catch-all" signature and thus most likely to not cause a type error (though for an overloaded function without a catch-all signature, this choice ends up being effectively completely arbitrary).
    • There was a promising-looking attempt to fix this at Improve overload and generic signature inference: Inference alternatives and linked inferencesΒ #52944, but apparently it had performance issues, and it's been sitting untouched for a couple of years now. Possibly there are profound reasons those performance issues are hard to solve though that's hard to tell for a casually-observing outsider.
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