[compiler][repro] Postfix operator is incorrectly compiled

This bug was reported via our wg and appears to only affect values created as a ref.

Currently, postfix operators used in a callback gets compiled to:

```js
modalId.current = modalId.current + 1; // 1
const id = modalId.current; // 1
return id;
```

which is semantically incorrect. The postfix increment operator should return the value before incrementing. In other words something like this should have been compiled instead:

```js
const id = modalId.current; // 0
modalId.current = modalId.current + 1; // 1
return id;
```

This bug does not trigger when the incremented value is a plain primitive, instead there is a TODO bailout.
This commit is contained in:
Lauren Tan
2025-06-11 12:05:23 -04:00
parent 6c86e56a0f
commit e565004ea5
2 changed files with 120 additions and 0 deletions
@@ -0,0 +1,91 @@
## Input
```javascript
import {useRef} from 'react';
/**
* The postfix increment operator should return the value before incrementing.
* ```js
* const id = modalId.current; // 0
* modalId.current = modalId.current + 1; // 1
* return id;
* ```
* Currently we increment before the expression is evaluated, which is incorrect.
* This bug does not trigger when the incremented value is a plain primitive.
*/
function useFoo() {
const modalId = useRef(0);
const showModal = () => {
const id = modalId.current++;
return id;
};
const showModal2 = () => {
const id = ++modalId.current;
return id;
};
return {modalId, showModal, showModal2};
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
/**
* The postfix increment operator should return the value before incrementing.
* ```js
* const id = modalId.current; // 0
* modalId.current = modalId.current + 1; // 1
* return id;
* ```
* The bug is that
* This bug does not trigger when the incremented value is a plain primitive.
*/
function useFoo() {
const $ = _c(2);
const modalId = useRef(0);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
modalId.current = modalId.current + 1;
const id = modalId.current;
return id;
};
$[0] = t0;
} else {
t0 = $[0];
}
const showModal = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
const showModal2 = () => {
const id_0 = (modalId.current = modalId.current + 1);
return id_0;
};
t1 = { modalId, showModal, showModal2 };
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
### Eval output
(kind: ok) {"modalId":{"current":0},"showModal":"[[ function params=0 ]]","showModal2":"[[ function params=0 ]]"}
@@ -0,0 +1,29 @@
import {useRef} from 'react';
/**
* The postfix increment operator should return the value before incrementing.
* ```js
* const id = modalId.current; // 0
* modalId.current = modalId.current + 1; // 1
* return id;
* ```
* The bug is that
* This bug does not trigger when the incremented value is a plain primitive.
*/
function useFoo() {
const modalId = useRef(0);
const showModal = () => {
const id = modalId.current++;
return id;
};
const showModal2 = () => {
const id = ++modalId.current;
return id;
};
return {modalId, showModal, showModal2};
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};