diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 1a4bebaa44f..0615c51a0a5 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -25,6 +25,8 @@ use crate::{ }; use std::cmp::{max, min, Ordering}; +use super::JsArgs; + /// JavaScript `Array` built-in implementation. #[derive(Debug, Clone, Copy)] pub(crate) struct Array; @@ -689,8 +691,8 @@ impl Array { // i. Let kValue be ? Get(O, Pk). let k_value = o.get(pk, context)?; // ii. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). - let this_arg = args.get(1).cloned().unwrap_or_else(JsValue::undefined); - callback.call(&this_arg, &[k_value, k.into(), o.clone().into()], context)?; + let this_arg = args.get_or_undefined(1); + callback.call(this_arg, &[k_value, k.into(), o.clone().into()], context)?; } // d. Set k to k + 1. } @@ -1024,7 +1026,7 @@ impl Array { return context.throw_type_error("Array.prototype.every: callback is not callable"); }; - let this_arg = args.get(1).cloned().unwrap_or_default(); + let this_arg = args.get_or_undefined(1); // 4. Let k be 0. // 5. Repeat, while k < len, @@ -1038,7 +1040,7 @@ impl Array { let k_value = o.get(k, context)?; // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). let test_result = callback - .call(&this_arg, &[k_value, k.into(), o.clone().into()], context)? + .call(this_arg, &[k_value, k.into(), o.clone().into()], context)? .to_boolean(); // iii. If testResult is false, return false. if !test_result { @@ -1072,7 +1074,7 @@ impl Array { // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = args.get(0).cloned().unwrap_or_default(); + let callback = args.get_or_undefined(0); if !callback.is_function() { return context.throw_type_error("Array.prototype.map: Callbackfn is not callable"); } @@ -1080,7 +1082,7 @@ impl Array { // 4. Let A be ? ArraySpeciesCreate(O, len). let a = Self::array_species_create(&o, len, context)?; - let this_arg = args.get(1).cloned().unwrap_or_default(); + let this_arg = args.get_or_undefined(1); // 5. Let k be 0. // 6. Repeat, while k < len, @@ -1094,7 +1096,7 @@ impl Array { let k_value = o.get(k, context)?; // ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). let mapped_value = - context.call(&callback, &this_arg, &[k_value, k.into(), this.into()])?; + context.call(callback, this_arg, &[k_value, k.into(), this.into()])?; // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). a.create_data_property_or_throw(k, mapped_value, context)?; } @@ -1158,7 +1160,7 @@ impl Array { } }; - let search_element = args.get(0).cloned().unwrap_or_default(); + let search_element = args.get_or_undefined(0); // 10. Repeat, while k < len, while k < len { @@ -1234,7 +1236,7 @@ impl Array { IntegerOrInfinity::Integer(n) => len + n, }; - let search_element = args.get(0).cloned().unwrap_or_default(); + let search_element = args.get_or_undefined(0); // 8. Repeat, while k ≥ 0, while k >= 0 { @@ -1246,7 +1248,7 @@ impl Array { let element_k = o.get(k, context)?; // ii. Let same be IsStrictlyEqual(searchElement, elementK). // iii. If same is true, return 𝔽(k). - if JsValue::strict_equals(&search_element, &element_k) { + if JsValue::strict_equals(search_element, &element_k) { return Ok(JsValue::new(k)); } } @@ -1288,7 +1290,7 @@ impl Array { } }; - let this_arg = args.get(1).cloned().unwrap_or_default(); + let this_arg = args.get_or_undefined(1); // 4. Let k be 0. let mut k = 0; @@ -1301,7 +1303,7 @@ impl Array { // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). let test_result = predicate .call( - &this_arg, + this_arg, &[k_value.clone(), k.into(), o.clone().into()], context, )? @@ -1349,7 +1351,7 @@ impl Array { } }; - let this_arg = args.get(1).cloned().unwrap_or_default(); + let this_arg = args.get_or_undefined(1); // 4. Let k be 0. let mut k = 0; @@ -1361,7 +1363,7 @@ impl Array { let k_value = o.get(pk, context)?; // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). let test_result = predicate - .call(&this_arg, &[k_value, k.into(), o.clone().into()], context)? + .call(this_arg, &[k_value, k.into(), o.clone().into()], context)? .to_boolean(); // d. If testResult is true, return 𝔽(k). if test_result { @@ -1453,7 +1455,7 @@ impl Array { let source_len = o.length_of_array_like(context)?; // 3. If ! IsCallable(mapperFunction) is false, throw a TypeError exception. - let mapper_function = args.get(0).cloned().unwrap_or_default(); + let mapper_function = args.get_or_undefined(0); if !mapper_function.is_function() { return context.throw_type_error("flatMap mapper function is not callable"); } @@ -1469,7 +1471,7 @@ impl Array { 0, 1, Some(mapper_function.as_object().unwrap()), - &args.get(1).cloned().unwrap_or_default(), + args.get_or_undefined(1), context, )?; @@ -1624,7 +1626,7 @@ impl Array { // 10. Else, let final be min(relativeEnd, len). let final_ = Self::get_relative_end(context, args.get(2), len)?; - let value = args.get(0).cloned().unwrap_or_default(); + let value = args.get_or_undefined(0); // 11. Repeat, while k < final, while k < final_ { @@ -1695,14 +1697,14 @@ impl Array { } } - let search_element = args.get(0).cloned().unwrap_or_default(); + let search_element = args.get_or_undefined(0); // 10. Repeat, while k < len, while k < len { // a. Let elementK be ? Get(O, ! ToString(𝔽(k))). let element_k = o.get(k, context)?; // b. If SameValueZero(searchElement, elementK) is true, return true. - if JsValue::same_value_zero(&search_element, &element_k) { + if JsValue::same_value_zero(search_element, &element_k) { return Ok(JsValue::new(true)); } // c. Set k to k + 1. @@ -1992,7 +1994,7 @@ impl Array { "missing argument 0 when calling function Array.prototype.filter", ) })?; - let this_val = args.get(1).cloned().unwrap_or_else(JsValue::undefined); + let this_arg = args.get_or_undefined(1); if !callback.is_callable() { return context.throw_type_error("the callback must be callable"); @@ -2016,7 +2018,7 @@ impl Array { let args = [element.clone(), JsValue::new(idx), JsValue::new(o.clone())]; // ii. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). - let selected = callback.call(&this_val, &args, context)?.to_boolean(); + let selected = callback.call(this_arg, &args, context)?.to_boolean(); // iii. If selected is true, then if selected { @@ -2078,9 +2080,9 @@ impl Array { // i. Let kValue be ? Get(O, Pk). let k_value = o.get(k, context)?; // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). - let this_arg = args.get(1).cloned().unwrap_or_default(); + let this_arg = args.get_or_undefined(1); let test_result = callback - .call(&this_arg, &[k_value, k.into(), o.clone().into()], context)? + .call(this_arg, &[k_value, k.into(), o.clone().into()], context)? .to_boolean(); // iii. If testResult is true, return true. if test_result { diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index 1e9a1553fff..499650f9e2a 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -13,8 +13,12 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt use crate::{ - builtins::BuiltIn, object::ConstructorBuilder, property::Attribute, symbol::WellKnownSymbols, - value::IntegerOrInfinity, BoaProfiler, Context, JsBigInt, JsResult, JsValue, + builtins::{BuiltIn, JsArgs}, + object::ConstructorBuilder, + property::Attribute, + symbol::WellKnownSymbols, + value::IntegerOrInfinity, + BoaProfiler, Context, JsBigInt, JsResult, JsValue, }; #[cfg(test)] mod tests; @@ -131,7 +135,7 @@ impl BigInt { // 1. Let x be ? thisBigIntValue(this value). let x = Self::this_bigint_value(this, context)?; - let radix = args.get(0).cloned().unwrap_or_default(); + let radix = args.get_or_undefined(0); // 2. If radix is undefined, let radixMV be 10. let radix_mv = if radix.is_undefined() { @@ -234,10 +238,8 @@ impl BigInt { fn calculate_as_uint_n(args: &[JsValue], context: &mut Context) -> JsResult<(JsBigInt, u32)> { use std::convert::TryFrom; - let undefined_value = JsValue::undefined(); - - let bits_arg = args.get(0).unwrap_or(&undefined_value); - let bigint_arg = args.get(1).unwrap_or(&undefined_value); + let bits_arg = args.get_or_undefined(0); + let bigint_arg = args.get_or_undefined(1); let bits = bits_arg.to_index(context)?; let bits = u32::try_from(bits).unwrap_or(u32::MAX); diff --git a/boa/src/builtins/console/mod.rs b/boa/src/builtins/console/mod.rs index 95b2561738f..e6c8abd586c 100644 --- a/boa/src/builtins/console/mod.rs +++ b/boa/src/builtins/console/mod.rs @@ -17,7 +17,7 @@ mod tests; use crate::{ - builtins::BuiltIn, + builtins::{BuiltIn, JsArgs}, object::ObjectInitializer, property::Attribute, value::{display::display_obj, JsValue}, @@ -90,7 +90,7 @@ pub fn formatter(data: &[JsValue], context: &mut Context) -> JsResult { } /* object, FIXME: how to render this properly? */ 'o' | 'O' => { - let arg = data.get(arg_index).cloned().unwrap_or_default(); + let arg = data.get_or_undefined(arg_index); formatted.push_str(&format!("{}", arg.display())); arg_index += 1 } @@ -564,9 +564,8 @@ impl Console { /// [spec]: https://console.spec.whatwg.org/#dir /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/dir pub(crate) fn dir(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let undefined = JsValue::undefined(); logger( - LogMessage::Info(display_obj(args.get(0).unwrap_or(&undefined), true)), + LogMessage::Info(display_obj(args.get_or_undefined(0), true)), context.console(), ); diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index fb056511e8e..a3be0770e6b 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -13,6 +13,8 @@ use crate::{ use chrono::{prelude::*, Duration, LocalResult}; use std::fmt::Display; +use super::JsArgs; + /// The number of nanoseconds in a millisecond. const NANOS_PER_MS: i64 = 1_000_000; /// The number of milliseconds in an hour. @@ -523,7 +525,7 @@ impl Date { return context.throw_type_error("Date.prototype[@@toPrimitive] called on non object"); }; - let hint = args.get(0).cloned().unwrap_or_default(); + let hint = args.get_or_undefined(0); let try_first = match hint.as_string().map(|s| s.as_str()) { // 3. If hint is "string" or "default", then diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 2df399cc2fa..64f63ed8685 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -26,6 +26,8 @@ use dyn_clone::DynClone; use sealed::Sealed; use std::fmt::{self, Debug}; +use super::JsArgs; + #[cfg(test)] mod tests; @@ -340,10 +342,10 @@ impl BuiltInFunctionObject { if !this.is_function() { return context.throw_type_error(format!("{} is not a function", this.display())); } - let this_arg: JsValue = args.get(0).cloned().unwrap_or_default(); + let this_arg = args.get_or_undefined(0); // TODO?: 3. Perform PrepareForTailCall let start = if !args.is_empty() { 1 } else { 0 }; - context.call(this, &this_arg, &args[start..]) + context.call(this, this_arg, &args[start..]) } /// `Function.prototype.apply` @@ -361,15 +363,15 @@ impl BuiltInFunctionObject { if !this.is_function() { return context.throw_type_error(format!("{} is not a function", this.display())); } - let this_arg = args.get(0).cloned().unwrap_or_default(); - let arg_array = args.get(1).cloned().unwrap_or_default(); + let this_arg = args.get_or_undefined(0); + let arg_array = args.get_or_undefined(1); if arg_array.is_null_or_undefined() { // TODO?: 3.a. PrepareForTailCall - return context.call(this, &this_arg, &[]); + return context.call(this, this_arg, &[]); } let arg_list = arg_array.create_list_from_array_like(&[], context)?; // TODO?: 5. PrepareForTailCall - context.call(this, &this_arg, &arg_list) + context.call(this, this_arg, &arg_list) } } diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index ed4d3e97d13..493c19b0e18 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -26,6 +26,8 @@ use crate::{ }; use serde_json::{self, Value as JSONValue}; +use super::JsArgs; + #[cfg(test)] mod tests; @@ -156,7 +158,7 @@ impl Json { let mut property_list = None; let mut replacer_function = None; - let replacer = args.get(1).cloned().unwrap_or_default(); + let replacer = args.get_or_undefined(1); // 4. If Type(replacer) is Object, then if let Some(replacer_obj) = replacer.as_object() { @@ -212,7 +214,7 @@ impl Json { } } - let mut space = args.get(2).cloned().unwrap_or_default(); + let mut space = args.get_or_undefined(2).clone(); // 5. If Type(space) is Object, then if let Some(space_obj) = space.as_object() { @@ -264,7 +266,7 @@ impl Json { // 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value). wrapper - .create_data_property_or_throw("", args.get(0).cloned().unwrap_or_default(), context) + .create_data_property_or_throw("", args.get_or_undefined(0).clone(), context) .expect("CreateDataPropertyOrThrow should never fail here"); // 11. Let state be the Record { [[ReplacerFunction]]: ReplacerFunction, [[Stack]]: stack, [[Indent]]: indent, [[Gap]]: gap, [[PropertyList]]: PropertyList }. diff --git a/boa/src/builtins/map/mod.rs b/boa/src/builtins/map/mod.rs index 2572de182d6..2dd3333256a 100644 --- a/boa/src/builtins/map/mod.rs +++ b/boa/src/builtins/map/mod.rs @@ -26,6 +26,8 @@ use map_iterator::MapIterator; use self::ordered_map::MapLock; +use super::JsArgs; + pub mod ordered_map; #[cfg(test)] mod tests; @@ -245,15 +247,12 @@ impl Map { args: &[JsValue], context: &mut Context, ) -> JsResult { - let (key, value) = match args.len() { - 0 => (JsValue::undefined(), JsValue::undefined()), - 1 => (args[0].clone(), JsValue::undefined()), - _ => (args[0].clone(), args[1].clone()), - }; + let key = args.get_or_undefined(0); + let value = args.get_or_undefined(1); let size = if let Some(object) = this.as_object() { if let Some(map) = object.borrow_mut().as_map_mut() { - map.insert(key, value); + map.insert(key.clone(), value.clone()); map.len() } else { return Err(context.construct_type_error("'this' is not a Map")); @@ -281,11 +280,11 @@ impl Map { args: &[JsValue], context: &mut Context, ) -> JsResult { - let key = args.get(0).cloned().unwrap_or_default(); + let key = args.get_or_undefined(0); let (deleted, size) = if let Some(object) = this.as_object() { if let Some(map) = object.borrow_mut().as_map_mut() { - let deleted = map.remove(&key).is_some(); + let deleted = map.remove(key).is_some(); (deleted, map.len()) } else { return Err(context.construct_type_error("'this' is not a Map")); @@ -312,12 +311,12 @@ impl Map { args: &[JsValue], context: &mut Context, ) -> JsResult { - let key = args.get(0).cloned().unwrap_or_default(); + let key = args.get_or_undefined(0); if let JsValue::Object(ref object) = this { let object = object.borrow(); if let Some(map) = object.as_map_ref() { - return Ok(if let Some(result) = map.get(&key) { + return Ok(if let Some(result) = map.get(key) { result.clone() } else { JsValue::undefined() @@ -361,12 +360,12 @@ impl Map { args: &[JsValue], context: &mut Context, ) -> JsResult { - let key = args.get(0).cloned().unwrap_or_default(); + let key = args.get_or_undefined(0); if let JsValue::Object(ref object) = this { let object = object.borrow(); if let Some(map) = object.as_map_ref() { - return Ok(map.contains_key(&key).into()); + return Ok(map.contains_key(key).into()); } } @@ -393,7 +392,7 @@ impl Map { } let callback_arg = &args[0]; - let this_arg = args.get(1).cloned().unwrap_or_else(JsValue::undefined); + let this_arg = args.get_or_undefined(1); let mut index = 0; @@ -416,7 +415,7 @@ impl Map { }; if let Some(arguments) = arguments { - context.call(callback_arg, &this_arg, &arguments)?; + context.call(callback_arg, this_arg, &arguments)?; } index += 1; diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index bf921f53aba..e9f17db73ba 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -112,3 +112,25 @@ pub fn init(context: &mut Context) { global_object.borrow_mut().insert(name, property); } } + +pub trait JsArgs { + /// Utility function to `get` a parameter from + /// a `[JsValue]` or default to `JsValue::Undefined` + /// if `get` returns `None`. + /// + /// Call this if you are thinking of calling something similar to + /// `args.get(n).cloned().unwrap_or_default()` or + /// `args.get(n).unwrap_or(&undefined)`. + /// + /// This returns a reference for efficiency, in case + /// you only need to call methods of `JsValue`, so + /// try to minimize calling `clone`. + fn get_or_undefined(&self, index: usize) -> &JsValue; +} + +impl JsArgs for [JsValue] { + fn get_or_undefined(&self, index: usize) -> &JsValue { + const UNDEFINED: &JsValue = &JsValue::Undefined; + self.get(index).unwrap_or(UNDEFINED) + } +} diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 5349865cd07..f44290ab2e1 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -13,8 +13,8 @@ //! [spec]: https://tc39.es/ecma262/#sec-number-object //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number -use super::function::make_builtin_fn; use super::string::is_trimmable_whitespace; +use super::{function::make_builtin_fn, JsArgs}; use crate::{ builtins::BuiltIn, object::{ConstructorBuilder, ObjectData, PROTOTYPE}, @@ -392,12 +392,12 @@ impl Number { args: &[JsValue], context: &mut Context, ) -> JsResult { - let precision = args.get(0).cloned().unwrap_or_default(); + let precision = args.get_or_undefined(0); // 1 & 6 let mut this_num = Self::this_number_value(this, context)?; // 2 - if precision == JsValue::undefined() { + if precision.is_undefined() { return Self::to_string(this, &[], context); } @@ -720,7 +720,7 @@ impl Number { args: &[JsValue], context: &mut Context, ) -> JsResult { - if let (Some(val), radix) = (args.get(0), args.get(1)) { + if let (Some(val), radix) = (args.get(0), args.get_or_undefined(1)) { // 1. Let inputString be ? ToString(string). let input_string = val.to_string(context)?; @@ -745,7 +745,7 @@ impl Number { } // 6. Let R be ℝ(? ToInt32(radix)). - let mut var_r = radix.cloned().unwrap_or_default().to_i32(context)?; + let mut var_r = radix.to_i32(context)?; // 7. Let stripPrefix be true. let mut strip_prefix = true; diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index d199647aafc..e1ca18c874e 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -14,7 +14,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object use crate::{ - builtins::BuiltIn, + builtins::{BuiltIn, JsArgs}, object::{ ConstructorBuilder, IntegrityLevel, JsObject, Object as BuiltinObject, ObjectData, ObjectInitializer, ObjectKind, PROTOTYPE, @@ -135,12 +135,12 @@ impl Object { /// [spec]: https://tc39.es/ecma262/#sec-object.create /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create pub fn create(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let prototype = args.get(0).cloned().unwrap_or_else(JsValue::undefined); - let properties = args.get(1).cloned().unwrap_or_else(JsValue::undefined); + let prototype = args.get_or_undefined(0); + let properties = args.get_or_undefined(1); let obj = match prototype { JsValue::Object(_) | JsValue::Null => JsObject::new(BuiltinObject::with_prototype( - prototype, + prototype.clone(), ObjectData::ordinary(), )), _ => { @@ -152,7 +152,7 @@ impl Object { }; if !properties.is_undefined() { - object_define_properties(&obj, properties, context)?; + object_define_properties(&obj, properties.clone(), context)?; return Ok(obj.into()); } @@ -174,10 +174,7 @@ impl Object { args: &[JsValue], context: &mut Context, ) -> JsResult { - let object = args - .get(0) - .unwrap_or(&JsValue::undefined()) - .to_object(context)?; + let object = args.get_or_undefined(0).to_object(context)?; if let Some(key) = args.get(1) { let key = key.to_property_key(context)?; @@ -276,10 +273,10 @@ impl Object { /// Uses the SameValue algorithm to check equality of objects pub fn is(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { - let x = args.get(0).cloned().unwrap_or_else(JsValue::undefined); - let y = args.get(1).cloned().unwrap_or_else(JsValue::undefined); + let x = args.get_or_undefined(0); + let y = args.get_or_undefined(1); - Ok(JsValue::same_value(&x, &y).into()) + Ok(JsValue::same_value(x, y).into()) } /// Get the `prototype` of an object. @@ -319,7 +316,7 @@ impl Object { .clone(); // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception. - let proto = args.get(1).cloned().unwrap_or_default(); + let proto = args.get_or_undefined(1); if !matches!(proto.get_type(), Type::Object | Type::Null) { return ctx.throw_type_error(format!( "expected an object or null, got {}", @@ -336,7 +333,7 @@ impl Object { let status = obj .as_object() .expect("obj was not an object") - .__set_prototype_of__(proto, ctx)?; + .__set_prototype_of__(proto.clone(), ctx)?; // 5. If status is false, throw a TypeError exception. if !status { @@ -362,11 +359,11 @@ impl Object { args: &[JsValue], context: &mut Context, ) -> JsResult { - let undefined = JsValue::undefined(); - let mut v = args.get(0).unwrap_or(&undefined).clone(); + let v = args.get_or_undefined(0); if !v.is_object() { return Ok(JsValue::new(false)); } + let mut v = v.clone(); let o = JsValue::new(this.to_object(context)?); loop { v = Self::get_prototype_of(this, &[v], context)?; @@ -385,7 +382,7 @@ impl Object { args: &[JsValue], context: &mut Context, ) -> JsResult { - let object = args.get(0).cloned().unwrap_or_else(JsValue::undefined); + let object = args.get_or_undefined(0); if let Some(object) = object.as_object() { let key = args .get(1) @@ -419,12 +416,12 @@ impl Object { args: &[JsValue], context: &mut Context, ) -> JsResult { - let arg = args.get(0).cloned().unwrap_or_default(); + let arg = args.get_or_undefined(0); let arg_obj = arg.as_object(); if let Some(obj) = arg_obj { - let props = args.get(1).cloned().unwrap_or_else(JsValue::undefined); - object_define_properties(&obj, props, context)?; - Ok(arg) + let props = args.get_or_undefined(1); + object_define_properties(&obj, props.clone(), context)?; + Ok(arg.clone()) } else { context.throw_type_error("Expected an object") } @@ -572,11 +569,7 @@ impl Object { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign pub fn assign(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let to be ? ToObject(target). - let to = args - .get(0) - .cloned() - .unwrap_or_default() - .to_object(context)?; + let to = args.get_or_undefined(0).to_object(context)?; // 2. If only one argument was passed, return to. if args.len() == 1 { diff --git a/boa/src/builtins/reflect/mod.rs b/boa/src/builtins/reflect/mod.rs index 97004609e50..c699f572bff 100644 --- a/boa/src/builtins/reflect/mod.rs +++ b/boa/src/builtins/reflect/mod.rs @@ -18,7 +18,7 @@ use crate::{ BoaProfiler, Context, JsResult, JsValue, }; -use super::Array; +use super::{Array, JsArgs}; #[cfg(test)] mod tests; @@ -81,14 +81,14 @@ impl Reflect { .get(0) .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be a function"))?; - let this_arg = args.get(1).cloned().unwrap_or_default(); - let args_list = args.get(2).cloned().unwrap_or_default(); + let this_arg = args.get_or_undefined(1); + let args_list = args.get_or_undefined(2); if !target.is_callable() { return context.throw_type_error("target must be a function"); } let args = args_list.create_list_from_array_like(&[], context)?; - target.call(&this_arg, &args, context) + target.call(this_arg, &args, context) } /// Calls a target function as a constructor with arguments. @@ -108,7 +108,7 @@ impl Reflect { .get(0) .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be a function"))?; - let args_list = args.get(1).cloned().unwrap_or_default(); + let args_list = args.get_or_undefined(1); if !target.is_constructable() { return context.throw_type_error("target must be a constructor"); @@ -140,12 +140,11 @@ impl Reflect { args: &[JsValue], context: &mut Context, ) -> JsResult { - let undefined = JsValue::undefined(); let target = args .get(0) .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be an object"))?; - let key = args.get(1).unwrap_or(&undefined).to_property_key(context)?; + let key = args.get_or_undefined(1).to_property_key(context)?; let prop_desc: JsValue = args .get(2) .and_then(|v| v.as_object()) @@ -170,12 +169,11 @@ impl Reflect { args: &[JsValue], context: &mut Context, ) -> JsResult { - let undefined = JsValue::undefined(); let target = args .get(0) .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be an object"))?; - let key = args.get(1).unwrap_or(&undefined).to_property_key(context)?; + let key = args.get_or_undefined(1).to_property_key(context)?; Ok(target.__delete__(&key, context)?.into()) } @@ -189,14 +187,13 @@ impl Reflect { /// [spec]: https://tc39.es/ecma262/#sec-reflect.get /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/get pub(crate) fn get(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let undefined = JsValue::undefined(); // 1. If Type(target) is not Object, throw a TypeError exception. let target = args .get(0) .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be an object"))?; // 2. Let key be ? ToPropertyKey(propertyKey). - let key = args.get(1).unwrap_or(&undefined).to_property_key(context)?; + let key = args.get_or_undefined(1).to_property_key(context)?; // 3. If receiver is not present, then let receiver = if let Some(receiver) = args.get(2).cloned() { receiver @@ -347,13 +344,12 @@ impl Reflect { /// [spec]: https://tc39.es/ecma262/#sec-reflect.set /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set pub(crate) fn set(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let undefined = JsValue::undefined(); let target = args .get(0) .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be an object"))?; - let key = args.get(1).unwrap_or(&undefined).to_property_key(context)?; - let value = args.get(2).unwrap_or(&undefined); + let key = args.get_or_undefined(1).to_property_key(context)?; + let value = args.get_or_undefined(2); let receiver = if let Some(receiver) = args.get(3).cloned() { receiver } else { @@ -377,12 +373,11 @@ impl Reflect { args: &[JsValue], context: &mut Context, ) -> JsResult { - let undefined = JsValue::undefined(); let mut target = args .get(0) .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be an object"))?; - let proto = args.get(1).unwrap_or(&undefined); + let proto = args.get_or_undefined(1); if !proto.is_null() && !proto.is_object() { return context.throw_type_error("proto must be an object or null"); } diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index b51a9683fa3..719924a1092 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -23,6 +23,8 @@ use crate::{ use regexp_string_iterator::RegExpStringIterator; use regress::Regex; +use super::JsArgs; + #[cfg(test)] mod tests; @@ -187,8 +189,8 @@ impl RegExp { args: &[JsValue], context: &mut Context, ) -> JsResult { - let pattern = args.get(0).cloned().unwrap_or_else(JsValue::undefined); - let flags = args.get(1).cloned().unwrap_or_else(JsValue::undefined); + let pattern = args.get_or_undefined(0); + let flags = args.get_or_undefined(1); // 1. Let patternIsRegExp be ? IsRegExp(pattern). let pattern_is_regexp = if let JsValue::Object(obj) = &pattern { @@ -233,12 +235,12 @@ impl RegExp { JsValue::new(regexp.original_flags.clone()), ) } else { - (JsValue::new(regexp.original_source.clone()), flags) + (JsValue::new(regexp.original_source.clone()), flags.clone()) } } else { // a. Let P be pattern. // b. Let F be flags. - (pattern, flags) + (pattern.clone(), flags.clone()) }; // 7. Let O be ? RegExpAlloc(newTarget). @@ -275,8 +277,8 @@ impl RegExp { /// /// [spec]: https://tc39.es/ecma262/#sec-regexpinitialize fn initialize(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let pattern = args.get(0).cloned().unwrap_or_else(JsValue::undefined); - let flags = args.get(1).cloned().unwrap_or_else(JsValue::undefined); + let pattern = args.get_or_undefined(0); + let flags = args.get_or_undefined(1); // 1. If pattern is undefined, let P be the empty String. // 2. Else, let P be ? ToString(pattern). @@ -1282,7 +1284,7 @@ impl RegExp { let length_arg_str = arg_str.encode_utf16().count(); // 5. Let functionalReplace be IsCallable(replaceValue). - let mut replace_value = args.get(1).cloned().unwrap_or_default(); + let mut replace_value = args.get_or_undefined(1).clone(); let functional_replace = replace_value.is_function(); // 6. If functionalReplace is false, then @@ -1619,7 +1621,7 @@ impl RegExp { let mut length_a = 0; // 13. If limit is undefined, let lim be 2^32 - 1; else let lim be ℝ(? ToUint32(limit)). - let limit = args.get(1).cloned().unwrap_or_default(); + let limit = args.get_or_undefined(1); let lim = if limit.is_undefined() { u32::MAX } else { diff --git a/boa/src/builtins/set/mod.rs b/boa/src/builtins/set/mod.rs index 8dec1714aa2..0cf4b991fb5 100644 --- a/boa/src/builtins/set/mod.rs +++ b/boa/src/builtins/set/mod.rs @@ -22,6 +22,8 @@ use ordered_set::OrderedSet; pub mod set_iterator; use set_iterator::SetIterator; +use super::JsArgs; + pub mod ordered_set; #[cfg(test)] mod tests; @@ -139,7 +141,7 @@ impl Set { // 3 set.set_data(ObjectData::set(OrderedSet::default())); - let iterable = args.get(0).cloned().unwrap_or_default(); + let iterable = args.get_or_undefined(0); // 4 if iterable.is_null_or_undefined() { return Ok(set); @@ -154,7 +156,7 @@ impl Set { } // 7 - let iterator_record = get_iterator(context, iterable)?; + let iterator_record = get_iterator(context, iterable.clone())?; // 8.a let mut next = iterator_record.next(context)?; @@ -206,14 +208,15 @@ impl Set { args: &[JsValue], context: &mut Context, ) -> JsResult { - let mut value = args.get(0).cloned().unwrap_or_default(); + let value = args.get_or_undefined(0); if let Some(object) = this.as_object() { if let Some(set) = object.borrow_mut().as_set_mut() { - if value.as_number().map(|n| n == -0f64).unwrap_or(false) { - value = JsValue::Integer(0); - } - set.add(value); + set.add(if value.as_number().map(|n| n == -0f64).unwrap_or(false) { + JsValue::Integer(0) + } else { + value.clone() + }); } else { return context.throw_type_error("'this' is not a Set"); } @@ -263,11 +266,11 @@ impl Set { args: &[JsValue], context: &mut Context, ) -> JsResult { - let value = args.get(0).cloned().unwrap_or_default(); + let value = args.get_or_undefined(0); let res = if let Some(object) = this.as_object() { if let Some(set) = object.borrow_mut().as_set_mut() { - set.delete(&value) + set.delete(value) } else { return context.throw_type_error("'this' is not a Set"); } @@ -332,12 +335,12 @@ impl Set { } let callback_arg = &args[0]; - let this_arg = args.get(1).cloned().unwrap_or_else(JsValue::undefined); + let this_arg = args.get_or_undefined(1); // TODO: if condition should also check that we are not in strict mode let this_arg = if this_arg.is_undefined() { JsValue::Object(context.global_object()) } else { - this_arg + this_arg.clone() }; let mut index = 0; @@ -380,12 +383,12 @@ impl Set { args: &[JsValue], context: &mut Context, ) -> JsResult { - let value = args.get(0).cloned().unwrap_or_default(); + let value = args.get_or_undefined(0); if let JsValue::Object(ref object) = this { let object = object.borrow(); if let Some(set) = object.as_set_ref() { - return Ok(set.contains(&value).into()); + return Ok(set.contains(value).into()); } } diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 9130f8f916f..a249d7b6543 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -29,6 +29,8 @@ use std::{ }; use unicode_normalization::UnicodeNormalization; +use super::JsArgs; + pub(crate) fn code_point_at(string: JsString, position: i32) -> Option<(u32, u8, bool)> { let size = string.encode_utf16().count() as i32; if position < 0 || position >= size { @@ -562,9 +564,9 @@ impl String { // Then we convert it into a Rust String by wrapping it in from_value let primitive_val = this.to_string(context)?; - let arg = args.get(0).cloned().unwrap_or_else(JsValue::undefined); + let arg = args.get_or_undefined(0); - if Self::is_regexp_object(&arg) { + if Self::is_regexp_object(arg) { context.throw_type_error( "First argument to String.prototype.startsWith must not be a regular expression", )?; @@ -576,12 +578,10 @@ impl String { let search_length = search_string.chars().count() as i32; // If less than 2 args specified, position is 'undefined', defaults to 0 - let position = if args.len() < 2 { - 0 + let position = if let Some(integer) = args.get(1) { + integer.to_integer(context)? as i32 } else { - args.get(1) - .expect("failed to get arg") - .to_integer(context)? as i32 + 0 }; let start = min(max(position, 0), length); @@ -617,9 +617,9 @@ impl String { // Then we convert it into a Rust String by wrapping it in from_value let primitive_val = this.to_string(context)?; - let arg = args.get(0).cloned().unwrap_or_else(JsValue::undefined); + let arg = args.get_or_undefined(0); - if Self::is_regexp_object(&arg) { + if Self::is_regexp_object(arg) { context.throw_type_error( "First argument to String.prototype.endsWith must not be a regular expression", )?; @@ -632,12 +632,10 @@ impl String { // If less than 2 args specified, end_position is 'undefined', defaults to // length of this - let end_position = if args.len() < 2 { - length + let end_position = if let Some(integer) = args.get(1) { + integer.to_integer(context)? as i32 } else { - args.get(1) - .expect("Could not get argument") - .to_integer(context)? as i32 + length }; let end = min(max(end_position, 0), length); @@ -671,9 +669,9 @@ impl String { // Then we convert it into a Rust String by wrapping it in from_value let primitive_val = this.to_string(context)?; - let arg = args.get(0).cloned().unwrap_or_else(JsValue::undefined); + let arg = args.get_or_undefined(0); - if Self::is_regexp_object(&arg) { + if Self::is_regexp_object(arg) { context.throw_type_error( "First argument to String.prototype.includes must not be a regular expression", )?; @@ -684,12 +682,11 @@ impl String { let length = primitive_val.chars().count() as i32; // If less than 2 args specified, position is 'undefined', defaults to 0 - let position = if args.len() < 2 { - 0 + + let position = if let Some(integer) = args.get(1) { + integer.to_integer(context)? as i32 } else { - args.get(1) - .expect("Could not get argument") - .to_integer(context)? as i32 + 0 }; let start = min(max(position, 0), length); @@ -730,9 +727,9 @@ impl String { // 1. Let O be ? RequireObjectCoercible(this value). this.require_object_coercible(context)?; - let search_value = args.get(0).cloned().unwrap_or_default(); + let search_value = args.get_or_undefined(0); - let replace_value = args.get(1).cloned().unwrap_or_default(); + let replace_value = args.get_or_undefined(1); // 2. If searchValue is neither undefined nor null, then if !search_value.is_null_or_undefined() { @@ -747,8 +744,8 @@ impl String { // i. Return ? Call(replacer, searchValue, « O, replaceValue »). return context.call( &replacer.into(), - &search_value, - &[this.clone(), replace_value], + search_value, + &[this.clone(), replace_value.clone()], ); } } @@ -787,7 +784,7 @@ impl String { // a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)). context .call( - &replace_value, + replace_value, &JsValue::undefined(), &[search_str.into(), position.into(), this_str.clone().into()], )? @@ -846,8 +843,8 @@ impl String { // 1. Let O be ? RequireObjectCoercible(this value). let o = this.require_object_coercible(context)?; - let search_value = args.get(0).cloned().unwrap_or_default(); - let replace_value = args.get(1).cloned().unwrap_or_default(); + let search_value = args.get_or_undefined(0); + let replace_value = args.get_or_undefined(1); // 2. If searchValue is neither undefined nor null, then if !search_value.is_null_or_undefined() { @@ -879,7 +876,7 @@ impl String { // d. If replacer is not undefined, then if let Some(replacer) = replacer { // i. Return ? Call(replacer, searchValue, « O, replaceValue »). - return replacer.call(&search_value, &[o.into(), replace_value], context); + return replacer.call(search_value, &[o.into(), replace_value.clone()], context); } } @@ -945,7 +942,7 @@ impl String { // i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)). context .call( - &replace_value, + replace_value, &JsValue::undefined(), &[ search_string.clone().into(), @@ -1109,14 +1106,14 @@ impl String { let o = this.require_object_coercible(context)?; // 2. If regexp is neither undefined nor null, then - let regexp = args.get(0).cloned().unwrap_or_default(); + let regexp = args.get_or_undefined(0); if !regexp.is_null_or_undefined() { // a. Let matcher be ? GetMethod(regexp, @@match). // b. If matcher is not undefined, then if let Some(obj) = regexp.as_object() { if let Some(matcher) = obj.get_method(context, WellKnownSymbols::match_())? { // i. Return ? Call(matcher, regexp, « O »). - return matcher.call(®exp, &[o.clone()], context); + return matcher.call(regexp, &[o.clone()], context); } } } @@ -1125,7 +1122,7 @@ impl String { let s = o.to_string(context)?; // 4. Let rx be ? RegExpCreate(regexp, undefined). - let rx = RegExp::create(regexp, JsValue::undefined(), context)?; + let rx = RegExp::create(regexp.clone(), JsValue::undefined(), context)?; // 5. Return ? Invoke(rx, @@match, « S »). let obj = rx.as_object().expect("RegExpCreate must return Object"); @@ -1370,21 +1367,17 @@ impl String { // Then we convert it into a Rust String by wrapping it in from_value let primitive_val = this.to_string(context)?; // If no args are specified, start is 'undefined', defaults to 0 - let start = if args.is_empty() { - 0 + let start = if let Some(integer) = args.get(0) { + integer.to_integer(context)? as i32 } else { - args.get(0) - .expect("failed to get argument for String method") - .to_integer(context)? as i32 + 0 }; let length = primitive_val.encode_utf16().count() as i32; // If less than 2 args specified, end is the length of the this object converted to a String - let end = if args.len() < 2 { - length + let end = if let Some(integer) = args.get(1) { + integer.to_integer(context)? as i32 } else { - args.get(1) - .expect("Could not get argument") - .to_integer(context)? as i32 + length }; // Both start and end args replaced by 0 if they were negative // or by the length of the String if they were greater @@ -1425,24 +1418,20 @@ impl String { // Then we convert it into a Rust String by wrapping it in from_value let primitive_val = this.to_string(context)?; // If no args are specified, start is 'undefined', defaults to 0 - let mut start = if args.is_empty() { - 0 + let mut start = if let Some(integer) = args.get(0) { + integer.to_integer(context)? as i32 } else { - args.get(0) - .expect("failed to get argument for String method") - .to_integer(context)? as i32 + 0 }; let length = primitive_val.chars().count() as i32; // If less than 2 args specified, end is +infinity, the maximum number value. // Using i32::max_value() should be safe because the final length used is at most // the number of code units from start to the end of the string, // which should always be smaller or equals to both +infinity and i32::max_value - let end = if args.len() < 2 { - i32::MAX + let end = if let Some(integer) = args.get(1) { + integer.to_integer(context)? as i32 } else { - args.get(1) - .expect("Could not get argument") - .to_integer(context)? as i32 + i32::MAX }; // If start is negative it become the number of code units from the end of the string if start < 0 { @@ -1485,8 +1474,8 @@ impl String { // 1. Let O be ? RequireObjectCoercible(this value). let this = this.require_object_coercible(context)?; - let separator = args.get(0).cloned().unwrap_or_default(); - let limit = args.get(1).cloned().unwrap_or_default(); + let separator = args.get_or_undefined(0); + let limit = args.get_or_undefined(1); // 2. If separator is neither undefined nor null, then if !separator.is_null_or_undefined() { @@ -1498,7 +1487,7 @@ impl String { .get_method(context, WellKnownSymbols::split())? { // i. Return ? Call(splitter, separator, « O, limit »). - return splitter.call(&separator, &[this.clone(), limit], context); + return splitter.call(separator, &[this.clone(), limit.clone()], context); } } @@ -1661,7 +1650,7 @@ impl String { let o = this.require_object_coercible(context)?; // 2. If regexp is neither undefined nor null, then - let regexp = args.get(0).cloned().unwrap_or_default(); + let regexp = args.get_or_undefined(0); if !regexp.is_null_or_undefined() { // a. Let isRegExp be ? IsRegExp(regexp). // b. If isRegExp is true, then @@ -1685,7 +1674,7 @@ impl String { if let Some(obj) = regexp.as_object() { if let Some(matcher) = obj.get_method(context, WellKnownSymbols::match_all())? { // i. Return ? Call(matcher, regexp, « O »). - return matcher.call(®exp, &[o.clone()], context); + return matcher.call(regexp, &[o.clone()], context); } } } @@ -1694,7 +1683,7 @@ impl String { let s = o.to_string(context)?; // 4. Let rx be ? RegExpCreate(regexp, "g"). - let rx = RegExp::create(regexp, JsValue::new("g"), context)?; + let rx = RegExp::create(regexp.clone(), JsValue::new("g"), context)?; // 5. Return ? Invoke(rx, @@matchAll, « S »). let obj = rx.as_object().expect("RegExpCreate must return Object"); @@ -1722,7 +1711,7 @@ impl String { ) -> JsResult { let this = this.require_object_coercible(context)?; let s = this.to_string(context)?; - let form = args.get(0).cloned().unwrap_or_default(); + let form = args.get_or_undefined(0); let f_str; @@ -1762,14 +1751,14 @@ impl String { let o = this.require_object_coercible(context)?; // 2. If regexp is neither undefined nor null, then - let regexp = args.get(0).cloned().unwrap_or_default(); + let regexp = args.get_or_undefined(0); if !regexp.is_null_or_undefined() { // a. Let searcher be ? GetMethod(regexp, @@search). // b. If searcher is not undefined, then if let Some(obj) = regexp.as_object() { if let Some(searcher) = obj.get_method(context, WellKnownSymbols::search())? { // i. Return ? Call(searcher, regexp, « O »). - return searcher.call(®exp, &[o.clone()], context); + return searcher.call(regexp, &[o.clone()], context); } } } @@ -1778,7 +1767,7 @@ impl String { let string = o.to_string(context)?; // 4. Let rx be ? RegExpCreate(regexp, undefined). - let rx = RegExp::create(regexp, JsValue::undefined(), context)?; + let rx = RegExp::create(regexp.clone(), JsValue::undefined(), context)?; // 5. Return ? Invoke(rx, @@search, « string »). let obj = rx.as_object().expect("RegExpCreate must return Object"); diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index 78c11defbf5..e7a91cd8989 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -31,6 +31,8 @@ use std::cell::RefCell; use rustc_hash::FxHashMap; +use super::JsArgs; + thread_local! { static GLOBAL_SYMBOL_REGISTRY: RefCell = RefCell::new(GlobalSymbolRegistry::new()); } @@ -276,7 +278,7 @@ impl Symbol { args: &[JsValue], context: &mut Context, ) -> JsResult { - let sym = args.get(0).cloned().unwrap_or_default(); + let sym = args.get_or_undefined(0); // 1. If Type(sym) is not Symbol, throw a TypeError exception. if let Some(sym) = sym.as_symbol() { // 2. For each element e of the GlobalSymbolRegistry List (see 20.4.2.2), do diff --git a/boa/src/class.rs b/boa/src/class.rs index 79938910016..4c4bf2041f6 100644 --- a/boa/src/class.rs +++ b/boa/src/class.rs @@ -7,6 +7,7 @@ //!# class::{Class, ClassBuilder}, //!# gc::{Finalize, Trace}, //!# Context, JsResult, JsValue, +//!# builtins::JsArgs, //!# }; //!# //! // This does not have to be an enum it can also be a struct. @@ -27,7 +28,7 @@ //! // This is what is called when we do `new Animal()` //! fn constructor(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { //! // This is equivalent to `String(arg)`. -//! let kind = args.get(0).cloned().unwrap_or_default().to_string(context)?; +//! let kind = args.get_or_undefined(0).to_string(context)?; //! //! let animal = match kind.as_str() { //! "cat" => Self::Cat,