Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tuple iteration and merging #26058

Open
4 tasks done
dead-claudia opened this issue Jul 30, 2018 · 14 comments
Open
4 tasks done

Tuple iteration and merging #26058

dead-claudia opened this issue Jul 30, 2018 · 14 comments
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@dead-claudia
Copy link

dead-claudia commented Jul 30, 2018

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:

  • This wouldn't be a breaking change in existing TypeScript / JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. new expression-level syntax)
@dead-claudia dead-claudia changed the title Tuple reducers Tuple iteration and merging Jul 30, 2018
@ajafff
Copy link
Contributor

ajafff commented Jul 30, 2018

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};

@dead-claudia
Copy link
Author

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

@AlCalzone
Copy link
Contributor

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

@dead-claudia
Copy link
Author

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

@bterlson
Copy link
Member

See also #5453 for part 1.

@mhegazy mhegazy added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Jul 30, 2018
@dead-claudia
Copy link
Author

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

@weswigham
Copy link
Member

weswigham commented Aug 2, 2018

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?

@dead-claudia
Copy link
Author

@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.

@weswigham
Copy link
Member

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.

@dead-claudia
Copy link
Author

@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?

@karol-majewski
Copy link

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;

@lieene
Copy link

lieene commented Nov 13, 2019

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>>;

@lieene
Copy link

lieene commented Nov 13, 2019

@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>>;

@ericbiewener
Copy link

@lieene That is wild! What is going on with the functions in MergO? It sort of seems like it's being used to funnel the U into infer T, I guess because you can't use infer inside the branches of a conditional?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

9 participants