From 3edcc918cf6bc8d1f8afbad7a65425f539fe1c26 Mon Sep 17 00:00:00 2001 From: Francis Murillo Date: Sat, 26 Jun 2021 10:23:39 +0800 Subject: [PATCH 1/3] Implement spread for objects --- boa/src/exec/tests.rs | 20 +++++ boa/src/syntax/ast/node/object/mod.rs | 29 ++++++- boa/src/syntax/ast/node/object/tests.rs | 75 +++++++++++++++++++ .../primary/object_initializer/tests.rs | 21 ++++++ boa/src/syntax/parser/tests.rs | 30 +++++++- 5 files changed, 173 insertions(+), 2 deletions(-) diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 62f2b3240e5..e90379fa49f 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -118,6 +118,26 @@ fn object_field_set() { assert_eq!(&exec(scenario), "22"); } +#[test] +fn object_spread() { + let mut context = Context::new(); + + let scenario = r#" + var b = {x: -1, z: -3} + var a = {x: 1, y: 2, ...b}; + "#; + forward(&mut context, scenario); + + let one = forward(&mut context, "a.x"); + assert_eq!(one, String::from("-1")); + + let two = forward(&mut context, "a.y"); + assert_eq!(two, String::from("2")); + + let three = forward(&mut context, "a.z"); + assert_eq!(three, String::from("-3")); +} + #[test] fn spread_with_arguments() { let scenario = r#" diff --git a/boa/src/syntax/ast/node/object/mod.rs b/boa/src/syntax/ast/node/object/mod.rs index e7cb47f3a33..51bf02c7c0a 100644 --- a/boa/src/syntax/ast/node/object/mod.rs +++ b/boa/src/syntax/ast/node/object/mod.rs @@ -146,7 +146,34 @@ impl Executable for Object { ) } }, - _ => {} //unimplemented!("{:?} type of property", i), + // [spec]: https://tc39.es/ecma262/#sec-runtime-semantics-propertydefinitionevaluation + PropertyDefinition::SpreadObject(node) => { + let val = node.run(context)?; + + if val.is_null_or_undefined() { + continue; + } + + let from = val.to_object(context)?; + + for key in from.__own_property_keys__(context)? { + if let Some(desc) = from.__get_own_property__(&key, context)? { + if let Some(true) = desc.enumerable() { + let property = from.__get__(&key, from.clone().into(), context)?; + + obj.set_property( + key.clone(), + PropertyDescriptor::builder() + .value(property) + .writable(true) + .enumerable(true) + .configurable(true), + ); + } + } + } + } + _ => {} // unimplemented!("{:?} type of property", i), } } diff --git a/boa/src/syntax/ast/node/object/tests.rs b/boa/src/syntax/ast/node/object/tests.rs index 64b4594ba9a..4d49abac81f 100644 --- a/boa/src/syntax/ast/node/object/tests.rs +++ b/boa/src/syntax/ast/node/object/tests.rs @@ -1,3 +1,78 @@ +use crate::exec; + +#[test] +fn spread_shallow_clone() { + let scenario = r#" + var a = { x: {} }; + var aClone = { ...a }; + + a.x === aClone.x + "#; + assert_eq!(&exec(scenario), "true"); +} + +#[test] +fn spread_merge() { + let scenario = r#" + var a = { x: 1, y: 2 }; + var b = { x: -1, z: -3, ...a }; + + (b.x === 1) && (b.y === 2) && (b.z === -3) + "#; + assert_eq!(&exec(scenario), "true"); +} + +#[test] +fn spread_overriding_properties() { + let scenario = r#" + var a = { x: 0, y: 0 }; + var aWithOverrides = { ...a, ...{ x: 1, y: 2 } }; + + (aWithOverrides.x === 1) && (aWithOverrides.y === 2) + "#; + assert_eq!(&exec(scenario), "true"); +} + +#[test] +fn spread_getters_in_initializer() { + let scenario = r#" + var a = { x: 42 }; + var aWithXGetter = { ...a, get x() { throw new Error('not thrown yet') } }; + "#; + assert_eq!(&exec(scenario), "undefined"); +} + +#[test] +fn spread_getters_in_object() { + let scenario = r#" + var a = { x: 42 }; + var aWithXGetter = { ...a, ... { get x() { throw new Error('not thrown yet') } } }; + "#; + assert_eq!(&exec(scenario), "\"Error\": \"not thrown yet\""); +} + +#[test] +fn spread_setters() { + let scenario = r#" + var z = { set x(nexX) { throw new Error() }, ... { x: 1 } }; + "#; + assert_eq!(&exec(scenario), "undefined"); +} + +#[test] +fn spread_null_and_undefined_ignored() { + let scenario = r#" + var a = { ...null, ...undefined }; + var count = 0; + + for (key in a) { count++; } + + count === 0 + "#; + + assert_eq!(&exec(scenario), "true"); +} + #[test] fn fmt() { super::super::test_formatting( diff --git a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs index eeae4ea58a8..29ca8c17b59 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs +++ b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs @@ -273,3 +273,24 @@ fn check_object_shorthand_multiple_properties() { ], ); } + +#[test] +fn check_object_spread() { + let object_properties = vec![ + PropertyDefinition::property("a", Const::from(1)), + PropertyDefinition::spread_object(Identifier::from("b")), + ]; + + check_parser( + "const x = { a: 1, ...b }; + ", + vec![DeclarationList::Const( + vec![Declaration::new_with_identifier( + "x", + Some(Object::from(object_properties).into()), + )] + .into(), + ) + .into()], + ); +} diff --git a/boa/src/syntax/parser/tests.rs b/boa/src/syntax/parser/tests.rs index b571e063184..12fae2072e4 100644 --- a/boa/src/syntax/parser/tests.rs +++ b/boa/src/syntax/parser/tests.rs @@ -4,7 +4,8 @@ use super::Parser; use crate::syntax::ast::{ node::{ field::GetConstField, ArrowFunctionDecl, Assign, BinOp, Call, Declaration, DeclarationList, - FormalParameter, FunctionDecl, Identifier, If, New, Node, Return, StatementList, UnaryOp, + FormalParameter, FunctionDecl, Identifier, If, New, Node, Object, PropertyDefinition, + Return, StatementList, UnaryOp, }, op::{self, CompOp, LogOp, NumOp}, Const, @@ -299,6 +300,33 @@ fn increment_in_comma_op() { ); } +#[test] +fn spread_in_object() { + let s = r#" + let x = { + a: 1, + ...b, + } + "#; + + let object_properties = vec![ + PropertyDefinition::property("a", Const::from(1)), + PropertyDefinition::spread_object(Identifier::from("b")), + ]; + + check_parser( + s, + vec![DeclarationList::Let( + vec![Declaration::new_with_identifier::<&str, Option>( + "x", + Some(Object::from(object_properties).into()), + )] + .into(), + ) + .into()], + ); +} + #[test] fn spread_in_arrow_function() { let s = r#" From 37f09096dcdea7e43739907704f0598816e8fa4c Mon Sep 17 00:00:00 2001 From: Francis Murillo Date: Sun, 29 Aug 2021 08:57:12 +0800 Subject: [PATCH 2/3] Use copy_data_properties for object spread --- boa/src/syntax/ast/node/object/mod.rs | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/boa/src/syntax/ast/node/object/mod.rs b/boa/src/syntax/ast/node/object/mod.rs index 51bf02c7c0a..ddc74fb0adf 100644 --- a/boa/src/syntax/ast/node/object/mod.rs +++ b/boa/src/syntax/ast/node/object/mod.rs @@ -154,24 +154,11 @@ impl Executable for Object { continue; } - let from = val.to_object(context)?; - - for key in from.__own_property_keys__(context)? { - if let Some(desc) = from.__get_own_property__(&key, context)? { - if let Some(true) = desc.enumerable() { - let property = from.__get__(&key, from.clone().into(), context)?; - - obj.set_property( - key.clone(), - PropertyDescriptor::builder() - .value(property) - .writable(true) - .enumerable(true) - .configurable(true), - ); - } - } - } + obj.as_object().unwrap().copy_data_properties::( + &val, + vec![], + context, + )?; } _ => {} // unimplemented!("{:?} type of property", i), } From 25c16b52b42ce47a508793e65e5874b7d5af18a5 Mon Sep 17 00:00:00 2001 From: Francis Murillo Date: Sun, 29 Aug 2021 10:06:09 +0800 Subject: [PATCH 3/3] Use check_output for object spread test --- boa/src/exec/tests.rs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index e90379fa49f..816b78f6bd1 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -120,22 +120,17 @@ fn object_field_set() { #[test] fn object_spread() { - let mut context = Context::new(); - let scenario = r#" - var b = {x: -1, z: -3} - var a = {x: 1, y: 2, ...b}; - "#; - forward(&mut context, scenario); - - let one = forward(&mut context, "a.x"); - assert_eq!(one, String::from("-1")); - - let two = forward(&mut context, "a.y"); - assert_eq!(two, String::from("2")); + var b = {x: -1, z: -3} + var a = {x: 1, y: 2, ...b}; + "#; - let three = forward(&mut context, "a.z"); - assert_eq!(three, String::from("-3")); + check_output(&[ + TestAction::Execute(scenario), + TestAction::TestEq("a.x", "-1"), + TestAction::TestEq("a.y", "2"), + TestAction::TestEq("a.z", "-3"), + ]); } #[test]