Shrinking an array with `length =` and growing it back loses the patches for the truncated indices

オープン 初心者向け
#1,355 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 4 日以内に返信

@RClaudiuM がすでに取り組んでいます。

2026年11月7日 から。

  • #1356 @RClaudiuM による — オープン

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
75/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
javascript
領域
backend

調査の方向性

src/plugins/patches.ts、特に generateArrayPatches_ から始め、draft 配列を縮小すると copy_ と assigned_ がどのように変化するかを追ってください。length を縮小して再び拡大する場合の、提供された回帰テストを追加し、patch 関連のテストを実行してください。完了条件は、順方向の patch が recipe の結果を再現し、逆方向の patch がベースを復元することです。

索引モデルが issue の本文から書いたものです。

説明

🐛 Bug Report

With enablePatches(), if a recipe shrinks a draft array by assigning .length and then grows it back to at least its original length, no replace patch (and no inverse patch) is produced for the truncated indices. Replaying the patches on the base gives a different state than the recipe, and the inverse patches do not restore the base. The same recipe written with splice(0) produces correct patches.

Link to repro

Not applicable: no CodeSandbox account; the script below and the unit test at the end reproduce it.

To Reproduce

npm i [email protected], save as t.mjs, run node t.mjs:

import { produceWithPatches, applyPatches, enablePatches } from "immer";
enablePatches();

const base = [1];
const [result, patches, inverse] = produceWithPatches(base, d => {
  d.length = 0; // truncate index 0
  d[1] = 2;     // grow again
});

console.log(result);
console.log(patches);
console.log(inverse);
console.log(applyPatches(base, patches));
console.log(applyPatches(result, inverse));

const [r2, p2, i2] = produceWithPatches([1, 2], d => { d.length = 0; d.length = 2; });
console.log(r2, p2, i2);

Observed behavior

[ <1 empty item>, 2 ]
[ { op: 'add', path: [ 1 ], value: 2 } ]
[ { op: 'remove', path: [ 1 ] } ]
[ 1, 2 ]
[ <1 empty item> ]
[ <2 empty items> ] [] []

applyPatches(base, patches) keeps the stale 1 at index 0, and applyPatches(result, inverse) does not bring it back. The second recipe changes both elements but produces no patches at all.

Expected behavior

The same patches as for d.splice(0); d[1] = 2, which immer already gets right:

[
  { op: 'replace', path: [ 0 ], value: undefined },
  { op: 'add', path: [ 1 ], value: 2 }
] [
  { op: 'replace', path: [ 0 ], value: 1 },
  { op: 'remove', path: [ 1 ] }
]

so that applyPatches(base, patches) gives [ undefined, 2 ] and applyPatches(result, inverse) gives [ 1 ].

generateArrayPatches_ in src/plugins/patches.ts only emits a replace for an index below base.length when assigned_.get(i) is set (or allIndicesReassigned_). Assigning a smaller length truncates copy_ without marking the dropped indices as assigned, so they are skipped once the array is long again.

Unit test:

test("length shrink then grow produces replace patches", () => {
  const base = [1]
  const [result, patches, inverse] = produceWithPatches(base, d => {
    d.length = 0
    d[1] = 2
  })
  expect(applyPatches(base, patches)).toEqual(result)
  expect(applyPatches(result, inverse)).toEqual(base)
})

Environment

We only accept bug reports against the latest Immer version.

  • Immer version: 11.2.9 (also main 75a290e, built from source), Node.js v26.13.0
  • I filed this report against the latest version of Immer
  • Occurs with setUseProxies(true)
  • Occurs with setUseProxies(false) (ES5 only)

Credit: https://ucsbplse.github.io/

主要言語
JavaScript
スター
29k
フォーク
905
平均マージ
3日 16時間
マージ済み PR(30日)
22

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

immerjs/immer のほかの issue

immerjs/immer の issue をすべて見る

似ている issue

JavaScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。