Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript 3 parameter list intersection type

Tags:

typescript

TypeScript 3 improved parameter lists.

I'm looking at the type definiton for Object.assign, it looks something like this:

interface ObjectConstructor {
    assign<T, U>(target: T, source: U): T & U;
    assign<T, U, V>(target: T, source1: U, source2: V): T & U & V;
    assign<T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W;
    ...

I'm wondering if we can rewrite that method signature without overloads now? i.e. can we express that the return value is the intersection of all input arguments, or is this still not possible?

I need something very similar for a function I'm working on.

like image 977
mpen Avatar asked Jul 30 '18 22:07

mpen


People also ask

What are intersection types in TypeScript?

An intersection type combines multiple types into one. This allows you to add together existing types to get a single type that has all the features you need. For example, Person & Serializable & Loggable is a type which is all of Person and Serializable and Loggable .

What does ?: Mean in TypeScript?

What does ?: mean in TypeScript? Using a question mark followed by a colon ( ?: ) means a property is optional. That said, a property can either have a value based on the type defined or its value can be undefined .

Can you pass a type as a parameter TypeScript?

To type a function as a parameter, type the function's parameter list and its return value, e.g. doMath: (a: number, b: number) => number . If the function's definition becomes too busy, extract the function type into a type alias.

What is @types in TypeScript?

What is a type in TypeScript. In TypeScript, a type is a convenient way to refer to the different properties and functions that a value has. A value is anything that you can assign to a variable e.g., a number, a string, an array, an object, and a function. See the following value: 'Hello'


2 Answers

Let's start with converting a tuple of types to a union of types in the tuple:

type TupleTypes<T> = { [P in keyof T]: T[P] } extends { [key: number]: infer V } ? V : never;
type A = TupleTypes<[1, "hello", true]>; // === 1 | "hello" | true

Then we borrow from this answer to convert from a union of types to an intersection of types:

type UnionToIntersection<U> = (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never;
type B = UnionToIntersection<A>; // === 1 & "hello" & true

Now that we have that, let's write a wrapper for Object.assign:

function assign<T extends object, S extends object[]>(target: T, ...sources: S): T & UnionToIntersection<TupleTypes<S>> {
    return Object.assign(target, ...sources);
}

const good = assign({ a: "2" }, { b: "3" }, { c: "4" }, { d: "4" }, { g: "5" });
// const good: { a: string; } & { b: string; } & { c: string; } & { d: string; } & { g: string; }
like image 145
y2bd Avatar answered Oct 11 '22 11:10

y2bd


While I like @y2bd's answer, I think it will have a problem if any of the passed-in parameters are themselves union types:

const eitherOr = Math.random() < 0.5 ? { c: "4" } : { c: 5 };
const notSoGood = assign({ a: "2" }, { b: "3" }, eitherOr);
notSoGood.c; // string & number ?!

You will find that eitherOr gets turned from a union to an intersection, and notSoGood is not the right type.

A way around that is to "box" and "unbox" the tuples so that you can distinguish single-parameters-which-are-unions from the union of multiple parameter types:

type BoxedTupleTypes<T extends any[]> =
  { [P in keyof T]: [T[P]] }[Exclude<keyof T, keyof any[]>]
type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
type UnboxIntersection<T> = T extends { 0: infer U } ? U : never;
declare function assign<T, S extends any[]>(
  target: T,
  ...sources: S
): T & UnboxIntersection<UnionToIntersection<BoxedTupleTypes<S>>>

Now you'll have the right type:

 const notSoGood = assign({ a: "2" }, { b: "3" }, eitherOr);
 notSoGood.c // string | number

That being said I don't know if this version has its own problems. Not to mention that intersection isn't really what you want anyway. You'd rather overwrite properties if possible, which intersection doesn't capture. So before we suggest changing the standard library we might want to think about the best signature that TypeScript has to offer us.

Hope that helps. Good luck!

like image 24
jcalz Avatar answered Oct 11 '22 10:10

jcalz