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 @@ -2566,24 +2566,132 @@ fn ox_codegen_dependency<'a>(

/// Convert a `BindingPattern` (from `ox_codegen_lvalue`) into an `AssignmentTarget`
/// for reassignment / `StoreLocal` emission.
///
/// oxc models declaration binding patterns (`BindingPattern`) and assignment targets
/// (`AssignmentTarget`) as distinct node families, so a destructuring reassignment
/// like `[x] = arr` or `({a} = obj)` must be re-shaped from the pattern produced by
/// `ox_codegen_lvalue` into the corresponding assignment-target tree. Mirrors Babel,
/// which reuses the same `ArrayPattern`/`ObjectPattern` nodes as assignment LHS.
fn ox_binding_pattern_to_assignment_target<'a>(
cx: &OxcContext<'a, '_, '_>,
pattern: oxc::BindingPattern<'a>,
) -> Result<oxc::AssignmentTarget<'a>, CompilerError> {
match pattern {
oxc::BindingPattern::BindingIdentifier(id) => {
let id = id.unbox();
Ok(oxc::AssignmentTarget::AssignmentTargetIdentifier(
oxc_ast::ast::IdentifierReference::boxed(SPAN, id.name, &cx.ast),
Ok(oxc::AssignmentTarget::new_assignment_target_identifier(SPAN, id.name, &cx.ast))
}
oxc::BindingPattern::ArrayPattern(arr) => {
let arr = arr.unbox();
let mut elements: oxc_allocator::Vec<
'a,
Option<oxc::AssignmentTargetMaybeDefault<'a>>,
> = oxc_allocator::ArenaVec::new_in(&cx.ast);
for element in arr.elements {
match element {
Some(bp) => elements.push(Some(ox_binding_pattern_to_maybe_default(cx, bp)?)),
None => elements.push(None),
}
}
let rest = ox_binding_rest_to_assignment_rest(cx, arr.rest)?;
Ok(oxc::AssignmentTarget::new_array_assignment_target(SPAN, elements, rest, &cx.ast))
}
oxc::BindingPattern::ObjectPattern(obj) => {
let obj = obj.unbox();
let mut properties: oxc_allocator::Vec<'a, oxc::AssignmentTargetProperty<'a>> =
oxc_allocator::ArenaVec::new_in(&cx.ast);
for prop in obj.properties {
properties.push(ox_binding_property_to_assignment_property(cx, prop)?);
}
let rest = ox_binding_rest_to_assignment_rest(cx, obj.rest)?;
Ok(oxc::AssignmentTarget::new_object_assignment_target(SPAN, properties, rest, &cx.ast))
}
// A top-level default (`x = 1`) is not a valid assignment target on its own;
// defaults only appear nested and are handled by `ox_binding_pattern_to_maybe_default`.
oxc::BindingPattern::AssignmentPattern(_) => {
Err(invariant_err("Unexpected default in destructuring assignment target", None))
}
}
}

/// Convert a `BindingPattern` element into an `AssignmentTargetMaybeDefault`, turning a
/// nested default (`[x = 1] = arr`) into an `AssignmentTargetWithDefault`.
fn ox_binding_pattern_to_maybe_default<'a>(
cx: &OxcContext<'a, '_, '_>,
pattern: oxc::BindingPattern<'a>,
) -> Result<oxc::AssignmentTargetMaybeDefault<'a>, CompilerError> {
match pattern {
oxc::BindingPattern::AssignmentPattern(assign) => {
let assign = assign.unbox();
let binding = ox_binding_pattern_to_assignment_target(cx, assign.left)?;
Ok(oxc::AssignmentTargetMaybeDefault::new_assignment_target_with_default(
SPAN,
binding,
assign.right,
&cx.ast,
))
}
_ => Err(unimplemented_err(
"Destructuring reassignment targets are not yet ported to oxc",
None,
other => Ok(oxc::AssignmentTargetMaybeDefault::from(
ox_binding_pattern_to_assignment_target(cx, other)?,
)),
}
}

/// Convert an object `BindingProperty` into an `AssignmentTargetProperty`, preserving the
/// shorthand form (`{a}` / `{a = 1}`) vs the keyed form (`{a: b}` / `{a: b = 1}`).
fn ox_binding_property_to_assignment_property<'a>(
cx: &OxcContext<'a, '_, '_>,
prop: oxc::BindingProperty<'a>,
) -> Result<oxc::AssignmentTargetProperty<'a>, CompilerError> {
if prop.shorthand {
let (binding, init) = match prop.value {
oxc::BindingPattern::BindingIdentifier(id) => (id.unbox(), None),
oxc::BindingPattern::AssignmentPattern(assign) => {
let assign = assign.unbox();
let oxc::BindingPattern::BindingIdentifier(id) = assign.left else {
return Err(invariant_err(
"Expected an identifier in shorthand destructuring property",
None,
));
};
(id.unbox(), Some(assign.right))
}
_ => {
return Err(invariant_err(
"Expected an identifier in shorthand destructuring property",
None,
));
}
};
let reference = oxc_ast::ast::IdentifierReference::new(SPAN, binding.name, &cx.ast);
return Ok(oxc::AssignmentTargetProperty::new_assignment_target_property_identifier(
SPAN, reference, init, &cx.ast,
));
}
let binding = ox_binding_pattern_to_maybe_default(cx, prop.value)?;
Ok(oxc::AssignmentTargetProperty::new_assignment_target_property_property(
SPAN,
prop.key,
binding,
prop.computed,
&cx.ast,
))
}

/// Convert a binding rest element (`...x`) into an assignment-target rest.
fn ox_binding_rest_to_assignment_rest<'a>(
cx: &OxcContext<'a, '_, '_>,
rest: Option<oxc_allocator::Box<'a, oxc::BindingRestElement<'a>>>,
) -> Result<Option<oxc::AssignmentTargetRest<'a>>, CompilerError> {
match rest {
Some(rest) => {
let target = ox_binding_pattern_to_assignment_target(cx, rest.unbox().argument)?;
Ok(Some(oxc_ast::ast::AssignmentTargetRest::new(SPAN, target, &cx.ast)))
}
None => Ok(None),
}
}

/// Convert an expression to a `SimpleAssignmentTarget` for update expressions.
fn ox_expression_to_simple_assignment_target<'a>(
cx: &OxcContext<'a, '_, '_>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,56 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/array-pattern-spread-creates-array.js
---
import { c as _c } from "react/compiler-runtime";
// @validatePreserveExistingMemoizationGuarantees @validateExhaustiveMemoizationDependencies:false
import { useMemo } from "react";
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
function Component() {}
function Component(props) {
const $ = _c(9);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = makeObject_Primitives();
$[0] = t0;
} else {
t0 = $[0];
}
const x = t0;
let rest;
if ($[1] !== props.array) {
[, ...rest] = props.array;
rest.push(x);
$[1] = props.array;
$[2] = rest;
} else {
rest = $[2];
}
const rest_0 = rest;
let t1;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <ValidateMemoization inputs={[]} output={x} />;
$[3] = t1;
} else {
t1 = $[3];
}
let t2;
if ($[4] !== props.array) {
t2 = [props.array];
$[4] = props.array;
$[5] = t2;
} else {
t2 = $[5];
}
let t3;
if ($[6] !== rest_0 || $[7] !== t2) {
t3 = <>{t1}<ValidateMemoization inputs={t2} output={rest_0} /></>;
$[6] = rest_0;
$[7] = t2;
$[8] = t3;
} else {
t3 = $[8];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ array: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,16 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/call-args-destructuring-assignment.js
---
function Component() {}
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(1);
let x;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
x = makeObject();
x.foo([x] = makeObject());
$[0] = x;
} else {
x = $[0];
}
return x;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@ source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/destructure-direct-reassignment.js
---
// @enablePreserveExistingMemoizationGuarantees:false
function foo() {}
function foo(props) {
let x;
let y;
({x, y} = {
x: props.a,
y: props.b
});
console.log(x);
x = props.c;
return x + y;
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: ["TodoAdd"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,46 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/destructure-in-branch-ssa.ts
---
function useFoo() {}
import { c as _c } from "react/compiler-runtime";
function useFoo(props) {
const $ = _c(9);
let x = null;
let y = null;
let z;
let myList;
if ($[0] !== props) {
myList = [];
if (props.doDestructure) {
({x, y, z} = props);
myList.push(z);
}
$[0] = props;
$[1] = myList;
$[2] = x;
$[3] = y;
$[4] = z;
} else {
myList = $[1];
x = $[2];
y = $[3];
z = $[4];
}
let t0;
if ($[5] !== myList || $[6] !== x || $[7] !== y) {
t0 = {
x,
y,
myList
};
$[5] = myList;
$[6] = x;
$[7] = y;
$[8] = t0;
} else {
t0 = $[8];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,26 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/destructuring-assignment-array-default.js
---
function Component() {}
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(2);
let x;
if (props.cond) {
const [t0] = props.y;
let t1;
if ($[0] !== t0) {
t1 = t0 === undefined ? ["default"] : t0;
$[0] = t0;
$[1] = t1;
} else {
t1 = $[1];
}
[x] = t1;
} else {
x = props.fallback;
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: ["TodoAdd"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,41 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/destructuring-assignment.js
---
function foo() {}
import { c as _c } from "react/compiler-runtime";
function foo(a, b, c) {
const $ = _c(5);
let d;
let g;
let n;
let o;
const [t0, t1] = a;
d = t0;
const [t2] = t1;
const { e: t3 } = t2;
({f: g} = t3);
const { l: t4, o: t5 } = b;
const { m: t6 } = t4;
const [t7] = t6;
[n] = t7;
o = t5;
let t8;
if ($[0] !== d || $[1] !== g || $[2] !== n || $[3] !== o) {
t8 = {
d,
g,
n,
o
};
$[0] = d;
$[1] = g;
$[2] = n;
$[3] = o;
$[4] = t8;
} else {
t8 = $[4];
}
return t8;
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: ["TodoAdd"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,32 @@
source: crates/oxc_react_compiler/tests/snapshot.rs
input_file: crates/oxc_react_compiler/fixtures/destructuring-object-pattern-within-rest.js
---
function Component() {}
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(6);
let t0;
let y;
if ($[0] !== props.value) {
[y, ...t0] = props.value;
$[0] = props.value;
$[1] = t0;
$[2] = y;
} else {
t0 = $[1];
y = $[2];
}
const { z } = t0;
let t1;
if ($[3] !== y || $[4] !== z) {
t1 = [y, z];
$[3] = y;
$[4] = z;
$[5] = t1;
} else {
t1 = $[5];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: ["y", { z: "z!" }] }]
Expand Down
Loading
Loading