Skip to content

Adds Compiler support for remaining Array methods - sort, reduce, and more #33535

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

lourd
Copy link

@lourd lourd commented Jun 15, 2025

Summary

Was discussing some unexpected bailouts I'd run into with the Compiler with @josephsavona on Bluesky https://bsky.app/profile/en-js.bsky.social/post/3lrlh3rdbok2s and he kindly pointed me towards where I could fix them myself 😄

These changes cover the cases of using array methods like sort in a useMemo, the Compiler will keep them memoized and won't bail on optimizing the component as is the current behavior.

How did you test this change?

  • Ran the Playground app locally and tested that patterns that would get bailed on before now didn't
  • Added Compiler test fixtures, updating the expected fixtures after running the tests. I don't think I got this part quite right, I don't know why the "(kind: exception) Fixture not implemented" section at the end of all of them is there. I didn't add them for every single array method I added, either.

@poteto
Copy link
Member

poteto commented Jun 16, 2025

Thanks for contributing!

Copy link
Member

@josephsavona josephsavona left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great start! Great job paying attention to the details on how each of these functions may affect the operands and which effects to use. There are a few cases that look like they might need to be addressed. Also, we'll need to make the tests more thorough to ensure that mutation of return values of these functions is correctly connected to mutation of the values within the original array.

I'm also happy to help, this type of testing can definitely be tedious.

Comment on lines +460 to +464
positionalParams: [],
restParam: Effect.Capture,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Mutable,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is subtle, but the behavior of fill(value, start, end) is that value is stored into the array (Effect.Capture), while the rest of the args are just read. So a more precise declaration would be:

positionalParams: [Effect.Capture],
restParam: Effect.Read,

Comment on lines +623 to +657
[
'toReversed',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
[
'toSorted',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
/*
* callee is ConditionallyMutate because items of the array
* flow into the lambda and may be mutated there, even though
* the array object itself is not modified
*/
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Mutable,
mutableOnlyIfOperandsAreMutable: true,
}),
],
[
'toSpliced',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Capture,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These need calleeEffect: Effect.Capture since the items of the original array can be modified through the returned array

Comment on lines +682 to +683
positionalParams: [],
restParam: Effect.Capture,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's be precise here too, it's with(index, value) so: positionalParams: [Effect.Read, Effect.Capture]

@@ -0,0 +1,4 @@
function Component() {
const array = ['c', 'b', 'a'];
return useMemo(() => array.toSorted(), [array]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the tests, it isn't sufficient to check that the useeMemo is preserved. We also have to check if the result of calling the function propagates mutability in the correct way. For example, we should add a varaint of this fixture (array-to-sorted-mutate) that mutates the result of toStorted. Something like:

function Component(props) {
  const sorted = useMemo(() => {
    const array = [{value: 'c'}, {value: 'b'}, {value: 'a'}];
    const sorted = array.toSorted();
    mutate(sorted, props.value);
    return sorted;
  }, [props.value]);
  return <ValidateMemoization inputs={[props.value]} output={sorted} />;
}

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

Successfully merging this pull request may close these issues.

4 participants