Skip to content

Infer overloads instead of union returns from flow analysis #17747

Closed

Description

Given the following code:

const flip = (x: number) => x.toString()
const flop = (x: string) => x.length

// What is the return type of flipFlop?
const flipFlop = (x: string | number) => isNumber(x) ? flip(x) : flop(x)

Today flipFlop is inferred as having a return type of string | number. Intuitively though, flipFlop is an overloaded function of type (x: number) => string & (x: string) => number.

Given that TypeScript already performs flow analysis in order to discover the union return type string | number, it seems like some useful information is being discarded. Specifically, when performing the analysis that yields a return type of number | string, it seems a shame to throw away the associated information that x is narrowed to number in the exit block where the function's return type is string, and vice versa.

If we instead utilize this information, it becomes possible to infer the much more accurate type (x: number) => string & (x: string) => number.


In general, it might be nice if union parameters, overloaded functions, and control flow could be integrated more smoothly.

A function type with a union parameter (x: number | string) => Date is basically equivalent to the overloaded function (x: number) => Date & (x: string) => Date. Similarly, a branched function with various return points (such as flipFlop) is overloaded with respect to the return type.

If the CFA treats each type in the union type of a parameter as a separate entry point, and solves the control flow for each indepenently, it should be possible to discover multiple independent mappings of input types to output type (i.e. overloads).

For functions with multiple parameters of union types, the overloads inferred must be a cartesian product of the unions. This can get expensive quickly, but some analysis of the function body to predict which types are actually discriminated against should allow "inert" unions that do not participate in control flow to be grouped together and treated as a single overload/entrypoint.

Activity

  1. added
    SuggestionAn idea for TypeScript
    Too ComplexAn issue which adding support for may be too complex for the value it adds
    and removed
    Needs InvestigationThis issue needs a team member to investigate its status.
    on Sep 18, 2019
  2. RyanCavanaugh commented on Sep 18, 2019

    @RyanCavanaugh
    Member

    After adding conditional types and trying out some related patterns with other features, this doesn't seem like a good fit. Once you start pushing the CFA through to the return type, you can quickly end up with a return type expression that looks like an unholy restating of the function implementation, and in general function implementations should be opaque by default - the caller today might return a string when given a number and vice versa, but the maintainable-by-default version is to not expose the internal logic of your function unless that's what you actually intend to do.

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

    SuggestionAn idea for TypeScriptToo ComplexAn issue which adding support for may be too complex for the value it adds

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions