Skip to content

Commit

Permalink
Implementation of instanceof operator (#908)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrej Onufrák <andrej.onufrak@student.tuke.sk>
  • Loading branch information
morrien and Andrej Onufrák authored Oct 28, 2020
1 parent fd181e2 commit f1d676e
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 7 deletions.
2 changes: 1 addition & 1 deletion boa/src/builtins/symbol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ impl WellKnownSymbols {
/// Called by the semantics of the instanceof operator.
#[inline]
pub fn has_instance_symbol(&self) -> RcSymbol {
self.async_iterator.clone()
self.has_instance.clone()
}

/// The `Symbol.isConcatSpreadable` well known symbol.
Expand Down
61 changes: 61 additions & 0 deletions boa/src/object/gcobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,67 @@ impl GcObject {
pub fn is_native_object(&self) -> bool {
self.borrow().is_native_object()
}

/// Retrieves value of specific property, when the value of the property is expected to be a function.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-getmethod
#[inline]
pub fn get_method<K>(&self, context: &mut Context, key: K) -> Result<Option<GcObject>>
where
K: Into<PropertyKey>,
{
let key = key.into();
let value = self.get(&key);

if value.is_null_or_undefined() {
return Ok(None);
}

match value.as_object() {
Some(object) if object.is_callable() => Ok(Some(object)),
_ => Err(context
.construct_type_error("value returned for property of object is not a function")),
}
}

/// Determines if `value` inherits from the instance object inheritance path.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance
#[inline]
pub fn ordinary_has_instance(&self, context: &mut Context, value: &Value) -> Result<bool> {
if !self.is_callable() {
return Ok(false);
}

// TODO: If C has a [[BoundTargetFunction]] internal slot, then
// Let BC be C.[[BoundTargetFunction]].
// Return ? InstanceofOperator(O, BC).

if let Some(object) = value.as_object() {
if let Some(prototype) = self.get(&"prototype".into()).as_object() {
let mut object = object.get_prototype_of();
while let Some(object_prototype) = object.as_object() {
if GcObject::equals(&prototype, &object_prototype) {
return Ok(true);
}
object = object_prototype.get_prototype_of();
}

Ok(false)
} else {
Err(context
.construct_type_error("function has non-object prototype in instanceof check"))
}
} else {
Ok(false)
}
}
}

impl AsRef<GcCell<Object>> for GcObject {
Expand Down
3 changes: 3 additions & 0 deletions boa/src/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ use std::{
ops::{Deref, DerefMut},
};

#[cfg(test)]
mod tests;

mod gcobject;
mod internal_methods;
mod iter;
Expand Down
19 changes: 19 additions & 0 deletions boa/src/object/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::exec;

#[test]
fn ordinary_has_instance_nonobject_prototype() {
let scenario = r#"
try {
function C() {}
C.prototype = 1
String instanceof C
} catch (err) {
err.toString()
}
"#;

assert_eq!(
&exec(scenario),
"\"TypeError: function has non-object prototype in instanceof check\""
);
}
23 changes: 17 additions & 6 deletions boa/src/syntax/ast/node/operator/bin_op/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,28 @@ impl Executable for BinOp {
interpreter.has_property(&y, &key)
}
CompOp::InstanceOf => {
if !y.is_object() {
if let Some(object) = y.as_object() {
let key = interpreter.well_known_symbols().has_instance_symbol();

match object.get_method(interpreter, key)? {
Some(instance_of_handler) => instance_of_handler
.call(&y, &[x], interpreter)?
.to_boolean(),
None if object.is_callable() => {
object.ordinary_has_instance(interpreter, &x)?
}
None => {
return interpreter.throw_type_error(
"right-hand side of 'instanceof' is not callable",
);
}
}
} else {
return interpreter.throw_type_error(format!(
"right-hand side of 'instanceof' should be an object, got {}",
y.get_type().as_str()
));
}

// TODO: implement the instanceof operator
// spec: https://tc39.es/ecma262/#sec-instanceofoperator

false
}
}))
}
Expand Down
34 changes: 34 additions & 0 deletions boa/src/syntax/ast/node/operator/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,37 @@ fn assignmentoperator_rhs_throws_error() {

assert_eq!(&exec(scenario), "\"ReferenceError: b is not defined\"");
}

#[test]
fn instanceofoperator_rhs_not_object() {
let scenario = r#"
try {
let s = new String();
s instanceof 1
} catch (err) {
err.toString()
}
"#;

assert_eq!(
&exec(scenario),
"\"TypeError: right-hand side of 'instanceof' should be an object, got number\""
);
}

#[test]
fn instanceofoperator_rhs_not_callable() {
let scenario = r#"
try {
let s = new String();
s instanceof {}
} catch (err) {
err.toString()
}
"#;

assert_eq!(
&exec(scenario),
"\"TypeError: right-hand side of 'instanceof' is not callable\""
);
}

0 comments on commit f1d676e

Please sign in to comment.