diff --git a/inputs/failing/collidingClasses.ts b/inputs/failing/collidingClasses.ts new file mode 100644 index 0000000..8faf3b8 --- /dev/null +++ b/inputs/failing/collidingClasses.ts @@ -0,0 +1,12 @@ +// //! test_output("foo") + +export default () => { + class X { + foo = "foo"; + } + + return new X().foo; +}; + +// deno-lint-ignore no-unused-vars +class X {} diff --git a/inputs/failing/compareClassInstancesWithEqualSources.ts b/inputs/failing/compareClassInstancesWithEqualSources.ts new file mode 100644 index 0000000..d6d6919 --- /dev/null +++ b/inputs/failing/compareClassInstancesWithEqualSources.ts @@ -0,0 +1,31 @@ +//! test_output(false) +// Should be: true + +// When functions (and therefore classes) have the same source (and references), they should compare +// equal due to the content hash system. This isn't implemented yet - the content hash is just faked +// using its location in the bytecode, which means you only get equal comparison when the functions +// are referentially equal (inconsistent with value semantics). + +export default () => { + const a = new classes[0](1, 2); + const b = new classes[1](1, 2); + + return a === b; +}; + +const classes = [ + class Point { + constructor(public x: number, public y: number) {} + + lenSq() { + return this.x ** 2 + this.y ** 2; + } + }, + class Point { + constructor(public x: number, public y: number) {} + + lenSq() { + return this.x ** 2 + this.y ** 2; + } + }, +]; diff --git a/inputs/passing/compareFunctionsAndClasses.ts b/inputs/passing/compareFunctionsAndClasses.ts new file mode 100644 index 0000000..76004e2 --- /dev/null +++ b/inputs/passing/compareFunctionsAndClasses.ts @@ -0,0 +1,22 @@ +//! test_output([true,true,false,true]) + +export default () => { + const a = new Point(1, 2); + const b = new Point(1, 2); + const c = new Point(1, 3); + + return [ + a.lenSq === b.lenSq, + a === b, + a === c, + c === c, + ]; +}; + +class Point { + constructor(public x: number, public y: number) {} + + lenSq() { + return this.x ** 2 + this.y ** 2; + } +} diff --git a/valuescript_vm/src/bytecode_decoder.rs b/valuescript_vm/src/bytecode_decoder.rs index 5d57c6e..5318e8a 100644 --- a/valuescript_vm/src/bytecode_decoder.rs +++ b/valuescript_vm/src/bytecode_decoder.rs @@ -281,12 +281,18 @@ impl BytecodeDecoder { } pub fn decode_function(&mut self, is_generator: bool) -> Val { + // TODO: Use actual content hash + let mut hash = [0u8; 32]; + hash[0] = (self.pos & 0xff) as u8; + hash[1] = ((self.pos >> 8) & 0xff) as u8; + // TODO: Support >256 let register_count = self.decode_byte() as usize; let parameter_count = self.decode_byte() as usize; VsFunction { bytecode: self.bytecode.clone(), + hash, is_generator, register_count, parameter_count, diff --git a/valuescript_vm/src/operations.rs b/valuescript_vm/src/operations.rs index 9f00aa7..4564aa9 100644 --- a/valuescript_vm/src/operations.rs +++ b/valuescript_vm/src/operations.rs @@ -146,6 +146,17 @@ pub fn op_eq_impl(left: &Val, right: &Val) -> Result { true } (Val::Object(left_object), Val::Object(right_object)) => 'b: { + match (&left_object.prototype, &right_object.prototype) { + (None, None) => {} + (None, Some(_)) => return Ok(false), + (Some(_), None) => return Ok(false), + (Some(ref left_proto), Some(ref right_proto)) => { + if !op_eq_impl(left_proto, right_proto)? { + return Ok(false); + } + } + } + if left_object.prototype.is_some() || right_object.prototype.is_some() { return Err("TODO: class instance comparison".to_internal_error()); } @@ -172,6 +183,19 @@ pub fn op_eq_impl(left: &Val, right: &Val) -> Result { true } + (Val::Function(left), Val::Function(right)) => { + if left.binds.len() != right.binds.len() { + return Ok(false); + } + + for i in 0..left.binds.len() { + if !op_eq_impl(&left.binds[i], &right.binds[i])? { + return Ok(false); + } + } + + left.hash == right.hash + } _ => { if left.is_truthy() != right.is_truthy() { return Ok(false); @@ -248,8 +272,15 @@ pub fn op_triple_eq_impl(left: &Val, right: &Val) -> Result { true } (Val::Object(left_object), Val::Object(right_object)) => 'b: { - if left_object.prototype.is_some() || right_object.prototype.is_some() { - return Err("TODO: class instance comparison".to_internal_error()); + match (&left_object.prototype, &right_object.prototype) { + (None, None) => {} + (None, Some(_)) => return Ok(false), + (Some(_), None) => return Ok(false), + (Some(ref left_proto), Some(ref right_proto)) => { + if !op_triple_eq_impl(left_proto, right_proto)? { + return Ok(false); + } + } } if std::ptr::eq(&**left_object, &**right_object) { @@ -274,6 +305,19 @@ pub fn op_triple_eq_impl(left: &Val, right: &Val) -> Result { true } + (Val::Function(left), Val::Function(right)) => { + if left.binds.len() != right.binds.len() { + return Ok(false); + } + + for i in 0..left.binds.len() { + if !op_triple_eq_impl(&left.binds[i], &right.binds[i])? { + return Ok(false); + } + } + + left.hash == right.hash + } (Val::Static(..) | Val::Dynamic(..) | Val::CopyCounter(..), _) | (_, Val::Static(..) | Val::Dynamic(..) | Val::CopyCounter(..)) => { if left.typeof_() != right.typeof_() { diff --git a/valuescript_vm/src/vs_function.rs b/valuescript_vm/src/vs_function.rs index e151318..2d198d1 100644 --- a/valuescript_vm/src/vs_function.rs +++ b/valuescript_vm/src/vs_function.rs @@ -12,6 +12,7 @@ use super::vs_value::Val; #[derive(Debug, Clone)] pub struct VsFunction { pub bytecode: Rc, + pub hash: [u8; 32], pub is_generator: bool, pub register_count: usize, pub parameter_count: usize, @@ -29,6 +30,7 @@ impl VsFunction { VsFunction { bytecode: self.bytecode.clone(), + hash: self.hash, is_generator: self.is_generator, register_count: self.register_count, parameter_count: self.parameter_count,