question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Add spread/rest higher-order types operator

See original GitHub issue

The spread type is a new type operator that types the TC39 stage 3 object spread operator. Its counterpart, the difference type, will type the proposed object rest destructuring operator. The spread type { ...A, ...B } combines the properties, but not the call or construct signatures, of entities A and B.

The pull request is at #11150. The original issue for spread/rest types is #2103. Note that this proposal deviates from the specification by keeping all properties except methods, not just own enumerable ones.

Proposal syntax

The type syntax in this proposal differs from the type syntax as implemented in order to treat spread as a binary operator. Three rules are needed to convert the { ...spread1, ...spread2 } syntax to binary syntax spread1 ... spread2.

  1. { ...spread } becomes {} ... spread.
  2. { a, b, c, ...d} becomes {a, b, c} ... d
  3. Multiple spreads inside an object literal are treated as sequences of binary spreads: { a, b, c, ...d, ...e, f, g} becomes {a, b, c} ... d ... e ... { f, g }.

Type Relationships

  • Identity: A ... A ... A is equivalent to A ... A and A ... A is equivalent to {} ... A.
  • Commutativity: A ... B is not equivalent to B ... A. Properties of B overwrite properties of A with the same name in A ... B.
  • Associativity: (A ... B) ... C is equivalent to A ... (B ... C). ... is right-associative.
  • Distributivity: Spread is distributive over |, so A ... (B | C) is equivalent to A ... B | A ... C.

Assignment compatibility

  • A ... B is assignable to X if the properties and index signatures of A ... B are assignable to those of X, and X has no call or construct signatures.
  • X is assignable to A ... B if the properties and index signatures of X are assignable to those of A ... B.

Type parameters

A spread type containing type parameters is assignable to another spread type if the type if the source and target types are both of the form T ... { some, object, type } and both source and target have the same type parameter and the source object type is assignable to the target object type.

Type inference

Spread types are not type inference targets.

Properties and index signatures

In the following definitions, ‘property’ means either a property or a get accessor.

The type A ... B has a property P if

  1. A has a property P or B has a property P, and
  2. Either A.P or B.P is not a method.

In this case (A ... B).P has the type

  1. Of B.P if B.P is not optional.
  2. Of A.P | B.P if B.P is optional and A has a property P.
  3. Of A.P otherwise.

private, protected and readonly behave the same way as optionality except that if A.P or B.P is private, protected or readonly, then (A ...B).P is private, protected or readonly, respectively.

Index signatures

The type A ... B has an index signature if A has an index signature and B has an index signature. The index signature’s type is the union of the two index signatures’ types.

Call and Construct signatures

A ... B has no call signatures and no construct signatures, since these are not properties.

Precedence

Precedence of ... is higher than & and |. Since the language syntax is that of object type literals, precedence doesn’t matter since the braces act as boundaries of the spread type.

Examples

Taken from the TC39 proposal and given types.

Shallow Clone (excluding prototype)

let aClone: { ...A } = { ...a };

Merging Two Objects

let ab: { ...A, ...B } = { ...a, ...b };

Overriding Properties

let aWithOverrides: { ...A, x: number, y: number } = { ...a, x: 1, y: 2 };
// equivalent to
let aWithOverrides: { ...A, ...{ x: number, y: number } } = { ...a, ...{ x: 1, y: 2 } };

Default Properties

let aWithDefaults: { x: number, y: number, ...A } = { x: 1, y: 2, ...a };

Multiple Merges

// Note: getters on a are executed twice
let xyWithAandB: { x: number, ...A, y: number, ...B, ...A } = { x: 1, ...a, y: 2, ...b, ...a };
// equivalent to
let xyWithAandB: { x: number, y: number, ...B, ...A } = { x: 1, ...a, y: 2, ...b, ...a };

Getters on the Object Initializer

// Does not throw because .x isn't evaluated yet. It's defined.
let aWithXGetter: { ...A, x: never } = { ...a, get x() { throw new Error('not thrown yet') } };

Getters in the Spread Object

// Throws because the .x property of the inner object is evaluated when the
// property value is copied over to the surrounding object initializer.
let runtimeError: { ...A, x: never } = { ...a, ...{ get x() { throw new Error('thrown now') } } };

Setters Are Not Executed When They’re Redefined

let z: { x: number } = { set x() { throw new Error(); }, ...{ x: 1 } }; // No error

Null/Undefined Are Ignored

let emptyObject: {} = { ...null, ...undefined }; // no runtime error

Updating Deep Immutable Object

let newVersion: { ...A, name: string, address: { address, zipCode: string }, items: { title: string }[] } = {
  ...previousVersion,
  name: 'New Name', // Override the name property
  address: { ...previousVersion.address, zipCode: '99999' } // Update nested zip code
  items: [...previousVersion.items, { title: 'New Item' }] // Add an item to the list of items
};

Note: If A = { name: string, address: { address, zipCode: string }, items: { title: string }[] }, then the type of newVersion is equivalent to A

Rest types

The difference type is the opposite of the spread type. It types the TC39 stage 3 object-rest destructuring operator. The difference type rest(T, a, b, c) represents the type T after the properties a, b and c have been removed, as well as call signatures and construct signatures.

A short example illustrates the way this type is used:

/** JavaScript version */
function removeX(o) {
  let { x, ...rest } = o;
  return rest;
}

/** Typescript version */
function removeX<T extends { x: number, y: number }>(o: T): rest(T, x) {
  let { x, ...rest }: T = o;
  return rest;
}

Type Relationships

  • rest(A) is not equivalent to A because it is missing call and construct signatures.
  • rest(rest(A)) is equivalent to rest(A).
  • rest(rest(A, a), b) is equivalent to rest(rest(A, b), a) and rest(A, a, b).
  • rest(A | B, a) is equivalent to rest(A, a) | rest(B, a).

Assignment compatibility

  • rest(T, x) is not assignable to T.
  • T is assignable to rest(T, x) because T has more properties and signatures.

Properties and index signatures

The type rest(A, P) removes P from A if it exists. Otherwise, it does nothing.

Call and Construct signatures

rest(A) does not have call or construct signatures.

Precedence

Difference types have similar precedence to - in the expression grammar, particularly compared to & and |. TODO: Find out what this precedence is.

Issue Analytics

  • State:open
  • Created 7 years ago
  • Reactions:528
  • Comments:82 (35 by maintainers)

github_iconTop GitHub Comments

69reactions
robbyemmertcommented, Sep 22, 2018

This has been open for over 2 years now. Any progress?

This feels like a bug, since destructuring is common practice now (though it probably wasn’t as much when this issue was created). { ...props } should behave the same way as Object.assign({}, props) (which works beautifully, by the way).

If the roadblock is simply time, resources, or technical reasons, do say something. You’ve got a line of people who want this fixed and may be able to offer help/suggestions. Thanks!

38reactions
ahejlsbergcommented, Nov 2, 2018

…and generic rest variables and parameters are now implemented in #28312. Along with #28234 this completes our implementation of higher-order object spread and rest.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Spread syntax (...) - JavaScript - MDN Web Docs - Mozilla
Spread syntax "expands" an array into its elements, while rest syntax collects multiple elements and "condenses" them into a single element. See ...
Read more >
What is the rest parameter and spread operator in JavaScript
Spread operator : The spread operator helps us expand an iterable such as an array where multiple arguments are needed, it also helps...
Read more >
How to Use the Spread Operator (…) in JavaScript - Medium
The spread operator is a useful and quick syntax for adding items to arrays, combining arrays or objects, and spreading an array out...
Read more >
Understanding Destructuring, Rest Parameters, and Spread ...
There are two types of destructuring: Object destructuring and Array ... Arrays in JavaScript are guaranteed to preserve their order, ...
Read more >
Rest parameters and spread syntax
The rest of the parameters can be included in the function definition by using three dots ... followed by the name of the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found