Skip to content
Closed
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
2 changes: 1 addition & 1 deletion src/ast/e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ pub struct Unary {
}

bitflags::bitflags! {
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(transparent)]
pub struct UnaryFlags: u8 {
/// The expression "typeof (0, x)" must not become "typeof x" if "x"
Expand Down
7 changes: 5 additions & 2 deletions src/react_compiler/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1853,14 +1853,17 @@ fn codegen_base_instruction_value(
))
}
InstructionValue::UnaryExpression {
operator, value, ..
operator,
value,
flags,
..
} => {
let arg = codegen_place_to_expression(cx, value)?;
Ok(Expr::init(
E::Unary {
op: convert_unary_operator(*operator),
value: arg,
flags: E::UnaryFlags::empty(),
flags: *flags,
},
loc,
))
Expand Down
2 changes: 2 additions & 0 deletions src/react_compiler/hir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,8 @@ pub enum InstructionValue {
UnaryExpression {
operator: UnaryOperator,
value: Place,
/// Parse-time `E::UnaryFlags`, threaded back to codegen for the printer.
flags: bun_ast::e::UnaryFlags,
loc: Option<SourceLocation>,
},
TypeCastExpression {
Expand Down
1 change: 1 addition & 0 deletions src/react_compiler/lowering/build_hir/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,7 @@ fn lower_unary(
Ok(InstructionValue::UnaryExpression {
operator,
value,
flags: unary.flags,
loc,
})
}
Expand Down
1 change: 1 addition & 0 deletions src/react_compiler/optimization/constant_propagation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ fn evaluate_instruction(
operator,
value,
loc,
..
} => match operator {
UnaryOperator::Not => {
let operand = read(constants, value);
Expand Down
67 changes: 67 additions & 0 deletions test/bundler/transpiler/react-compiler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -891,4 +891,71 @@ describe("bundler", () => {
expect(out).toMatch(/__MEMO_CACHE_SENTINEL\)\s*\{[^}]*globalFn\(\)/);
},
});

// Regression: codegen.rs UnaryExpression emitted `E::Unary` with
// `UnaryFlags::empty()`. The parser sets `WAS_ORIGINALLY_TYPEOF_IDENTIFIER`
// for `typeof <identifier>`; without that flag the printer re-wraps an
// unbound operand as `typeof (0, x)`, which evaluates `x` and throws a
// ReferenceError instead of returning "undefined".
itBundled("react-compiler/TypeofUnboundGlobalReturnsUndefined", {
files: {
"/entry.jsx": /* jsx */ `
import { useMemo } from "react";
export function useThing(a) {
return useMemo(() => [typeof SomeUnboundGlobal, a], [a]);
}
console.log(JSON.stringify(useThing(1)));
`,
"/node_modules/react/index.js": `exports.useMemo = (f) => f();`,
"/node_modules/react/compiler-runtime.js": `exports.c = n => new Array(n).fill(Symbol.for("react.memo_cache_sentinel"));`,
"/node_modules/react/package.json": `{"name":"react","main":"./index.js"}`,
},
reactCompiler: true,
target: "browser",
backend: "cli",
run: { stdout: '["undefined",1]' },
onAfterBundle(api) {
const out = api.readFile("/out.js");
// The hook must be compiled (codegen, not a bailout, is on trial).
// With react bundled the `_c` import is renamed, so assert on the
// compiler-runtime body being linked in instead.
expect(out).toContain("react.memo_cache_sentinel");
// `typeof (0, SomeUnboundGlobal)` evaluates the reference and throws.
expect(out).not.toMatch(/typeof\s*\(\s*0\s*,\s*SomeUnboundGlobal/);
expect(out).toMatch(/typeof\s+SomeUnboundGlobal\b/);
},
});

// Control: the parse-time flag is NOT set for `typeof (0, x)`. Lowering
// discards the side-effect-free `0`, so codegen sees a bare identifier
// again; the printer must still re-wrap because the source did not have
// `typeof <identifier>` semantics.
itBundled("react-compiler/TypeofSequenceUnboundGlobalStillThrows", {
files: {
"/entry.jsx": /* jsx */ `
import { useMemo } from "react";
export function useThing(a) {
return useMemo(() => [typeof (0, SomeUnboundGlobal), a], [a]);
}
try {
useThing(1);
console.log("no throw");
} catch (e) {
console.log(e instanceof ReferenceError ? "ReferenceError" : "other");
}
`,
"/node_modules/react/index.js": `exports.useMemo = (f) => f();`,
"/node_modules/react/compiler-runtime.js": `exports.c = n => new Array(n).fill(Symbol.for("react.memo_cache_sentinel"));`,
"/node_modules/react/package.json": `{"name":"react","main":"./index.js"}`,
},
reactCompiler: true,
target: "browser",
backend: "cli",
run: { stdout: "ReferenceError" },
onAfterBundle(api) {
const out = api.readFile("/out.js");
expect(out).toContain("react.memo_cache_sentinel");
expect(out).toMatch(/typeof\s*\(\s*0\s*,\s*SomeUnboundGlobal/);
},
});
});
Loading