Typescript: Tuple iteration and merging

Created on 30 Jul 2018  ·  13Comments  ·  Source: microsoft/TypeScript

Edit: Remove indexing part
Edit 2: Replace tuple mapping syntax with normal mapped types

Search Terms

Concatentate and merge tuples' entries.

Suggestion

  1. I would like to be able to extract and spread in positions other than the last part of a tuple.
  2. I would like to merge a tuple into an intersection or concatenation of its members.

Use Cases

  • Converting a tuple to its intersection, such as with Object.assign's assignee.
  • Typing Array.prototype.concat correctly for tuples.

Syntax

The syntax comes in a few new forms:

  • [...A, B] appends B to the tuple A. Similarly, [...infer A, any] extends B ? A : [] drops the last item of the tuple and [...any[], infer A] extends B ? A : never extracts the first.
  • {... ...T} for an n-ary merge and [... ...T] an n-ary concatenation.

Examples

Here's the types for each method I mentioned above.

interface Array<T> {
    concat<U extends any[]>(...others: U): [...this, ... ...{[I in keyof U]: (
        U[I] extends any[] ? N : U[I] extends ArrayLike<infer R> ? R[] : [U[I]]
    )}];
}

interface ObjectConstructor {
    assign(target: {... ...typeof sources}, ...sources: object[]): typeof target;
}

Checklist

My suggestion meets these guidelines:

  • [x] This wouldn't be a breaking change in existing TypeScript / JavaScript code
  • [x] This wouldn't change the runtime behavior of existing JavaScript code
  • [x] This could be implemented without emitting different JS based on the types of the expressions
  • [x] This isn't a runtime feature (e.g. new expression-level syntax)
In Discussion Suggestion

Most helpful comment

Ignoring tuples for a second, I want a way to easily convert any union to an intersection

@weswigham check this out:

/**
 * @author https://stackoverflow.com/users/2887218/jcalz
 * @see https://stackoverflow.com/a/50375286/10325032
 */
type UnionToIntersection<Union> =
  (Union extends any
    ? (argument: Union) => void
    : never
  ) extends (argument: infer Intersection) => void
      ? Intersection
      : never;

All 13 comments

And finally, merging is just syntax sugar now: {...A, ...B}(& ...[A, B])

I don't think this is correct. It should behave like object spread in JS and override existing properties.

type T = {... {a: string}, ...{a: number}};
// should result in
type T = {a: number};
// instead of
type T = {a: string} & {a: number};

@ajafff Fixed by removing the line. I forgot about that bit.

The 1st part:

I would like to be able to extract and spread in positions other than the last part of a tuple.

Seems to be a duplicate of: #25717 and therefore #1360

@AlCalzone For the first half, probably, but the second half is likely not.

See also #5453 for part 1.

BTW, I updated the proposal to leverage this recently-merged PR.

Converting a tuple to its union, such as with Promise.race's return value.

Doesn't

type TupleTypes<T extends unknown[]> = T[number]

?
work for that?

@weswigham Good catch - no clue how that escaped me. I updated the proposal comment to drop that (and those covered by #26063), but the issue with concatenation and Object.assign still remains.

I changed up the proposed syntax a little, too, to better fit in:

  • Merge all: {... ...T}
  • Concatenate all: [... ...T]

Of course, that space in the middle isn't significant, just explains how it's tokenized.

Converting a tuple to its intersection, such as with Object.assign's assignee.

Ignoring tuples for a second, I want a way to easily convert any union to an intersection. But y'know, I think it is doable, if cumbersome:

type MergeArguments<T, K extends string = "whatever"> =
    {
        [Key in K]: T extends (first: infer A) => void ? A :
        MergeOnePlus<T, K>
    }[K];

type MergeOnePlus<T, K extends string> =
    {
        [Key in K]: T extends (first: infer A, ...args: infer U) => void ? A & MergeArguments<(...args: U) => void, K> :
        never
    }[K];

type IntoSignature<T extends unknown[]> = (...args: T) => void;

type MergeTupleMembers<T extends unknown[]> = MergeArguments<IntoSignature<T>>;

type SomeTuple = [{ x: 1 }, { y: 2 }, { z: 3 }];

type Merged = MergeTupleMembers<SomeTuple>;

const x: Merged = {
    x: 1,
    y: 2,
    z: 3
};

It's a bit ridiculous, but.... possible.

@weswigham That might very well solve the issue of both concatenation and merging, if I'm reading it correctly.

By any chance, would that still work if you did this to do concatenation?

type ConcatenateOnePlus<T, K extends string> =
    {
        [Key in K]: T extends (first: infer A, ...args: infer U) => void ? [A, ...ConcatenateArguments<(...args: U) => void, K>] :
        never
    }[K];

Or would that be dependent on #25717 / #1360?

Ignoring tuples for a second, I want a way to easily convert any union to an intersection

@weswigham check this out:

/**
 * @author https://stackoverflow.com/users/2887218/jcalz
 * @see https://stackoverflow.com/a/50375286/10325032
 */
type UnionToIntersection<Union> =
  (Union extends any
    ? (argument: Union) => void
    : never
  ) extends (argument: infer Intersection) => void
      ? Intersection
      : never;

I'm working on a utility lib with Merg(Intersection), object only Merg, the well-known symbol iterator walkaround. and type morphing. Please check if they are okay. (this is my first ts lib)

export type Omit2<T, K extends keyof T> = T extends { [Symbol.iterator]: infer U } ? { [Symbol.iterator]: U } & Omit<T, K> : Omit<T, K>;
export type ExcludeO<T extends object, U extends object> =
  U extends { [Symbol.iterator]: any } ? Omit<T, keyof U> :
  T extends { [Symbol.iterator]: infer IT } ? { [Symbol.iterator]: IT } & Omit<T, keyof U> : Omit<T, keyof U>;

export type ExtractO<T extends object, U extends object> =
  U extends { [Symbol.iterator]: any } ? T extends { [Symbol.iterator]: infer IT } ?
  { [Symbol.iterator]: IT } & Pick<T, Extract<keyof T, keyof U>> :
  Pick<T, Extract<keyof T, keyof U>> : Pick<T, Extract<keyof T, keyof U>>;

export type Merg<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
export type MergO<U extends object> = (U extends object ? (k: U) => void : never) extends (k: infer T) => void ?
  (T extends object ? T : object) : object;

export type UnionTupleType<A extends any[]> = A extends { [n: number]: infer T } ? T extends object ? T : never : never;
export type MergTupleType<A extends any[]> = MergO<UnionTupleType<A>>;

export type Alter<T extends object, U extends object> = ExcludeO<T, U> & ExtractO<U, T>;
export type Extend<T extends object, U extends object> = T & ExcludeO<U, T>;
export type Override<T extends object, U extends object> = ExcludeO<T, U> & U;

export type AlterOver<T extends object, U extends object, X extends object> = Alter<T, ExcludeO<U, X>>;
export type ExtendOver<T extends object, U extends object, X extends object> = Extend<T, ExcludeO<U, X>>;
export type OverrideOver<T extends object, U extends object, X extends object> = Override<T, ExcludeO<U, X>>;

export type AlterLike<T extends object, U extends object, X extends object> = Alter<T, ExtractO<U, X>>;
export type ExtendLike<T extends object, U extends object, X extends object> = Extend<T, ExtractO<U, X>>;
export type OverrideLike<T extends object, U extends object, X extends object> = Override<T, ExtractO<U, X>>;

@weswigham this should fit your need and it enforces object
and they are here

export type MergO<U extends object> = (U extends object ? (k: U) => void : never) extends (k: infer T) => void ? (T extends object ? T : object) : object;
export type UnionTupleType<A extends any[]> = A extends { [n: number]: infer T } ? T extends object ? T : never : never;
export type MergTupleType<A extends any[]> = MergO<UnionTupleType<A>>;
Was this page helpful?
0 / 5 - 0 ratings

Related issues

fwanicka picture fwanicka  ·  3Comments

manekinekko picture manekinekko  ·  3Comments

siddjain picture siddjain  ·  3Comments

blendsdk picture blendsdk  ·  3Comments

Antony-Jones picture Antony-Jones  ·  3Comments