Files
OxideJS-New/crates/oxide_vm/tests/coercion_tests.rs
T

371 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_parser::Allocator;
use oxide_runtime_api as coercion;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(source: &str) -> String {
let allocator = Allocator::default();
let program = match oxide_parser::parse(&allocator, source) {
Ok(p) => p,
Err(e) => return format!("parse error: {}", e[0].message),
};
let module = match Compiler::new().compile(&program) {
Ok(m) => m,
Err(e) => return format!("compile error: {e}"),
};
let mut vm = Vm::new();
match vm.run(&Arc::new(module)) {
Ok(result) => format!("{result}"),
Err(e) => format!("vm error: {e}"),
}
}
#[test]
fn eval_coercion_null_equals_null() {
assert_eq!(eval("null == null"), "true");
}
#[test]
fn eval_coercion_bool_equals_int() {
assert_eq!(eval("false == 0"), "true");
}
#[test]
fn eval_not_falsy() {
assert_eq!(eval("!0"), "true");
}
#[test]
fn test_same_value_signed_zero() {
assert!(!coercion::same_value(JsValue::float(0.0), JsValue::float(-0.0)));
}
#[test]
fn test_strict_equality_signed_zero() {
assert!(coercion::strict_equality(JsValue::float(0.0), JsValue::float(-0.0)));
}
#[test]
fn test_same_value_nan() {
assert!(coercion::same_value(JsValue::float(f64::NAN), JsValue::float(f64::NAN)));
}
#[test]
fn test_same_value_type_mismatch() {
assert!(!coercion::same_value(JsValue::int(1), JsValue::bool(true)));
}
#[test]
fn test_same_value_int_float_equal() {
assert!(coercion::same_value(JsValue::int(1), JsValue::float(1.0)));
assert!(!coercion::same_value(JsValue::int(1), JsValue::float(2.0)));
}
#[test]
fn test_strict_equality_nan() {
assert!(!coercion::strict_equality(JsValue::float(f64::NAN), JsValue::float(f64::NAN)));
}
#[test]
fn test_strict_equality_type_mismatch() {
assert!(!coercion::strict_equality(JsValue::int(1), JsValue::bool(true)));
}
#[test]
fn test_strict_equality_int_float_equal() {
assert!(coercion::strict_equality(JsValue::int(1), JsValue::float(1.0)));
assert!(!coercion::strict_equality(JsValue::int(1), JsValue::float(2.0)));
}
#[test]
fn test_strict_equality_null_undefined() {
assert!(!coercion::strict_equality(JsValue::null(), JsValue::undefined()));
}
#[test]
fn test_to_int32_and_to_uint32() {
let mut vm = Vm::new();
let string_three = vm.new_string("3");
let string_bad = vm.new_string("x");
assert_eq!(coercion::to_int32(string_three), 3);
assert_eq!(coercion::to_int32(string_bad), 0);
assert_eq!(coercion::to_int32(JsValue::undefined()), 0);
assert_eq!(coercion::to_int32(JsValue::float(1.9)), 1);
assert_eq!(coercion::to_int32(JsValue::float(-1.9)), -1);
assert_eq!(coercion::to_int32(JsValue::float(f64::NAN)), 0);
assert_eq!(coercion::to_int32(JsValue::float(f64::INFINITY)), 0);
assert_eq!(coercion::to_uint32(JsValue::int(-1)), 4_294_967_295);
}
#[test]
fn primitive_property_write_sloppy_auto_boxes() {
// sloppy 模式:原始值接收者 auto-box,写盒后丢弃盒,不抛 TypeError。
for source in ["'abc'.x = 1", "(5).foo = 1", "true.foo = 1"] {
let result = eval(source);
assert_eq!(result, "1", "sloppy auto-box should return 1 for {source}, got {result}");
}
}
#[test]
fn primitive_property_write_strict_throws_clean_type_error() {
// strict 模式:原始值接收者抛 clean TypeError(非内部 opcode 消息)。
for source in ["'abc'.x = 1", "(5).foo = 1", "true.foo = 1"] {
let err = eval(&format!("\"use strict\"; {source}"));
assert!(err.contains("TypeError"), "expected TypeError for {source}, got {err}");
assert!(
!err.contains("IC_SET_PROP on non-object")
&& !err.contains("SET_PROP on non-object")
&& !err.contains("SET_PROP_DYNAMIC on non-object"),
"unexpected internal opcode message for {source}: {err}"
);
}
}
// --- 类型转换基础设施测试 ---
// eval() 返回最终值的 Display;以下断言都针对 bool/number 结果,
// 避免字符串引用的歧义。
// ToPrimitive:装箱 Number 经 valueOf 解包。
#[test]
fn to_primitive_number_boxed() {
assert_eq!(eval("+new Number(42)"), "42");
}
// ToPrimitive:装箱 String 在 == 中强转为原始值(对象 vs 字符串)。
#[test]
fn to_primitive_string_boxed() {
assert_eq!(eval("new String('x') == 'x'"), "true");
}
// ToPrimitive:优先调用 obj[Symbol.toPrimitive](回调执行并返回 7)。
#[test]
fn to_primitive_symbol_hint_invoked() {
assert_eq!(eval("var o = {}; o[Symbol.toPrimitive] = function (h) { return 7; }; +o"), "7");
}
// ToNumber:to_number_full 对 Symbol 抛 TypeError。
#[test]
fn to_number_full_symbol_throws() {
let mut vm = Vm::new();
let sym = JsValue::symbol(0);
let result = coercion::to_number_full(sym, &mut vm);
assert!(result.is_err(), "to_number_full(Symbol) must error, got {result:?}");
assert!(result.unwrap_err().contains("Symbol"), "expected a Symbol TypeError message");
}
// 运算符层:一元加号对 Symbol 抛 TypeError(ToNumber(Symbol),§7.1.4)。
#[test]
fn unary_plus_symbol_throws() {
let err = eval("+Symbol('x')");
assert!(err.contains("TypeError"), "expected TypeError for unary +symbol, got {err}");
}
// 运算符层:二元 `+` 的数值与字符串两条分支对 Symbol 均抛 TypeError。
#[test]
fn add_symbol_throws_both_branches() {
for source in [
"Symbol('x') + ''",
"'' + Symbol('x')",
"Symbol('x') + 0",
"0 + Symbol('x')",
"Symbol('x') + Symbol('y')",
"Symbol.iterator + {}",
] {
let err = eval(source);
assert!(err.contains("TypeError"), "expected TypeError for {source}, got {err}");
}
}
// 运算符层:其余数值算术(减/乘/除/模/幂/位运算/复合赋值)对 Symbol 抛 TypeError。
#[test]
fn numeric_operators_symbol_throw() {
for source in [
"Symbol('x') - 1",
"Symbol('x') * 2",
"Symbol('x') / 2",
"Symbol('x') % 2",
"Symbol('x') ** 2",
"Symbol('x') | 0",
"(-Symbol('x'))",
"var v = Symbol('x'); v += ''",
"var v = Symbol('x'); v -= 1",
] {
let err = eval(source);
assert!(err.contains("TypeError"), "expected TypeError for {source}, got {err}");
}
}
// Number 构造器对 Symbol 抛 TypeError(ToNumber 语义),不走 NaN 兜底。
#[test]
fn number_constructor_symbol_throws() {
let err = eval("Number(Symbol('x'))");
assert!(err.contains("TypeError"), "expected TypeError for Number(symbol), got {err}");
}
// String 构造器的 Symbol 专属分支保留:返回 `Symbol(desc)`,不经 ToString。
// 模板字面量则走 ToString,Symbol 仍抛 TypeError。
#[test]
fn string_constructor_and_template_symbol() {
let (vm, result) = eval_ty("String(Symbol('x'))").expect("String(Symbol) 求值失败");
assert_eq!(vm.lookup_str(result).as_deref(), Some("Symbol(x)"));
let (vm, result) = eval_ty("Symbol('x').description").expect("description 求值失败");
assert_eq!(vm.lookup_str(result).as_deref(), Some("x"));
let err = eval("`v=${Symbol('x')}`");
assert!(err.contains("TypeError"), "expected TypeError for template symbol, got {err}");
}
// 抽象相等:字符串与数字互相强转。
#[test]
fn abstract_eq_string_number() {
assert_eq!(eval("'1' == 1"), "true");
}
// 抽象相等:布尔转数字的递归 + 字符串转数字。
#[test]
fn abstract_eq_bool_number() {
assert_eq!(eval("'1' == true"), "true");
}
// 抽象相等:对象 vs 数字经 ToPrimitive([42] -> "42" -> 42)。
#[test]
fn abstract_eq_object_primitive() {
assert_eq!(eval("[42] == 42"), "true");
}
// 抽象相等:null == undefined(回归锚,规范第 2-3 步)。
#[test]
fn abstract_eq_null_undefined() {
assert_eq!(eval("null == undefined"), "true");
}
// 抽象相等:null == 0 为 false(规范落到第 14 步)。
#[test]
fn abstract_eq_null_zero_mismatch() {
assert_eq!(eval("null == 0"), "false");
}
// 抽象相等:同类型委托给严格相等。
#[test]
fn abstract_eq_same_type_string() {
assert_eq!(eval("'abc' == 'abc'"), "true");
}
// 严格相等:+0 === -0 为 true(仅验证)。
#[test]
fn strict_eq_signed_zero_eval() {
assert_eq!(eval("+0 === -0"), "true");
}
// 严格相等:NaN === NaN 为 false(仅验证)。
#[test]
fn strict_eq_nan_eval() {
assert_eq!(eval("NaN === NaN"), "false");
}
// 严格相等:int 0 与 double -0.0 混合表示按数值语义相等(=== 的 ±0 修复)。
#[test]
fn strict_equality_int_zero_vs_float_neg_zero() {
assert!(coercion::strict_equality(JsValue::int(0), JsValue::float(-0.0)));
assert!(coercion::strict_equality(JsValue::int(0), JsValue::float(0.0)));
assert!(!coercion::strict_equality(JsValue::int(1), JsValue::float(-0.0)));
}
// 严格相等:字面量 0(int)与 -0(double)跨表示 === 为 true。
#[test]
fn eval_int_zero_vs_neg_zero_strict() {
assert_eq!(eval("0 === -0"), "true");
assert_eq!(eval("-0 === -0"), "true");
assert_eq!(eval("0 !== -0"), "false");
assert_eq!(eval("42 === 42.0"), "true");
}
// 抽象相等:0 == -0 跨表示按数值相等。
#[test]
fn eval_int_zero_vs_neg_zero_loose() {
assert_eq!(eval("0 == -0"), "true");
assert_eq!(eval("0 != -0"), "false");
}
// SameValue:int 0 与 double -0.0 视为不同(Object.is 依赖)。
#[test]
fn same_value_int_zero_vs_float_neg_zero() {
assert!(!coercion::same_value(JsValue::int(0), JsValue::float(-0.0)));
assert!(coercion::same_value(JsValue::int(0), JsValue::float(0.0)));
}
// SameValueZero:int 0 与 double -0.0 视为相同(includes 依赖)。
#[test]
fn same_value_zero_int_zero_vs_float_neg_zero() {
assert!(coercion::same_value_zero(JsValue::int(0), JsValue::float(-0.0)));
assert!(coercion::same_value_zero(JsValue::float(f64::NAN), JsValue::float(f64::NAN)));
assert!(coercion::same_value_zero(JsValue::float(f64::NAN), JsValue::float(-f64::NAN)));
assert!(!coercion::same_value_zero(JsValue::int(1), JsValue::float(2.0)));
}
// eval 实况:Object.is / indexOf / includes 的 ±0 与 NaN 语义。
#[test]
fn eval_signed_zero_object_is_and_array_methods() {
assert_eq!(eval("Object.is(0, -0)"), "false");
assert_eq!(eval("Object.is(0, 0)"), "true");
assert_eq!(eval("[0].indexOf(-0)"), "0");
assert_eq!(eval("[0].includes(-0)"), "true");
assert_eq!(eval("[NaN].indexOf(NaN)"), "-1");
assert_eq!(eval("[NaN].includes(NaN)"), "true");
}
// typeof 结果断言需要真实字符串内容,单独用 (Vm, JsValue) 形态的辅助。
fn eval_ty(source: &str) -> Result<(Vm, JsValue), String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
let mut vm = Vm::new();
let result = vm.run(&Arc::new(module))?;
Ok((vm, result))
}
// typeof:js_type 分派 + perm_string 的结果与语言类型一一对应。
#[test]
fn eval_typeof_all_kinds() {
let cases: &[(&str, &str)] = &[
("typeof 1", "number"),
("typeof 1.5", "number"),
("typeof NaN", "number"),
("typeof 's'", "string"),
("typeof true", "boolean"),
("typeof null", "object"),
("typeof undefined", "undefined"),
("typeof function(){}", "function"),
("typeof Symbol('x')", "symbol"),
("typeof 1n", "bigint"),
("typeof {}", "object"),
("typeof []", "object"),
];
for (src, expected) in cases {
let (vm, result) = eval_ty(src).unwrap_or_else(|e| panic!("{src} failed: {e}"));
let actual = vm.lookup_str(result).unwrap_or_default();
assert_eq!(actual, *expected, "{src} 的 typeof 结果不符");
}
}
// 算术 +:对象操作数经 valueOf 强转。
#[test]
fn add_object_valueof_coercion() {
assert_eq!(eval("var o = {valueOf: function () { return 42; }}; o + 1"), "43");
}
// 关系比较:对象操作数经 ToPrimitive 后按字符串比较。
#[test]
fn relational_object_coercion() {
assert_eq!(eval("[3] > [2]"), "true");
}
// Date 对象在 == 中强转为自身时间戳(默认 hint -> valueOf)。
#[test]
fn date_object_equals_timestamp() {
assert_eq!(eval("new Date(0) == 0"), "true");
}