Skip to content

[compiler] Add definitions for Object entries/keys/values #34047

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

Merged
merged 2 commits into from
Aug 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,99 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
returnValueKind: ValueKind.Mutable,
}),
],
[
'entries',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
aliasing: {
receiver: '@receiver',
params: ['@object'],
rest: null,
returns: '@returns',
temporaries: [],
effects: [
{
kind: 'Create',
into: '@returns',
reason: ValueReason.KnownReturnSignature,
value: ValueKind.Mutable,
},
// Object values are captured into the return
{
kind: 'Capture',
from: '@object',
into: '@returns',
},
],
},
}),
],
[
'keys',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
aliasing: {
receiver: '@receiver',
params: ['@object'],
rest: null,
returns: '@returns',
temporaries: [],
effects: [
{
kind: 'Create',
into: '@returns',
reason: ValueReason.KnownReturnSignature,
value: ValueKind.Mutable,
},
// Only keys are captured, and keys are immutable
{
kind: 'ImmutableCapture',
from: '@object',
into: '@returns',
},
],
},
}),
],
[
'values',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
aliasing: {
receiver: '@receiver',
params: ['@object'],
rest: null,
returns: '@returns',
temporaries: [],
effects: [
{
kind: 'Create',
into: '@returns',
reason: ValueReason.KnownReturnSignature,
value: ValueKind.Mutable,
},
// Object values are captured into the return
{
kind: 'Capture',
from: '@object',
into: '@returns',
},
],
},
}),
],
]),
],
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ function parseAliasingSignatureConfig(
const effects = typeConfig.effects.map(
(effect: AliasingEffectConfig): AliasingEffect => {
switch (effect.kind) {
case 'ImmutableCapture':
case 'CreateFrom':
case 'Capture':
case 'Alias':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,19 @@ export const AliasEffectSchema: z.ZodType<AliasEffectConfig> = z.object({
into: LifetimeIdSchema,
});

export type ImmutableCaptureEffectConfig = {
kind: 'ImmutableCapture';
from: string;
into: string;
};

export const ImmutableCaptureEffectSchema: z.ZodType<ImmutableCaptureEffectConfig> =
z.object({
kind: z.literal('ImmutableCapture'),
from: LifetimeIdSchema,
into: LifetimeIdSchema,
});

export type CaptureEffectConfig = {
kind: 'Capture';
from: string;
Expand Down Expand Up @@ -187,6 +200,7 @@ export type AliasingEffectConfig =
| AssignEffectConfig
| AliasEffectConfig
| CaptureEffectConfig
| ImmutableCaptureEffectConfig
| ImpureEffectConfig
| MutateEffectConfig
| MutateTransitiveConditionallyConfig
Expand All @@ -199,6 +213,7 @@ export const AliasingEffectSchema: z.ZodType<AliasingEffectConfig> = z.union([
AssignEffectSchema,
AliasEffectSchema,
CaptureEffectSchema,
ImmutableCaptureEffectSchema,
ImpureEffectSchema,
MutateEffectSchema,
MutateTransitiveConditionallySchema,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

## Input

```javascript
// @validatePreserveExistingMemoizationGuarantees
import {makeObject_Primitives, Stringify} from 'shared-runtime';

function Component(props) {
const object = {object: props.object};
const entries = useMemo(() => Object.entries(object), [object]);
entries.map(([, value]) => {
value.updated = true;
});
return <Stringify entries={entries} />;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{object: {key: makeObject_Primitives()}}],
};

```


## Error

```
Found 2 errors:

Memoization: Compilation skipped because existing memoization could not be preserved

React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.

error.validate-object-entries-mutation.ts:6:57
4 | function Component(props) {
5 | const object = {object: props.object};
> 6 | const entries = useMemo(() => Object.entries(object), [object]);
| ^^^^^^ This dependency may be modified later
7 | entries.map(([, value]) => {
8 | value.updated = true;
9 | });

Memoization: Compilation skipped because existing memoization could not be preserved

React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.

error.validate-object-entries-mutation.ts:6:18
4 | function Component(props) {
5 | const object = {object: props.object};
> 6 | const entries = useMemo(() => Object.entries(object), [object]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Could not preserve existing memoization
7 | entries.map(([, value]) => {
8 | value.updated = true;
9 | });
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// @validatePreserveExistingMemoizationGuarantees
import {makeObject_Primitives, Stringify} from 'shared-runtime';

function Component(props) {
const object = {object: props.object};
const entries = useMemo(() => Object.entries(object), [object]);
entries.map(([, value]) => {
value.updated = true;
});
return <Stringify entries={entries} />;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{object: {key: makeObject_Primitives()}}],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

## Input

```javascript
// @validatePreserveExistingMemoizationGuarantees
import {makeObject_Primitives, Stringify} from 'shared-runtime';

function Component(props) {
const object = {object: props.object};
const values = useMemo(() => Object.values(object), [object]);
values.map(value => {
value.updated = true;
});
return <Stringify values={values} />;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{object: {key: makeObject_Primitives()}}],
};

```


## Error

```
Found 2 errors:

Memoization: Compilation skipped because existing memoization could not be preserved

React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.

error.validate-object-values-mutation.ts:6:55
4 | function Component(props) {
5 | const object = {object: props.object};
> 6 | const values = useMemo(() => Object.values(object), [object]);
| ^^^^^^ This dependency may be modified later
7 | values.map(value => {
8 | value.updated = true;
9 | });

Memoization: Compilation skipped because existing memoization could not be preserved

React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.

error.validate-object-values-mutation.ts:6:17
4 | function Component(props) {
5 | const object = {object: props.object};
> 6 | const values = useMemo(() => Object.values(object), [object]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Could not preserve existing memoization
7 | values.map(value => {
8 | value.updated = true;
9 | });
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// @validatePreserveExistingMemoizationGuarantees
import {makeObject_Primitives, Stringify} from 'shared-runtime';

function Component(props) {
const object = {object: props.object};
const values = useMemo(() => Object.values(object), [object]);
values.map(value => {
value.updated = true;
});
return <Stringify values={values} />;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{object: {key: makeObject_Primitives()}}],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

## Input

```javascript
import {makeObject_Primitives, Stringify} from 'shared-runtime';

function Component(props) {
const object = {object: props.object};
const entries = Object.entries(object);
entries.map(([, value]) => {
value.updated = true;
});
return <Stringify entries={entries} />;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{object: {key: makeObject_Primitives()}}],
};

```

## Code

```javascript
import { c as _c } from "react/compiler-runtime";
import { makeObject_Primitives, Stringify } from "shared-runtime";

function Component(props) {
const $ = _c(2);
let t0;
if ($[0] !== props.object) {
const object = { object: props.object };
const entries = Object.entries(object);
entries.map(_temp);
t0 = <Stringify entries={entries} />;
$[0] = props.object;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
function _temp(t0) {
const [, value] = t0;
value.updated = true;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ object: { key: makeObject_Primitives() } }],
};

```

### Eval output
(kind: ok) <div>{"entries":[["object",{"key":{"a":0,"b":"value1","c":true},"updated":true}]]}</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {makeObject_Primitives, Stringify} from 'shared-runtime';

function Component(props) {
const object = {object: props.object};
const entries = Object.entries(object);
entries.map(([, value]) => {
value.updated = true;
});
return <Stringify entries={entries} />;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{object: {key: makeObject_Primitives()}}],
};
Loading
Loading