microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.11k stars 12.5k forks source link

Enable the programmatic generation of overloads like feature in types. #23669

Closed sledorze closed 6 years ago

sledorze commented 6 years ago

I've not found anything related: Search Terms: overloads, programmatic

The context that explain the need to help understand the suggestion:

I'm using the io-ts (https://github.com/gcanti/io-ts) library which enables the creation of encoders/decoders by combining simpler ones.

This library can be extended by creating new types of codecs. From a codec representation, I want to derive several generations programmatically (jsonSchema, random generators, etc..). So I'm creating a conversion from a selected union of all base standard io-ts codecs and provides the end user of the library with a function to defines the remaining one it needs to handle for a particular codec it uses (for instance he may use a specific codec for jsJoda date in its User codec to represent its birthDate). He may want to generate random data from that Codec but only providing the definition of it's jsJoda codec.

So the use case is a library that needs to expose a function to handles cases based on a union of types (say A | B) to represent the non standard types (codecs in the example) and requires to return specific values based on those types ( Result<A> | Result<B> ) but depending on the actual input values, like so: (x: A) => Result<A> and (x: B) => Result<B> and NOT like so: (x: B) => Result<A> or (x: A) => Result<B>.

As of today, there's no way (known to me) to derive a type ((x: A) => Result<A>) & ((x: B) => Result<B>) from A | B and, given that signature ((x: A) => Result<A>) & ((x: B) => Result<B>), to implement it.

However; the abstraction seems to exist in classes via Overloads.

Related code:


interface Result<T> {
  res: T
}

class A {
  type: 'a' = 'a'
}
class B {
  type : 'b' = 'b'
}
type All = A | B

type Expected = ((x: A) => Result<A>) & ((x: B) => Result<B>)

const foo: Expected = (x: All) => { // Type '(x: All) => Result<A> | Result<B>' is not assignable to type 'Expected'
  if (x.type === 'a') {
    return 1 as any as Result<A>
  } else {
    return 1 as any as Result<B>
  }
}

// I need foo to unify with `((x: A) => Result<A>) & ((x: B) => Result<B>)` based on its control flow and not only `(x: All) => Result<A> | Result<B>`

// note about types correctly dispatching the result type based on the input type (like with overloads)
const f: Expected = 1 as any
f(1 as any as A) // Result<A> (ok) (and shows ‘overload info)
f(1 as any as B) // Result<B> (ok) (and shows overload info)

The suggestion is then to support that needs by providing both a way to encode overloads programatically (from types and not on classes) and their implementation (dispatch)

SimonMeskens commented 6 years ago

I'm not sure we need this, you could just use this definition of Expected and it works:

type Expected = <T extends All>(x: T) => 
    T extends A ? Result<A> : 
    T extends B ? Result<B> : 
    any;
SimonMeskens commented 6 years ago

Actually, I think I get what you mean, it's not possible to assign to such a type either. The problem is not creating a type that maps different types to results, the problem is assigning to it.

I'll have a look if the language is currently capable of that.

sledorze commented 6 years ago

@SimonMeskens indeed, there is two issues.

You seem to consider that passing from A | B to T extends A ? Result<A> : T extends B ? Result<B> : any; may not be an issue; do you have an implementation example to share?

SimonMeskens commented 6 years ago

Not from any cursory testing no. It seems we can neither assign to the intersection or the conditional, unless there's a way I'm not seeing.

SimonMeskens commented 6 years ago

There's a third option btw:

interface Expected  {
    (x: A): Result<A>
    (x: B): Result<B>
}

We can't assign to this one either.

sledorze commented 6 years ago

That's a bunch of interesting use cases to crack!

mhegazy commented 6 years ago

I think the type should be defined as:

type Expected = <T extends A | B>(x: T) => T extends A ? Result<A> : Result<B>;

The problem is today there is no way to implement this function without casts. we have been using https://github.com/Microsoft/TypeScript/issues/23132 to track that.

sledorze commented 6 years ago

@mhegazy well, its not exactly a Duplicate. I be correct, I would say that one potential solution to this issue has problems which are tracked by #23132 but I guess there's no label for that.

typescript-bot commented 6 years ago

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

sledorze commented 6 years ago

Provided its not a duplicate of the other issue. There's some over Automation swallowing a real non addressed issue here...