diff --git a/crates/oxide_emit/src/expr/assignment.rs b/crates/oxide_emit/src/expr/assignment.rs index b6e1f6d5..fe72159b 100644 --- a/crates/oxide_emit/src/expr/assignment.rs +++ b/crates/oxide_emit/src/expr/assignment.rs @@ -214,22 +214,97 @@ impl Emitter { self.emit_computed_member_dynamic(assign, member, ctx) } } else if let oxide_parser::AssignmentTarget::PrivateFieldExpression(member) = &assign.left { - if assign.operator != AssignmentOperator::Assign { - return Err("compound assignment to private fields not supported".into()); + let name = member.field.name.as_str(); + // 逻辑赋值:GET_PRIVATE 读旧值(brand 检查在读时执行)→ 短路测试 → + // 通过才求值 RHS 并 SET_PRIVATE 写回,结果统一为旧值或新值。 + if let Some(logical_op) = assign.operator.to_logical_operator() { + let store_label = ctx.next_label_id(); + let end_label = ctx.next_label_id(); + let obj_reg = self.emit_expression(&member.object, ctx)?; + let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?; + let key_reg = self.emit_private_id_reg(name, ctx)?; + let result_reg = ctx.alloc_reg(); + ctx.inst(Inst::get_private( + Operand::Reg(result_reg), + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + brand_reg, + brand_id, + )); + self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?; + ctx.labels.set_label_pos(store_label, ctx.insts.len()); + let val_reg = self.emit_expression(&assign.right, ctx)?; + ctx.inst(Inst::set_private( + Operand::Reg(obj_reg), + Operand::Reg(val_reg), + Operand::Reg(key_reg), + brand_reg, + brand_id, + )); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(result_reg), + Operand::Reg(val_reg), + Operand::None, + )); + ctx.labels.set_label_pos(end_label, ctx.insts.len()); + return Ok(result_reg); } let obj_reg = self.emit_expression(&member.object, ctx)?; - let val_reg = self.emit_expression(&assign.right, ctx)?; - let name = member.field.name.as_str(); let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?; let key_reg = self.emit_private_id_reg(name, ctx)?; - ctx.inst(Inst::set_private( - Operand::Reg(obj_reg), - Operand::Reg(val_reg), - Operand::Reg(key_reg), - brand_reg, - brand_id, - )); - Ok(val_reg) + if assign.operator != AssignmentOperator::Assign { + // 复合赋值保规范求值序:先 GET_PRIVATE 读旧值(brand 检查先于 RHS 副作用), + // 再求值 RHS,运算后 SET_PRIVATE 写回。 + let val_reg = ctx.alloc_reg(); + ctx.inst(Inst::get_private( + Operand::Reg(val_reg), + Operand::Reg(obj_reg), + Operand::Reg(key_reg), + brand_reg, + brand_id, + )); + let rhs = self.emit_expression(&assign.right, ctx)?; + let op = match assign.operator { + AssignmentOperator::Addition => OpCode::ADD, + AssignmentOperator::Subtraction => OpCode::SUB, + AssignmentOperator::Multiplication => OpCode::MUL, + AssignmentOperator::Division => OpCode::DIV, + AssignmentOperator::Remainder => OpCode::MOD, + AssignmentOperator::Exponential => OpCode::COMPOUND_EXP, + AssignmentOperator::BitwiseAnd => OpCode::BIT_AND, + AssignmentOperator::BitwiseOR => OpCode::BIT_OR, + AssignmentOperator::BitwiseXOR => OpCode::BIT_XOR, + AssignmentOperator::ShiftLeft => OpCode::SHL, + AssignmentOperator::ShiftRight => OpCode::SHR, + AssignmentOperator::ShiftRightZeroFill => OpCode::USHR, + _ => return Err(format!("compound assignment operator {:?} not supported", assign.operator)), + }; + if assign.operator == AssignmentOperator::Exponential { + // 指数无独立二元指令,COMPOUND_EXP 语义 rd=rd^a:rhs 放 a 槽。 + ctx.inst(Inst::new(op, Operand::Reg(val_reg), Operand::Reg(rhs), Operand::None)); + } else { + ctx.inst(Inst::new(op, Operand::Reg(val_reg), Operand::Reg(val_reg), Operand::Reg(rhs))); + } + ctx.inst(Inst::set_private( + Operand::Reg(obj_reg), + Operand::Reg(val_reg), + Operand::Reg(key_reg), + brand_reg, + brand_id, + )); + Ok(val_reg) + } else { + let val_reg = self.emit_expression(&assign.right, ctx)?; + ctx.inst(Inst::set_private( + Operand::Reg(obj_reg), + Operand::Reg(val_reg), + Operand::Reg(key_reg), + brand_reg, + brand_id, + )); + Ok(val_reg) + } } else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(id_ref) = &assign.left { if assign.operator != AssignmentOperator::Assign { if let Some(logical_op) = assign.operator.to_logical_operator() { diff --git a/crates/oxide_vm/tests/class_tests.rs b/crates/oxide_vm/tests/class_tests.rs index 6cf59bce..64293bb7 100644 --- a/crates/oxide_vm/tests/class_tests.rs +++ b/crates/oxide_vm/tests/class_tests.rs @@ -637,3 +637,176 @@ fn class_generator_method_return_closes_with_value() { .unwrap(); assert_eq!(vm.lookup_str(result).unwrap(), "1,99,true"); } + +// ── 私有字段复合赋值(+= 等)与逻辑赋值(&&= ||= ??=)── + +// data 字段复合赋值:旧值读取 → 运算 → 写回,表达式结果与后续读取一致。 +#[test] +fn private_field_compound_assignment_writes_back() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "class C { #x = 1; m(){ return this.#x += 2; } get(){ return this.#x; } } var c = new C(); c.m()*10 + c.get()", + ) + .unwrap(); + assert_num(result, 33.0); +} + +// 复合赋值先读旧值再求值 RHS:RHS 改写同一字段时仍以旧值参与运算。 +#[test] +fn private_field_compound_assignment_reads_old_value_before_rhs() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "class C { #x = 1; m(){ return this.#x += (this.#x = 5); } get(){ return this.#x; } } var c = new C(); c.m()*10 + c.get()", + ) + .unwrap(); + // 旧值 1 先读,RHS 把 #x 写为 5,1+5=6 再写回 → 结果 6。 + assert_num(result, 66.0); +} + +// 减法/乘法/除法复合赋值序列,验证三操作数二元运算路径。 +#[test] +fn private_field_compound_assignment_arithmetic_operators() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "class C { #x = 10; a(){ return this.#x -= 3; } b(){ return this.#x *= 2; } c(){ return this.#x /= 4; } } var c = new C(); c.a()*10000 + c.b()*100 + c.c()", + ) + .unwrap(); + assert_num(result, 7.0 * 10000.0 + 14.0 * 100.0 + 3.5); +} + +// 位与/左移/位或复合赋值序列。 +#[test] +fn private_field_compound_assignment_bitwise_and_shift() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "class C { #x = 0b1010; a(){ return this.#x &= 0b1100; } b(){ return this.#x <<= 1; } c(){ return this.#x |= 0b1; } } var c = new C(); c.a()*100 + c.b()*10 + c.c()", + ) + .unwrap(); + assert_num(result, 8.0 * 100.0 + 16.0 * 10.0 + 17.0); +} + +// 指数复合赋值走 COMPOUND_EXP(rhs 在 a 槽)。 +#[test] +fn private_field_exponential_compound_assignment() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "class C { #x = 2; m(){ return this.#x **= 3; } } new C().m()").unwrap(); + assert_num(result, 8.0); +} + +// 私有访问器复合赋值:getter 读旧值、setter 写新值。 +#[test] +fn private_accessor_compound_assignment_uses_getter_setter() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "class C { static get #x(){ return this._v; } static set #x(v){ this._v = v; } static m(){ this.#x = 1; return this.#x += 2; } static get(){ return this._v; } } C.m()*10 + C.get()", + ) + .unwrap(); + assert_num(result, 33.0); +} + +// readonly-accessor 复合赋值:无 setter 抛 TypeError。 +#[test] +fn private_readonly_accessor_compound_assignment_throws_type_error() { + let mut vm = Vm::new(); + let err = eval( + &mut vm, + "class C { static get #x(){ return 1; } static m(){ return this.#x += 1; } } C.m()", + ) + .unwrap_err(); + assert!(err.contains("TypeError"), "expected TypeError, got: {err}"); +} + +// 私有方法槽复合赋值:写方法槽抛 TypeError。 +#[test] +fn private_method_compound_assignment_throws_type_error() { + let mut vm = Vm::new(); + let err = eval(&mut vm, "class C { #m(){ return 1; } m(){ return this.#m += 1; } } new C().m()").unwrap_err(); + assert!(err.contains("TypeError"), "expected TypeError, got: {err}"); +} + +// 私有方法槽逻辑赋值(&&= 走写回路径)同样抛 TypeError。 +#[test] +fn private_method_logical_assignment_throws_type_error() { + let mut vm = Vm::new(); + let err = eval(&mut vm, "class C { #m(){ return 1; } m(){ return this.#m &&= 2; } } new C().m()").unwrap_err(); + assert!(err.contains("TypeError"), "expected TypeError, got: {err}"); +} + +// ||= 对 truthy 旧值短路:RHS 副作用不执行,字段保持原值。 +#[test] +fn private_field_logical_or_keeps_truthy_value_and_short_circuits() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "class C { #x = 1; m(){ var n = 0; var r = this.#x ||= ++n; return r*10 + n; } } new C().m()", + ) + .unwrap(); + // #x=1 为 truthy → 短路,RHS 不执行,结果保持 1。 + assert_num(result, 10.0); +} + +// ||= 对 falsy 旧值写回 RHS 值。 +#[test] +fn private_field_logical_or_assigns_when_falsy() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "class C { #x = 0; m(){ return this.#x ||= 5; } get(){ return this.#x; } } var c = new C(); c.m()*10 + c.get()", + ) + .unwrap(); + assert_num(result, 55.0); +} + +// &&= 对 truthy 旧值写回 RHS 值。 +#[test] +fn private_field_logical_and_assigns_when_truthy() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "class C { #x = 1; m(){ return this.#x &&= 2; } get(){ return this.#x; } } var c = new C(); c.m()*10 + c.get()", + ) + .unwrap(); + assert_num(result, 22.0); +} + +// &&= 对 falsy 旧值短路:RHS 副作用不执行,字段保持原值。 +#[test] +fn private_field_logical_and_short_circuits_on_falsy() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "class C { #x = 0; m(){ var n = 0; var r = this.#x &&= ++n; return r*10 + n; } } new C().m()", + ) + .unwrap(); + // #x=0 为 falsy → 短路,RHS 不执行,结果保持 0。 + assert_num(result, 0.0); +} + +// ??= 对 nullish(未初始化字段为 undefined)写回 RHS 值。 +#[test] +fn private_field_nullish_coalesce_assigns_when_nullish() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "class C { #x; m(){ return this.#x ??= 7; } get(){ return this.#x; } } var c = new C(); c.m()*10 + c.get()", + ) + .unwrap(); + assert_num(result, 77.0); +} + +// ??= 对已定义旧值短路:RHS 副作用不执行。 +#[test] +fn private_field_nullish_coalesce_short_circuits_on_defined() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "class C { #x = 1; m(){ var n = 0; var r = this.#x ??= ++n; return r*10 + n; } } new C().m()", + ) + .unwrap(); + assert_num(result, 10.0); +}