immerjs / immerjs/immer

TS errors for draft objects with properties that are indexed using well-known symbols

Open
#710 3 comments 5 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

typescript
Dominant language
JavaScript
Stars
29k
Forks
881
PR merge metrics
No merged PRs in 30d

Description

🐛 Bug Report

Draft<T> objects (at least according to TS) don't bring along any symbol-indexed properties from the original object. Is this expected? I'm not sure if this is only an issue in the TS types, or is the underlying JS draft object also missing its symbol-indexed properties?

Although well-known symbols are usually used with classes, I was able to repro the problem with a plain object too.

I'm using TS 4.1, if it matters.

FWIW, I ran into this because I was trying to call produce() with a deeply-nested object which had a Uint8Array-valued property far down in the tree. When I tried to pass the draft object to another method that expected the original type, TS complained that [Symbol.iterator] and [Symbol.toStringTag] properties that were required on Uint8Array but missing on the Draft<T> object. I'm not trying to mutate the array (that'd be #696); I'm just expecting a deep clone but (at least from TS's perspective) the symbol properties are missing.

Link to repro

import produce, { Draft } from 'immer';

const foo = {
  a() { return 42; },
  get [Symbol.toStringTag]() { return 'foo'; }
}

type Foo = typeof foo;

function withFoo(f: Foo) {
  return f[Symbol.toStringTag];
}

const ok = foo[Symbol.toStringTag];  // no error
const ok2 = withFoo(foo);  // no error

const producer = produce((draft: Draft<Foo>) => {
  draft.a();  // no error
  withFoo(draft);
  /*      ^^^^^
    Argument of type 'WritableDraft<{ a(): number; readonly [Symbol.toStringTag]: string; }>' 
    is not assignable to parameter of type '{ a(): number; readonly [Symbol.toStringTag]: string; }'.
    Property '[Symbol.toStringTag]' is missing in type 'WritableDraft<{ a(): number; 
    readonly [Symbol.toStringTag]: string; }>' but required in type 
    '{ a(): number; readonly [Symbol.toStringTag]: string; }'. (2345)
  */

  const fail = draft[Symbol.toStringTag];
  /*           ^^^^^^^^^^^^^^^^^^^^^^^^^
     Element implicitly has an 'any' type because expression of type 'symbol' can't be used
     to index type 'WritableDraft<{ a(): number; readonly [Symbol.toStringTag]: string; }>'.(7053)
  */
});

https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBGKEAmBXAxgUwDRwN5wAiUAhgGbwC+cZSIcA5KCJlAwNwBQn6EAdgGd4ZCBDgBefJzhwSACgCU+OFEwxUUPnAAsAJnZxK2aXADmauAG0AygE8QAIwgAbAHQwI1mFGB9TAFRJTAF1FZVV1TUYRCA5DTkpuGFswTDgAMVEJOGTUiDIaUS5OMlQ+dBhgfjgAd2AYAAtMiDkyAC4M0SU8EwiNLTIbeyc3Dy8fP0CQrkSefiE4CABrbJihxxd3T29fAKDggzgAeiO4PjFWJCg5wXhl3Wy6xubWrsOTs4uoK+5eW4QkGgsFBsogUBhMHI5MhSBQOsRyDAADzNAB8SnEqKkMhhiNc8gU71O5zgl2gJieTVE0NhMEJJiOACoZCy4AA9DkckwyACCUFMqBYfDuBVyaQYAHUfDASA5nJgERQkQQCR0+IKHKwDKoSMh+M5bFY7BtRtsJnsQh0hOaDJRUQw4Ny4MABJ94CQBAJgKY+LL5TkxGASKQWDBWItRSlxSrFGqNVqVJhdfrDesRltxrspsErTs-LaGK4nQAFJCpWCGhhpzZjPMW4IOl1wEAur1+Z1aMWMKX1P0K2nK2Sxs7xqAGJ06vV8A1G4Y1s1Z-a5m2Ge1wByoeCqACOqGAqmQHZyUcdrIYMYUcccCcnKdnJozdezy92BdccDkugAzNoAKwKExGSObgZD+BYyBIYBnGyXEKGrU1M0mfYuBkJlWXQmROSw7CcNwp04AAUXlIV4FAMBnGAdB6hnBoPVkLQGBIPhbAdLtNXQEhUAENJMAAD0QTBPSqLR8mPVJGAEOdnAdDi+AYeBNTgLjMGQfCPA7ZA+LE8UexlOV+0RQdVRHa8x0TZNp1TY101rc1nzga1X1XQs5AAdgABl-L8AJkICEnpIA

LMK if you want me to turn the TS playground code above into a CodeSandbox example; happy to do so.

To Reproduce

Steps to reproduce the behavior:

  1. Create a producer that accepts a TS type that has a well-known symbol property
  2. Try to read that property or call a function that accepts a parameter of T from Draft<T>. Note that mutation is not required-- this problem affects read-only access.

Observed behavior

TS errors as noted above.

Expected behavior

No TS errors.

Environment

We only accept bug reports against the latest Immer version.

  • Immer version:
  • I filed this report against the latest version of Immer
  • [n/a (TS only)] Occurs with setUseProxies(true)
  • [n/a (TS only)] Occurs with setUseProxies(false) (ES5 only)

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating the type definitions for Draft and WritableDraft and compare how they handle well-known symbol properties. Use the TypeScript repro in the issue to check both property access and passing a draft to withFoo; done means the repro produces no TS errors without changing the intended mutation behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript
Domain
developer-experience, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.