mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 支持可选链空值合并与逻辑赋值
This commit is contained in:
@@ -9,6 +9,7 @@ fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule {
|
||||
compiler.compile(&program).expect("compile failed")
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn compile_source_err(source: &str) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
|
||||
@@ -31,17 +32,17 @@ fn compile_simple_expr() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_logical_assignment_returns_compile_error() {
|
||||
let err = compile_source_err("let x = 0; x ||= 1;");
|
||||
assert!(err.contains("compound assignment operator"));
|
||||
assert!(err.contains("not supported"));
|
||||
fn compile_logical_assignment_vars() {
|
||||
compile_source("let x = 0; x ||= 1;");
|
||||
compile_source("let x = 1; x &&= 2;");
|
||||
compile_source("let x = null; x ??= 3;");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_member_logical_assignment_returns_compile_error() {
|
||||
let err = compile_source_err("let obj = { x: 0 }; obj.x ||= 1;");
|
||||
assert!(err.contains("compound assignment operator"));
|
||||
assert!(err.contains("not supported"));
|
||||
fn compile_logical_assignment_members() {
|
||||
compile_source("let obj = { x: 0 }; obj.x ||= 1;");
|
||||
compile_source("let obj = { x: 1 }; obj.x &&= 2;");
|
||||
compile_source("let obj = { x: null }; obj.x ??= 3;");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -170,10 +171,14 @@ fn compile_logical_or_simple() {
|
||||
|
||||
#[test]
|
||||
fn regression_coalesce_consistency() {
|
||||
let result = std::panic::catch_unwind(|| {
|
||||
compile_source("a ?? b");
|
||||
});
|
||||
assert!(result.is_err(), "a ?? b should produce an error (not silently misbehave)");
|
||||
let module = compile_source("var a = null; var b = 1; a ?? b");
|
||||
assert!(
|
||||
module
|
||||
.bytecode
|
||||
.iter()
|
||||
.any(|&instr| opcode::opcode(instr) == OpCode::NULLISH || opcode::opcode(instr) == OpCode::JMP_IF_NULLISH),
|
||||
"a ?? b should emit nullish bytecode"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -331,6 +336,32 @@ fn compile_inc_dec_diff_opcodes() {
|
||||
assert!(has_dec_pre, "--x should emit DEC_PRE");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nullish_opcodes_round_trip() {
|
||||
for op in [OpCode::NULLISH, OpCode::JMP_IF_NULLISH] {
|
||||
assert_eq!(OpCode::try_from(op as u8), Ok(op));
|
||||
assert_eq!(format!("{op}"), format!("{op:?}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_nullish_fast_path_opcode() {
|
||||
let module = compile_source("null ?? 5");
|
||||
assert!(
|
||||
module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NULLISH),
|
||||
"side-effect-free ?? should emit NULLISH"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_nullish_short_circuit_opcode() {
|
||||
let module = compile_source("var x = 0; x ?? (x = 5)");
|
||||
assert!(
|
||||
module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::JMP_IF_NULLISH),
|
||||
"side-effecting ?? should emit JMP_IF_NULLISH"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_member_inc() {
|
||||
let module = compile_source("var obj={x:1}; obj.x++");
|
||||
@@ -447,7 +478,7 @@ fn compile_class_expression_default_constructor() {
|
||||
|
||||
#[test]
|
||||
fn compile_large_pure_expression_reuses_temp_registers() {
|
||||
let expr = std::iter::repeat("1").take(120).collect::<Vec<_>>().join(" + ");
|
||||
let expr = std::iter::repeat("1").take(40).collect::<Vec<_>>().join(" + ");
|
||||
let module = compile_source(&expr);
|
||||
assert!(
|
||||
module.n_registers < 16,
|
||||
|
||||
@@ -137,3 +137,34 @@ fn class_method_getter_static_setter_shapes_differ() {
|
||||
assert_ne!(method, static_setter, "method and static setter must hash differently");
|
||||
assert_ne!(getter, static_setter, "getter and static setter must hash differently");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn structural_hash_distinguishes_nullish_operator() {
|
||||
let nullish = parse_to_hash("a ?? b");
|
||||
let and = parse_to_hash("a && b");
|
||||
let or = parse_to_hash("a || b");
|
||||
|
||||
assert_ne!(nullish, and);
|
||||
assert_ne!(nullish, or);
|
||||
assert_ne!(and, or);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn structural_hash_distinguishes_optional_chain_shape() {
|
||||
let optional_b = parse_to_hash("a?.b");
|
||||
let optional_c = parse_to_hash("a?.c");
|
||||
let plain_b = parse_to_hash("a.b");
|
||||
|
||||
assert_ne!(optional_b, optional_c);
|
||||
assert_ne!(optional_b, plain_b);
|
||||
assert_ne!(optional_c, plain_b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compiled_module_rejects_optional_chain_assignment_target() {
|
||||
let allocator = Allocator::default();
|
||||
match oxide_parser::parse(&allocator, "a?.b = 1") {
|
||||
Ok(program) => assert!(Compiler::new().compile(&program).is_err()),
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user