Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added undefined property to global scope #501

Merged
merged 22 commits into from
Jun 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions boa/src/builtins/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ impl Array {
.realm()
.environment
.get_binding_value("Array")
.expect("Array was not initialized")
.borrow()
.get_field(PROTOTYPE),
);
Expand Down
3 changes: 3 additions & 0 deletions boa/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -33,6 +34,7 @@ pub(crate) use self::{
regexp::RegExp,
string::String,
symbol::Symbol,
undefined::Undefined,
value::{ResultValue, Value, ValueData},
};

Expand Down Expand Up @@ -62,6 +64,7 @@ pub fn init(global: &Value) {
NaN::init,
Infinity::init,
GlobalThis::init,
Undefined::init,
];

match global.data() {
Expand Down
32 changes: 32 additions & 0 deletions boa/src/builtins/undefined/mod.rs
Original file line number Diff line number Diff line change
@@ -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())
}
}
18 changes: 18 additions & 0 deletions boa/src/builtins/undefined/tests.rs
Original file line number Diff line number Diff line change
@@ -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");
}
5 changes: 2 additions & 3 deletions boa/src/environment/lexical_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Value> {
self.environments()
.find(|env| env.borrow().has_binding(name))
.map(|env| env.borrow().get_binding_value(name, false))
.unwrap_or_else(Value::undefined)
}
}

Expand Down Expand Up @@ -319,7 +318,7 @@ mod tests {
err.message
}
"#;
// awaiting agreement on error throw testing

assert_eq!(&exec(scenario), "bar is not defined");
}

Expand Down
21 changes: 6 additions & 15 deletions boa/src/exec/identifier/mod.rs
Original file line number Diff line number Diff line change
@@ -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)
}
7 changes: 6 additions & 1 deletion boa/src/exec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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,
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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()));
Expand Down Expand Up @@ -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())),
Expand Down
5 changes: 4 additions & 1 deletion boa/src/exec/operator/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//! Operator execution.
#[cfg(test)]
mod tests;

use super::{Executable, Interpreter};
use crate::{
Expand Down Expand Up @@ -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(
Expand Down
28 changes: 28 additions & 0 deletions boa/src/exec/operator/tests.rs
Original file line number Diff line number Diff line change
@@ -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");
}
31 changes: 25 additions & 6 deletions boa/src/exec/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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");
}

Expand Down Expand Up @@ -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;
Expand All @@ -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#"
Expand Down Expand Up @@ -756,24 +761,30 @@ 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;

var x;
x;
"#;
assert_eq!(&exec(scenario), "5");
}

#[test]
fn var_decl_hoisting_with_initialization() {
let scenario = r#"
x = 5;

var x = 10;
x;
"#;
assert_eq!(&exec(scenario), "10");
}

#[test]
#[ignore]
fn var_decl_hoisting_2_variables_hoisting() {
let scenario = r#"
x = y;

Expand All @@ -783,15 +794,23 @@ 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;

var y = 5;
x;
"#;
assert_eq!(&exec(scenario), "undefined");
}

#[test]
#[ignore]
fn var_decl_hoisting_2_variables_hoisting_3() {
let scenario = r#"
let y = x;
x = 5;
Expand Down
4 changes: 0 additions & 4 deletions boa/src/syntax/parser/expression/primary/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down