Skip to content

Type inference regression with overloads #27972

Description

TypeScript Version: 3.2.0-dev.20181018

Search Terms:

Code

declare function f<T>(cf: (() => T) | ((x: T) => boolean)): void;

declare function myFn(): Date;
declare function myFn(n: number): void;

f<Date>(myFn); // works
f(myFn); // error

Expected behavior:

No error, as in typescript@3.1.

Actual behavior:

src/a.ts:7:3 - error TS2345: Argument of type '{ (): Date; (n: number): void; }' is not assignable to parameter of type '(() => number) | ((x: number) => boolean)'.
  Type '{ (): Date; (n: number): void; }' is not assignable to type '() => number'.
    Type 'Date' is not assignable to type 'number'.

7 f(myFn); // error

Looks like this was introduced by #27028. Discovered in bluebird on DefinitelyTyped -- in that case it had to do with type CatchFilter<E> = (new (...args: any[]) => E) | ((error: E) => boolean) | (object & E); which unions a construct and call signature which more clearly should be separate.

Activity

  1. ghost added
    BugA bug in TypeScript
    on Oct 18, 2018
  2. ahejlsberg commented on Nov 4, 2018

    @ahejlsberg
    Member

    This is working as intended. First, some clarifications. This is a change introduced in 3.1, not a new change in 3.2 as implied above, and the issue surfaces only in --strictFunctionTypes mode.

    The change in 3.1 is that when, in --strictFunctionTypes mode, we have both co- and contra-variant inferences for a type parameter, we prefer the contra-variant inference unless the co-variant inference is a subtype and not never. That causes us to infer number instead of void in the example (which, for those two candidates, is the correct choice).

    Now, the real issue is that we only make inferences from the last overload because we're inferring from a type with two signatures to a type with only one signature. We've always had the rule that we match signatures pair-wise from the bottom when doing inference, so nothing new there. However, it means that nothing is or was ever inferred from the first overload, and it just so happened to work previously because we made a bad inference from the second overload.

  3. added
    Working as IntendedThe behavior described is the intended behavior; this is not a bug
    and removed
    BugA bug in TypeScript
    on Nov 4, 2018
  4. demurgos commented on Dec 2, 2018

    @demurgos

    Hi,

    I think I have a related bug that is failing due to this issue:

    Playground

    export interface DocumentType<T> {
        hasKey(key: keyof T): boolean;
    }
    
    export interface DocumentTypeConstructor {
      new<T>(value: T): DocumentType<T>;
    }
    
    export const DocumentType: DocumentTypeConstructor = class <T> {
        private readonly value: T;
    
        constructor(value: T) {
            this.value = value;
        }
    
        hasKey(key: keyof T): boolean {
            return key in this.value;
        }
    };

    I get error TS2394: Overload signature is not compatible with function implementation. associated to hasKey in the first interface (see playground).
    This code was compiling in 3.0 but started to fail in 3.1.

    I found that the following code fixes the issue, but I don't understand why:

    export interface DocumentType<T> {
        hasKey(key: keyof T): boolean;
    }
    
    export interface DocumentTypeConstructor {
      new<T>(value: T): DocumentType<T>;
    }
    
    export const DocumentType: DocumentTypeConstructor = class <T> {
        private readonly value: T;
    
        constructor(value: T) {
            this.value = value;
        }
    
        hasKey<TT extends keyof T>(key: TT): boolean {
            return key in this.value;
        }
    };

    The only difference is that I modified the signature of the implementation from hasKey(key: keyof T): boolean to hasKey<TT extends keyof T>(key: TT): boolean but in my opinion both should be equivalent. What am I missing?

  5. typescript-bot commented on Dec 13, 2018

    @typescript-bot
    Contributor

    This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  6. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Working as IntendedThe behavior described is the intended behavior; this is not a bug

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions