fix: forward thisArg in the array-methods plugin - #1292
Open
giaBaoJS wants to merge 1 commit into
Open
Conversation
The plugin hand-rolls filter, find and findLast and invoked the predicate as a bare call, dropping the optional thisArg argument. Any predicate reading `this` threw a TypeError once enableArrayMethods() was on. findIndex, findLastIndex, some and every were unaffected: they fall through to the native method, which forwards thisArg itself. The Map/Set plugin already does the same with cb.call(thisArg, ...).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
With
enableArrayMethods()enabled,filter,findandfindLastignore the optionalthisArgargument, so any predicate that readsthisthrows at runtime.findandfindLastfail the same way. With the plugin disabled all three return the native result, so enabling the plugin for its performance benefit silently breaks working code. The usual shape is a method passing its own instance through:Cause
src/plugins/arrayMethods.tshand rolls these three methods for the proxy avoidance optimization, and invokes the predicate as a bare call, soargs[1]is never read:filter: predicate read at line 449, called at line 454find/findLast: predicate read at line 464, called at line 470The other predicate taking methods are unaffected precisely because they are not hand rolled:
findIndex,findLastIndex,someandeveryfall through tosource[method](...args)at line 500, which forwardsthisArgnatively. So the plugin already honoursthisArgfor half of its predicate methods. The Map/Set plugin does it explicitly, atsrc/plugins/mapset.ts:113and:309:Spec
ECMA-262, 15th edition (ES2024), https://262.ecma-international.org/15.0/
23.1.3.8
Array.prototype.filter ( callbackfn [ , thisArg ] ), step 7.c.ii:and Note 1 of the same section:
23.1.3.12.1
FindViaPredicate ( O, len, direction, predicate, thisArg ), whichArray.prototype.find(23.1.3.9) andArray.prototype.findLast(23.1.3.11) both delegate to:and its step 4.d:
Fix
Read
args[1]and invoke the predicate throughpredicate.call(thisArg, ...)in both branches. Four lines. WhenthisArgis omitted it isundefined, which is exactly what a bare call already produced, so the common path is unchanged.The third argument stays
source(the base array) rather than the draft. That is the plugin's existing documented behaviour, "callbacks receive base values", and is out of scope here.Verification
yarn vitest run: 3764 passed, 8 skipped before, 3809 passed, 8 skipped after. The 45 extra results are 5 new tests times the 9 harness variants in__tests__/base.js.src/plugins/arrayMethods.tswhile keeping the tests gives 3 failures, allTypeError: Cannot read properties of undefined, with stacks pointing atarrayMethods.ts:454andarrayMethods.ts:470. All 3 are in thearray-plugin=truevariant only; the 8 plugin disabled variants stay green, which is what pins this as plugin specific.findIndex,findLastIndex,someandeverystill receivethisArg, and one pins that an omittedthisArgleavesthisundefined per Note 1 above.yarn test:build(against the built bundle): 3258 passed, 8 skipped.prettier --check src/plugins/arrayMethods.tsis clean, and the block added to__tests__/base.jsis prettier clean as well. I deliberately left the 5 pre-existingfunction()occurrences elsewhere in that file untouched, so the test diff is a pure insertion with no reformatting noise.yarn test:flowcould not run locally:flow-binships an x86-64 binary and fails to spawn on arm64 withError: spawn Unknown system error -86, before reading any file. It is unaffected either way, since this change touches no Flow types.One related defect I deliberately left out
Same function, same spec section:
filteralso skips step 7.b,Let kPresent be ? HasProperty(O, Pk), so it visits array holes.I left it out because the boundary is not the plugin's. Immer's own
shallowCopydensifies sparse arrays, so once a draft has been modified the plugin disabled path visits holes too:Fixing the plugin alone would make it match the plugin disabled path, but that path is itself lossy, so the outcome would still not be spec conformant and the real question is whether Immer wants to preserve holes at all. That reads like a decision for you rather than something to fold into a crash fix. Happy to open a follow up if you want the plugin brought in line with core regardless.