diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 0517e0c3ccf..27c445ae069 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -54,6 +54,7 @@ impl Array { .realm() .environment .get_binding_value("Array") + .expect("Array was not initialized") .borrow() .get_field(PROTOTYPE), ); diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 8996a1318f7..d46ba79f154 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -17,6 +17,7 @@ pub mod property; pub mod regexp; pub mod string; pub mod symbol; +pub mod undefined; pub mod value; pub(crate) use self::{ @@ -33,6 +34,7 @@ pub(crate) use self::{ regexp::RegExp, string::String, symbol::Symbol, + undefined::Undefined, value::{ResultValue, Value, ValueData}, }; @@ -62,6 +64,7 @@ pub fn init(global: &Value) { NaN::init, Infinity::init, GlobalThis::init, + Undefined::init, ]; match global.data() { diff --git a/boa/src/builtins/undefined/mod.rs b/boa/src/builtins/undefined/mod.rs new file mode 100644 index 00000000000..922c284c639 --- /dev/null +++ b/boa/src/builtins/undefined/mod.rs @@ -0,0 +1,32 @@ +//! This module implements the global `undefined` property. +//! +//! The global undefined property represents the primitive value undefined. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript reference][spec] +//! +//! [spec]: https://tc39.es/ecma262/#sec-undefined +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined + +#[cfg(test)] +mod tests; + +use crate::{builtins::value::Value, BoaProfiler}; + +/// JavaScript global `undefined` property. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct Undefined; + +impl Undefined { + /// The binding name of the property. + pub(crate) const NAME: &'static str = "undefined"; + + /// Initialize the `undefined` property on the global object. + #[inline] + pub(crate) fn init(_: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + (Self::NAME, Value::undefined()) + } +} diff --git a/boa/src/builtins/undefined/tests.rs b/boa/src/builtins/undefined/tests.rs new file mode 100644 index 00000000000..39e7cee8cee --- /dev/null +++ b/boa/src/builtins/undefined/tests.rs @@ -0,0 +1,18 @@ +use crate::exec; + +#[test] +fn undefined_direct_evaluation() { + let scenario = r#" + undefined; + "#; + assert_eq!(&exec(scenario), "undefined"); +} + +#[test] +fn undefined_assignment() { + let scenario = r#" + a = undefined; + a + "#; + assert_eq!(&exec(scenario), "undefined"); +} diff --git a/boa/src/environment/lexical_environment.rs b/boa/src/environment/lexical_environment.rs index 8d339514a9c..57112aa3cb3 100644 --- a/boa/src/environment/lexical_environment.rs +++ b/boa/src/environment/lexical_environment.rs @@ -212,11 +212,10 @@ impl LexicalEnvironment { .any(|env| env.borrow().has_binding(name)) } - pub fn get_binding_value(&self, name: &str) -> Value { + pub fn get_binding_value(&self, name: &str) -> Option { self.environments() .find(|env| env.borrow().has_binding(name)) .map(|env| env.borrow().get_binding_value(name, false)) - .unwrap_or_else(Value::undefined) } } @@ -319,7 +318,7 @@ mod tests { err.message } "#; - // awaiting agreement on error throw testing + assert_eq!(&exec(scenario), "bar is not defined"); } diff --git a/boa/src/exec/identifier/mod.rs b/boa/src/exec/identifier/mod.rs index 9b5033c02a7..19bf6e203cb 100644 --- a/boa/src/exec/identifier/mod.rs +++ b/boa/src/exec/identifier/mod.rs @@ -1,21 +1,12 @@ use super::{Executable, Interpreter}; -use crate::{ - builtins::value::{ResultValue, Value, ValueData}, - syntax::ast::node::identifier::Identifier, -}; +use crate::{builtins::value::ResultValue, syntax::ast::node::identifier::Identifier}; impl Executable for Identifier { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { - let reference = resolve_binding(interpreter, self.as_ref()); - match reference.data() { - ValueData::Undefined => Err(interpreter - .throw_reference_error(self.as_ref()) - .expect_err("throw_reference_error() must return an error")), - _ => Ok(reference), - } + interpreter + .realm() + .environment + .get_binding_value(self.as_ref()) + .ok_or_else(|| interpreter.construct_reference_error(self.as_ref())) } } - -pub(crate) fn resolve_binding(interpreter: &mut Interpreter, name: &str) -> Value { - interpreter.realm().environment.get_binding_value(name) -} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 6f97503a55f..0082422d610 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -420,6 +420,7 @@ impl Interpreter { .realm .environment .get_binding_value("Boolean") + .expect("Boolean was not initialized") .get_field(PROTOTYPE); Ok(Value::new_object_from_prototype( @@ -432,6 +433,7 @@ impl Interpreter { .realm .environment .get_binding_value("Number") + .expect("Number was not initialized") .get_field(PROTOTYPE); Ok(Value::new_object_from_prototype( proto, @@ -443,6 +445,7 @@ impl Interpreter { .realm .environment .get_binding_value("Number") + .expect("Number was not initialized") .get_field(PROTOTYPE); Ok(Value::new_object_from_prototype( @@ -455,6 +458,7 @@ impl Interpreter { .realm .environment .get_binding_value("String") + .expect("String was not initialized") .get_field(PROTOTYPE); Ok(Value::new_object_from_prototype( @@ -467,6 +471,7 @@ impl Interpreter { .realm .environment .get_binding_value("Symbol") + .expect("Symbol was not initialized") .get_field(PROTOTYPE); Ok(Value::new_object_from_prototype( @@ -479,6 +484,7 @@ impl Interpreter { .realm .environment .get_binding_value("BigInt") + .expect("BigInt was not initialized") .get_field(PROTOTYPE); let bigint_obj = Value::new_object_from_prototype(proto, ObjectData::BigInt(bigint.clone())); @@ -573,7 +579,6 @@ impl Executable for Node { let _timer = BoaProfiler::global().start_event("Executable", "exec"); match *self { Node::Const(Const::Null) => Ok(Value::null()), - Node::Const(Const::Undefined) => Ok(Value::undefined()), Node::Const(Const::Num(num)) => Ok(Value::rational(num)), Node::Const(Const::Int(num)) => Ok(Value::integer(num)), Node::Const(Const::BigInt(ref num)) => Ok(Value::from(num.clone())), diff --git a/boa/src/exec/operator/mod.rs b/boa/src/exec/operator/mod.rs index 35a099dc51b..86953a923e5 100644 --- a/boa/src/exec/operator/mod.rs +++ b/boa/src/exec/operator/mod.rs @@ -1,4 +1,6 @@ //! Operator execution. +#[cfg(test)] +mod tests; use super::{Executable, Interpreter}; use crate::{ @@ -118,7 +120,8 @@ impl Executable for BinOp { let v_a = interpreter .realm() .environment - .get_binding_value(name.as_ref()); + .get_binding_value(name.as_ref()) + .ok_or_else(|| interpreter.construct_reference_error(name.as_ref()))?; let v_b = self.rhs().run(interpreter)?; let value = Self::run_assign(op, v_a, v_b); interpreter.realm.environment.set_mutable_binding( diff --git a/boa/src/exec/operator/tests.rs b/boa/src/exec/operator/tests.rs new file mode 100644 index 00000000000..4d4fcbe6076 --- /dev/null +++ b/boa/src/exec/operator/tests.rs @@ -0,0 +1,28 @@ +use crate::exec; + +#[test] +fn assignmentoperator_lhs_not_defined() { + let scenario = r#" + try { + a += 5 + } catch (err) { + err.message + } + "#; + + assert_eq!(&exec(scenario), "a is not defined"); +} + +#[test] +fn assignmentoperator_rhs_throws_error() { + let scenario = r#" + try { + let a; + a += b + } catch (err) { + err.message + } + "#; + + assert_eq!(&exec(scenario), "b is not defined"); +} diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 13e636800e2..f0b3161904d 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -48,7 +48,6 @@ fn property_accessor_member_expression_bracket_notation_on_function() { } #[test] -#[ignore] // will be solved with undefined added to global property fn empty_let_decl_undefined() { let scenario = r#" let a; @@ -70,7 +69,6 @@ fn semicolon_expression_stop() { } #[test] -#[ignore] // will be fixed with undefined added as global property fn empty_var_decl_undefined() { let scenario = r#" let b; @@ -402,7 +400,7 @@ fn for_loop_iteration_variable_does_not_leak() { err.message } "#; - // awaiting agreement on unhandled error handling + assert_eq!(&exec(inner_scope), "i is not defined"); } @@ -477,7 +475,6 @@ fn typeof_rational() { } #[test] -#[ignore] // Will be fixed when global property undefined is added fn typeof_undefined() { let typeof_undefined = r#" let a = undefined; @@ -486,6 +483,14 @@ fn typeof_undefined() { assert_eq!(&exec(typeof_undefined), "undefined"); } +#[test] +fn typeof_undefined_directly() { + let typeof_undefined = r#" + typeof undefined; + "#; + assert_eq!(&exec(typeof_undefined), "undefined"); +} + #[test] fn typeof_boolean() { let typeof_boolean = r#" @@ -756,8 +761,7 @@ mod in_operator { } #[test] -#[ignore] // maybe will be solved when undefined added to global property -fn var_decl_hoisting() { +fn var_decl_hoisting_simple() { let scenario = r#" x = 5; @@ -765,7 +769,10 @@ fn var_decl_hoisting() { x; "#; assert_eq!(&exec(scenario), "5"); +} +#[test] +fn var_decl_hoisting_with_initialization() { let scenario = r#" x = 5; @@ -773,7 +780,11 @@ fn var_decl_hoisting() { x; "#; assert_eq!(&exec(scenario), "10"); +} +#[test] +#[ignore] +fn var_decl_hoisting_2_variables_hoisting() { let scenario = r#" x = y; @@ -783,7 +794,11 @@ fn var_decl_hoisting() { x; "#; assert_eq!(&exec(scenario), "10"); +} +#[test] +#[ignore] +fn var_decl_hoisting_2_variables_hoisting_2() { let scenario = r#" var x = y; @@ -791,7 +806,11 @@ fn var_decl_hoisting() { x; "#; assert_eq!(&exec(scenario), "undefined"); +} +#[test] +#[ignore] +fn var_decl_hoisting_2_variables_hoisting_3() { let scenario = r#" let y = x; x = 5; diff --git a/boa/src/syntax/parser/expression/primary/mod.rs b/boa/src/syntax/parser/expression/primary/mod.rs index 0e31e581cc5..5118423d56a 100644 --- a/boa/src/syntax/parser/expression/primary/mod.rs +++ b/boa/src/syntax/parser/expression/primary/mod.rs @@ -85,10 +85,6 @@ impl TokenParser for PrimaryExpression { .into()) } TokenKind::BooleanLiteral(boolean) => Ok(Const::from(*boolean).into()), - // TODO: ADD TokenKind::UndefinedLiteral - TokenKind::Identifier(ref i) if i.as_ref() == "undefined" => { - Ok(Const::Undefined.into()) - } TokenKind::NullLiteral => Ok(Const::Null.into()), TokenKind::Identifier(ident) => Ok(Identifier::from(ident.as_ref()).into()), // TODO: IdentifierReference TokenKind::StringLiteral(s) => Ok(Const::from(s.as_ref()).into()),