Skip to content

fix: guard indexed access in applyPatches and shallowCopy under noUncheckedIndexedAccess - #1297

Closed
nirmal-shaji wants to merge 1 commit into
immerjs:mainfrom
nirmal-shaji:fix/applypatches-strict-index-1143
Closed

fix: guard indexed access in applyPatches and shallowCopy under noUncheckedIndexedAccess#1297
nirmal-shaji wants to merge 1 commit into
immerjs:mainfrom
nirmal-shaji:fix/applypatches-strict-index-1143

Conversation

@nirmal-shaji

Copy link
Copy Markdown

Problem

Enabling TypeScript's noUncheckedIndexedAccess breaks the build with TS18048: '...' is possibly 'undefined' in two spots that read a value straight out of an indexed access:

  • Immer.applyPatches (src/core/immerClass.ts) — const patch = patches[i], then patch.path / patch.op / patch.value.
  • shallowCopy (src/utils/common.ts) — const desc = descriptors[key], then desc[WRITABLE] / desc.get / desc.set / desc[ENUMERABLE].

Reported in #1143.

Cause

Under noUncheckedIndexedAccess, arr[i] / record[key] are typed as T | undefined, so accessing members of the result without first narrowing it is a compile error. The indices are in fact always in range here (a reverse loop bounded by patches.length, and keys taken from Reflect.ownKeys(descriptors)), but the compiler cannot know that.

Fix

Guards only — no runtime behavior change. Add an early if (!patch) continue / if (!desc) continue immediately after each indexed read. This narrows the value to a non-undefined type for the rest of the loop body, satisfying the compiler. Because the indices are always valid at runtime, the guard condition can never be true and the continue never executes, so existing behavior is preserved exactly.

 		const patch = patches[i]
+		if (!patch) continue
 		if (patch.path.length === 0 && patch.op === "replace") {
 		const desc = descriptors[key]
+		if (!desc) continue
 		if (desc[WRITABLE] === false) {

An early guard is used rather than per-access optional chaining because it also covers the later patch.value read / the descriptor rewrite in one place, and reads more clearly.

Testing

  • yarn test:src (vitest) — 3764 passed, 8 skipped, full suite green.
  • Type check: compiled the source entry points with noUncheckedIndexedAccess enabled (a tsconfig extending the repo's own tsconfig.json). Before: 9 TS18048 errors across these two files; after: 0 — both files compile clean under strict indexed access. The default tsconfig.json build is unaffected.

Note: this PR is scoped to the two locations #1143 identifies. src/plugins/patches.ts has a few remaining, different indexed-access errors (TS2345/TS2538) that would need to be addressed before the whole codebase compiles under noUncheckedIndexedAccess; happy to follow up separately if the maintainers want full opt-in.

Under `noUncheckedIndexedAccess`, indexing an array/record yields
`T | undefined`, so `patches[i]` in `applyPatches` and `descriptors[key]`
in `shallowCopy` are typed as possibly-undefined. Reading their members
then fails to compile with TS18048 ("'patch'/'desc' is possibly
'undefined'"), breaking builds that enable this option.

Add an early `if (!patch) continue` / `if (!desc) continue` guard right
after each indexed read so TypeScript narrows the value for the rest of
the loop body. These indices are always in range at runtime, so the
guards can never fire and runtime behavior is unchanged.

Fixes immerjs#1143
@markerikson

Copy link
Copy Markdown
Collaborator

That sounds like your TS is trying to check code inside Immer, and checking library code while building your app is generally a bad idea. Is there a reason why you're doing that?

@nirmal-shaji

Copy link
Copy Markdown
Author

Fair point. It only bites when Immer's own src is compiled under noUncheckedIndexedAccess, which isn't a normal consumer setup. It's guards only, no runtime change, so there's no strong case to keep it. Your call.

@nirmal-shaji

Copy link
Copy Markdown
Author

Closing this, not worth the maintenance if the source isn't built under that flag. Thanks for taking a look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants