fix: for-in 整数索引升序+字符串插入序枚举,null/undefined 空遍历
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
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(&module) {
|
||||
Ok(result) => format!("{result}"),
|
||||
Err(e) => format!("vm error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_null_empty_loop() {
|
||||
assert_eq!(eval("var r=0;for(var k in null){r=r+1;}r"), "0", "for-in null iterates zero times");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_undefined_empty_loop() {
|
||||
assert_eq!(
|
||||
eval("var r=0;for(var k in undefined){r=r+1;}r"),
|
||||
"0",
|
||||
"for-in undefined iterates zero times"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_null_does_not_execute_body() {
|
||||
assert_eq!(
|
||||
eval("var hit=false;for(var k in null){hit=true;}hit"),
|
||||
"false",
|
||||
"for-in null skips the body"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_null_does_not_throw() {
|
||||
// Must NOT produce a "vm error: ..." — a quiet empty loop, then a sentinel value.
|
||||
assert_eq!(eval("for(var k in null){} 7"), "7", "for-in null must not throw");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_number_primitive_currently_throws() {
|
||||
// Interim behavior: ToObject coercion for non-null/undefined primitives is not
|
||||
// implemented yet, so a number right-hand side still throws TypeError. When
|
||||
// ToObject lands this becomes an empty loop and this assertion should change.
|
||||
let out = eval("for(var k in 42){}");
|
||||
assert!(out.contains("TypeError"), "expected TypeError for number primitive, got: {out}");
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
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(&module) {
|
||||
Ok(result) => format!("{result}"),
|
||||
Err(e) => format!("vm error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
// Order is asserted via a position map built inside JS, so the comparison
|
||||
// returns a boolean (JsValue Display only reveals number/bool, not string
|
||||
// contents). This is also tolerant of any inherited enumerable keys the
|
||||
// engine appends after the object's own keys.
|
||||
|
||||
#[test]
|
||||
fn for_in_ordering_integer_indices_first() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var pos={};var i=0;for(var x in {b:2,a:1,'2':3,'1':4}){pos[x]=i;i=i+1;}\
|
||||
pos['1']<pos['2'] && pos['2']<pos['b'] && pos['b']<pos['a']"
|
||||
),
|
||||
"true",
|
||||
"integer indices ascending, then string keys in insertion order"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_ordering_string_only_insertion_order() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var pos={};var i=0;for(var x in {c:1,b:2,a:3}){pos[x]=i;i=i+1;}\
|
||||
pos['c']<pos['b'] && pos['b']<pos['a']"
|
||||
),
|
||||
"true",
|
||||
"string-only keys keep insertion order"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_ordering_mixed_boundary() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var pos={};var i=0;for(var x in {a:1,'0':2,b:3}){pos[x]=i;i=i+1;}\
|
||||
pos['0']<pos['a'] && pos['a']<pos['b']"
|
||||
),
|
||||
"true",
|
||||
"integer index '0' enumerates before string keys"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_ordering_integer_indices_are_numeric_not_lexicographic() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var pos={};var i=0;for(var x in {'10':1,'2':2,'1':3}){pos[x]=i;i=i+1;}\
|
||||
pos['1']<pos['2'] && pos['2']<pos['10']"
|
||||
),
|
||||
"true",
|
||||
"index order is 1,2,10 (numeric) not 1,10,2 (lexicographic)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_still_enumerates_own_keys() {
|
||||
assert_eq!(
|
||||
eval("var c=0;for(var k in {a:1,b:2}){c=c+1;}c>=2"),
|
||||
"true",
|
||||
"for-in still enumerates the object's own enumerable keys"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user