Skip to content

Prevent undefined assignments to optional values #26438

@pygy

Description

@pygy

Search Terms

mapped optional undefined

Suggestion

I would like a way to have optional object fields that can't be assigned undefined. Getting that value may yield undefined if the field is absent, but it should not be possible to set it.

Use Cases

I'm in the process of writing typings for the Patchinko JS library that allows one to patch objects like a bit like Object.assign, but also supports efficient deep patching (left out of the examples for clarity):

const patched = patch({a: 5, b: 6}, {a: 4})

Where patch is defined as

const Delete = Symbol()

type Patch<T> = {
	[P in keyof T]?: T[P] | (undefined extends T[P] ? typeof Delete : never)
}

declare function patch<T>(x: T, ...rest: Patch<T>[]): T

The problem here is that Patch<T> lets one define objects that have undefined values, and patch({a: 5}, {}) doesn't behave like patch({a: 5}, {a: undefined}).

Sugested syntax: allow ?! where ? is legal.

Note that this would still let one delete fields defined as mandatory but have undefined as a valid type. It would be nice to also have a predicate to tell apart optional fields from "nullable" ones.

Examples

Pseudo-TS:

const Delete = Symbol()

type Patch<T> = {
  [P in keyof T] ?! : T[P] | (undefined extends T[P] ? typeof Delete : never)
}

function patch<T>(source: T, ...patches: Patch<T>[]): T {
  for (const patch of patches) {
    for (const k in patch) {
      if (patch[k] === Delete) delete source[k]
      // currently a type error if you replace the with a ?! with a ?
      else source[k] = patch[k]
    }
  }
  return source
}



interface X {
  a: number,
  b?: number
}

const o = {a: 5} as X

const p = patch(o, {b: 6})

const q = patch(p, {b: Delete})

const r = patch (q, {a: undefined}) // this should be a type error.

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions