Skip to content

Proposal: Add a strictly typed spread operator #30826

Closed as not planned
Closed as not planned
@vbfox

Description

@vbfox

Search Terms

  • spread
  • strict spread

There are a few related issues closed with a link to #12632 (For example #17422) but it's a lot more generic, this proposal is very specific to the spread operator where I believe there is a relatively simple fix that would handle a lot of situations.

Suggestion

Add an operator similar to the spread operator that ensure that the type of the resulting object is the same as the type of the input one.

It would serve the exact same purpose as the with operator in F#, the | operator in Elm or the ... operator in ReasonML.

For example:

interface State {
    readonly id: number,
    readonly validated: boolean
}

// if the operator is `....` (same as ReasonML with an additional dot) : 
const state: State = { id: 0, validated: false };
const newState = { ....state, validated: true }; // newState: State
const newState2 = { ....state, failed: true }; // Compilation error, 'failed' isn't in the keys of State

The following limitations would apply:

  • The operator is only valid in the first position of an anonymous object build with spread
  • The target type can be created by an anonymous object (so an interface not a class)

It would be compiled to ... as the difference is only relative to typechecking

Use Cases

The central use case is to mutate objects that are used as immutable like React state or Redux state.

While it currently work most of the time as in the end the signature generated by the spread operator will be matched against the real type it produce problems:

  • Errors are often shown far away from where the problem really is, requiring to hunt what function actually generated a type that had an invalid property
  • Any function returning the result of a spread has a hard to read signature, often for nothing as an interface matching it exists
  • The solution is often to sprinkle manual type annotations everywhere that would be unnecessary if the type wasn't lost in the first place

Examples

interface State {
    readonly id: number,
    readonly validated: boolean
}

// State => State
const validate = (s: State) => {....s, validated: true};

const state: State = { id: 0, validated: false };
const newState = { ....state, id: 42 }; // : State
const validatedState = validate(newState); // : State

Would also work for anonymous types :

const state = { id: 0, validated: false };
const newState = { ....state, id: 42 }; // Ok
const newState2 = { ....state, isInvalid: true }; // Compilation error, 'isInvalid' isn't in the keys of '{ id: number, validated: boolean }'

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. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

Metadata

Metadata

Assignees

No one assigned

    Labels

    SuggestionAn idea for TypeScriptToo ComplexAn issue which adding support for may be too complex for the value it adds

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions