quickjs-oxide 0.4.1

A safe Rust JavaScript engine targeting semantic parity with QuickJS
Documentation
use super::*;

#[test]
fn compiles_bigint_literals_without_a_fixed_width_limit() {
    let expected = JsBigInt::parse_radix("10000000000000000000000000000000000000000", 16).unwrap();
    assert_eq!(
        evaluate("0x10000000000000000000000000000000000000000n"),
        Value::BigInt(expected)
    );
}

#[test]
fn use_strict_directive_rejects_legacy_literals() {
    assert!(compile_script("'use strict'; 010").is_err());
    assert!(compile_script("'use strict'; '\\1'").is_err());
    assert!(compile_script("'\\1'; 'use strict'; 0").is_err());
    assert!(compile_script("'\\8'; 'use strict'; 0").is_err());
    assert!(compile_script("; 'use strict'; 010").is_ok());
    assert!(compile_script("'use\\x20strict'; 010").is_ok());
    assert!(compile_script("'not strict'\n'use strict'\n010").is_err());
    assert!(compile_script("'not strict'\n+ 'use strict'; 010").is_ok());
    assert!(compile_script("'use strict' + ''; 010").is_ok());
    assert!(compile_script("'use strict'\n!0; 010").is_ok());
    assert!(compile_script("'use strict'\nvoid 0; 010").is_ok());
}

#[test]
fn unlinked_script_preserves_strict_mode_metadata() {
    let strict = compile_unlinked_script("'use strict'; 0").unwrap();
    let sloppy = compile_unlinked_script("'use\\x20strict'; 0").unwrap();

    assert!(strict.metadata().strict);
    assert!(!sloppy.metadata().strict);
}

#[test]
fn unlinked_script_preserves_verified_maximum_stack() {
    let source = "'left' + (0.5 * 2.5)";
    let bytecode = compile_script(source).unwrap();
    let verified = bytecode.verify().unwrap();
    let unlinked = compile_unlinked_script(source).unwrap();

    assert_eq!(bytecode.max_stack, 3);
    assert_eq!(unlinked.metadata().max_stack, bytecode.max_stack);
    assert_eq!(unlinked.metadata().max_stack, verified.max_stack);
}

#[test]
fn unlinked_script_converts_every_compiled_constant_to_a_primitive() {
    let source = "'\\ud800x'; 3.5; 0x100000000000000000000000000000000n";
    let bytecode = compile_script(source).unwrap();
    let unlinked = compile_unlinked_script(source).unwrap();

    assert_eq!(bytecode.constants.len(), 3);
    assert_eq!(unlinked.constants().len(), bytecode.constants.len());
    for (constant, expected) in unlinked.constants().iter().zip(&bytecode.constants) {
        assert_eq!(constant.as_primitive(), Some(expected));
        assert!(constant.as_child().is_none());
    }
}

#[test]
fn detached_string_literals_follow_quickjs_atom_identity_boundaries() {
    let atomized = compile_script("['same','same']").unwrap();
    let crate::engine::value::PrimitiveValue::String(first) = &atomized.constants[0] else {
        panic!("first atomized literal was not a String");
    };
    let crate::engine::value::PrimitiveValue::String(second) = &atomized.constants[1] else {
        panic!("second atomized literal was not a String");
    };
    assert!(first.same_representation(second));

    let immediate = compile_script("['2147483647','2147483647']").unwrap();
    let crate::engine::value::PrimitiveValue::String(first) = &immediate.constants[0] else {
        panic!("first immediate-atom literal was not a String");
    };
    let crate::engine::value::PrimitiveValue::String(second) = &immediate.constants[1] else {
        panic!("second immediate-atom literal was not a String");
    };
    assert!(!first.same_representation(second));

    let table_backed = compile_script("['2147483648','2147483648']").unwrap();
    let crate::engine::value::PrimitiveValue::String(first) = &table_backed.constants[0] else {
        panic!("first table-backed numeric literal was not a String");
    };
    let crate::engine::value::PrimitiveValue::String(second) = &table_backed.constants[1] else {
        panic!("second table-backed numeric literal was not a String");
    };
    assert!(first.same_representation(second));
}

#[test]
fn integer_fast_path_preserves_rounding_at_mantissa_and_accumulator_boundaries() {
    use crate::engine::compiler::lexer::{Lexer, TokenKind};
    use crate::engine::compiler::parser::literals::parse_number;
    use num_bigint::BigUint;
    use num_traits::ToPrimitive;

    for bits in [31usize, 32, 52, 53, 54, 63, 64, 65, 1023, 1024] {
        let boundary = BigUint::from(1u32) << bits;
        for delta in 0..=3u32 {
            for integer in [&boundary + delta, &boundary - delta] {
                let expected = crate::engine::value::PrimitiveValue::number(
                    integer.to_f64().unwrap_or(f64::INFINITY),
                );
                for (radix, prefix) in [(2, "0b"), (8, "0o"), (10, ""), (16, "0x")] {
                    let digits = integer.to_str_radix(radix);
                    let separated = digits
                        .chars()
                        .map(|ch| ch.to_string())
                        .collect::<Vec<_>>()
                        .join("_");
                    for digits in [&digits, &separated] {
                        let source = format!("{prefix}{digits}");
                        let TokenKind::Number(number) =
                            Lexer::new(&source).next_token().unwrap().kind
                        else {
                            panic!("number token expected: {source}");
                        };
                        assert_eq!(parse_number(&number).unwrap(), expected, "{source}");
                    }
                }
            }
        }
    }
}