Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -872,11 +872,33 @@ function inferBlock(
reason: new Set([ValueReason.Other]),
context: new Set(),
};

for (const element of instrValue.elements) {
if (element.kind === 'Spread') {
state.referenceAndRecordEffects(
freezeActions,
element.place,
isArrayType(element.place.identifier)
? Effect.Capture
: Effect.ConditionallyMutate,
ValueReason.Other,
);
} else if (element.kind === 'Identifier') {
state.referenceAndRecordEffects(
freezeActions,
element,
Effect.Capture,
ValueReason.Other,
);
} else {
let _: 'Hole' = element.kind;
}
}
state.initialize(instrValue, valueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = Effect.Store;
continuation = {
kind: 'initialize',
valueKind,
effect: {kind: Effect.Capture, reason: ValueReason.Other},
lvalueEffect: Effect.Store,
kind: 'funeffects',
};
break;
}
Expand Down Expand Up @@ -1241,21 +1263,12 @@ function inferBlock(
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === 'Identifier' ? arg : arg.place;
if (effects !== null) {
state.referenceAndRecordEffects(
freezeActions,
place,
effects[i],
ValueReason.Other,
);
} else {
state.referenceAndRecordEffects(
freezeActions,
place,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
state.referenceAndRecordEffects(
freezeActions,
place,
getArgumentEffect(effects != null ? effects[i] : null, arg),
ValueReason.Other,
);
hasCaptureArgument ||= place.effect === Effect.Capture;
}
if (signature !== null) {
Expand Down Expand Up @@ -1307,7 +1320,10 @@ function inferBlock(
signature !== null
? {
kind: signature.returnValueKind,
reason: new Set([ValueReason.Other]),
reason: new Set([
signature.returnValueReason ??
ValueReason.KnownReturnSignature,
]),
context: new Set(),
}
: {
Expand Down Expand Up @@ -1356,25 +1372,16 @@ function inferBlock(
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === 'Identifier' ? arg : arg.place;
if (effects !== null) {
/*
* If effects are inferred for an argument, we should fail invalid
* mutating effects
*/
state.referenceAndRecordEffects(
freezeActions,
place,
effects[i],
ValueReason.Other,
);
} else {
state.referenceAndRecordEffects(
freezeActions,
place,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
/*
* If effects are inferred for an argument, we should fail invalid
* mutating effects
*/
state.referenceAndRecordEffects(
freezeActions,
place,
getArgumentEffect(effects != null ? effects[i] : null, arg),
ValueReason.Other,
);
hasCaptureArgument ||= place.effect === Effect.Capture;
}
if (signature !== null) {
Expand Down Expand Up @@ -2049,3 +2056,31 @@ function areArgumentsImmutableAndNonMutating(
}
return true;
}

function getArgumentEffect(
signatureEffect: Effect | null,
arg: Place | SpreadPattern,
): Effect {
if (signatureEffect != null) {
if (arg.kind === 'Identifier') {
return signatureEffect;
} else if (
signatureEffect === Effect.Mutate ||
signatureEffect === Effect.ConditionallyMutate
) {
return signatureEffect;
} else {
// see call-spread-argument-mutable-iterator test fixture
if (signatureEffect === Effect.Freeze) {
CompilerError.throwTodo({
reason: 'Support spread syntax for hook arguments',
loc: arg.place.loc,
});
}
// effects[i] is Effect.Capture | Effect.Read | Effect.Store
return Effect.ConditionallyMutate;
}
} else {
return Effect.ConditionallyMutate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@

## Input

```javascript
function useBar({arg}) {
/**
* Note that mutableIterator is mutated by the later object spread. Therefore,
* `s.values()` should be memoized within the same block as the object spread.
* In terms of compiler internals, they should have the same reactive scope.
*/
const obj = {};
const s = new Set([obj, 5, 4]);
const mutableIterator = s.values();
const arr = [...mutableIterator];

obj.x = arg;
return arr;
}

export const FIXTURE_ENTRYPOINT = {
fn: useBar,
params: [{arg: 3}],
sequentialRenders: [{arg: 3}, {arg: 3}, {arg: 4}],
};

```

## Code

```javascript
import { c as _c } from "react/compiler-runtime";
function useBar(t0) {
const $ = _c(2);
const { arg } = t0;
let arr;
if ($[0] !== arg) {
const obj = {};
const s = new Set([obj, 5, 4]);
const mutableIterator = s.values();
arr = [...mutableIterator];

obj.x = arg;
$[0] = arg;
$[1] = arr;
} else {
arr = $[1];
}
return arr;
}

export const FIXTURE_ENTRYPOINT = {
fn: useBar,
params: [{ arg: 3 }],
sequentialRenders: [{ arg: 3 }, { arg: 3 }, { arg: 4 }],
};

```

### Eval output
(kind: ok) [{"x":3},5,4]
[{"x":3},5,4]
[{"x":4},5,4]
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
function useBar({arg}) {
/**
* Note that mutableIterator is mutated by the later object spread. Therefore,
* `s.values()` should be memoized within the same block as the object spread.
* In terms of compiler internals, they should have the same reactive scope.
*/
const obj = {};
const s = new Set([obj, 5, 4]);
const mutableIterator = s.values();
const arr = [...mutableIterator];

obj.x = arg;
return arr;
}

export const FIXTURE_ENTRYPOINT = {
fn: useBar,
params: [{arg: 3}],
sequentialRenders: [{arg: 3}, {arg: 3}, {arg: 4}],
};
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,20 @@ import { c as _c } from "react/compiler-runtime"; /**

function useBar(t0) {
"use memo";
const $ = _c(3);
const $ = _c(2);
const { arg } = t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
if ($[0] !== arg) {
const s = new Set([1, 5, 4]);
t1 = s.values();
$[0] = t1;
} else {
t1 = $[0];
}
const mutableIterator = t1;
let t2;
if ($[1] !== arg) {
t2 = [arg, ...mutableIterator];
$[1] = arg;
$[2] = t2;
const mutableIterator = s.values();

t1 = [arg, ...mutableIterator];
$[0] = arg;
$[1] = t1;
} else {
t2 = $[2];
t1 = $[1];
}
return t2;
return t1;
}

export const FIXTURE_ENTRYPOINT = {
Expand All @@ -84,4 +78,8 @@ export const FIXTURE_ENTRYPOINT = {
};

```


### Eval output
(kind: ok) [3,1,5,4]
[3,1,5,4]
[4,1,5,4]
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@

## Input

```javascript
import {useIdentity} from 'shared-runtime';

function useFoo() {
const it = new Set([1, 2]).values();
useIdentity();
return Math.max(...it);
}

export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{}],
sequentialRenders: [{}, {}],
};

```

## Code

```javascript
import { useIdentity } from "shared-runtime";

function useFoo() {
const it = new Set([1, 2]).values();
useIdentity();
return Math.max(...it);
}

export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{}],
sequentialRenders: [{}, {}],
};

```

### Eval output
(kind: ok) 2
2
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {useIdentity} from 'shared-runtime';

function useFoo() {
const it = new Set([1, 2]).values();
useIdentity();
return Math.max(...it);
}

export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{}],
sequentialRenders: [{}, {}],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

## Input

```javascript
import {useIdentity} from 'shared-runtime';

function Component() {
const items = makeArray(0, 1, 2, null, 4, false, 6);
return useIdentity(...items.values());
Comment on lines +8 to +9
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the hook call bailout case. This new bailout is good (better correctness, as we were previously memoizing the items.values() iterable)

Ideally we can rewrite this to 1) create an intermediate array to consume the iterable and (2) capture and spread the array at the callsite.

const items = makeArray(0, 1, 2, null, 4, false, 6);
const tmp = [...items.values()]; // this spread has conditionallyMutate effects
return useIdentity([...tmp]); // but this spread has freeze effects

}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
sequentialRenders: [{}, {}],
};

```


## Error

```
3 | function Component() {
4 | const items = makeArray(0, 1, 2, null, 4, false, 6);
> 5 | return useIdentity(...items.values());
| ^^^^^^^^^^^^^^ Todo: Support spread syntax for hook arguments (5:5)
6 | }
7 |
8 | export const FIXTURE_ENTRYPOINT = {
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {useIdentity} from 'shared-runtime';

function Component() {
const items = makeArray(0, 1, 2, null, 4, false, 6);
return useIdentity(...items.values());
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
sequentialRenders: [{}, {}],
};
Loading
Loading