-
Notifications
You must be signed in to change notification settings - Fork 46.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
compiler: treat pruned scope outputs as reactive
Mostly addresses the issue with non-reactive pruned scopes. Before, values from pruned scopes would not be memoized, but could still be depended upon by downstream scopes. However, those downstream scopes would assume the value could never change. This could allow the developer to observe two different versions of a value - the freshly created one (if observed outside a scope) or a cached one (if observed inside, or through) a scope which used the value but didn't depend on it. The fix here is to consider the outputs of pruned reactive scopes as reactive. Note that this is a partial fix because of things like aliasing and control variables — the full solution would be to mark these values as reactive, and then re-run InferReactivePlaces. We can do this once we've fully converted our pipeline to use HIR everywhere. For now, this should fix most issues in practice. ghstack-source-id: ce19c5c26f33653765970cfd546be47943f7aad2 Pull Request resolved: #29790
- Loading branch information
1 parent
942c9b4
commit 67ef114
Showing
11 changed files
with
217 additions
and
222 deletions.
There are no files selected for viewing
This file contains 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
119 changes: 0 additions & 119 deletions
119
.../src/__tests__/fixtures/compiler/bug-invalid-pruned-scope-leaks-value.expect.md
This file was deleted.
Oops, something went wrong.
43 changes: 0 additions & 43 deletions
43
...in-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-pruned-scope-leaks-value.ts
This file was deleted.
Oops, something went wrong.
This file contains 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
This file contains 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
110 changes: 110 additions & 0 deletions
110
...rc/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
|
||
## Input | ||
|
||
```javascript | ||
import invariant from "invariant"; | ||
import { | ||
makeObject_Primitives, | ||
mutate, | ||
sum, | ||
useIdentity, | ||
} from "shared-runtime"; | ||
|
||
/** | ||
* Here, `z`'s original memo block is removed due to the inner hook call. | ||
* However, we also infer that `z` is non-reactive, so by default we would create | ||
* the memo block for `thing = [y, z]` as only depending on `y`. | ||
* | ||
* This could then mean that `thing[1]` and `z` may not refer to the same value, | ||
* since z recreates every time but `thing` doesn't correspondingly invalidate. | ||
* | ||
* The fix is to consider pruned memo block outputs as reactive, since they will | ||
* recreate on every render. This means `thing` depends on both y and z. | ||
*/ | ||
function MyApp({ count }) { | ||
const z = makeObject_Primitives(); | ||
const x = useIdentity(2); | ||
const y = sum(x, count); | ||
mutate(z); | ||
const thing = [y, z]; | ||
if (thing[1] !== z) { | ||
invariant(false, "oh no!"); | ||
} | ||
return thing; | ||
} | ||
|
||
export const FIXTURE_ENTRYPOINT = { | ||
fn: MyApp, | ||
params: [{ count: 2 }], | ||
sequentialRenders: [{ count: 2 }, { count: 2 }, { count: 3 }], | ||
}; | ||
|
||
``` | ||
|
||
## Code | ||
|
||
```javascript | ||
import { c as _c } from "react/compiler-runtime"; | ||
import invariant from "invariant"; | ||
import { | ||
makeObject_Primitives, | ||
mutate, | ||
sum, | ||
useIdentity, | ||
} from "shared-runtime"; | ||
|
||
/** | ||
* Here, `z`'s original memo block is removed due to the inner hook call. | ||
* However, we also infer that `z` is non-reactive, so by default we would create | ||
* the memo block for `thing = [y, z]` as only depending on `y`. | ||
* | ||
* This could then mean that `thing[1]` and `z` may not refer to the same value, | ||
* since z recreates every time but `thing` doesn't correspondingly invalidate. | ||
* | ||
* The fix is to consider pruned memo block outputs as reactive, since they will | ||
* recreate on every render. This means `thing` depends on both y and z. | ||
*/ | ||
function MyApp(t0) { | ||
const $ = _c(6); | ||
const { count } = t0; | ||
const z = makeObject_Primitives(); | ||
const x = useIdentity(2); | ||
let t1; | ||
if ($[0] !== x || $[1] !== count) { | ||
t1 = sum(x, count); | ||
$[0] = x; | ||
$[1] = count; | ||
$[2] = t1; | ||
} else { | ||
t1 = $[2]; | ||
} | ||
const y = t1; | ||
mutate(z); | ||
let t2; | ||
if ($[3] !== y || $[4] !== z) { | ||
t2 = [y, z]; | ||
$[3] = y; | ||
$[4] = z; | ||
$[5] = t2; | ||
} else { | ||
t2 = $[5]; | ||
} | ||
const thing = t2; | ||
if (thing[1] !== z) { | ||
invariant(false, "oh no!"); | ||
} | ||
return thing; | ||
} | ||
|
||
export const FIXTURE_ENTRYPOINT = { | ||
fn: MyApp, | ||
params: [{ count: 2 }], | ||
sequentialRenders: [{ count: 2 }, { count: 2 }, { count: 3 }], | ||
}; | ||
|
||
``` | ||
### Eval output | ||
(kind: ok) [4,{"a":0,"b":"value1","c":true,"wat0":"joe"}] | ||
[4,{"a":0,"b":"value1","c":true,"wat0":"joe"}] | ||
[5,{"a":0,"b":"value1","c":true,"wat0":"joe"}] |
Oops, something went wrong.