Typescript: Inconsistent inferred overloads in conditional types for no-arg signatures with strictFunctionTypes

Created on 5 Dec 2018  路  2Comments  路  Source: microsoft/TypeScript

TypeScript Version: 3.3.0-dev.20181205

Search Terms:
conditional type, infer, overload, no-arg, zero arguments, strictFunctionTypes

Note that this issue is not about the behavior where the last overload is examined in conditional type inference of a single signature, where some might expect a union or an argument-based choice.

Code

// turn on --strictFunctionTypes

type InferTwoOverloads<F extends Function> = 
  F extends { (...a1: infer A1): infer R1, (...a0: infer A0): infer R0 } ? 
  [(...a1: A1) => R1, (...a0: A0) => R0] : 
  never;

type Expected = InferTwoOverloads<((x: string) => number) & (() => string)>
// [(x: string) => number, () => string]    

type JustOneSignature = InferTwoOverloads<((x: string) => number)>;
// never

type JustTheOtherSignature = InferTwoOverloads<(() => string)>;
// [(...a1: unknown[]) => {}, () => string]

Expected behavior:
I would expect that either both JustOneSignature and JustTheOtherSignature would be never, or both JustOneSignature and JustTheOtherSignature would evaluate to a two-tuple where the first signature is... something. I suppose (...a1: unknown[])=>{}? Or maybe it should be a copy of one of the other overloads? Or something in between?

Actual behavior:
It looks a zero-argument function is being treated pathologically during conditional type inference; if a type is callable with zero arguments, it will be considered assignable to any number of overloads with what looks like failed inference for the types of the arguments (unknown[]) and the return ({}). Otherwise, if a type is only callable with at least one argument, it will only be considered assignable to the matching number of overloads.

This distinction only seems to happen with --strictFunctionTypes on. If you turn it off, JustOneSignature above becomes [(...a1: unknown[]) => {}, (x: string) => number], which is at least consistent with the zero-argument situation.

Playground Link:
馃敆

Related Issues:

  • Not the same issue as #26591 or #27027, or the choose-last-overload behavior described in #21496
  • Maybe related to #27439?
  • Or maybe #22615?
Conditional Types Needs Investigation Rescheduled

Most helpful comment

As i mentioned in duplicate issue - problem not only in no arg function. also with (x: unknown) => any, (x: any) => any, (...args: unknown[]) => any etc.

type Overloads<F> =
    F extends {
          (...args: infer A1): infer R1
          (...args: infer A2): infer R2;
      } ? {rule: 2, variants: [A1, R1] | [A2, R2]} :
    F extends {
          (...args: infer A1): infer R1;
      } ? {rule: 1, variants: [A1, R1]} :
    never;

declare const ok1: Overloads<(x: number) => boolean>;
// {rule: 1, variants: [[number], boolean]}

declare const ok2: Overloads<{(): 1; (x: number): 2}>;
// {rule: 2, variants: [[], 1] | [[number], 2]}

declare const wrong1: Overloads<() => boolean>;
// {rule: 2, variants: [[], boolean] | [unknown[], {}]}
// expected {rule: 1, variants: [[], boolean]}

declare const wrong2: Overloads<(...args: unknown[]) => boolean>;
// {rule: 2, variants: [unknown[], boolean] | [unknown[], {}]}
// expected {rule: 1, variants: [unknown[], boolean]}

declare const wrong3: Overloads<(x: unknown) => boolean>;
// {rule: 2, variants: [[unknown], boolean] | [unknown[], {}]}
// expected {rule: 1, variants: [[unknown], boolean]}

declare const wrong4: Overloads<(x: any) => boolean>;
// {rule: 2, variants: [[any], boolean] | [unknown[], {}]}
// expected {rule: 1, variants: [[any], boolean]}

All 2 comments

As i mentioned in duplicate issue - problem not only in no arg function. also with (x: unknown) => any, (x: any) => any, (...args: unknown[]) => any etc.

type Overloads<F> =
    F extends {
          (...args: infer A1): infer R1
          (...args: infer A2): infer R2;
      } ? {rule: 2, variants: [A1, R1] | [A2, R2]} :
    F extends {
          (...args: infer A1): infer R1;
      } ? {rule: 1, variants: [A1, R1]} :
    never;

declare const ok1: Overloads<(x: number) => boolean>;
// {rule: 1, variants: [[number], boolean]}

declare const ok2: Overloads<{(): 1; (x: number): 2}>;
// {rule: 2, variants: [[], 1] | [[number], 2]}

declare const wrong1: Overloads<() => boolean>;
// {rule: 2, variants: [[], boolean] | [unknown[], {}]}
// expected {rule: 1, variants: [[], boolean]}

declare const wrong2: Overloads<(...args: unknown[]) => boolean>;
// {rule: 2, variants: [unknown[], boolean] | [unknown[], {}]}
// expected {rule: 1, variants: [unknown[], boolean]}

declare const wrong3: Overloads<(x: unknown) => boolean>;
// {rule: 2, variants: [[unknown], boolean] | [unknown[], {}]}
// expected {rule: 1, variants: [[unknown], boolean]}

declare const wrong4: Overloads<(x: any) => boolean>;
// {rule: 2, variants: [[any], boolean] | [unknown[], {}]}
// expected {rule: 1, variants: [[any], boolean]}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

blendsdk picture blendsdk  路  3Comments

MartynasZilinskas picture MartynasZilinskas  路  3Comments

uber5001 picture uber5001  路  3Comments

kyasbal-1994 picture kyasbal-1994  路  3Comments

bgrieder picture bgrieder  路  3Comments