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

Implementation of instanceof operator #908

Merged
merged 1 commit into from
Oct 28, 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
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\""
);
}