Browse Source

Implement Latin1 JsString (#3450)

pull/3838/head
Haled Odat 6 months ago committed by GitHub
parent
commit
3f6ee2276c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 9
      cli/src/debug/function.rs
  2. 20
      cli/src/debug/limits.rs
  3. 25
      cli/src/debug/mod.rs
  4. 6
      cli/src/debug/optimizer.rs
  5. 93
      cli/src/debug/string.rs
  6. 18
      core/engine/src/bigint.rs
  7. 3
      core/engine/src/builtins/array/array_iterator.rs
  8. 56
      core/engine/src/builtins/array/mod.rs
  9. 17
      core/engine/src/builtins/array/tests.rs
  10. 10
      core/engine/src/builtins/async_generator/mod.rs
  11. 7
      core/engine/src/builtins/atomics/mod.rs
  12. 12
      core/engine/src/builtins/bigint/tests.rs
  13. 18
      core/engine/src/builtins/builder.rs
  14. 124
      core/engine/src/builtins/date/mod.rs
  15. 11
      core/engine/src/builtins/date/tests.rs
  16. 179
      core/engine/src/builtins/date/utils.rs
  17. 11
      core/engine/src/builtins/error/aggregate.rs
  18. 9
      core/engine/src/builtins/error/eval.rs
  19. 21
      core/engine/src/builtins/error/mod.rs
  20. 9
      core/engine/src/builtins/error/range.rs
  21. 9
      core/engine/src/builtins/error/reference.rs
  22. 9
      core/engine/src/builtins/error/syntax.rs
  23. 58
      core/engine/src/builtins/error/tests.rs
  24. 11
      core/engine/src/builtins/error/type.rs
  25. 9
      core/engine/src/builtins/error/uri.rs
  26. 8
      core/engine/src/builtins/escape/mod.rs
  27. 3
      core/engine/src/builtins/eval/mod.rs
  28. 51
      core/engine/src/builtins/function/mod.rs
  29. 9
      core/engine/src/builtins/function/tests.rs
  30. 62
      core/engine/src/builtins/intl/collator/mod.rs
  31. 33
      core/engine/src/builtins/intl/date_time_format.rs
  32. 31
      core/engine/src/builtins/intl/list_format/mod.rs
  33. 25
      core/engine/src/builtins/intl/locale/mod.rs
  34. 4
      core/engine/src/builtins/intl/locale/utils.rs
  35. 14
      core/engine/src/builtins/intl/number_format/mod.rs
  36. 56
      core/engine/src/builtins/intl/number_format/options.rs
  37. 4
      core/engine/src/builtins/intl/options.rs
  38. 60
      core/engine/src/builtins/intl/plural_rules/mod.rs
  39. 30
      core/engine/src/builtins/intl/segmenter/iterator.rs
  40. 40
      core/engine/src/builtins/intl/segmenter/mod.rs
  41. 2
      core/engine/src/builtins/intl/segmenter/segments.rs
  42. 12
      core/engine/src/builtins/iterable/async_from_sync_iterator.rs
  43. 10
      core/engine/src/builtins/iterable/mod.rs
  44. 31
      core/engine/src/builtins/json/mod.rs
  45. 18
      core/engine/src/builtins/json/tests.rs
  46. 7
      core/engine/src/builtins/map/mod.rs
  47. 19
      core/engine/src/builtins/map/tests.rs
  48. 10
      core/engine/src/builtins/mod.rs
  49. 228
      core/engine/src/builtins/number/globals.rs
  50. 9
      core/engine/src/builtins/number/mod.rs
  51. 350
      core/engine/src/builtins/number/tests.rs
  52. 49
      core/engine/src/builtins/object/mod.rs
  53. 27
      core/engine/src/builtins/object/tests.rs
  54. 4
      core/engine/src/builtins/options.rs
  55. 38
      core/engine/src/builtins/promise/mod.rs
  56. 33
      core/engine/src/builtins/proxy/mod.rs
  57. 10
      core/engine/src/builtins/reflect/tests.rs
  58. 199
      core/engine/src/builtins/regexp/mod.rs
  59. 6
      core/engine/src/builtins/regexp/regexp_string_iterator.rs
  60. 17
      core/engine/src/builtins/regexp/tests.rs
  61. 11
      core/engine/src/builtins/set/mod.rs
  62. 212
      core/engine/src/builtins/string/mod.rs
  63. 110
      core/engine/src/builtins/string/tests.rs
  64. 10
      core/engine/src/builtins/symbol/mod.rs
  65. 5
      core/engine/src/builtins/symbol/tests.rs
  66. 22
      core/engine/src/builtins/temporal/calendar/mod.rs
  67. 46
      core/engine/src/builtins/temporal/calendar/object.rs
  68. 59
      core/engine/src/builtins/temporal/duration/mod.rs
  69. 35
      core/engine/src/builtins/temporal/instant/mod.rs
  70. 4
      core/engine/src/builtins/temporal/mod.rs
  71. 6
      core/engine/src/builtins/temporal/options.rs
  72. 38
      core/engine/src/builtins/temporal/plain_date/mod.rs
  73. 43
      core/engine/src/builtins/temporal/plain_date_time/mod.rs
  74. 39
      core/engine/src/builtins/temporal/plain_time/mod.rs
  75. 18
      core/engine/src/builtins/temporal/plain_year_month/mod.rs
  76. 10
      core/engine/src/builtins/temporal/tests.rs
  77. 7
      core/engine/src/builtins/temporal/time_zone/custom.rs
  78. 4
      core/engine/src/builtins/temporal/time_zone/mod.rs
  79. 20
      core/engine/src/builtins/typed_array/builtin.rs
  80. 5
      core/engine/src/builtins/typed_array/mod.rs
  81. 4
      core/engine/src/builtins/typed_array/object.rs
  82. 26
      core/engine/src/builtins/uri/mod.rs
  83. 5
      core/engine/src/builtins/weak_map/mod.rs
  84. 5
      core/engine/src/builtins/weak_set/mod.rs
  85. 6
      core/engine/src/bytecompiler/declarations.rs
  86. 20
      core/engine/src/bytecompiler/expression/mod.rs
  87. 18
      core/engine/src/bytecompiler/mod.rs
  88. 12
      core/engine/src/class.rs
  89. 19
      core/engine/src/context/intrinsics.rs
  90. 16
      core/engine/src/context/mod.rs
  91. 22
      core/engine/src/error.rs
  92. 11
      core/engine/src/lib.rs
  93. 6
      core/engine/src/module/source.rs
  94. 4
      core/engine/src/object/builtins/jsdate.rs
  95. 68
      core/engine/src/object/builtins/jsmap.rs
  96. 12
      core/engine/src/object/builtins/jspromise.rs
  97. 34
      core/engine/src/object/builtins/jsproxy.rs
  98. 3
      core/engine/src/object/internal_methods/string.rs
  99. 32
      core/engine/src/object/jsobject.rs
  100. 11
      core/engine/src/object/mod.rs
  101. Some files were not shown because too many files have changed in this diff Show More

9
cli/src/debug/function.rs

@ -1,6 +1,6 @@
use boa_engine::{
builtins::function::OrdinaryFunction,
js_string,
js_str, js_string,
object::ObjectInitializer,
vm::flowgraph::{Direction, Graph},
Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction,
@ -68,10 +68,9 @@ fn flowgraph(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResu
let mut direction = Direction::LeftToRight;
if let Some(arguments) = args.get(1) {
if let Some(arguments) = arguments.as_object() {
format = flowgraph_parse_format_option(&arguments.get(js_string!("format"), context)?)?;
direction = flowgraph_parse_direction_option(
&arguments.get(js_string!("direction"), context)?,
)?;
format = flowgraph_parse_format_option(&arguments.get(js_str!("format"), context)?)?;
direction =
flowgraph_parse_direction_option(&arguments.get(js_str!("direction"), context)?)?;
} else if value.is_string() {
format = flowgraph_parse_format_option(value)?;
} else {

20
cli/src/debug/limits.rs

@ -1,5 +1,5 @@
use boa_engine::{
js_string,
js_str,
object::{FunctionObjectBuilder, ObjectInitializer},
property::Attribute,
Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction,
@ -51,51 +51,51 @@ fn set_recursion(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResu
pub(super) fn create_object(context: &mut Context) -> JsObject {
let get_loop =
FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_loop))
.name("get loop")
.name(js_str!("get loop"))
.length(0)
.build();
let set_loop =
FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_loop))
.name("set loop")
.name(js_str!("set loop"))
.length(1)
.build();
let get_stack =
FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_stack))
.name("get stack")
.name(js_str!("get stack"))
.length(0)
.build();
let set_stack =
FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_stack))
.name("set stack")
.name(js_str!("set stack"))
.length(1)
.build();
let get_recursion =
FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_recursion))
.name("get recursion")
.name(js_str!("get recursion"))
.length(0)
.build();
let set_recursion =
FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_recursion))
.name("set recursion")
.name(js_str!("set recursion"))
.length(1)
.build();
ObjectInitializer::new(context)
.accessor(
js_string!("loop"),
js_str!("loop"),
Some(get_loop),
Some(set_loop),
Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.accessor(
js_string!("stack"),
js_str!("stack"),
Some(get_stack),
Some(set_stack),
Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.accessor(
js_string!("recursion"),
js_str!("recursion"),
Some(get_recursion),
Some(set_recursion),
Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,

25
cli/src/debug/mod.rs

@ -1,7 +1,7 @@
// Allow lint so it, doesn't warn about `JsResult<>` unneeded return on functions.
#![allow(clippy::unnecessary_wraps)]
use boa_engine::{js_string, object::ObjectInitializer, property::Attribute, Context, JsObject};
use boa_engine::{js_str, object::ObjectInitializer, property::Attribute, Context, JsObject};
mod function;
mod gc;
@ -10,6 +10,7 @@ mod object;
mod optimizer;
mod realm;
mod shape;
mod string;
fn create_boa_object(context: &mut Context) -> JsObject {
let function_module = function::create_object(context);
@ -19,43 +20,49 @@ fn create_boa_object(context: &mut Context) -> JsObject {
let gc_module = gc::create_object(context);
let realm_module = realm::create_object(context);
let limits_module = limits::create_object(context);
let string_module = string::create_string(context);
ObjectInitializer::new(context)
.property(
js_string!("function"),
js_str!("function"),
function_module,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
js_string!("object"),
js_str!("object"),
object_module,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
js_string!("shape"),
js_str!("shape"),
shape_module,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
js_string!("optimizer"),
js_str!("optimizer"),
optimizer_module,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
js_string!("gc"),
js_str!("gc"),
gc_module,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
js_string!("realm"),
js_str!("realm"),
realm_module,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
js_string!("limits"),
js_str!("limits"),
limits_module,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
js_str!("string"),
string_module,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.build()
}
@ -64,7 +71,7 @@ pub(crate) fn init_boa_debug_object(context: &mut Context) {
let boa_object = create_boa_object(context);
context
.register_global_property(
js_string!("$boa"),
js_str!("$boa"),
boa_object,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)

6
cli/src/debug/optimizer.rs

@ -1,5 +1,5 @@
use boa_engine::{
js_string,
js_str,
object::{FunctionObjectBuilder, ObjectInitializer},
optimizer::OptimizerOptions,
property::Attribute,
@ -64,13 +64,13 @@ pub(super) fn create_object(context: &mut Context) -> JsObject {
.build();
ObjectInitializer::new(context)
.accessor(
js_string!("constantFolding"),
js_str!("constantFolding"),
Some(get_constant_folding),
Some(set_constant_folding),
Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.accessor(
js_string!("statistics"),
js_str!("statistics"),
Some(get_statistics),
Some(set_statistics),
Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,

93
cli/src/debug/string.rs

@ -0,0 +1,93 @@
use boa_engine::{
js_string, object::ObjectInitializer, property::Attribute, string::JsStrVariant, Context,
JsNativeError, JsObject, JsResult, JsValue, NativeFunction,
};
fn storage(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
let Some(value) = args.first() else {
return Err(JsNativeError::typ()
.with_message("expected string argument")
.into());
};
let Some(string) = value.as_string() else {
return Err(JsNativeError::typ()
.with_message(format!("expected string, got {}", value.type_of()))
.into());
};
let storage = if string.is_static() { "static" } else { "heap" };
Ok(js_string!(storage).into())
}
fn encoding(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
let Some(value) = args.first() else {
return Err(JsNativeError::typ()
.with_message("expected string argument")
.into());
};
let Some(string) = value.as_string() else {
return Err(JsNativeError::typ()
.with_message(format!("expected string, got {}", value.type_of()))
.into());
};
let str = string.as_str();
let encoding = match str.variant() {
JsStrVariant::Latin1(_) => "latin1",
JsStrVariant::Utf16(_) => "utf16",
};
Ok(js_string!(encoding).into())
}
fn summary(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let Some(value) = args.first() else {
return Err(JsNativeError::typ()
.with_message("expected string argument")
.into());
};
let Some(string) = value.as_string() else {
return Err(JsNativeError::typ()
.with_message(format!("expected string, got {}", value.type_of()))
.into());
};
let storage = if string.is_static() { "static" } else { "heap" };
let encoding = match string.as_str().variant() {
JsStrVariant::Latin1(_) => "latin1",
JsStrVariant::Utf16(_) => "utf16",
};
let summary = ObjectInitializer::new(context)
.property(js_string!("storage"), js_string!(storage), Attribute::all())
.property(
js_string!("encoding"),
js_string!(encoding),
Attribute::all(),
)
.build();
Ok(summary.into())
}
pub(super) fn create_string(context: &mut Context) -> JsObject {
ObjectInitializer::new(context)
.function(
NativeFunction::from_fn_ptr(storage),
js_string!("storage"),
1,
)
.function(
NativeFunction::from_fn_ptr(encoding),
js_string!("encoding"),
1,
)
.function(
NativeFunction::from_fn_ptr(summary),
js_string!("summary"),
1,
)
.build()
}

18
core/engine/src/bigint.rs

@ -1,6 +1,6 @@
//! Boa's implementation of ECMAScript's bigint primitive type.
use crate::{builtins::Number, error::JsNativeError, JsData, JsResult};
use crate::{builtins::Number, error::JsNativeError, JsData, JsResult, JsString};
use boa_gc::{Finalize, Trace};
use num_integer::Integer;
use num_traits::{pow::Pow, FromPrimitive, One, ToPrimitive, Zero};
@ -89,6 +89,22 @@ impl JsBigInt {
})
}
/// Abstract operation `StringToBigInt ( str )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-stringtobigint
pub(crate) fn from_js_string(string: &JsString) -> Option<JsBigInt> {
// 1. Let text be ! StringToCodePoints(str).
// 2. Let literal be ParseText(text, StringIntegerLiteral).
// 3. If literal is a List of errors, return undefined.
// 4. Let mv be the MV of literal.
// 5. Assert: mv is an integer.
// 6. Return ℤ(mv).
JsBigInt::from_string(string.to_std_string().ok().as_ref()?)
}
/// This function takes a string and converts it to `BigInt` type.
///
/// More information:

3
core/engine/src/builtins/array/array_iterator.rs

@ -20,6 +20,7 @@ use crate::{
Context, JsData, JsResult,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
/// The Array Iterator object represents an iteration over an array. It implements the iterator protocol.
@ -52,7 +53,7 @@ impl IntrinsicObject for ArrayIterator {
.static_method(Self::next, js_string!("next"), 0)
.static_property(
JsSymbol::to_string_tag(),
js_string!("Array Iterator"),
js_str!("Array Iterator"),
Attribute::CONFIGURABLE,
)
.build();

56
core/engine/src/builtins/array/mod.rs

@ -10,7 +10,7 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
use boa_gc::{Finalize, Trace};
use boa_macros::utf16;
use boa_macros::js_str;
use boa_profiler::Profiler;
use thin_vec::ThinVec;
@ -161,12 +161,12 @@ impl IntrinsicObject for Array {
.method(Self::unshift, js_string!("unshift"), 1)
.method(Self::with, js_string!("with"), 2)
.property(
utf16!("toString"),
js_string!("toString"),
to_string_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
utf16!("values"),
js_string!("values"),
values_function.clone(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
@ -1012,7 +1012,7 @@ impl Array {
for k in 0..len {
// a. If k > 0, set R to the string-concatenation of R and sep.
if k > 0 {
r.extend_from_slice(&separator);
r.push(separator.clone());
}
// b. Let element be ? Get(O, ! ToString(𝔽(k))).
let element = o.get(k, context)?;
@ -1023,7 +1023,7 @@ impl Array {
element.to_string(context)?
};
// d. Set R to the string-concatenation of R and next.
r.extend_from_slice(&next);
r.push(next.clone());
// e. Set k to k + 1.
}
// 8. Return R.
@ -1051,7 +1051,7 @@ impl Array {
// 1. Let array be ? ToObject(this value).
let array = this.to_object(context)?;
// 2. Let func be ? Get(array, "join").
let func = array.get(utf16!("join"), context)?;
let func = array.get(js_string!("join"), context)?;
// 3. If IsCallable(func) is false, set func to the intrinsic function %Object.prototype.toString%.
// 4. Return ? Call(func, array).
if let Some(func) = func.as_callable() {
@ -2203,12 +2203,12 @@ impl Array {
#[cfg(feature = "intl")]
{
// TODO: this should eventually return a locale-sensitive separator.
utf16!(", ")
js_str!(", ")
}
#[cfg(not(feature = "intl"))]
{
utf16!(", ")
js_str!(", ")
}
};
@ -2221,7 +2221,7 @@ impl Array {
// a. If k > 0, then
if k > 0 {
// i. Set R to the string-concatenation of R and separator.
r.extend_from_slice(separator);
r.extend(separator.iter());
}
// b. Let nextElement be ? Get(array, ! ToString(k)).
@ -2231,16 +2231,16 @@ impl Array {
if !next.is_null_or_undefined() {
// i. Let S be ? ToString(? Invoke(nextElement, "toLocaleString", « locales, options »)).
let s = next
.invoke(utf16!("toLocaleString"), args, context)?
.invoke(js_str!("toLocaleString"), args, context)?
.to_string(context)?;
// ii. Set R to the string-concatenation of R and S.
r.extend_from_slice(&s);
r.extend(s.iter());
}
// d. Increase k by 1.
}
// 7. Return R.
Ok(js_string!(r).into())
Ok(js_string!(&r[..]).into())
}
/// Gets the delete count of a splice operation.
@ -3304,37 +3304,37 @@ impl Array {
{
let mut obj = unscopable_list.borrow_mut();
// 2. Perform ! CreateDataPropertyOrThrow(unscopableList, "at", true).
obj.insert(utf16!("at"), true_prop.clone());
obj.insert(js_str!("at"), true_prop.clone());
// 3. Perform ! CreateDataPropertyOrThrow(unscopableList, "copyWithin", true).
obj.insert(utf16!("copyWithin"), true_prop.clone());
obj.insert(js_str!("copyWithin"), true_prop.clone());
// 4. Perform ! CreateDataPropertyOrThrow(unscopableList, "entries", true).
obj.insert(utf16!("entries"), true_prop.clone());
obj.insert(js_str!("entries"), true_prop.clone());
// 5. Perform ! CreateDataPropertyOrThrow(unscopableList, "fill", true).
obj.insert(utf16!("fill"), true_prop.clone());
obj.insert(js_str!("fill"), true_prop.clone());
// 6. Perform ! CreateDataPropertyOrThrow(unscopableList, "find", true).
obj.insert(utf16!("find"), true_prop.clone());
obj.insert(js_str!("find"), true_prop.clone());
// 7. Perform ! CreateDataPropertyOrThrow(unscopableList, "findIndex", true).
obj.insert(utf16!("findIndex"), true_prop.clone());
obj.insert(js_str!("findIndex"), true_prop.clone());
// 8. Perform ! CreateDataPropertyOrThrow(unscopableList, "findLast", true).
obj.insert(utf16!("findLast"), true_prop.clone());
obj.insert(js_str!("findLast"), true_prop.clone());
// 9. Perform ! CreateDataPropertyOrThrow(unscopableList, "findLastIndex", true).
obj.insert(utf16!("findLastIndex"), true_prop.clone());
obj.insert(js_str!("findLastIndex"), true_prop.clone());
// 10. Perform ! CreateDataPropertyOrThrow(unscopableList, "flat", true).
obj.insert(utf16!("flat"), true_prop.clone());
obj.insert(js_str!("flat"), true_prop.clone());
// 11. Perform ! CreateDataPropertyOrThrow(unscopableList, "flatMap", true).
obj.insert(utf16!("flatMap"), true_prop.clone());
obj.insert(js_str!("flatMap"), true_prop.clone());
// 12. Perform ! CreateDataPropertyOrThrow(unscopableList, "includes", true).
obj.insert(utf16!("includes"), true_prop.clone());
obj.insert(js_str!("includes"), true_prop.clone());
// 13. Perform ! CreateDataPropertyOrThrow(unscopableList, "keys", true).
obj.insert(utf16!("keys"), true_prop.clone());
obj.insert(js_str!("keys"), true_prop.clone());
// 14. Perform ! CreateDataPropertyOrThrow(unscopableList, "toReversed", true).
obj.insert(utf16!("toReversed"), true_prop.clone());
obj.insert(js_str!("toReversed"), true_prop.clone());
// 15. Perform ! CreateDataPropertyOrThrow(unscopableList, "toSorted", true).
obj.insert(utf16!("toSorted"), true_prop.clone());
obj.insert(js_str!("toSorted"), true_prop.clone());
// 16. Perform ! CreateDataPropertyOrThrow(unscopableList, "toSpliced", true).
obj.insert(utf16!("toSpliced"), true_prop.clone());
obj.insert(js_str!("toSpliced"), true_prop.clone());
// 17. Perform ! CreateDataPropertyOrThrow(unscopableList, "values", true).
obj.insert(utf16!("values"), true_prop);
obj.insert(js_str!("values"), true_prop);
}
// 13. Return unscopableList.

17
core/engine/src/builtins/array/tests.rs

@ -2,6 +2,7 @@ use super::Array;
use crate::{
builtins::Number, js_string, run_test_actions, Context, JsNativeErrorKind, JsValue, TestAction,
};
use boa_macros::js_str;
use indoc::indoc;
#[test]
@ -38,7 +39,7 @@ fn of() {
TestAction::assert("arrayEquals(Array.of(), [])"),
TestAction::run("let a = Array.of.call(Date, 'a', undefined, 3);"),
TestAction::assert("a instanceof Date"),
TestAction::assert_eq("a[0]", js_string!("a")),
TestAction::assert_eq("a[0]", js_str!("a")),
TestAction::assert_eq("a[1]", JsValue::undefined()),
TestAction::assert_eq("a[2]", 3),
TestAction::assert_eq("a.length", 3),
@ -75,9 +76,9 @@ fn copy_within() {
fn join() {
run_test_actions([
TestAction::assert_eq("[].join('.')", js_string!()),
TestAction::assert_eq("['a'].join('.')", js_string!("a")),
TestAction::assert_eq("['a', 'b', 'c'].join('.')", js_string!("a.b.c")),
TestAction::assert_eq("let a=[];a[0]=a;a[1]=a;a[2]=a;a.join()", js_string!(",,")),
TestAction::assert_eq("['a'].join('.')", js_str!("a")),
TestAction::assert_eq("['a', 'b', 'c'].join('.')", js_str!("a.b.c")),
TestAction::assert_eq("let a=[];a[0]=a;a[1]=a;a[2]=a;a.join()", js_str!(",,")),
]);
}
@ -85,8 +86,8 @@ fn join() {
fn to_string() {
run_test_actions([
TestAction::assert_eq("[].toString()", js_string!()),
TestAction::assert_eq("['a'].toString()", js_string!("a")),
TestAction::assert_eq("['a', 'b', 'c'].toString()", js_string!("a,b,c")),
TestAction::assert_eq("['a'].toString()", js_str!("a")),
TestAction::assert_eq("['a', 'b', 'c'].toString()", js_str!("a,b,c")),
]);
}
@ -116,7 +117,7 @@ fn every() {
fn find() {
run_test_actions([TestAction::assert_eq(
"['a', 'b', 'c'].find(e => e == 'a')",
js_string!("a"),
js_str!("a"),
)]);
}
@ -350,7 +351,7 @@ fn fill_obj_ref() {
let a = new Array(3).fill(obj);
obj.hi = 'hi'
"#}),
TestAction::assert_eq("a[2].hi", js_string!("hi")),
TestAction::assert_eq("a[2].hi", js_str!("hi")),
]);
}

10
core/engine/src/builtins/async_generator/mod.rs

@ -7,8 +7,10 @@
use crate::{
builtins::{
generator::GeneratorContext, iterable::create_iter_result_object,
promise::if_abrupt_reject_promise, promise::PromiseCapability, Promise,
generator::GeneratorContext,
iterable::create_iter_result_object,
promise::{if_abrupt_reject_promise, PromiseCapability},
Promise,
},
context::intrinsics::Intrinsics,
error::JsNativeError,
@ -576,7 +578,7 @@ impl AsyncGenerator {
generator.clone(),
),
)
.name("")
.name(js_string!(""))
.length(1)
.build();
@ -611,7 +613,7 @@ impl AsyncGenerator {
generator,
),
)
.name("")
.name(js_string!(""))
.length(1)
.build();

7
core/engine/src/builtins/atomics/mod.rs

@ -21,6 +21,7 @@ use crate::{
JsString, JsValue,
};
use boa_macros::js_str;
use boa_profiler::Profiler;
use super::{
@ -463,9 +464,9 @@ impl Atomics {
};
Ok(match result {
futex::AtomicsWaitResult::NotEqual => js_string!("not-equal"),
futex::AtomicsWaitResult::TimedOut => js_string!("timed-out"),
futex::AtomicsWaitResult::Ok => js_string!("ok"),
futex::AtomicsWaitResult::NotEqual => js_str!("not-equal"),
futex::AtomicsWaitResult::TimedOut => js_str!("timed-out"),
futex::AtomicsWaitResult::Ok => js_str!("ok"),
}
.into())
}

12
core/engine/src/builtins/bigint/tests.rs

@ -1,4 +1,6 @@
use crate::{js_string, run_test_actions, JsBigInt, JsNativeErrorKind, TestAction};
use boa_macros::js_str;
use crate::{run_test_actions, JsBigInt, JsNativeErrorKind, TestAction};
#[test]
fn equality() {
@ -147,10 +149,10 @@ fn operations() {
#[test]
fn to_string() {
run_test_actions([
TestAction::assert_eq("1000n.toString()", js_string!("1000")),
TestAction::assert_eq("1000n.toString(2)", js_string!("1111101000")),
TestAction::assert_eq("255n.toString(16)", js_string!("ff")),
TestAction::assert_eq("1000n.toString(36)", js_string!("rs")),
TestAction::assert_eq("1000n.toString()", js_str!("1000")),
TestAction::assert_eq("1000n.toString(2)", js_str!("1111101000")),
TestAction::assert_eq("255n.toString(16)", js_str!("ff")),
TestAction::assert_eq("1000n.toString(36)", js_str!("rs")),
]);
}

18
core/engine/src/builtins/builder.rs

@ -1,4 +1,4 @@
use boa_macros::utf16;
use boa_macros::js_str;
use crate::{
js_string,
@ -115,7 +115,7 @@ impl<S: ApplyToObject + IsConstructor> ApplyToObject for Callable<S> {
.configurable(true),
);
object.insert(
utf16!("name"),
js_str!("name"),
PropertyDescriptor::builder()
.value(self.name)
.writable(false)
@ -363,8 +363,8 @@ impl BuiltInConstructorWithPrototype<'_> {
let length = self.length;
let name = self.name.clone();
let prototype = self.prototype.clone();
self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE);
self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE);
self = self.static_property(js_str!("length"), length, Attribute::CONFIGURABLE);
self = self.static_property(js_str!("name"), name, Attribute::CONFIGURABLE);
self = self.static_property(PROTOTYPE, prototype, Attribute::empty());
let attributes = self.attributes;
@ -411,8 +411,8 @@ impl BuiltInConstructorWithPrototype<'_> {
pub(crate) fn build_without_prototype(mut self) {
let length = self.length;
let name = self.name.clone();
self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE);
self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE);
self = self.static_property(js_str!("length"), length, Attribute::CONFIGURABLE);
self = self.static_property(js_str!("name"), name, Attribute::CONFIGURABLE);
let mut object = self.object.borrow_mut();
let function = object
@ -483,7 +483,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
realm,
function,
length: 0,
name: js_string!(""),
name: js_string!(),
}
}
@ -496,7 +496,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
object: I::get(realm.intrinsics()),
kind: Callable {
function,
name: js_string!(""),
name: js_string!(),
length: 0,
kind: OrdinaryFunction,
realm: realm.clone(),
@ -515,7 +515,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
object,
kind: Callable {
function,
name: js_string!(""),
name: js_string!(),
length: 0,
kind: OrdinaryFunction,
realm: realm.clone(),

124
core/engine/src/builtins/date/mod.rs

@ -27,12 +27,13 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
symbol::JsSymbol,
value::{JsValue, PreferredType},
Context, JsArgs, JsData, JsError, JsResult, JsString,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
pub(crate) mod utils;
@ -1416,34 +1417,40 @@ impl Date {
// including all format elements and the UTC offset representation "Z".
let year = year_from_time(tv);
let year = if year.is_positive() && year >= 10000 {
js_string!(utf16!("+"), &pad_six(year.unsigned_abs()))
js_string!(js_str!("+"), pad_six(year.unsigned_abs(), &mut [0; 6]))
} else if year.is_positive() {
JsString::from(&pad_four(year.unsigned_abs()))
pad_four(year.unsigned_abs(), &mut [0; 4]).into()
} else {
js_string!(utf16!("-"), &pad_six(year.unsigned_abs()))
js_string!(js_str!("-"), pad_six(year.unsigned_abs(), &mut [0; 6]))
};
let month = pad_two(month_from_time(tv) + 1);
let day = pad_two(date_from_time(tv));
let hour = pad_two(hour_from_time(tv));
let minute = pad_two(min_from_time(tv));
let second = pad_two(sec_from_time(tv));
let millisecond = pad_three(ms_from_time(tv));
let mut binding = [0; 2];
let month = pad_two(month_from_time(tv) + 1, &mut binding);
let mut binding = [0; 2];
let day = pad_two(date_from_time(tv), &mut binding);
let mut binding = [0; 2];
let hour = pad_two(hour_from_time(tv), &mut binding);
let mut binding = [0; 2];
let minute = pad_two(min_from_time(tv), &mut binding);
let mut binding = [0; 2];
let second = pad_two(sec_from_time(tv), &mut binding);
let mut binding = [0; 3];
let millisecond = pad_three(ms_from_time(tv), &mut binding);
Ok(JsValue::from(js_string!(
&year,
utf16!("-"),
&month,
utf16!("-"),
&day,
utf16!("T"),
&hour,
utf16!(":"),
&minute,
utf16!(":"),
&second,
utf16!("."),
&millisecond,
utf16!("Z")
js_str!("-"),
month,
js_str!("-"),
day,
js_str!("T"),
hour,
js_str!(":"),
minute,
js_str!(":"),
second,
js_str!("."),
millisecond,
js_str!("Z")
)))
}
@ -1473,7 +1480,7 @@ impl Date {
}
// 4. Return ? Invoke(O, "toISOString").
let func = o.get(utf16!("toISOString"), context)?;
let func = o.get(js_string!("toISOString"), context)?;
func.call(this, &[], context)
}
@ -1632,50 +1639,51 @@ impl Date {
// 5. Let weekday be the Name of the entry in Table 63 with the Number WeekDay(tv).
let weekday = match week_day(tv) {
0 => utf16!("Sun"),
1 => utf16!("Mon"),
2 => utf16!("Tue"),
3 => utf16!("Wed"),
4 => utf16!("Thu"),
5 => utf16!("Fri"),
6 => utf16!("Sat"),
0 => js_str!("Sun"),
1 => js_str!("Mon"),
2 => js_str!("Tue"),
3 => js_str!("Wed"),
4 => js_str!("Thu"),
5 => js_str!("Fri"),
6 => js_str!("Sat"),
_ => unreachable!(),
};
// 6. Let month be the Name of the entry in Table 64 with the Number MonthFromTime(tv).
let month = match month_from_time(tv) {
0 => utf16!("Jan"),
1 => utf16!("Feb"),
2 => utf16!("Mar"),
3 => utf16!("Apr"),
4 => utf16!("May"),
5 => utf16!("Jun"),
6 => utf16!("Jul"),
7 => utf16!("Aug"),
8 => utf16!("Sep"),
9 => utf16!("Oct"),
10 => utf16!("Nov"),
11 => utf16!("Dec"),
0 => js_str!("Jan"),
1 => js_str!("Feb"),
2 => js_str!("Mar"),
3 => js_str!("Apr"),
4 => js_str!("May"),
5 => js_str!("Jun"),
6 => js_str!("Jul"),
7 => js_str!("Aug"),
8 => js_str!("Sep"),
9 => js_str!("Oct"),
10 => js_str!("Nov"),
11 => js_str!("Dec"),
_ => unreachable!(),
};
// 7. Let day be ToZeroPaddedDecimalString(ℝ(DateFromTime(tv)), 2).
let day = pad_two(date_from_time(tv));
let mut binding = [0; 2];
let day = pad_two(date_from_time(tv), &mut binding);
// 8. Let yv be YearFromTime(tv).
let yv = year_from_time(tv);
// 9. If yv is +0𝔽 or yv > +0𝔽, let yearSign be the empty String; otherwise, let yearSign be "-".
let year_sign = if yv >= 0 { utf16!("") } else { utf16!("-") };
let year_sign = if yv >= 0 { js_str!("") } else { js_str!("-") };
// 10. Let paddedYear be ToZeroPaddedDecimalString(abs(ℝ(yv)), 4).
let yv = yv.unsigned_abs();
let padded_year = if yv >= 100_000 {
js_string!(&pad_six(yv))
let padded_year: JsString = if yv >= 100_000 {
pad_six(yv, &mut [0; 6]).into()
} else if yv >= 10000 {
js_string!(&pad_five(yv))
pad_five(yv, &mut [0; 5]).into()
} else {
js_string!(&pad_four(yv))
pad_four(yv, &mut [0; 4]).into()
};
// 11. Return the string-concatenation of
@ -1692,15 +1700,15 @@ impl Date {
// and TimeString(tv).
Ok(JsValue::from(js_string!(
weekday,
utf16!(","),
utf16!(" "),
&day,
utf16!(" "),
js_str!(","),
js_str!(" "),
day,
js_str!(" "),
month,
utf16!(" "),
js_str!(" "),
year_sign,
&padded_year,
utf16!(" "),
js_str!(" "),
&time_string(tv)
)))
}
@ -1755,12 +1763,10 @@ impl Date {
let try_first = match hint.as_string() {
// 3. If hint is "string" or "default", then
// a. Let tryFirst be string.
Some(string) if string == utf16!("string") || string == utf16!("default") => {
PreferredType::String
}
Some(string) if string == "string" || string == "default" => PreferredType::String,
// 4. Else if hint is "number", then
// a. Let tryFirst be number.
Some(number) if number == utf16!("number") => PreferredType::Number,
Some(number) if number == "number" => PreferredType::Number,
// 5. Else, throw a TypeError exception.
_ => {
return Err(JsNativeError::typ()

11
core/engine/src/builtins/date/tests.rs

@ -1,4 +1,5 @@
use crate::{js_string, run_test_actions, JsNativeErrorKind, TestAction};
use boa_macros::js_str;
use indoc::indoc;
use time::{macros::format_description, util::local_offset, OffsetDateTime};
@ -787,7 +788,7 @@ fn date_proto_set_utc_seconds() {
fn date_proto_to_date_string() {
run_test_actions([TestAction::assert_eq(
"new Date(2020, 6, 8, 9, 16, 15, 779).toDateString()",
js_string!("Wed Jul 08 2020"),
js_str!("Wed Jul 08 2020"),
)]);
}
@ -795,7 +796,7 @@ fn date_proto_to_date_string() {
fn date_proto_to_gmt_string() {
run_test_actions([TestAction::assert_eq(
"new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toGMTString()",
js_string!("Wed, 08 Jul 2020 09:16:15 GMT"),
js_str!("Wed, 08 Jul 2020 09:16:15 GMT"),
)]);
}
@ -803,7 +804,7 @@ fn date_proto_to_gmt_string() {
fn date_proto_to_iso_string() {
run_test_actions([TestAction::assert_eq(
"new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toISOString()",
js_string!("2020-07-08T09:16:15.779Z"),
js_str!("2020-07-08T09:16:15.779Z"),
)]);
}
@ -811,7 +812,7 @@ fn date_proto_to_iso_string() {
fn date_proto_to_json() {
run_test_actions([TestAction::assert_eq(
"new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toJSON()",
js_string!("2020-07-08T09:16:15.779Z"),
js_str!("2020-07-08T09:16:15.779Z"),
)]);
}
@ -849,7 +850,7 @@ fn date_proto_to_time_string() {
fn date_proto_to_utc_string() {
run_test_actions([TestAction::assert_eq(
"new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toUTCString()",
js_string!("Wed, 08 Jul 2020 09:16:15 GMT"),
js_str!("Wed, 08 Jul 2020 09:16:15 GMT"),
)]);
}

179
core/engine/src/builtins/date/utils.rs

@ -1,5 +1,5 @@
use crate::{context::HostHooks, js_string, value::IntegerOrInfinity, JsString};
use boa_macros::utf16;
use crate::{context::HostHooks, js_string, value::IntegerOrInfinity, JsStr, JsString};
use boa_macros::js_str;
use std::{iter::Peekable, str::Chars};
use time::{macros::format_description, OffsetDateTime, PrimitiveDateTime};
@ -498,13 +498,16 @@ pub(crate) fn time_clip(time: f64) -> f64 {
/// [spec]: https://tc39.es/ecma262/#sec-timestring
pub(super) fn time_string(tv: f64) -> JsString {
// 1. Let hour be ToZeroPaddedDecimalString(ℝ(HourFromTime(tv)), 2).
let hour = pad_two(hour_from_time(tv));
let mut binding = [0; 2];
let hour = pad_two(hour_from_time(tv), &mut binding);
// 2. Let minute be ToZeroPaddedDecimalString(ℝ(MinFromTime(tv)), 2).
let minute = pad_two(min_from_time(tv));
let mut binding = [0; 2];
let minute = pad_two(min_from_time(tv), &mut binding);
// 3. Let second be ToZeroPaddedDecimalString(ℝ(SecFromTime(tv)), 2).
let second = pad_two(sec_from_time(tv));
// 3. Let second be ToZeroPaddedDecimalStringbindingFromTime(tv)), 2).
let mut binding = [0; 2];
let second = pad_two(sec_from_time(tv), &mut binding);
// 4. Return the string-concatenation of
// hour,
@ -515,12 +518,12 @@ pub(super) fn time_string(tv: f64) -> JsString {
// the code unit 0x0020 (SPACE),
// and "GMT".
js_string!(
&hour,
utf16!(":"),
&minute,
utf16!(":"),
&second,
utf16!(" GMT")
hour,
js_str!(":"),
minute,
js_str!(":"),
second,
js_str!(" GMT")
)
}
@ -533,50 +536,51 @@ pub(super) fn time_string(tv: f64) -> JsString {
pub(super) fn date_string(tv: f64) -> JsString {
// 1. Let weekday be the Name of the entry in Table 63 with the Number WeekDay(tv).
let weekday = match week_day(tv) {
0 => utf16!("Sun"),
1 => utf16!("Mon"),
2 => utf16!("Tue"),
3 => utf16!("Wed"),
4 => utf16!("Thu"),
5 => utf16!("Fri"),
6 => utf16!("Sat"),
0 => js_str!("Sun"),
1 => js_str!("Mon"),
2 => js_str!("Tue"),
3 => js_str!("Wed"),
4 => js_str!("Thu"),
5 => js_str!("Fri"),
6 => js_str!("Sat"),
_ => unreachable!(),
};
// 2. Let month be the Name of the entry in Table 64 with the Number MonthFromTime(tv).
let month = match month_from_time(tv) {
0 => utf16!("Jan"),
1 => utf16!("Feb"),
2 => utf16!("Mar"),
3 => utf16!("Apr"),
4 => utf16!("May"),
5 => utf16!("Jun"),
6 => utf16!("Jul"),
7 => utf16!("Aug"),
8 => utf16!("Sep"),
9 => utf16!("Oct"),
10 => utf16!("Nov"),
11 => utf16!("Dec"),
0 => js_str!("Jan"),
1 => js_str!("Feb"),
2 => js_str!("Mar"),
3 => js_str!("Apr"),
4 => js_str!("May"),
5 => js_str!("Jun"),
6 => js_str!("Jul"),
7 => js_str!("Aug"),
8 => js_str!("Sep"),
9 => js_str!("Oct"),
10 => js_str!("Nov"),
11 => js_str!("Dec"),
_ => unreachable!(),
};
// 3. Let day be ToZeroPaddedDecimalString(ℝ(DateFromTime(tv)), 2).
let day = pad_two(date_from_time(tv));
let mut binding = [0; 2];
let day = pad_two(date_from_time(tv), &mut binding);
// 4. Let yv be YearFromTime(tv).
let yv = year_from_time(tv);
// 5. If yv is +0𝔽 or yv > +0𝔽, let yearSign be the empty String; otherwise, let yearSign be "-".
let year_sign = if yv >= 0 { utf16!("") } else { utf16!("-") };
let year_sign = if yv >= 0 { js_str!("") } else { js_str!("-") };
// 6. Let paddedYear be ToZeroPaddedDecimalString(abs(ℝ(yv)), 4).
let yv = yv.unsigned_abs();
let padded_year = if yv >= 100_000 {
js_string!(&pad_six(yv))
let padded_year: JsString = if yv >= 100_000 {
pad_six(yv, &mut [0; 6]).into()
} else if yv >= 10000 {
js_string!(&pad_five(yv))
pad_five(yv, &mut [0; 5]).into()
} else {
js_string!(&pad_four(yv))
pad_four(yv, &mut [0; 4]).into()
};
// 7. Return the string-concatenation of
@ -590,11 +594,11 @@ pub(super) fn date_string(tv: f64) -> JsString {
// and paddedYear.
js_string!(
weekday,
utf16!(" "),
js_str!(" "),
month,
utf16!(" "),
&day,
utf16!(" "),
js_str!(" "),
day,
js_str!(" "),
year_sign,
&padded_year
)
@ -620,26 +624,28 @@ pub(super) fn time_zone_string(t: f64, hooks: &dyn HostHooks) -> JsString {
let (offset_sign, abs_offset) = if offset >= 0.0 {
// a. Let offsetSign be "+".
// b. Let absOffset be offset.
(utf16!("+"), offset)
(js_str!("+"), offset)
}
// 6. Else,
else {
// a. Let offsetSign be "-".
// b. Let absOffset be -offset.
(utf16!("-"), -offset)
(js_str!("-"), -offset)
};
// 7. Let offsetMin be ToZeroPaddedDecimalString(ℝ(MinFromTime(absOffset)), 2).
let offset_min = pad_two(min_from_time(abs_offset));
let mut binding = [0; 2];
let offset_min = pad_two(min_from_time(abs_offset), &mut binding);
// 8. Let offsetHour be ToZeroPaddedDecimalString(ℝ(HourFromTime(absOffset)), 2).
let offset_hour = pad_two(hour_from_time(abs_offset));
let mut binding = [0; 2];
let offset_hour = pad_two(hour_from_time(abs_offset), &mut binding);
// 9. Let tzName be an implementation-defined string that is either the empty String or the
// string-concatenation of the code unit 0x0020 (SPACE), the code unit 0x0028 (LEFT PARENTHESIS),
// an implementation-defined timezone name, and the code unit 0x0029 (RIGHT PARENTHESIS).
// 10. Return the string-concatenation of offsetSign, offsetHour, offsetMin, and tzName.
js_string!(offset_sign, &offset_hour, &offset_min)
js_string!(offset_sign, offset_hour, offset_min)
}
/// Abstract operation `ToDateString ( tv )`
@ -651,7 +657,7 @@ pub(super) fn time_zone_string(t: f64, hooks: &dyn HostHooks) -> JsString {
pub(super) fn to_date_string_t(tv: f64, hooks: &dyn HostHooks) -> JsString {
// 1. If tv is NaN, return "Invalid Date".
if tv.is_nan() {
return JsString::from("Invalid Date");
return js_string!("Invalid Date");
}
// 2. Let t be LocalTime(tv).
@ -664,7 +670,7 @@ pub(super) fn to_date_string_t(tv: f64, hooks: &dyn HostHooks) -> JsString {
// and TimeZoneString(tv).
js_string!(
&date_string(t),
utf16!(" "),
js_str!(" "),
&time_string(t),
&time_zone_string(t, hooks)
)
@ -676,46 +682,61 @@ fn local_timezone_offset_seconds(t: f64, hooks: &dyn HostHooks) -> i32 {
hooks.local_timezone_offset_seconds(seconds)
}
pub(super) fn pad_two(t: u8) -> [u16; 2] {
if t < 10 {
[0x30, 0x30 + u16::from(t)]
pub(super) fn pad_two(t: u8, output: &mut [u8; 2]) -> JsStr<'_> {
*output = if t < 10 {
[b'0', b'0' + t]
} else {
[0x30 + (u16::from(t) / 10), 0x30 + (u16::from(t) % 10)]
}
[b'0' + (t / 10), b'0' + (t % 10)]
};
debug_assert!(output.is_ascii());
JsStr::latin1(output)
}
pub(super) fn pad_three(t: u16) -> [u16; 3] {
[0x30 + t / 100, 0x30 + ((t / 10) % 10), 0x30 + (t % 10)]
pub(super) fn pad_three(t: u16, output: &mut [u8; 3]) -> JsStr<'_> {
*output = [
b'0' + (t / 100) as u8,
b'0' + ((t / 10) % 10) as u8,
b'0' + (t % 10) as u8,
];
JsStr::latin1(output)
}
pub(super) fn pad_four(t: u32) -> [u16; 4] {
[
0x30 + (t / 1000) as u16,
0x30 + ((t / 100) % 10) as u16,
0x30 + ((t / 10) % 10) as u16,
0x30 + (t % 10) as u16,
]
pub(super) fn pad_four(t: u32, output: &mut [u8; 4]) -> JsStr<'_> {
*output = [
b'0' + (t / 1000) as u8,
b'0' + ((t / 100) % 10) as u8,
b'0' + ((t / 10) % 10) as u8,
b'0' + (t % 10) as u8,
];
JsStr::latin1(output)
}
pub(super) fn pad_five(t: u32) -> [u16; 5] {
[
0x30 + (t / 10_000) as u16,
0x30 + ((t / 1000) % 10) as u16,
0x30 + ((t / 100) % 10) as u16,
0x30 + ((t / 10) % 10) as u16,
0x30 + (t % 10) as u16,
]
pub(super) fn pad_five(t: u32, output: &mut [u8; 5]) -> JsStr<'_> {
*output = [
b'0' + (t / 10_000) as u8,
b'0' + ((t / 1000) % 10) as u8,
b'0' + ((t / 100) % 10) as u8,
b'0' + ((t / 10) % 10) as u8,
b'0' + (t % 10) as u8,
];
JsStr::latin1(output)
}
pub(super) fn pad_six(t: u32) -> [u16; 6] {
[
0x30 + (t / 100_000) as u16,
0x30 + ((t / 10_000) % 10) as u16,
0x30 + ((t / 1000) % 10) as u16,
0x30 + ((t / 100) % 10) as u16,
0x30 + ((t / 10) % 10) as u16,
0x30 + (t % 10) as u16,
]
pub(super) fn pad_six(t: u32, output: &mut [u8; 6]) -> JsStr<'_> {
*output = [
b'0' + (t / 100_000) as u8,
b'0' + ((t / 10_000) % 10) as u8,
b'0' + ((t / 1000) % 10) as u8,
b'0' + ((t / 100) % 10) as u8,
b'0' + ((t / 10) % 10) as u8,
b'0' + (t % 10) as u8,
];
JsStr::latin1(output)
}
/// Parse a date string according to the steps specified in [`Date.parse`][spec].

11
core/engine/src/builtins/error/aggregate.rs

@ -17,9 +17,10 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject},
property::{Attribute, PropertyDescriptorBuilder},
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_macros::js_str;
use boa_profiler::Profiler;
use super::{Error, ErrorObject};
@ -35,8 +36,8 @@ impl IntrinsicObject for AggregateError {
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.prototype(realm.intrinsics().constructors().error().constructor())
.inherits(Some(realm.intrinsics().constructors().error().prototype()))
.property(utf16!("name"), Self::NAME, attribute)
.property(utf16!("message"), js_string!(), attribute)
.property(js_str!("name"), Self::NAME, attribute)
.property(js_str!("message"), js_string!(), attribute)
.build();
}
@ -95,7 +96,7 @@ impl BuiltInConstructor for AggregateError {
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context);
o.create_non_enumerable_data_property_or_throw(js_str!("message"), msg, context);
}
// 4. Perform ? InstallErrorCause(O, options).
@ -112,7 +113,7 @@ impl BuiltInConstructor for AggregateError {
// [[Value]]: CreateArrayFromList(errorsList)
// }).
o.define_property_or_throw(
utf16!("errors"),
js_str!("errors"),
PropertyDescriptorBuilder::new()
.configurable(true)
.enumerable(false)

9
core/engine/src/builtins/error/eval.rs

@ -18,9 +18,10 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_macros::js_str;
use boa_profiler::Profiler;
use super::{Error, ErrorObject};
@ -37,8 +38,8 @@ impl IntrinsicObject for EvalError {
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.prototype(realm.intrinsics().constructors().error().constructor())
.inherits(Some(realm.intrinsics().constructors().error().prototype()))
.property(utf16!("name"), Self::NAME, attribute)
.property(utf16!("message"), js_string!(), attribute)
.property(js_str!("name"), Self::NAME, attribute)
.property(js_str!("message"), js_string!(), attribute)
.build();
}
@ -94,7 +95,7 @@ impl BuiltInConstructor for EvalError {
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context);
o.create_non_enumerable_data_property_or_throw(js_str!("message"), msg, context);
}
// 4. Perform ? InstallErrorCause(O, options).

21
core/engine/src/builtins/error/mod.rs

@ -18,10 +18,11 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsData, JsResult, JsString, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
pub(crate) mod aggregate;
@ -136,8 +137,8 @@ impl IntrinsicObject for Error {
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.property(utf16!("name"), Self::NAME, attribute)
.property(utf16!("message"), js_string!(), attribute)
.property(js_string!("name"), Self::NAME, attribute)
.property(js_string!("message"), js_string!(), attribute)
.method(Self::to_string, js_string!("toString"), 0)
.build();
}
@ -191,7 +192,7 @@ impl BuiltInConstructor for Error {
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context);
o.create_non_enumerable_data_property_or_throw(js_string!("message"), msg, context);
}
// 4. Perform ? InstallErrorCause(O, options).
@ -210,12 +211,12 @@ impl Error {
) -> JsResult<()> {
// 1. If Type(options) is Object and ? HasProperty(options, "cause") is true, then
if let Some(options) = options.as_object() {
if options.has_property(utf16!("cause"), context)? {
if options.has_property(js_str!("cause"), context)? {
// a. Let cause be ? Get(options, "cause").
let cause = options.get(utf16!("cause"), context)?;
let cause = options.get(js_str!("cause"), context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "cause", cause).
o.create_non_enumerable_data_property_or_throw(utf16!("cause"), cause, context);
o.create_non_enumerable_data_property_or_throw(js_str!("cause"), cause, context);
}
}
@ -246,7 +247,7 @@ impl Error {
.ok_or_else(|| JsNativeError::typ().with_message("'this' is not an Object"))?;
// 3. Let name be ? Get(O, "name").
let name = o.get(js_string!("name"), context)?;
let name = o.get(js_str!("name"), context)?;
// 4. If name is undefined, set name to "Error"; otherwise set name to ? ToString(name).
let name = if name.is_undefined() {
@ -256,7 +257,7 @@ impl Error {
};
// 5. Let msg be ? Get(O, "message").
let msg = o.get(js_string!("message"), context)?;
let msg = o.get(js_str!("message"), context)?;
// 6. If msg is undefined, set msg to the empty String; otherwise set msg to ? ToString(msg).
let msg = if msg.is_undefined() {
@ -277,6 +278,6 @@ impl Error {
// 9. Return the string-concatenation of name, the code unit 0x003A (COLON),
// the code unit 0x0020 (SPACE), and msg.
Ok(js_string!(&name, utf16!(": "), &msg).into())
Ok(js_string!(&name, js_str!(": "), &msg).into())
}
}

9
core/engine/src/builtins/error/range.rs

@ -16,9 +16,10 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_macros::js_str;
use boa_profiler::Profiler;
use super::{Error, ErrorObject};
@ -35,8 +36,8 @@ impl IntrinsicObject for RangeError {
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.prototype(realm.intrinsics().constructors().error().constructor())
.inherits(Some(realm.intrinsics().constructors().error().prototype()))
.property(utf16!("name"), Self::NAME, attribute)
.property(utf16!("message"), js_string!(), attribute)
.property(js_str!("name"), Self::NAME, attribute)
.property(js_str!("message"), js_string!(), attribute)
.build();
}
@ -92,7 +93,7 @@ impl BuiltInConstructor for RangeError {
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context);
o.create_non_enumerable_data_property_or_throw(js_str!("message"), msg, context);
}
// 4. Perform ? InstallErrorCause(O, options).

9
core/engine/src/builtins/error/reference.rs

@ -16,9 +16,10 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_macros::js_str;
use boa_profiler::Profiler;
use super::{Error, ErrorObject};
@ -34,8 +35,8 @@ impl IntrinsicObject for ReferenceError {
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.prototype(realm.intrinsics().constructors().error().constructor())
.inherits(Some(realm.intrinsics().constructors().error().prototype()))
.property(js_string!("name"), Self::NAME, attribute)
.property(js_string!("message"), js_string!(), attribute)
.property(js_str!("name"), Self::NAME, attribute)
.property(js_str!("message"), js_string!(), attribute)
.build();
}
@ -94,7 +95,7 @@ impl BuiltInConstructor for ReferenceError {
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context);
o.create_non_enumerable_data_property_or_throw(js_str!("message"), msg, context);
}
// 4. Perform ? InstallErrorCause(O, options).

9
core/engine/src/builtins/error/syntax.rs

@ -18,9 +18,10 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_macros::js_str;
use boa_profiler::Profiler;
use super::{Error, ErrorObject};
@ -37,8 +38,8 @@ impl IntrinsicObject for SyntaxError {
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.prototype(realm.intrinsics().constructors().error().constructor())
.inherits(Some(realm.intrinsics().constructors().error().prototype()))
.property(utf16!("name"), Self::NAME, attribute)
.property(utf16!("message"), js_string!(), attribute)
.property(js_str!("name"), Self::NAME, attribute)
.property(js_str!("message"), js_string!(), attribute)
.build();
}
@ -97,7 +98,7 @@ impl BuiltInConstructor for SyntaxError {
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context);
o.create_non_enumerable_data_property_or_throw(js_str!("message"), msg, context);
}
// 4. Perform ? InstallErrorCause(O, options).

58
core/engine/src/builtins/error/tests.rs

@ -1,42 +1,34 @@
use crate::{js_string, run_test_actions, TestAction};
use crate::{run_test_actions, TestAction};
use boa_macros::js_str;
use indoc::indoc;
#[test]
fn error_to_string() {
run_test_actions([
TestAction::assert_eq("(new Error('1')).toString()", js_string!("Error: 1")),
TestAction::assert_eq(
"(new RangeError('2')).toString()",
js_string!("RangeError: 2"),
),
TestAction::assert_eq("(new Error('1')).toString()", js_str!("Error: 1")),
TestAction::assert_eq("(new RangeError('2')).toString()", js_str!("RangeError: 2")),
TestAction::assert_eq(
"(new ReferenceError('3')).toString()",
js_string!("ReferenceError: 3"),
js_str!("ReferenceError: 3"),
),
TestAction::assert_eq(
"(new SyntaxError('4')).toString()",
js_string!("SyntaxError: 4"),
),
TestAction::assert_eq(
"(new TypeError('5')).toString()",
js_string!("TypeError: 5"),
),
TestAction::assert_eq(
"(new EvalError('6')).toString()",
js_string!("EvalError: 6"),
js_str!("SyntaxError: 4"),
),
TestAction::assert_eq("(new URIError('7')).toString()", js_string!("URIError: 7")),
TestAction::assert_eq("(new TypeError('5')).toString()", js_str!("TypeError: 5")),
TestAction::assert_eq("(new EvalError('6')).toString()", js_str!("EvalError: 6")),
TestAction::assert_eq("(new URIError('7')).toString()", js_str!("URIError: 7")),
// no message
TestAction::assert_eq("(new Error()).toString()", js_string!("Error")),
TestAction::assert_eq("(new RangeError()).toString()", js_string!("RangeError")),
TestAction::assert_eq("(new Error()).toString()", js_str!("Error")),
TestAction::assert_eq("(new RangeError()).toString()", js_str!("RangeError")),
TestAction::assert_eq(
"(new ReferenceError()).toString()",
js_string!("ReferenceError"),
js_str!("ReferenceError"),
),
TestAction::assert_eq("(new SyntaxError()).toString()", js_string!("SyntaxError")),
TestAction::assert_eq("(new TypeError()).toString()", js_string!("TypeError")),
TestAction::assert_eq("(new EvalError()).toString()", js_string!("EvalError")),
TestAction::assert_eq("(new URIError()).toString()", js_string!("URIError")),
TestAction::assert_eq("(new SyntaxError()).toString()", js_str!("SyntaxError")),
TestAction::assert_eq("(new TypeError()).toString()", js_str!("TypeError")),
TestAction::assert_eq("(new EvalError()).toString()", js_str!("EvalError")),
TestAction::assert_eq("(new URIError()).toString()", js_str!("URIError")),
// no name
TestAction::assert_eq(
indoc! {r#"
@ -44,7 +36,7 @@ fn error_to_string() {
message.name = '';
message.toString()
"#},
js_string!("message"),
js_str!("message"),
),
]);
}
@ -52,14 +44,14 @@ fn error_to_string() {
#[test]
fn error_names() {
run_test_actions([
TestAction::assert_eq("Error.name", js_string!("Error")),
TestAction::assert_eq("EvalError.name", js_string!("EvalError")),
TestAction::assert_eq("RangeError.name", js_string!("RangeError")),
TestAction::assert_eq("ReferenceError.name", js_string!("ReferenceError")),
TestAction::assert_eq("SyntaxError.name", js_string!("SyntaxError")),
TestAction::assert_eq("URIError.name", js_string!("URIError")),
TestAction::assert_eq("TypeError.name", js_string!("TypeError")),
TestAction::assert_eq("AggregateError.name", js_string!("AggregateError")),
TestAction::assert_eq("Error.name", js_str!("Error")),
TestAction::assert_eq("EvalError.name", js_str!("EvalError")),
TestAction::assert_eq("RangeError.name", js_str!("RangeError")),
TestAction::assert_eq("ReferenceError.name", js_str!("ReferenceError")),
TestAction::assert_eq("SyntaxError.name", js_str!("SyntaxError")),
TestAction::assert_eq("URIError.name", js_str!("URIError")),
TestAction::assert_eq("TypeError.name", js_str!("TypeError")),
TestAction::assert_eq("AggregateError.name", js_str!("AggregateError")),
]);
}

11
core/engine/src/builtins/error/type.rs

@ -24,9 +24,10 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsResult, JsString, JsValue, NativeFunction,
};
use boa_macros::js_str;
use boa_profiler::Profiler;
use super::{Error, ErrorObject};
@ -43,8 +44,8 @@ impl IntrinsicObject for TypeError {
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.prototype(realm.intrinsics().constructors().error().constructor())
.inherits(Some(realm.intrinsics().constructors().error().prototype()))
.property(utf16!("name"), Self::NAME, attribute)
.property(utf16!("message"), js_string!(), attribute)
.property(js_str!("name"), Self::NAME, attribute)
.property(js_str!("message"), js_string!(), attribute)
.build();
}
@ -100,7 +101,7 @@ impl BuiltInConstructor for TypeError {
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context);
o.create_non_enumerable_data_property_or_throw(js_str!("message"), msg, context);
}
// 4. Perform ? InstallErrorCause(O, options).
@ -119,7 +120,7 @@ impl IntrinsicObject for ThrowTypeError {
let obj = BuiltInBuilder::with_intrinsic::<Self>(realm)
.prototype(realm.intrinsics().constructors().function().prototype())
.static_property(StaticJsStrings::LENGTH, 0, Attribute::empty())
.static_property(utf16!("name"), js_string!(), Attribute::empty())
.static_property(js_str!("name"), js_string!(), Attribute::empty())
.build();
let mut obj = obj.borrow_mut();

9
core/engine/src/builtins/error/uri.rs

@ -17,9 +17,10 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_macros::js_str;
use boa_profiler::Profiler;
use super::{Error, ErrorObject};
@ -36,8 +37,8 @@ impl IntrinsicObject for UriError {
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.prototype(realm.intrinsics().constructors().error().constructor())
.inherits(Some(realm.intrinsics().constructors().error().prototype()))
.property(utf16!("name"), Self::NAME, attribute)
.property(utf16!("message"), js_string!(), attribute)
.property(js_str!("name"), Self::NAME, attribute)
.property(js_str!("message"), js_string!(), attribute)
.build();
}
@ -93,7 +94,7 @@ impl BuiltInConstructor for UriError {
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context);
o.create_non_enumerable_data_property_or_throw(js_str!("message"), msg, context);
}
// 4. Perform ? InstallErrorCause(O, options).

8
core/engine/src/builtins/escape/mod.rs

@ -59,7 +59,7 @@ fn escape(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsVa
// 5. Let k be 0.
// 6. Repeat, while k < len,
// a. Let C be the code unit at index k within string.
for &cp in &*string {
for cp in &string {
// b. If unescapedSet contains C, then
if is_unescaped(cp) {
// i. Let S be C.
@ -86,7 +86,7 @@ fn escape(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsVa
}
// 7. Return R.
Ok(js_string!(vec).into())
Ok(js_string!(&vec[..]).into())
}
/// The `unescape` function
@ -124,7 +124,7 @@ fn unescape(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<Js
// 3. Let R be the empty String.
let mut vec = Vec::with_capacity(string.len());
let mut codepoints = <PeekableN<_, 6>>::new(string.iter().copied());
let mut codepoints = <PeekableN<_, 6>>::new(string.iter());
// 2. Let len be the length of string.
// 4. Let k be 0.
@ -192,7 +192,7 @@ fn unescape(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<Js
vec.push(unescaped_cp);
}
// 6. Return R.
Ok(js_string!(vec).into())
Ok(js_string!(&vec[..]).into())
}
/// An iterator that can peek `N` items.

3
core/engine/src/builtins/eval/mod.rs

@ -122,7 +122,8 @@ impl Eval {
// b. If script is a List of errors, throw a SyntaxError exception.
// c. If script Contains ScriptBody is false, return undefined.
// d. Let body be the ScriptBody of script.
let mut parser = Parser::new(Source::from_utf16(x));
let x = x.to_vec();
let mut parser = Parser::new(Source::from_utf16(&x));
parser.set_identifier(context.next_parser_identifier());
if strict {
parser.set_strict();

51
core/engine/src/builtins/function/mod.rs

@ -21,18 +21,20 @@ use crate::{
error::JsNativeError,
js_string,
native_function::NativeFunctionObject,
object::{internal_methods::get_prototype_from_constructor, JsObject},
object::{
internal_methods::{CallValue, InternalObjectMethods, ORDINARY_INTERNAL_METHODS},
JsData, JsFunction, PrivateElement, PrivateName,
internal_methods::{
get_prototype_from_constructor, CallValue, InternalObjectMethods,
ORDINARY_INTERNAL_METHODS,
},
JsData, JsFunction, JsObject, PrivateElement, PrivateName,
},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
symbol::JsSymbol,
value::IntegerOrInfinity,
vm::{ActiveRunnable, CallFrame, CallFrameFlags, CodeBlock},
Context, JsArgs, JsResult, JsString, JsValue,
Context, JsArgs, JsResult, JsStr, JsString, JsValue,
};
use boa_ast::{
function::{FormalParameterList, FunctionBody},
@ -43,6 +45,7 @@ use boa_ast::{
};
use boa_gc::{self, custom_trace, Finalize, Gc, Trace};
use boa_interner::Sym;
use boa_macros::js_str;
use boa_parser::{Parser, Source};
use boa_profiler::Profiler;
use thin_vec::ThinVec;
@ -312,13 +315,13 @@ impl IntrinsicObject for BuiltInFunctionObject {
.method(Self::to_string, js_string!("toString"), 0)
.property(JsSymbol::has_instance(), has_instance, Attribute::default())
.accessor(
utf16!("caller"),
js_string!("caller"),
Some(throw_type_error.clone()),
Some(throw_type_error.clone()),
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("arguments"),
js_string!("arguments"),
Some(throw_type_error.clone()),
Some(throw_type_error),
Attribute::CONFIGURABLE,
@ -473,7 +476,15 @@ impl BuiltInFunctionObject {
// i. Let nextArgString be parameterStrings[k].
// ii. Set P to the string-concatenation of P, "," (a comma), and nextArgString.
// iii. Set k to k + 1.
let parameters = param_list.join(utf16!(","));
// TODO: Replace with standard `Iterator::intersperse` iterator method when it's stabilized.
// See: <https://github.com/rust-lang/rust/issues/79524>
let parameters = itertools::Itertools::intersperse(
param_list.iter().map(JsString::iter),
js_str!(",").iter(),
)
.flatten()
.collect::<Vec<_>>();
let mut parser = Parser::new(Source::from_utf16(&parameters));
parser.set_identifier(context.next_parser_identifier());
@ -518,7 +529,7 @@ impl BuiltInFunctionObject {
// 14. Let bodyParseString be the string-concatenation of 0x000A (LINE FEED), bodyString, and 0x000A (LINE FEED).
let mut body_parse = Vec::with_capacity(body.len());
body_parse.push(u16::from(b'\n'));
body_parse.extend_from_slice(&body);
body_parse.extend(body.iter());
body_parse.push(u16::from(b'\n'));
// 19. Let body be ParseText(StringToCodePoints(bodyParseString), bodySym).
@ -743,7 +754,7 @@ impl BuiltInFunctionObject {
.expect("defining the `length` property for a new object should not fail");
// 8. Let targetName be ? Get(Target, "name").
let target_name = target.get(utf16!("name"), context)?;
let target_name = target.get(js_str!("name"), context)?;
// 9. If Type(targetName) is not String, set targetName to the empty String.
let target_name = target_name
@ -751,7 +762,7 @@ impl BuiltInFunctionObject {
.map_or_else(JsString::default, Clone::clone);
// 10. Perform SetFunctionName(F, targetName, "bound").
set_function_name(&f, &target_name.into(), Some(js_string!("bound")), context);
set_function_name(&f, &target_name.into(), Some(js_str!("bound")), context);
// 11. Return F.
Ok(f.into())
@ -816,7 +827,7 @@ impl BuiltInFunctionObject {
let name = {
// Is there a case here where if there is no name field on a value
// name should default to None? Do all functions have names set?
let value = object.get(utf16!("name"), &mut *context)?;
let value = object.get(js_str!("name"), &mut *context)?;
if value.is_null_or_undefined() {
js_string!()
} else {
@ -824,10 +835,10 @@ impl BuiltInFunctionObject {
}
};
return Ok(
js_string!(utf16!("function "), &name, utf16!("() { [native code] }")).into(),
js_string!(js_str!("function "), &name, js_str!("() { [native code] }")).into(),
);
} else if object_borrow.is::<Proxy>() || object_borrow.is::<BoundFunction>() {
return Ok(js_string!(utf16!("function () { [native code] }")).into());
return Ok(js_string!("function () { [native code] }").into());
}
let function = object_borrow
@ -837,9 +848,9 @@ impl BuiltInFunctionObject {
let code = function.codeblock();
Ok(js_string!(
utf16!("function "),
js_str!("function "),
code.name(),
utf16!("() { [native code] }")
js_str!("() { [native code] }")
)
.into())
}
@ -871,7 +882,7 @@ impl BuiltInFunctionObject {
pub(crate) fn set_function_name(
function: &JsObject,
name: &PropertyKey,
prefix: Option<JsString>,
prefix: Option<JsStr<'_>>,
context: &mut Context,
) {
// 1. Assert: F is an extensible object that does not have a "name" own property.
@ -883,7 +894,7 @@ pub(crate) fn set_function_name(
// c. Else, set name to the string-concatenation of "[", description, and "]".
sym.description().map_or_else(
|| js_string!(),
|desc| js_string!(utf16!("["), &desc, utf16!("]")),
|desc| js_string!(js_str!("["), &desc, js_str!("]")),
)
}
PropertyKey::String(string) => string.clone(),
@ -900,7 +911,7 @@ pub(crate) fn set_function_name(
// 5. If prefix is present, then
if let Some(prefix) = prefix {
name = js_string!(&prefix, utf16!(" "), &name);
name = js_string!(prefix, js_str!(" "), &name);
// b. If F has an [[InitialName]] internal slot, then
// i. Optionally, set F.[[InitialName]] to name.
// todo: implement [[InitialName]] for builtins
@ -910,7 +921,7 @@ pub(crate) fn set_function_name(
// [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }).
function
.define_property_or_throw(
utf16!("name"),
js_str!("name"),
PropertyDescriptor::builder()
.value(name)
.writable(false)

9
core/engine/src/builtins/function/tests.rs

@ -6,6 +6,7 @@ use crate::{
property::{Attribute, PropertyDescriptor},
run_test_actions, JsNativeErrorKind, JsValue, TestAction,
};
use boa_macros::js_str;
use indoc::indoc;
#[allow(clippy::float_cmp)]
@ -69,7 +70,7 @@ fn function_prototype() {
fn function_prototype_call() {
run_test_actions([TestAction::assert_eq(
"Object.prototype.toString.call(new Error())",
js_string!("[object Error]"),
js_str!("[object Error]"),
)]);
}
@ -168,13 +169,13 @@ fn closure_capture_clone() {
(string, object),
),
)
.name("closure")
.name(js_str!("closure"))
.build();
ctx.register_global_property(js_string!("closure"), func, Attribute::default())
ctx.register_global_property(js_str!("closure"), func, Attribute::default())
.unwrap();
}),
TestAction::assert_eq("closure()", js_string!("Hello world!")),
TestAction::assert_eq("closure()", js_str!("Hello world!")),
]);
}

62
core/engine/src/builtins/intl/collator/mod.rs

@ -1,4 +1,5 @@
use boa_gc::{custom_trace, Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
use icu_collator::{
provider::CollationMetadataV1Marker, AlternateHandling, CaseFirst, Collator as NativeCollator,
@ -28,7 +29,7 @@ use crate::{
},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
symbol::JsSymbol,
Context, JsArgs, JsData, JsNativeError, JsResult, JsString, JsValue,
};
@ -241,28 +242,28 @@ impl BuiltInConstructor for Collator {
// a. Let localeData be %Collator%.[[SortLocaleData]].
// 6. Else,
// a. Let localeData be %Collator%.[[SearchLocaleData]].
let usage = get_option(&options, utf16!("usage"), context)?.unwrap_or_default();
let usage = get_option(&options, js_str!("usage"), context)?.unwrap_or_default();
// 7. Let opt be a new Record.
// 8. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
// 9. Set opt.[[localeMatcher]] to matcher.
let matcher = get_option(&options, utf16!("localeMatcher"), context)?.unwrap_or_default();
let matcher = get_option(&options, js_str!("localeMatcher"), context)?.unwrap_or_default();
// 10. Let collation be ? GetOption(options, "collation", string, empty, undefined).
// 11. If collation is not undefined, then
// a. If collation does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
// 12. Set opt.[[co]] to collation.
let collation = get_option(&options, utf16!("collation"), context)?;
let collation = get_option(&options, js_str!("collation"), context)?;
// 13. Let numeric be ? GetOption(options, "numeric", boolean, empty, undefined).
// 14. If numeric is not undefined, then
// a. Let numeric be ! ToString(numeric).
// 15. Set opt.[[kn]] to numeric.
let numeric = get_option(&options, utf16!("numeric"), context)?;
let numeric = get_option(&options, js_str!("numeric"), context)?;
// 16. Let caseFirst be ? GetOption(options, "caseFirst", string, « "upper", "lower", "false" », undefined).
// 17. Set opt.[[kf]] to caseFirst.
let case_first = get_option(&options, utf16!("caseFirst"), context)?;
let case_first = get_option(&options, js_str!("caseFirst"), context)?;
let mut intl_options = IntlOptions {
matcher,
@ -314,7 +315,7 @@ impl BuiltInConstructor for Collator {
// 26. Let sensitivity be ? GetOption(options, "sensitivity", string, « "base", "accent", "case", "variant" », undefined).
// 28. Set collator.[[Sensitivity]] to sensitivity.
let sensitivity = get_option(&options, utf16!("sensitivity"), context)?
let sensitivity = get_option(&options, js_str!("sensitivity"), context)?
// 27. If sensitivity is undefined, then
// a. If usage is "sort", then
// i. Let sensitivity be "variant".
@ -327,7 +328,7 @@ impl BuiltInConstructor for Collator {
// 29. Let ignorePunctuation be ? GetOption(options, "ignorePunctuation", boolean, empty, false).
// 30. Set collator.[[IgnorePunctuation]] to ignorePunctuation.
let ignore_punctuation: bool =
get_option(&options, utf16!("ignorePunctuation"), context)?.unwrap_or_default();
get_option(&options, js_str!("ignorePunctuation"), context)?.unwrap_or_default();
let (strength, case_level) = sensitivity.map(Sensitivity::to_collator_options).unzip();
@ -442,13 +443,22 @@ impl Collator {
// 3. If x is not provided, let x be undefined.
// 5. Let X be ? ToString(x).
let x = args.get_or_undefined(0).to_string(context)?;
let x = args
.get_or_undefined(0)
.to_string(context)?
.iter()
.collect::<Vec<_>>();
// 4. If y is not provided, let y be undefined.
// 6. Let Y be ? ToString(y).
let y = args.get_or_undefined(1).to_string(context)?;
let y = args
.get_or_undefined(1)
.to_string(context)?
.iter()
.collect::<Vec<_>>();
// 7. Return CompareStrings(collator, X, Y).
let result = collator.collator.compare_utf16(&x, &y) as i32;
Ok(result.into())
@ -507,58 +517,58 @@ impl Collator {
// 5. Return options.
options
.create_data_property_or_throw(
utf16!("locale"),
js_str!("locale"),
js_string!(collator.locale.to_string()),
context,
)
.expect("operation must not fail per the spec");
options
.create_data_property_or_throw(
utf16!("usage"),
js_str!("usage"),
match collator.usage {
Usage::Search => js_string!("search"),
Usage::Sort => js_string!("sort"),
Usage::Search => js_str!("search"),
Usage::Sort => js_str!("sort"),
},
context,
)
.expect("operation must not fail per the spec");
options
.create_data_property_or_throw(
utf16!("sensitivity"),
js_str!("sensitivity"),
match collator.sensitivity {
Sensitivity::Base => js_string!("base"),
Sensitivity::Accent => js_string!("accent"),
Sensitivity::Case => js_string!("case"),
Sensitivity::Variant => js_string!("variant"),
Sensitivity::Base => js_str!("base"),
Sensitivity::Accent => js_str!("accent"),
Sensitivity::Case => js_str!("case"),
Sensitivity::Variant => js_str!("variant"),
},
context,
)
.expect("operation must not fail per the spec");
options
.create_data_property_or_throw(
js_string!("ignorePunctuation"),
js_str!("ignorePunctuation"),
collator.ignore_punctuation,
context,
)
.expect("operation must not fail per the spec");
options
.create_data_property_or_throw(
js_string!("collation"),
js_str!("collation"),
js_string!(collator.collation.to_string()),
context,
)
.expect("operation must not fail per the spec");
options
.create_data_property_or_throw(utf16!("numeric"), collator.numeric, context)
.create_data_property_or_throw(js_str!("numeric"), collator.numeric, context)
.expect("operation must not fail per the spec");
if let Some(kf) = collator.case_first {
options
.create_data_property_or_throw(
js_string!("caseFirst"),
js_str!("caseFirst"),
match kf {
CaseFirst::Off => js_string!("false"),
CaseFirst::LowerFirst => js_string!("lower"),
CaseFirst::UpperFirst => js_string!("upper"),
CaseFirst::Off => js_str!("false"),
CaseFirst::LowerFirst => js_str!("lower"),
CaseFirst::UpperFirst => js_str!("upper"),
_ => unreachable!(),
},
context,

33
core/engine/src/builtins/intl/date_time_format.rs

@ -17,11 +17,12 @@ use crate::{
js_string,
object::{internal_methods::get_prototype_from_constructor, JsObject},
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsData, JsResult, JsString, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
use icu_datetime::options::preferences::HourCycle;
@ -205,10 +206,10 @@ pub(crate) fn to_date_time_options(
if [DateTimeReqs::Date, DateTimeReqs::AnyAll].contains(required) {
// a. For each property name prop of « "weekday", "year", "month", "day" », do
for property in [
utf16!("weekday"),
utf16!("year"),
utf16!("month"),
utf16!("day"),
js_str!("weekday"),
js_str!("year"),
js_str!("month"),
js_str!("day"),
] {
// i. Let value be ? Get(options, prop).
let value = options.get(property, context)?;
@ -225,11 +226,11 @@ pub(crate) fn to_date_time_options(
// a. For each property name prop of « "dayPeriod", "hour", "minute", "second",
// "fractionalSecondDigits" », do
for property in [
utf16!("dayPeriod"),
utf16!("hour"),
utf16!("minute"),
utf16!("second"),
utf16!("fractionalSecondDigits"),
js_str!("dayPeriod"),
js_str!("hour"),
js_str!("minute"),
js_str!("second"),
js_str!("fractionalSecondDigits"),
] {
// i. Let value be ? Get(options, prop).
let value = options.get(property, context)?;
@ -242,10 +243,10 @@ pub(crate) fn to_date_time_options(
}
// 6. Let dateStyle be ? Get(options, "dateStyle").
let date_style = options.get(utf16!("dateStyle"), context)?;
let date_style = options.get(js_str!("dateStyle"), context)?;
// 7. Let timeStyle be ? Get(options, "timeStyle").
let time_style = options.get(utf16!("timeStyle"), context)?;
let time_style = options.get(js_str!("timeStyle"), context)?;
// 8. If dateStyle is not undefined or timeStyle is not undefined, let needDefaults be false.
if !date_style.is_undefined() || !time_style.is_undefined() {
@ -271,18 +272,18 @@ pub(crate) fn to_date_time_options(
// 11. If needDefaults is true and defaults is either "date" or "all", then
if need_defaults && [DateTimeReqs::Date, DateTimeReqs::AnyAll].contains(defaults) {
// a. For each property name prop of « "year", "month", "day" », do
for property in [utf16!("year"), utf16!("month"), utf16!("day")] {
for property in [js_str!("year"), js_str!("month"), js_str!("day")] {
// i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
options.create_data_property_or_throw(property, js_string!("numeric"), context)?;
options.create_data_property_or_throw(property, js_str!("numeric"), context)?;
}
}
// 12. If needDefaults is true and defaults is either "time" or "all", then
if need_defaults && [DateTimeReqs::Time, DateTimeReqs::AnyAll].contains(defaults) {
// a. For each property name prop of « "hour", "minute", "second" », do
for property in [utf16!("hour"), utf16!("minute"), utf16!("second")] {
for property in [js_str!("hour"), js_str!("minute"), js_str!("second")] {
// i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
options.create_data_property_or_throw(property, js_string!("numeric"), context)?;
options.create_data_property_or_throw(property, js_str!("numeric"), context)?;
}
}

31
core/engine/src/builtins/intl/list_format/mod.rs

@ -1,6 +1,7 @@
use std::fmt::Write;
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
use icu_list::{provider::AndListV1Marker, ListFormatter, ListLength};
use icu_locid::Locale;
@ -16,7 +17,7 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
symbol::JsSymbol,
Context, JsArgs, JsData, JsNativeError, JsResult, JsString, JsValue,
};
@ -114,7 +115,7 @@ impl BuiltInConstructor for ListFormat {
// 5. Let opt be a new Record.
// 6. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
let matcher = get_option(&options, utf16!("localeMatcher"), context)?.unwrap_or_default();
let matcher = get_option(&options, js_str!("localeMatcher"), context)?.unwrap_or_default();
// 7. Set opt.[[localeMatcher]] to matcher.
// 8. Let localeData be %ListFormat%.[[LocaleData]].
@ -131,11 +132,11 @@ impl BuiltInConstructor for ListFormat {
// 11. Let type be ? GetOption(options, "type", string, « "conjunction", "disjunction", "unit" », "conjunction").
// 12. Set listFormat.[[Type]] to type.
let typ = get_option(&options, utf16!("type"), context)?.unwrap_or_default();
let typ = get_option(&options, js_str!("type"), context)?.unwrap_or_default();
// 13. Let style be ? GetOption(options, "style", string, « "long", "short", "narrow" », "long").
// 14. Set listFormat.[[Style]] to style.
let style = get_option(&options, utf16!("style"), context)?.unwrap_or(ListLength::Wide);
let style = get_option(&options, js_str!("style"), context)?.unwrap_or(ListLength::Wide);
// 15. Let dataLocale be r.[[dataLocale]].
// 16. Let dataLocaleData be localeData.[[<dataLocale>]].
@ -380,11 +381,11 @@ impl ListFormat {
.create(OrdinaryObject, vec![]);
// b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
o.create_data_property_or_throw(utf16!("type"), js_string!(part.typ()), context)
o.create_data_property_or_throw(js_str!("type"), js_string!(part.typ()), context)
.expect("operation must not fail per the spec");
// c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
o.create_data_property_or_throw(utf16!("value"), js_string!(part.value()), context)
o.create_data_property_or_throw(js_str!("value"), js_string!(part.value()), context)
.expect("operation must not fail per the spec");
// d. Perform ! CreateDataPropertyOrThrow(result, ! ToString(n), O).
@ -435,29 +436,29 @@ impl ListFormat {
// d. Perform ! CreateDataPropertyOrThrow(options, p, v).
options
.create_data_property_or_throw(
utf16!("locale"),
js_str!("locale"),
js_string!(lf.locale.to_string()),
context,
)
.expect("operation must not fail per the spec");
options
.create_data_property_or_throw(
js_string!("type"),
js_str!("type"),
match lf.typ {
ListFormatType::Conjunction => js_string!("conjunction"),
ListFormatType::Disjunction => js_string!("disjunction"),
ListFormatType::Unit => js_string!("unit"),
ListFormatType::Conjunction => js_str!("conjunction"),
ListFormatType::Disjunction => js_str!("disjunction"),
ListFormatType::Unit => js_str!("unit"),
},
context,
)
.expect("operation must not fail per the spec");
options
.create_data_property_or_throw(
js_string!("style"),
js_str!("style"),
match lf.style {
ListLength::Wide => js_string!("long"),
ListLength::Short => js_string!("short"),
ListLength::Narrow => js_string!("narrow"),
ListLength::Wide => js_str!("long"),
ListLength::Short => js_str!("short"),
ListLength::Narrow => js_str!("narrow"),
_ => unreachable!(),
},
context,

25
core/engine/src/builtins/intl/locale/mod.rs

@ -1,8 +1,5 @@
use crate::{
builtins::options::get_option,
realm::Realm,
string::{common::StaticJsStrings, utf16},
};
use crate::{builtins::options::get_option, realm::Realm, string::common::StaticJsStrings};
use boa_macros::js_str;
use boa_profiler::Profiler;
use icu_collator::CaseFirst;
use icu_datetime::options::preferences::HourCycle;
@ -236,17 +233,17 @@ impl BuiltInConstructor for Locale {
// 4. Let language be ? GetOption(options, "language", string, empty, undefined).
// 5. If language is not undefined, then
// a. If language does not match the unicode_language_subtag production, throw a RangeError exception.
let language = get_option(options, utf16!("language"), context)?;
let language = get_option(options, js_str!("language"), context)?;
// 6. Let script be ? GetOption(options, "script", string, empty, undefined).
// 7. If script is not undefined, then
// a. If script does not match the unicode_script_subtag production, throw a RangeError exception.
let script = get_option(options, utf16!("script"), context)?;
let script = get_option(options, js_str!("script"), context)?;
// 8. Let region be ? GetOption(options, "region", string, empty, undefined).
// 9. If region is not undefined, then
// a. If region does not match the unicode_region_subtag production, throw a RangeError exception.
let region = get_option(options, utf16!("region"), context)?;
let region = get_option(options, js_str!("region"), context)?;
// 10. Set tag to ! CanonicalizeUnicodeLocaleId(tag).
context
@ -294,17 +291,17 @@ impl BuiltInConstructor for Locale {
// 14. If calendar is not undefined, then
// 15. Set opt.[[ca]] to calendar.
// a. If calendar does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
let ca = get_option(options, utf16!("calendar"), context)?;
let ca = get_option(options, js_str!("calendar"), context)?;
// 16. Let collation be ? GetOption(options, "collation", string, empty, undefined).
// 17. If collation is not undefined, then
// 18. Set opt.[[co]] to collation.
// a. If collation does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
let co = get_option(options, utf16!("collation"), context)?;
let co = get_option(options, js_str!("collation"), context)?;
// 19. Let hc be ? GetOption(options, "hourCycle", string, « "h11", "h12", "h23", "h24" », undefined).
// 20. Set opt.[[hc]] to hc.
let hc = get_option(options, utf16!("hourCycle"), context)?.map(|hc| match hc {
let hc = get_option(options, js_str!("hourCycle"), context)?.map(|hc| match hc {
HourCycle::H24 => value!("h24"),
HourCycle::H23 => value!("h23"),
HourCycle::H12 => value!("h12"),
@ -313,7 +310,7 @@ impl BuiltInConstructor for Locale {
// 21. Let kf be ? GetOption(options, "caseFirst", string, « "upper", "lower", "false" », undefined).
// 22. Set opt.[[kf]] to kf.
let kf = get_option(options, utf16!("caseFirst"), context)?.map(|kf| match kf {
let kf = get_option(options, js_str!("caseFirst"), context)?.map(|kf| match kf {
CaseFirst::UpperFirst => value!("upper"),
CaseFirst::LowerFirst => value!("lower"),
CaseFirst::Off => value!("false"),
@ -323,7 +320,7 @@ impl BuiltInConstructor for Locale {
// 23. Let kn be ? GetOption(options, "numeric", boolean, empty, undefined).
// 24. If kn is not undefined, set kn to ! ToString(kn).
// 25. Set opt.[[kn]] to kn.
let kn = get_option(options, utf16!("numeric"), context)?.map(|b| {
let kn = get_option(options, js_str!("numeric"), context)?.map(|b| {
if b {
value!("true")
} else {
@ -335,7 +332,7 @@ impl BuiltInConstructor for Locale {
// 27. If numberingSystem is not undefined, then
// 28. Set opt.[[nu]] to numberingSystem.
// a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
let nu = get_option(options, utf16!("numberingSystem"), context)?;
let nu = get_option(options, js_str!("numberingSystem"), context)?;
// 29. Let r be ! ApplyUnicodeExtensionToTag(tag, opt, relevantExtensionKeys).
// 30. Set locale.[[Locale]] to r.[[locale]].

4
core/engine/src/builtins/intl/locale/utils.rs

@ -10,10 +10,10 @@ use crate::{
context::icu::IntlProvider,
js_string,
object::JsObject,
string::utf16,
Context, JsNativeError, JsResult, JsValue,
};
use boa_macros::js_str;
use icu_collator::provider::CollationMetadataV1Marker;
use icu_locid::{
extensions::unicode::{Key, Value},
@ -551,7 +551,7 @@ where
let options = coerce_options_to_object(options, context)?;
// 2. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
let matcher = get_option(&options, utf16!("localeMatcher"), context)?.unwrap_or_default();
let matcher = get_option(&options, js_str!("localeMatcher"), context)?.unwrap_or_default();
let elements = match matcher {
// 4. Else,

14
core/engine/src/builtins/intl/number_format/mod.rs

@ -1,7 +1,7 @@
use std::borrow::Cow;
use boa_gc::{Finalize, Trace};
use boa_macros::utf16;
use boa_macros::js_str;
use boa_profiler::Profiler;
use fixed_decimal::{FixedDecimal, FloatPrecision, SignDisplay};
use icu_decimal::{
@ -221,13 +221,13 @@ impl BuiltInConstructor for NumberFormat {
// 4. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
// 5. Set opt.[[localeMatcher]] to matcher.
let matcher = get_option(&options, utf16!("localeMatcher"), context)?.unwrap_or_default();
let matcher = get_option(&options, js_str!("localeMatcher"), context)?.unwrap_or_default();
// 6. Let numberingSystem be ? GetOption(options, "numberingSystem", string, empty, undefined).
// 7. If numberingSystem is not undefined, then
// a. If numberingSystem cannot be matched by the type Unicode locale nonterminal, throw a RangeError exception.
// 8. Set opt.[[nu]] to numberingSystem.
let numbering_system = get_option(&options, utf16!("numberingSystem"), context)?;
let numbering_system = get_option(&options, js_str!("numberingSystem"), context)?;
let mut intl_options = IntlOptions {
matcher,
@ -277,7 +277,7 @@ impl BuiltInConstructor for NumberFormat {
// 18. Let notation be ? GetOption(options, "notation", string, « "standard", "scientific", "engineering", "compact" », "standard").
// 19. Set numberFormat.[[Notation]] to notation.
let notation = get_option(&options, utf16!("notation"), context)?.unwrap_or_default();
let notation = get_option(&options, js_str!("notation"), context)?.unwrap_or_default();
// 20. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation).
let digit_options = DigitFormatOptions::from_options(
@ -290,7 +290,7 @@ impl BuiltInConstructor for NumberFormat {
// 21. Let compactDisplay be ? GetOption(options, "compactDisplay", string, « "short", "long" », "short").
let compact_display =
get_option(&options, utf16!("compactDisplay"), context)?.unwrap_or_default();
get_option(&options, js_str!("compactDisplay"), context)?.unwrap_or_default();
// 22. Let defaultUseGrouping be "auto".
let mut default_use_grouping = GroupingStrategy::Auto;
@ -325,7 +325,7 @@ impl BuiltInConstructor for NumberFormat {
// <https://tc39.es/ecma402/#sec-getbooleanorstringnumberformatoption>
// 1. Let value be ? Get(options, property).
let value = options.get(utf16!("useGrouping"), context)?;
let value = options.get(js_str!("useGrouping"), context)?;
// 2. If value is undefined, return fallback.
if value.is_undefined() {
@ -363,7 +363,7 @@ impl BuiltInConstructor for NumberFormat {
// 29. Let signDisplay be ? GetOption(options, "signDisplay", string, « "auto", "never", "always", "exceptZero", "negative" », "auto").
// 30. Set numberFormat.[[SignDisplay]] to signDisplay.
let sign_display =
get_option(&options, utf16!("signDisplay"), context)?.unwrap_or(SignDisplay::Auto);
get_option(&options, js_str!("signDisplay"), context)?.unwrap_or(SignDisplay::Auto);
let formatter = FixedDecimalFormatter::try_new_unstable(
context.intl_provider(),

56
core/engine/src/builtins/intl/number_format/options.rs

@ -2,7 +2,7 @@ use std::fmt;
use fixed_decimal::{FixedDecimal, FloatPrecision, RoundingIncrement as BaseMultiple, SignDisplay};
use boa_macros::utf16;
use boa_macros::js_str;
use tinystr::TinyAsciiStr;
use crate::{
@ -10,7 +10,7 @@ use crate::{
intl::options::{default_number_option, get_number_option},
options::{get_option, OptionType, ParsableOptionType, RoundingMode},
},
js_string, Context, JsNativeError, JsObject, JsResult, JsString,
js_string, Context, JsNativeError, JsObject, JsResult, JsStr, JsString,
};
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
@ -245,9 +245,9 @@ impl ParsableOptionType for Currency {}
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct Unit {
// INVARIANT: `numerator` must only contain ASCII lowercase alphabetic letters or `-`.
numerator: &'static str,
numerator: JsStr<'static>,
// INVARIANT: if `denominator` is not empty, it must only contain ASCII lowercase alphabetic letters or `-`
denominator: &'static str,
denominator: JsStr<'static>,
}
impl Unit {
@ -258,9 +258,7 @@ impl Unit {
} else {
// TODO: this is not optimal for now, but the new JS strings should
// allow us to optimize this to simple casts from ASCII to JsString.
let numerator: Vec<u16> = self.numerator.encode_utf16().collect();
let denominator: Vec<u16> = self.denominator.encode_utf16().collect();
js_string!(&numerator, utf16!("-per-"), &denominator)
js_string!(self.numerator, js_str!("-per-"), self.denominator)
}
}
}
@ -281,7 +279,7 @@ impl std::str::FromStr for Unit {
///
/// [spec]: https://tc39.es/ecma402/#sec-iswellformedunitidentifier
fn from_str(s: &str) -> Result<Self, Self::Err> {
static SANCTIONED_UNITS: [&str; 45] = [
const SANCTIONED_UNITS: [&str; 45] = [
"acre",
"bit",
"byte",
@ -339,13 +337,17 @@ impl std::str::FromStr for Unit {
.map(|i| SANCTIONED_UNITS[i])
.map_err(|_| ParseUnitError)?;
let num = JsStr::latin1(num.as_bytes());
let den = if den.is_empty() {
""
JsStr::EMPTY
} else {
SANCTIONED_UNITS
let value = SANCTIONED_UNITS
.binary_search(&den)
.map(|i| SANCTIONED_UNITS[i])
.map_err(|_| ParseUnitError)?
.map_err(|_| ParseUnitError)?;
JsStr::latin1(value.as_bytes())
};
Ok(Self {
@ -390,12 +392,12 @@ impl UnitFormatOptions {
pub(crate) fn from_options(options: &JsObject, context: &mut Context) -> JsResult<Self> {
// 1. Let style be ? GetOption(options, "style", string, « "decimal", "percent", "currency", "unit" », "decimal").
// 2. Set intlObj.[[Style]] to style.
let style: Style = get_option(options, utf16!("style"), context)?.unwrap_or_default();
let style: Style = get_option(options, js_str!("style"), context)?.unwrap_or_default();
// 3. Let currency be ? GetOption(options, "currency", string, empty, undefined).
// 5. Else,
// a. If IsWellFormedCurrencyCode(currency) is false, throw a RangeError exception.
let currency = get_option(options, utf16!("currency"), context)?;
let currency = get_option(options, js_str!("currency"), context)?;
// 4. If currency is undefined, then
if currency.is_none() {
@ -411,16 +413,16 @@ impl UnitFormatOptions {
// 6. Let currencyDisplay be ? GetOption(options, "currencyDisplay", string, « "code", "symbol", "narrowSymbol", "name" », "symbol").
let currency_display =
get_option(options, utf16!("currencyDisplay"), context)?.unwrap_or_default();
get_option(options, js_str!("currencyDisplay"), context)?.unwrap_or_default();
// 7. Let currencySign be ? GetOption(options, "currencySign", string, « "standard", "accounting" », "standard").
let currency_sign =
get_option(options, utf16!("currencySign"), context)?.unwrap_or_default();
get_option(options, js_str!("currencySign"), context)?.unwrap_or_default();
// 8. Let unit be ? GetOption(options, "unit", string, empty, undefined).
// 10. Else,
// a. If IsWellFormedUnitIdentifier(unit) is false, throw a RangeError exception.
let unit = get_option(options, utf16!("unit"), context)?;
let unit = get_option(options, js_str!("unit"), context)?;
// 9. If unit is undefined, then
if unit.is_none() {
// a. If style is "unit", throw a TypeError exception.
@ -434,7 +436,8 @@ impl UnitFormatOptions {
}
// 11. Let unitDisplay be ? GetOption(options, "unitDisplay", string, « "short", "narrow", "long" », "short").
let unit_display = get_option(options, utf16!("unitDisplay"), context)?.unwrap_or_default();
let unit_display =
get_option(options, js_str!("unitDisplay"), context)?.unwrap_or_default();
// 14. Return unused.
Ok(match style {
@ -489,25 +492,26 @@ impl DigitFormatOptions {
) -> JsResult<Self> {
// 1. Let mnid be ? GetNumberOption(options, "minimumIntegerDigits,", 1, 21, 1).
let minimum_integer_digits =
get_number_option(options, utf16!("minimumIntegerDigits"), 1, 21, context)?
get_number_option(options, js_str!("minimumIntegerDigits"), 1, 21, context)?
.unwrap_or(1);
// 2. Let mnfd be ? Get(options, "minimumFractionDigits").
let min_float_digits = options.get(utf16!("minimumFractionDigits"), context)?;
let min_float_digits = options.get(js_str!("minimumFractionDigits"), context)?;
// 3. Let mxfd be ? Get(options, "maximumFractionDigits").
let max_float_digits = options.get(utf16!("maximumFractionDigits"), context)?;
let max_float_digits = options.get(js_str!("maximumFractionDigits"), context)?;
// 4. Let mnsd be ? Get(options, "minimumSignificantDigits").
let min_sig_digits = options.get(utf16!("minimumSignificantDigits"), context)?;
let min_sig_digits = options.get(js_str!("minimumSignificantDigits"), context)?;
// 5. Let mxsd be ? Get(options, "maximumSignificantDigits").
let max_sig_digits = options.get(utf16!("maximumSignificantDigits"), context)?;
let max_sig_digits = options.get(js_str!("maximumSignificantDigits"), context)?;
// 7. Let roundingPriority be ? GetOption(options, "roundingPriority", string, « "auto", "morePrecision", "lessPrecision" », "auto").
let mut rounding_priority =
get_option(options, utf16!("roundingPriority"), context)?.unwrap_or_default();
get_option(options, js_str!("roundingPriority"), context)?.unwrap_or_default();
// 8. Let roundingIncrement be ? GetNumberOption(options, "roundingIncrement", 1, 5000, 1).
// 9. If roundingIncrement is not in « 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000 », throw a RangeError exception.
let rounding_increment =
get_number_option(options, utf16!("roundingIncrement"), 1, 5000, context)?.unwrap_or(1);
get_number_option(options, js_str!("roundingIncrement"), 1, 5000, context)?
.unwrap_or(1);
let rounding_increment =
RoundingIncrement::from_u16(rounding_increment).ok_or_else(|| {
@ -516,11 +520,11 @@ impl DigitFormatOptions {
// 10. Let roundingMode be ? GetOption(options, "roundingMode", string, « "ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven" », "halfExpand").
let rounding_mode =
get_option(options, utf16!("roundingMode"), context)?.unwrap_or_default();
get_option(options, js_str!("roundingMode"), context)?.unwrap_or_default();
// 11. Let trailingZeroDisplay be ? GetOption(options, "trailingZeroDisplay", string, « "auto", "stripIfInteger" », "auto").
let trailing_zero_display =
get_option(options, utf16!("trailingZeroDisplay"), context)?.unwrap_or_default();
get_option(options, js_str!("trailingZeroDisplay"), context)?.unwrap_or_default();
// 12. NOTE: All fields required by SetNumberFormatDigitOptions have now been read from options. The remainder of this AO interprets the options and may throw exceptions.

4
core/engine/src/builtins/intl/options.rs

@ -5,7 +5,7 @@ use num_traits::FromPrimitive;
use crate::{
builtins::{options::ParsableOptionType, OrdinaryObject},
object::JsObject,
Context, JsNativeError, JsResult, JsValue,
Context, JsNativeError, JsResult, JsStr, JsValue,
};
/// `IntlOptions` aggregates the `locale_matcher` selector and any other object
@ -60,7 +60,7 @@ impl ParsableOptionType for LocaleMatcher {}
/// [spec]: https://tc39.es/ecma402/#sec-getnumberoption
pub(super) fn get_number_option<T>(
options: &JsObject,
property: &[u16],
property: JsStr<'_>,
minimum: T,
maximum: T,
context: &mut Context,

60
core/engine/src/builtins/intl/plural_rules/mod.rs

@ -1,7 +1,7 @@
mod options;
use boa_gc::{Finalize, Trace};
use boa_macros::utf16;
use boa_macros::js_str;
use boa_profiler::Profiler;
use fixed_decimal::FixedDecimal;
use icu_locid::Locale;
@ -22,7 +22,7 @@ use crate::{
property::Attribute,
realm::Realm,
string::common::StaticJsStrings,
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsStr, JsString, JsSymbol, JsValue,
};
use super::{
@ -121,12 +121,12 @@ impl BuiltInConstructor for PluralRules {
// 3. Let opt be a new Record.
// 4. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
// 5. Set opt.[[localeMatcher]] to matcher.
let matcher = get_option(&options, utf16!("localeMatcher"), context)?.unwrap_or_default();
let matcher = get_option(&options, js_str!("localeMatcher"), context)?.unwrap_or_default();
// 6. Let t be ? GetOption(options, "type", string, « "cardinal", "ordinal" », "cardinal").
// 7. Set pluralRules.[[Type]] to t.
let rule_type =
get_option(&options, utf16!("type"), context)?.unwrap_or(PluralRuleType::Cardinal);
get_option(&options, js_str!("type"), context)?.unwrap_or(PluralRuleType::Cardinal);
// 8. Perform ? SetNumberFormatDigitOptions(pluralRules, options, +0𝔽, 3𝔽, "standard").
let format_options =
@ -330,21 +330,21 @@ impl PluralRules {
let mut options = ObjectInitializer::new(context);
options
.property(
js_string!("locale"),
js_str!("locale"),
js_string!(plural_rules.locale.to_string()),
Attribute::all(),
)
.property(
js_string!("type"),
js_str!("type"),
match plural_rules.rule_type {
PluralRuleType::Cardinal => js_string!("cardinal"),
PluralRuleType::Ordinal => js_string!("ordinal"),
_ => js_string!("unknown"),
PluralRuleType::Cardinal => js_str!("cardinal"),
PluralRuleType::Ordinal => js_str!("ordinal"),
_ => js_str!("unknown"),
},
Attribute::all(),
)
.property(
js_string!("minimumIntegerDigits"),
js_str!("minimumIntegerDigits"),
plural_rules.format_options.minimum_integer_digits,
Attribute::all(),
);
@ -353,16 +353,8 @@ impl PluralRules {
plural_rules.format_options.rounding_type.fraction_digits()
{
options
.property(
js_string!("minimumFractionDigits"),
minimum,
Attribute::all(),
)
.property(
js_string!("maximumFractionDigits"),
maximum,
Attribute::all(),
);
.property(js_str!("minimumFractionDigits"), minimum, Attribute::all())
.property(js_str!("maximumFractionDigits"), maximum, Attribute::all());
}
if let Some(Extrema { minimum, maximum }) = plural_rules
@ -372,12 +364,12 @@ impl PluralRules {
{
options
.property(
js_string!("minimumSignificantDigits"),
js_str!("minimumSignificantDigits"),
minimum,
Attribute::all(),
)
.property(
js_string!("maximumSignificantDigits"),
js_str!("maximumSignificantDigits"),
maximum,
Attribute::all(),
);
@ -385,17 +377,17 @@ impl PluralRules {
options
.property(
js_string!("roundingMode"),
js_str!("roundingMode"),
js_string!(plural_rules.format_options.rounding_mode.to_js_string()),
Attribute::all(),
)
.property(
js_string!("roundingIncrement"),
js_str!("roundingIncrement"),
plural_rules.format_options.rounding_increment.to_u16(),
Attribute::all(),
)
.property(
js_string!("trailingZeroDisplay"),
js_str!("trailingZeroDisplay"),
plural_rules
.format_options
.trailing_zero_display
@ -416,7 +408,7 @@ impl PluralRules {
// 6. Perform ! CreateDataProperty(options, "pluralCategories", CreateArrayFromList(pluralCategories)).
options.property(
js_string!("pluralCategories"),
js_str!("pluralCategories"),
plural_categories,
Attribute::all(),
);
@ -428,7 +420,7 @@ impl PluralRules {
// 9. Else,
// a. Perform ! CreateDataPropertyOrThrow(options, "roundingPriority", "auto").
options.property(
js_string!("roundingPriority"),
js_str!("roundingPriority"),
js_string!(plural_rules.format_options.rounding_priority.to_js_string()),
Attribute::all(),
);
@ -480,13 +472,13 @@ fn resolve_plural(plural_rules: &PluralRules, n: f64) -> ResolvedPlural {
}
}
fn plural_category_to_js_string(category: PluralCategory) -> JsString {
fn plural_category_to_js_string(category: PluralCategory) -> JsStr<'static> {
match category {
PluralCategory::Zero => js_string!("zero"),
PluralCategory::One => js_string!("one"),
PluralCategory::Two => js_string!("two"),
PluralCategory::Few => js_string!("few"),
PluralCategory::Many => js_string!("many"),
PluralCategory::Other => js_string!("other"),
PluralCategory::Zero => js_str!("zero"),
PluralCategory::One => js_str!("one"),
PluralCategory::Two => js_str!("two"),
PluralCategory::Few => js_str!("few"),
PluralCategory::Many => js_str!("many"),
PluralCategory::Other => js_str!("other"),
}
}

30
core/engine/src/builtins/intl/segmenter/iterator.rs

@ -1,7 +1,9 @@
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
use icu_segmenter::{
GraphemeClusterBreakIteratorUtf16, SentenceBreakIteratorUtf16, WordBreakIteratorUtf16,
GraphemeClusterBreakIteratorLatin1, GraphemeClusterBreakIteratorUtf16,
SentenceBreakIteratorLatin1, SentenceBreakIteratorUtf16, WordBreakIteratorLatin1,
WordBreakIteratorUtf16,
};
use crate::{
@ -16,9 +18,12 @@ use crate::{
use super::{create_segment_data_object, Segmenter};
pub(crate) enum NativeSegmentIterator<'l, 's> {
Grapheme(GraphemeClusterBreakIteratorUtf16<'l, 's>),
Word(WordBreakIteratorUtf16<'l, 's>),
Sentence(SentenceBreakIteratorUtf16<'l, 's>),
GraphemeUtf16(GraphemeClusterBreakIteratorUtf16<'l, 's>),
WordUtf16(WordBreakIteratorUtf16<'l, 's>),
SentenceUtf16(SentenceBreakIteratorUtf16<'l, 's>),
GraphemeLatin1(GraphemeClusterBreakIteratorLatin1<'l, 's>),
WordLatin1(WordBreakIteratorLatin1<'l, 's>),
SentenceLatin1(SentenceBreakIteratorLatin1<'l, 's>),
}
impl Iterator for NativeSegmentIterator<'_, '_> {
@ -26,9 +31,12 @@ impl Iterator for NativeSegmentIterator<'_, '_> {
fn next(&mut self) -> Option<Self::Item> {
match self {
NativeSegmentIterator::Grapheme(g) => g.next(),
NativeSegmentIterator::Word(w) => w.next(),
NativeSegmentIterator::Sentence(s) => s.next(),
NativeSegmentIterator::GraphemeUtf16(g) => g.next(),
NativeSegmentIterator::WordUtf16(w) => w.next(),
NativeSegmentIterator::SentenceUtf16(s) => s.next(),
NativeSegmentIterator::GraphemeLatin1(g) => g.next(),
NativeSegmentIterator::WordLatin1(w) => w.next(),
NativeSegmentIterator::SentenceLatin1(s) => s.next(),
}
}
}
@ -37,10 +45,10 @@ impl NativeSegmentIterator<'_, '_> {
/// If the iterator is a word break iterator, returns `Some(true)` when the segment preceding
/// the current boundary is word-like.
pub(crate) fn is_word_like(&self) -> Option<bool> {
if let Self::Word(w) = self {
Some(w.is_word_like())
} else {
None
match self {
Self::WordLatin1(w) => Some(w.is_word_like()),
Self::WordUtf16(w) => Some(w.is_word_like()),
_ => None,
}
}
}

40
core/engine/src/builtins/intl/segmenter/mod.rs

@ -1,7 +1,7 @@
use std::ops::Range;
use boa_gc::{Finalize, Trace};
use boa_macros::utf16;
use boa_macros::js_str;
use boa_profiler::Profiler;
use icu_locid::Locale;
use icu_segmenter::{
@ -19,7 +19,7 @@ use crate::{
property::Attribute,
realm::Realm,
string::common::StaticJsStrings,
Context, JsArgs, JsData, JsNativeError, JsResult, JsString, JsSymbol, JsValue,
Context, JsArgs, JsData, JsNativeError, JsResult, JsStr, JsString, JsSymbol, JsValue,
};
mod iterator;
@ -62,11 +62,18 @@ impl NativeSegmenter {
/// Segment the passed string, returning an iterator with the index boundaries
/// of the segments.
pub(crate) fn segment<'l, 's>(&'l self, input: &'s [u16]) -> NativeSegmentIterator<'l, 's> {
match self {
Self::Grapheme(g) => NativeSegmentIterator::Grapheme(g.segment_utf16(input)),
Self::Word(w) => NativeSegmentIterator::Word(w.segment_utf16(input)),
Self::Sentence(s) => NativeSegmentIterator::Sentence(s.segment_utf16(input)),
pub(crate) fn segment<'l, 's>(&'l self, input: JsStr<'s>) -> NativeSegmentIterator<'l, 's> {
match input.variant() {
crate::string::JsStrVariant::Latin1(input) => match self {
Self::Grapheme(g) => NativeSegmentIterator::GraphemeLatin1(g.segment_latin1(input)),
Self::Word(w) => NativeSegmentIterator::WordLatin1(w.segment_latin1(input)),
Self::Sentence(s) => NativeSegmentIterator::SentenceLatin1(s.segment_latin1(input)),
},
crate::string::JsStrVariant::Utf16(input) => match self {
Self::Grapheme(g) => NativeSegmentIterator::GraphemeUtf16(g.segment_utf16(input)),
Self::Word(w) => NativeSegmentIterator::WordUtf16(w.segment_utf16(input)),
Self::Sentence(s) => NativeSegmentIterator::SentenceUtf16(s.segment_utf16(input)),
},
}
}
}
@ -134,7 +141,7 @@ impl BuiltInConstructor for Segmenter {
// 6. Let opt be a new Record.
// 7. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
let matcher = get_option(&options, utf16!("localeMatcher"), context)?.unwrap_or_default();
let matcher = get_option(&options, js_str!("localeMatcher"), context)?.unwrap_or_default();
// 8. Set opt.[[localeMatcher]] to matcher.
// 9. Let localeData be %Segmenter%.[[LocaleData]].
@ -150,7 +157,8 @@ impl BuiltInConstructor for Segmenter {
);
// 12. Let granularity be ? GetOption(options, "granularity", string, « "grapheme", "word", "sentence" », "grapheme").
let granularity = get_option(&options, utf16!("granularity"), context)?.unwrap_or_default();
let granularity =
get_option(&options, js_str!("granularity"), context)?.unwrap_or_default();
// 13. Set segmenter.[[SegmenterGranularity]] to granularity.
let native = match granularity {
@ -241,12 +249,12 @@ impl Segmenter {
// d. Perform ! CreateDataPropertyOrThrow(options, p, v).
let options = ObjectInitializer::new(context)
.property(
js_string!("locale"),
js_str!("locale"),
js_string!(segmenter.locale.to_string()),
Attribute::all(),
)
.property(
js_string!("granularity"),
js_str!("granularity"),
js_string!(segmenter.native.granularity().to_string()),
Attribute::all(),
)
@ -301,25 +309,25 @@ fn create_segment_data_object(
let start = range.start;
// 6. Let segment be the substring of string from startIndex to endIndex.
let segment = js_string!(&string[range]);
let segment = string.get(range).expect("range already checked");
// 5. Let result be OrdinaryObjectCreate(%Object.prototype%).
let object = &mut ObjectInitializer::new(context);
object
// 7. Perform ! CreateDataPropertyOrThrow(result, "segment", segment).
.property(js_string!("segment"), segment, Attribute::all())
.property(js_str!("segment"), segment, Attribute::all())
// 8. Perform ! CreateDataPropertyOrThrow(result, "index", 𝔽(startIndex)).
.property(js_string!("index"), start, Attribute::all())
.property(js_str!("index"), start, Attribute::all())
// 9. Perform ! CreateDataPropertyOrThrow(result, "input", string).
.property(js_string!("input"), string, Attribute::all());
.property(js_str!("input"), string, Attribute::all());
// 10. Let granularity be segmenter.[[SegmenterGranularity]].
// 11. If granularity is "word", then
if let Some(is_word_like) = is_word_like {
// a. Let isWordLike be a Boolean value indicating whether the segment in string is "word-like" according to locale segmenter.[[Locale]].
// b. Perform ! CreateDataPropertyOrThrow(result, "isWordLike", isWordLike).
object.property(js_string!("isWordLike"), is_word_like, Attribute::all());
object.property(js_str!("isWordLike"), is_word_like, Attribute::all());
}
// 12. Return result.

2
core/engine/src/builtins/intl/segmenter/segments.rs

@ -89,7 +89,7 @@ impl Segments {
// 8. Let startIndex be ! FindBoundary(segmenter, string, n, before).
// 9. Let endIndex be ! FindBoundary(segmenter, string, n, after).
let (range, is_word_like) = {
let mut segments = segmenter.native.segment(&segments.string);
let mut segments = segmenter.native.segment(segments.string.as_str());
std::iter::from_fn(|| segments.next().map(|i| (i, segments.is_word_like())))
.tuple_windows()
.find(|((i, _), (j, _))| (*i..*j).contains(&n))

12
core/engine/src/builtins/iterable/async_from_sync_iterator.rs

@ -9,10 +9,10 @@ use crate::{
native_function::NativeFunction,
object::{FunctionObjectBuilder, JsObject},
realm::Realm,
string::utf16,
Context, JsArgs, JsData, JsError, JsNativeError, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
/// `%AsyncFromSyncIteratorPrototype%` object.
@ -80,7 +80,7 @@ impl AsyncFromSyncIterator {
// 3. Let nextMethod be ! Get(asyncIterator, "next").
let next_method = async_iterator
.get(utf16!("next"), context)
.get(js_str!("next"), context)
.expect("async from sync iterator prototype must have next method");
// 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: asyncIterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
@ -166,7 +166,7 @@ impl AsyncFromSyncIterator {
.expect("cannot fail with promise constructor");
// 6. Let return be Completion(GetMethod(syncIterator, "return")).
let r#return = sync_iterator.get_method(utf16!("return"), context);
let r#return = sync_iterator.get_method(js_str!("return"), context);
// 7. IfAbruptRejectPromise(return, promiseCapability).
let r#return = if_abrupt_reject_promise!(r#return, promise_capability, context);
@ -243,7 +243,7 @@ impl AsyncFromSyncIterator {
.expect("cannot fail with promise constructor");
// 6. Let throw be Completion(GetMethod(syncIterator, "throw")).
let throw = sync_iterator.get_method(utf16!("throw"), context);
let throw = sync_iterator.get_method(js_str!("throw"), context);
// 7. IfAbruptRejectPromise(throw, promiseCapability).
let throw = if_abrupt_reject_promise!(throw, promise_capability, context);
@ -368,7 +368,7 @@ impl AsyncFromSyncIterator {
))
}),
)
.name("")
.name(js_str!(""))
.length(1)
.build();
@ -401,7 +401,7 @@ impl AsyncFromSyncIterator {
sync_iterator_record,
),
)
.name("")
.name(js_str!(""))
.length(1)
.build(),
)

10
core/engine/src/builtins/iterable/mod.rs

@ -4,13 +4,13 @@ use crate::{
builtins::{BuiltInBuilder, IntrinsicObject},
context::intrinsics::Intrinsics,
error::JsNativeError,
js_string,
object::JsObject,
realm::Realm,
symbol::JsSymbol,
Context, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
mod async_from_sync_iterator;
@ -282,7 +282,7 @@ impl JsValue {
})?;
// 5. Let nextMethod be ? GetV(iterator, "next").
let next_method = iterator.get_v(js_string!("next"), context)?;
let next_method = iterator.get_v(js_str!("next"), context)?;
// 6. Let iteratorRecord be the Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
// 7. Return iteratorRecord.
@ -326,7 +326,7 @@ impl IteratorResult {
#[inline]
pub fn complete(&self, context: &mut Context) -> JsResult<bool> {
// 1. Return ToBoolean(? Get(iterResult, "done")).
Ok(self.object.get(js_string!("done"), context)?.to_boolean())
Ok(self.object.get(js_str!("done"), context)?.to_boolean())
}
/// `IteratorValue ( iterResult )`
@ -342,7 +342,7 @@ impl IteratorResult {
#[inline]
pub fn value(&self, context: &mut Context) -> JsResult<JsValue> {
// 1. Return ? Get(iterResult, "value").
self.object.get(js_string!("value"), context)
self.object.get(js_str!("value"), context)
}
}
@ -524,7 +524,7 @@ impl IteratorRecord {
let iterator = &self.iterator;
// 3. Let innerResult be Completion(GetMethod(iterator, "return")).
let inner_result = iterator.get_method(js_string!("return"), context);
let inner_result = iterator.get_method(js_str!("return"), context);
// 4. If innerResult.[[Type]] is normal, then
let inner_result = match inner_result {

31
core/engine/src/builtins/json/mod.rs

@ -15,6 +15,7 @@
use std::{borrow::Cow, iter::once};
use boa_macros::{js_str, utf16};
use itertools::Itertools;
use crate::{
@ -26,7 +27,7 @@ use crate::{
object::{internal_methods::InternalMethodContext, JsObject},
property::{Attribute, PropertyNameKind},
realm::Realm,
string::{common::StaticJsStrings, utf16, CodePoint},
string::{common::StaticJsStrings, CodePoint},
symbol::JsSymbol,
value::IntegerOrInfinity,
vm::{CallFrame, CallFrameFlags},
@ -148,11 +149,11 @@ impl Json {
// b. Let rootName be the empty String.
// c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered).
root.create_data_property_or_throw(utf16!(""), unfiltered, context)
root.create_data_property_or_throw(js_str!(""), unfiltered, context)
.expect("CreateDataPropertyOrThrow should never throw here");
// d. Return ? InternalizeJSONProperty(root, rootName, reviver).
Self::internalize_json_property(&root, "".into(), obj, context)
Self::internalize_json_property(&root, js_string!(), obj, context)
} else {
// 12. Else,
// a. Return unfiltered.
@ -368,7 +369,7 @@ impl Json {
// 7. Else if Type(space) is String, then
} else if let Some(s) = space.as_string() {
// a. If the length of space is 10 or less, let gap be space; otherwise let gap be the substring of space from 0 to 10.
js_string!(s.get(..10).unwrap_or(s))
js_string!(s.get(..10).unwrap_or(s.as_str()))
// 8. Else,
} else {
// a. Let gap be the empty String.
@ -380,7 +381,7 @@ impl Json {
// 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value).
wrapper
.create_data_property_or_throw(utf16!(""), args.get_or_undefined(0).clone(), context)
.create_data_property_or_throw(js_str!(""), 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 }.
@ -418,7 +419,7 @@ impl Json {
// 2. If Type(value) is Object or BigInt, then
if value.is_object() || value.is_bigint() {
// a. Let toJSON be ? GetV(value, "toJSON").
let to_json = value.get_v(utf16!("toJSON"), context)?;
let to_json = value.get_v(js_str!("toJSON"), context)?;
// b. If IsCallable(toJSON) is true, then
if let Some(obj) = to_json.as_object() {
@ -627,7 +628,7 @@ impl Json {
// b. If strP is not undefined, then
if let Some(str_p) = str_p {
// i. Let member be QuoteJSONString(P).
let mut member = Self::quote_json_string(p).to_vec();
let mut member = Self::quote_json_string(p).iter().collect::<Vec<_>>();
// ii. Set member to the string-concatenation of member and ":".
member.push(':' as u16);
@ -639,7 +640,7 @@ impl Json {
}
// iv. Set member to the string-concatenation of member and strP.
member.extend_from_slice(&str_p);
member.extend(str_p.iter());
// v. Append member to partial.
partial.push(member);
@ -674,20 +675,20 @@ impl Json {
// i. Let separator be the string-concatenation of the code unit 0x002C (COMMA),
// the code unit 0x000A (LINE FEED), and state.[[Indent]].
let mut separator = utf16!(",\n").to_vec();
separator.extend_from_slice(&state.indent);
separator.extend(state.indent.iter());
// ii. Let properties be the String value formed by concatenating all the element Strings of partial
// with each adjacent pair of Strings separated with separator.
// The separator String is not inserted either before the first String or after the last String.
// iii. Let final be the string-concatenation of "{", the code
// unit 0x000A (LINE FEED), state.[[Indent]], properties,
// the code unit 0x000A (LINE FEED), stepback, and "}".
let result = [utf16!("{\n"), &state.indent[..]]
let result = [utf16!("{\n"), &state.indent.to_vec()[..]]
.into_iter()
.chain(Itertools::intersperse(
partial.iter().map(Vec::as_slice),
&separator,
))
.chain([utf16!("\n"), &stepback[..], utf16!("}")])
.chain([utf16!("\n"), &stepback.to_vec()[..], utf16!("}")])
.flatten()
.copied()
.collect::<Vec<_>>();
@ -750,7 +751,7 @@ impl Json {
// b. If strP is undefined, then
if let Some(str_p) = str_p {
// i. Append strP to partial.
partial.push(Cow::Owned(str_p.to_vec()));
partial.push(Cow::Owned(str_p.iter().collect::<_>()));
// c. Else,
} else {
// i. Append "null" to partial.
@ -789,18 +790,18 @@ impl Json {
// i. Let separator be the string-concatenation of the code unit 0x002C (COMMA),
// the code unit 0x000A (LINE FEED), and state.[[Indent]].
let mut separator = utf16!(",\n").to_vec();
separator.extend_from_slice(&state.indent);
separator.extend(state.indent.iter());
// ii. Let properties be the String value formed by concatenating all the element Strings of partial
// with each adjacent pair of Strings separated with separator.
// The separator String is not inserted either before the first String or after the last String.
// iii. Let final be the string-concatenation of "[", the code unit 0x000A (LINE FEED), state.[[Indent]], properties, the code unit 0x000A (LINE FEED), stepback, and "]".
let result = [utf16!("[\n"), &state.indent[..]]
let result = [utf16!("[\n"), &state.indent.to_vec()[..]]
.into_iter()
.chain(Itertools::intersperse(
partial.iter().map(Cow::as_ref),
&separator,
))
.chain([utf16!("\n"), &stepback[..], utf16!("]")])
.chain([utf16!("\n"), &stepback.to_vec()[..], utf16!("]")])
.flatten()
.copied()
.collect::<Vec<_>>();

18
core/engine/src/builtins/json/tests.rs

@ -1,3 +1,4 @@
use boa_macros::js_str;
use indoc::indoc;
use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction};
@ -5,7 +6,7 @@ use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction}
#[test]
fn json_sanity() {
run_test_actions([
TestAction::assert_eq(r#"JSON.parse('{"aaa":"bbb"}').aaa"#, js_string!("bbb")),
TestAction::assert_eq(r#"JSON.parse('{"aaa":"bbb"}').aaa"#, js_str!("bbb")),
TestAction::assert_eq(
r#"JSON.stringify({aaa: 'bbb'})"#,
js_string!(r#"{"aaa":"bbb"}"#),
@ -89,7 +90,7 @@ fn json_stringify_object_array() {
fn json_stringify_array_converts_undefined_to_null() {
run_test_actions([TestAction::assert_eq(
"JSON.stringify([undefined])",
js_string!("[null]"),
js_str!("[null]"),
)]);
}
@ -97,7 +98,7 @@ fn json_stringify_array_converts_undefined_to_null() {
fn json_stringify_array_converts_function_to_null() {
run_test_actions([TestAction::assert_eq(
"JSON.stringify([() => {}])",
js_string!("[null]"),
js_str!("[null]"),
)]);
}
@ -105,7 +106,7 @@ fn json_stringify_array_converts_function_to_null() {
fn json_stringify_array_converts_symbol_to_null() {
run_test_actions([TestAction::assert_eq(
"JSON.stringify([Symbol()])",
js_string!("[null]"),
js_str!("[null]"),
)]);
}
#[test]
@ -150,10 +151,7 @@ fn json_stringify_no_args() {
#[test]
fn json_stringify_fractional_numbers() {
run_test_actions([TestAction::assert_eq(
"JSON.stringify(1.2)",
js_string!("1.2"),
)]);
run_test_actions([TestAction::assert_eq("JSON.stringify(1.2)", js_str!("1.2"))]);
}
#[test]
@ -278,8 +276,8 @@ fn json_parse_object_with_reviver() {
var jsonObj = JSON.parse(jsonString, dataReviver);
"#}),
TestAction::assert_eq("jsonObj.firstname", js_string!("boa")),
TestAction::assert_eq("jsonObj.lastname", js_string!("interpreter")),
TestAction::assert_eq("jsonObj.firstname", js_str!("boa")),
TestAction::assert_eq("jsonObj.lastname", js_str!("interpreter")),
]);
}

7
core/engine/src/builtins/map/mod.rs

@ -18,10 +18,11 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject},
property::{Attribute, PropertyNameKind},
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
symbol::JsSymbol,
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_macros::js_str;
use boa_profiler::Profiler;
use num_traits::Zero;
@ -63,7 +64,7 @@ impl IntrinsicObject for Map {
Attribute::CONFIGURABLE,
)
.property(
utf16!("entries"),
js_string!("entries"),
entries_function.clone(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
@ -148,7 +149,7 @@ impl BuiltInConstructor for Map {
};
// 5. Let adder be ? Get(map, "set").
let adder = map.get(utf16!("set"), context)?;
let adder = map.get(js_str!("set"), context)?;
// 6. Return ? AddEntriesFromIterable(map, iterable, adder).
add_entries_from_iterable(&map, iterable, &adder, context)

19
core/engine/src/builtins/map/tests.rs

@ -1,4 +1,5 @@
use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction};
use boa_macros::js_str;
use indoc::indoc;
#[test]
@ -74,7 +75,7 @@ fn merge() {
let merged2 = new Map([...second, ...third]);
"#}),
TestAction::assert_eq("merged1.size", 3),
TestAction::assert_eq("merged1.get('2')", js_string!("second two")),
TestAction::assert_eq("merged1.get('2')", js_str!("second two")),
TestAction::assert_eq("merged2.size", 4),
]);
}
@ -85,8 +86,8 @@ fn get() {
TestAction::run(indoc! {r#"
let map = new Map([["1", "one"], ["2", "two"]]);
"#}),
TestAction::assert_eq("map.get('1')", js_string!("one")),
TestAction::assert_eq("map.get('2')", js_string!("two")),
TestAction::assert_eq("map.get('1')", js_str!("one")),
TestAction::assert_eq("map.get('2')", js_str!("two")),
TestAction::assert_eq("map.get('3')", JsValue::undefined()),
TestAction::assert_eq("map.get()", JsValue::undefined()),
]);
@ -98,7 +99,7 @@ fn set() {
TestAction::run("let map = new Map();"),
TestAction::assert("map.set(); map.has(undefined)"),
TestAction::assert_eq("map.get()", JsValue::undefined()),
TestAction::assert_eq("map.set('1', 'one'); map.get('1')", js_string!("one")),
TestAction::assert_eq("map.set('1', 'one'); map.get('1')", js_str!("one")),
TestAction::assert("map.set('2'); map.has('2')"),
TestAction::assert_eq("map.get('2')", JsValue::undefined()),
]);
@ -148,7 +149,7 @@ fn keys() {
let item2 = keysIterator.next();
let item3 = keysIterator.next();
"#}),
TestAction::assert_eq("item1.value", js_string!("0")),
TestAction::assert_eq("item1.value", js_str!("0")),
TestAction::assert_eq("item2.value", 1),
TestAction::assert("item3.done"),
]);
@ -187,8 +188,8 @@ fn values() {
let item2 = valuesIterator.next();
let item3 = valuesIterator.next();
"#}),
TestAction::assert_eq("item1.value", js_string!("foo")),
TestAction::assert_eq("item2.value", js_string!("bar")),
TestAction::assert_eq("item1.value", js_str!("foo")),
TestAction::assert_eq("item2.value", js_str!("bar")),
TestAction::assert("item3.done"),
]);
}
@ -201,7 +202,7 @@ fn modify_key() {
let map = new Map([[obj, "one"]]);
obj.field = "Value";
"#}),
TestAction::assert_eq("map.get(obj)", js_string!("one")),
TestAction::assert_eq("map.get(obj)", js_str!("one")),
]);
}

10
core/engine/src/builtins/mod.rs

@ -36,6 +36,7 @@ pub mod weak_set;
mod builder;
use boa_macros::js_str;
use builder::BuiltInBuilder;
#[cfg(feature = "annex-b")]
@ -108,7 +109,6 @@ use crate::{
object::JsObject,
property::{Attribute, PropertyDescriptor},
realm::Realm,
string::utf16,
Context, JsResult, JsString, JsValue,
};
@ -307,7 +307,7 @@ pub(crate) fn set_default_global_bindings(context: &mut Context) -> JsResult<()>
let global_object = context.global_object();
global_object.define_property_or_throw(
utf16!("globalThis"),
js_str!("globalThis"),
PropertyDescriptor::builder()
.value(context.realm().global_this().clone())
.writable(true)
@ -320,17 +320,17 @@ pub(crate) fn set_default_global_bindings(context: &mut Context) -> JsResult<()>
.enumerable(false)
.configurable(false);
global_object.define_property_or_throw(
utf16!("Infinity"),
js_str!("Infinity"),
restricted.clone().value(f64::INFINITY),
context,
)?;
global_object.define_property_or_throw(
utf16!("NaN"),
js_str!("NaN"),
restricted.clone().value(f64::NAN),
context,
)?;
global_object.define_property_or_throw(
utf16!("undefined"),
js_str!("undefined"),
restricted.value(JsValue::undefined()),
context,
)?;

228
core/engine/src/builtins/number/globals.rs

@ -1,15 +1,13 @@
use boa_macros::utf16;
use crate::{
builtins::{string::is_trimmable_whitespace, BuiltInBuilder, BuiltInObject, IntrinsicObject},
context::intrinsics::Intrinsics,
object::JsObject,
realm::Realm,
string::{common::StaticJsStrings, Utf16Trim},
Context, JsArgs, JsResult, JsString, JsValue,
string::common::StaticJsStrings,
Context, JsArgs, JsResult, JsStr, JsString, JsValue,
};
use num_traits::Num;
use boa_macros::js_str;
/// Builtin javascript 'isFinite(number)' function.
///
@ -95,6 +93,55 @@ impl BuiltInObject for IsNaN {
const NAME: JsString = StaticJsStrings::IS_NAN;
}
fn from_js_str_radix(src: JsStr<'_>, radix: u8) -> Option<f64> {
/// Determines if a string of text of that length of that radix could be guaranteed to be
/// stored in the given type T.
/// Note that if the radix is known to the compiler, it is just the check of digits.len that
/// is done at runtime.
fn can_not_overflow(radix: u8, digits_len: usize) -> bool {
usize::from(radix) <= 16 && digits_len <= std::mem::size_of::<u64>() * 2
}
const fn to_digit(input: u8, radix: u8) -> Option<u8> {
// If not a digit, a number greater than radix will be created.
let mut digit = input.wrapping_sub(b'0');
if radix > 10 {
debug_assert!(radix <= 36, "to_digit: radix is too high (maximum 36)");
if digit < 10 {
return Some(digit);
}
// Force the 6th bit to be set to ensure ascii is lower case.
digit = (input | 0b10_0000).wrapping_sub(b'a').saturating_add(10);
}
// FIXME: once then_some is const fn, use it here
if digit < radix {
Some(digit)
} else {
None
}
}
let src = src
.iter()
.map(|x| u8::try_from(x).expect("should be ascii string"));
let result = if can_not_overflow(radix, src.len()) {
let mut result = 0;
for c in src {
result = result * u64::from(radix) + u64::from(to_digit(c, radix)?);
}
result as f64
} else {
let mut result = 0f64;
for c in src {
result = result * f64::from(radix) + f64::from(to_digit(c, radix)?);
}
result
};
Some(result)
}
/// Builtin javascript 'parseInt(str, radix)' function.
///
/// Parses the given string as an integer using the given radix as a base.
@ -110,107 +157,108 @@ impl BuiltInObject for IsNaN {
/// [spec]: https://tc39.es/ecma262/#sec-parseint-string-radix
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt
pub(crate) fn parse_int(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
if let (Some(val), radix) = (args.first(), args.get_or_undefined(1)) {
// 1. Let inputString be ? ToString(string).
let input_string = val.to_string(context)?;
// 2. Let S be ! TrimString(inputString, start).
let mut var_s = input_string.trim_start();
// 3. Let sign be 1.
// 4. If S is not empty and the first code unit of S is the code unit 0x002D (HYPHEN-MINUS),
// set sign to -1.
let sign = if !var_s.is_empty() && var_s.starts_with(utf16!("-")) {
-1
} else {
1
};
let (Some(val), radix) = (args.first(), args.get_or_undefined(1)) else {
// Not enough arguments to parseInt.
return Ok(JsValue::nan());
};
// 5. If S is not empty and the first code unit of S is the code unit 0x002B (PLUS SIGN) or
// the code unit 0x002D (HYPHEN-MINUS), remove the first code unit from S.
if !var_s.is_empty() && (var_s.starts_with(utf16!("+")) || var_s.starts_with(utf16!("-"))) {
var_s = &var_s[1..];
}
// 1. Let inputString be ? ToString(string).
let input_string = val.to_string(context)?;
// 6. Let R be ℝ(? ToInt32(radix)).
let mut var_r = radix.to_i32(context)?;
// 2. Let S be ! TrimString(inputString, start).
let mut s = input_string.trim_start();
// let mut
// 7. Let stripPrefix be true.
let mut strip_prefix = true;
// 3. Let sign be 1.
// 4. If S is not empty and the first code unit of S is the code unit 0x002D (HYPHEN-MINUS),
// set sign to -1.
let sign = if !s.is_empty() && s.starts_with(js_str!("-")) {
-1
} else {
1
};
// 8. If R ≠ 0, then
#[allow(clippy::if_not_else)]
if var_r != 0 {
// a. If R < 2 or R > 36, return NaN.
if !(2..=36).contains(&var_r) {
return Ok(JsValue::nan());
}
// 5. If S is not empty and the first code unit of S is the code unit 0x002B (PLUS SIGN) or
// the code unit 0x002D (HYPHEN-MINUS), remove the first code unit from S.
if !s.is_empty() && (s.starts_with(js_str!("+")) || s.starts_with(js_str!("-"))) {
s = s.get(1..).expect("already checked that it's not empty");
}
// b. If R ≠ 16, set stripPrefix to false.
if var_r != 16 {
strip_prefix = false;
}
} else {
// 9. Else,
// a. Set R to 10.
var_r = 10;
}
// 6. Let R be ℝ(? ToInt32(radix)).
let r = radix.to_i32(context)?;
// 10. If stripPrefix is true, then
// a. If the length of S is at least 2 and the first two code units of S are either "0x" or "0X", then
// i. Remove the first two code units from S.
// ii. Set R to 16.
if strip_prefix
&& var_s.len() >= 2
&& (var_s.starts_with(utf16!("0x")) || var_s.starts_with(utf16!("0X")))
{
var_s = &var_s[2..];
// 7. Let stripPrefix be true.
let mut strip_prefix = true;
var_r = 16;
// 8. If R ≠ 0, then
#[allow(clippy::if_not_else)]
let mut r = if r != 0 {
// a. If R < 2 or R > 36, return NaN.
if !(2..=36).contains(&r) {
return Ok(JsValue::nan());
}
// 11. If S contains a code unit that is not a radix-R digit, let end be the index within S of the
// first such code unit; otherwise, let end be the length of S.
let end = char::decode_utf16(var_s.iter().copied())
.position(|code| !code.is_ok_and(|c| c.is_digit(var_r as u32)))
.unwrap_or(var_s.len());
// b. If R ≠ 16, set stripPrefix to false.
if r != 16 {
strip_prefix = false;
}
r as u8
} else {
// 9. Else,
// a. Set R to 10.
10
};
// 10. If stripPrefix is true, then
// a. If the length of S is at least 2 and the first two code units of S are either "0x" or "0X", then
// i. Remove the first two code units from S.
// ii. Set R to 16.
if strip_prefix
&& s.len() >= 2
&& (s.starts_with(js_str!("0x")) || s.starts_with(js_str!("0X")))
{
s = s
.get(2..)
.expect("already checked that it contains at least two chars");
r = 16;
}
// 12. Let Z be the substring of S from 0 to end.
let var_z = String::from_utf16_lossy(&var_s[..end]);
// 11. If S contains a code unit that is not a radix-R digit, let end be the index within S of the
// first such code unit; otherwise, let end be the length of S.
let end = char::decode_utf16(s.iter())
.position(|code| !code.is_ok_and(|c| c.is_digit(u32::from(r))))
.unwrap_or(s.len());
// 13. If Z is empty, return NaN.
if var_z.is_empty() {
return Ok(JsValue::nan());
}
// 12. Let Z be the substring of S from 0 to end.
let z = s.get(..end).expect("should be in range");
// 14. Let mathInt be the integer value that is represented by Z in radix-R notation, using the
// letters A-Z and a-z for digits with values 10 through 35. (However, if R is 10 and Z contains
// more than 20 significant digits, every significant digit after the 20th may be replaced by a
// 0 digit, at the option of the implementation; and if R is not 2, 4, 8, 10, 16, or 32, then
// mathInt may be an implementation-approximated value representing the integer value that is
// represented by Z in radix-R notation.)
let math_int = u64::from_str_radix(&var_z, var_r as u32).map_or_else(
|_| f64::from_str_radix(&var_z, var_r as u32).expect("invalid_float_conversion"),
|i| i as f64,
);
// 15. If mathInt = 0, then
// a. If sign = -1, return -0𝔽.
// b. Return +0𝔽.
if math_int == 0_f64 {
if sign == -1 {
return Ok(JsValue::new(-0_f64));
}
// 13. If Z is empty, return NaN.
if z.is_empty() {
return Ok(JsValue::nan());
}
return Ok(JsValue::new(0_f64));
// 14. Let mathInt be the integer value that is represented by Z in radix-R notation, using the
// letters A-Z and a-z for digits with values 10 through 35. (However, if R is 10 and Z contains
// more than 20 significant digits, every significant digit after the 20th may be replaced by a
// 0 digit, at the option of the implementation; and if R is not 2, 4, 8, 10, 16, or 32, then
// mathInt may be an implementation-approximated value representing the integer value that is
// represented by Z in radix-R notation.)
let math_int = from_js_str_radix(z, r).expect("Already checked");
// 15. If mathInt = 0, then
// a. If sign = -1, return -0𝔽.
// b. Return +0𝔽.
if math_int == 0_f64 {
if sign == -1 {
return Ok(JsValue::new(-0_f64));
}
// 16. Return 𝔽(sign × mathInt).
Ok(JsValue::new(f64::from(sign) * math_int))
} else {
// Not enough arguments to parseInt.
Ok(JsValue::nan())
return Ok(JsValue::new(0_f64));
}
// 16. Return 𝔽(sign × mathInt).
Ok(JsValue::new(f64::from(sign) * math_int))
}
pub(crate) struct ParseInt;

9
core/engine/src/builtins/number/mod.rs

@ -25,6 +25,7 @@ use crate::{
value::{AbstractRelation, IntegerOrInfinity, JsValue},
Context, JsArgs, JsResult, JsString,
};
use boa_macros::js_str;
use boa_profiler::Profiler;
use num_traits::float::FloatCore;
@ -690,13 +691,13 @@ impl Number {
}
if x == -0. {
return Ok(JsValue::new(js_string!("0")));
return Ok(JsValue::new(js_str!("0")));
} else if x.is_nan() {
return Ok(JsValue::new(js_string!("NaN")));
return Ok(JsValue::new(js_str!("NaN")));
} else if x.is_infinite() && x.is_sign_positive() {
return Ok(JsValue::new(js_string!("Infinity")));
return Ok(JsValue::new(js_str!("Infinity")));
} else if x.is_infinite() && x.is_sign_negative() {
return Ok(JsValue::new(js_string!("-Infinity")));
return Ok(JsValue::new(js_str!("-Infinity")));
}
// This is a Optimization from the v8 source code to print values that can fit in a single character

350
core/engine/src/builtins/number/tests.rs

@ -1,11 +1,12 @@
use boa_macros::js_str;
use crate::{
builtins::Number, js_string, run_test_actions, value::AbstractRelation, JsNativeErrorKind,
TestAction,
builtins::Number, run_test_actions, value::AbstractRelation, JsNativeErrorKind, TestAction,
};
#[test]
fn integer_number_primitive_to_number_object() {
run_test_actions([TestAction::assert_eq("(100).toString()", js_string!("100"))]);
run_test_actions([TestAction::assert_eq("(100).toString()", js_str!("100"))]);
}
#[test]
@ -25,136 +26,133 @@ fn call_number() {
#[test]
fn to_exponential() {
run_test_actions([
TestAction::assert_eq("Number().toExponential()", js_string!("0e+0")),
TestAction::assert_eq("Number(5).toExponential()", js_string!("5e+0")),
TestAction::assert_eq("Number(1.234).toExponential()", js_string!("1.234e+0")),
TestAction::assert_eq("Number(1234).toExponential()", js_string!("1.234e+3")),
TestAction::assert_eq("Number().toExponential()", js_str!("0e+0")),
TestAction::assert_eq("Number(5).toExponential()", js_str!("5e+0")),
TestAction::assert_eq("Number(1.234).toExponential()", js_str!("1.234e+0")),
TestAction::assert_eq("Number(1234).toExponential()", js_str!("1.234e+3")),
TestAction::assert_eq(
"Number('I am also not a number').toExponential()",
js_string!("NaN"),
js_str!("NaN"),
),
TestAction::assert_eq("Number('1.23e+2').toExponential()", js_string!("1.23e+2")),
TestAction::assert_eq("Number('1.23e+2').toExponential()", js_str!("1.23e+2")),
]);
}
#[test]
fn to_fixed() {
run_test_actions([
TestAction::assert_eq("Number().toFixed()", js_string!("0")),
TestAction::assert_eq("Number('3.456e+4').toFixed()", js_string!("34560")),
TestAction::assert_eq("Number('3.456e-4').toFixed()", js_string!("0")),
TestAction::assert_eq("Number(5).toFixed()", js_string!("5")),
TestAction::assert_eq(
"Number('I am also not a number').toFixed()",
js_string!("NaN"),
),
TestAction::assert_eq("(1.35).toFixed(1)", js_string!("1.4")),
TestAction::assert_eq("Number().toFixed()", js_str!("0")),
TestAction::assert_eq("Number('3.456e+4').toFixed()", js_str!("34560")),
TestAction::assert_eq("Number('3.456e-4').toFixed()", js_str!("0")),
TestAction::assert_eq("Number(5).toFixed()", js_str!("5")),
TestAction::assert_eq("Number('I am also not a number').toFixed()", js_str!("NaN")),
TestAction::assert_eq("(1.35).toFixed(1)", js_str!("1.4")),
// Test cases from https://source.chromium.org/chromium/chromium/src/+/main:v8/test/mjsunit/number-tostring-func.js;l=157-240;drc=aa3518a0f37245ebe8f062dce97ee492e2a41652
TestAction::assert_eq("(NaN).toFixed(2)", js_string!("NaN")),
TestAction::assert_eq("(1/0).toFixed(2)", js_string!("Infinity")),
TestAction::assert_eq("(-1/0).toFixed(2)", js_string!("-Infinity")),
TestAction::assert_eq("(NaN).toFixed(2)", js_str!("NaN")),
TestAction::assert_eq("(1/0).toFixed(2)", js_str!("Infinity")),
TestAction::assert_eq("(-1/0).toFixed(2)", js_str!("-Infinity")),
TestAction::assert_eq(
"(1111111111111111111111).toFixed(8)",
js_string!("1.1111111111111111e+21"),
),
TestAction::assert_eq("(0.1).toFixed(1)", js_string!("0.1")),
TestAction::assert_eq("(0.1).toFixed(2)", js_string!("0.10")),
TestAction::assert_eq("(0.1).toFixed(3)", js_string!("0.100")),
TestAction::assert_eq("(0.01).toFixed(2)", js_string!("0.01")),
TestAction::assert_eq("(0.01).toFixed(3)", js_string!("0.010")),
TestAction::assert_eq("(0.01).toFixed(4)", js_string!("0.0100")),
TestAction::assert_eq("(0.001).toFixed(2)", js_string!("0.00")),
TestAction::assert_eq("(0.001).toFixed(3)", js_string!("0.001")),
TestAction::assert_eq("(0.001).toFixed(4)", js_string!("0.0010")),
TestAction::assert_eq("(1).toFixed(4)", js_string!("1.0000")),
TestAction::assert_eq("(1).toFixed(1)", js_string!("1.0")),
TestAction::assert_eq("(1).toFixed(0)", js_string!("1")),
TestAction::assert_eq("(12).toFixed(0)", js_string!("12")),
TestAction::assert_eq("(1.1).toFixed(0)", js_string!("1")),
TestAction::assert_eq("(12.1).toFixed(0)", js_string!("12")),
TestAction::assert_eq("(1.12).toFixed(0)", js_string!("1")),
TestAction::assert_eq("(12.12).toFixed(0)", js_string!("12")),
TestAction::assert_eq("(0.0000006).toFixed(7)", js_string!("0.0000006")),
TestAction::assert_eq("(0.00000006).toFixed(8)", js_string!("0.00000006")),
TestAction::assert_eq("(0.00000006).toFixed(9)", js_string!("0.000000060")),
TestAction::assert_eq("(0.00000006).toFixed(10)", js_string!("0.0000000600")),
TestAction::assert_eq("(0).toFixed(0)", js_string!("0")),
TestAction::assert_eq("(0).toFixed(1)", js_string!("0.0")),
TestAction::assert_eq("(0).toFixed(2)", js_string!("0.00")),
js_str!("1.1111111111111111e+21"),
),
TestAction::assert_eq("(0.1).toFixed(1)", js_str!("0.1")),
TestAction::assert_eq("(0.1).toFixed(2)", js_str!("0.10")),
TestAction::assert_eq("(0.1).toFixed(3)", js_str!("0.100")),
TestAction::assert_eq("(0.01).toFixed(2)", js_str!("0.01")),
TestAction::assert_eq("(0.01).toFixed(3)", js_str!("0.010")),
TestAction::assert_eq("(0.01).toFixed(4)", js_str!("0.0100")),
TestAction::assert_eq("(0.001).toFixed(2)", js_str!("0.00")),
TestAction::assert_eq("(0.001).toFixed(3)", js_str!("0.001")),
TestAction::assert_eq("(0.001).toFixed(4)", js_str!("0.0010")),
TestAction::assert_eq("(1).toFixed(4)", js_str!("1.0000")),
TestAction::assert_eq("(1).toFixed(1)", js_str!("1.0")),
TestAction::assert_eq("(1).toFixed(0)", js_str!("1")),
TestAction::assert_eq("(12).toFixed(0)", js_str!("12")),
TestAction::assert_eq("(1.1).toFixed(0)", js_str!("1")),
TestAction::assert_eq("(12.1).toFixed(0)", js_str!("12")),
TestAction::assert_eq("(1.12).toFixed(0)", js_str!("1")),
TestAction::assert_eq("(12.12).toFixed(0)", js_str!("12")),
TestAction::assert_eq("(0.0000006).toFixed(7)", js_str!("0.0000006")),
TestAction::assert_eq("(0.00000006).toFixed(8)", js_str!("0.00000006")),
TestAction::assert_eq("(0.00000006).toFixed(9)", js_str!("0.000000060")),
TestAction::assert_eq("(0.00000006).toFixed(10)", js_str!("0.0000000600")),
TestAction::assert_eq("(0).toFixed(0)", js_str!("0")),
TestAction::assert_eq("(0).toFixed(1)", js_str!("0.0")),
TestAction::assert_eq("(0).toFixed(2)", js_str!("0.00")),
TestAction::assert_eq(
"(-1111111111111111111111).toFixed(8)",
js_string!("-1.1111111111111111e+21"),
),
TestAction::assert_eq("(-0.1).toFixed(1)", js_string!("-0.1")),
TestAction::assert_eq("(-0.1).toFixed(2)", js_string!("-0.10")),
TestAction::assert_eq("(-0.1).toFixed(3)", js_string!("-0.100")),
TestAction::assert_eq("(-0.01).toFixed(2)", js_string!("-0.01")),
TestAction::assert_eq("(-0.01).toFixed(3)", js_string!("-0.010")),
TestAction::assert_eq("(-0.01).toFixed(4)", js_string!("-0.0100")),
TestAction::assert_eq("(-0.001).toFixed(2)", js_string!("-0.00")),
TestAction::assert_eq("(-0.001).toFixed(3)", js_string!("-0.001")),
TestAction::assert_eq("(-0.001).toFixed(4)", js_string!("-0.0010")),
TestAction::assert_eq("(-1).toFixed(4)", js_string!("-1.0000")),
TestAction::assert_eq("(-1).toFixed(1)", js_string!("-1.0")),
TestAction::assert_eq("(-1).toFixed(0)", js_string!("-1")),
TestAction::assert_eq("(-1.1).toFixed(0)", js_string!("-1")),
TestAction::assert_eq("(-12.1).toFixed(0)", js_string!("-12")),
TestAction::assert_eq("(-1.12).toFixed(0)", js_string!("-1")),
TestAction::assert_eq("(-12.12).toFixed(0)", js_string!("-12")),
TestAction::assert_eq("(-0.0000006).toFixed(7)", js_string!("-0.0000006")),
TestAction::assert_eq("(-0.00000006).toFixed(8)", js_string!("-0.00000006")),
TestAction::assert_eq("(-0.00000006).toFixed(9)", js_string!("-0.000000060")),
TestAction::assert_eq("(-0.00000006).toFixed(10)", js_string!("-0.0000000600")),
TestAction::assert_eq("(-0).toFixed(0)", js_string!("0")),
TestAction::assert_eq("(-0).toFixed(1)", js_string!("0.0")),
TestAction::assert_eq("(-0).toFixed(2)", js_string!("0.00")),
TestAction::assert_eq("(0.00001).toFixed(5)", js_string!("0.00001")),
js_str!("-1.1111111111111111e+21"),
),
TestAction::assert_eq("(-0.1).toFixed(1)", js_str!("-0.1")),
TestAction::assert_eq("(-0.1).toFixed(2)", js_str!("-0.10")),
TestAction::assert_eq("(-0.1).toFixed(3)", js_str!("-0.100")),
TestAction::assert_eq("(-0.01).toFixed(2)", js_str!("-0.01")),
TestAction::assert_eq("(-0.01).toFixed(3)", js_str!("-0.010")),
TestAction::assert_eq("(-0.01).toFixed(4)", js_str!("-0.0100")),
TestAction::assert_eq("(-0.001).toFixed(2)", js_str!("-0.00")),
TestAction::assert_eq("(-0.001).toFixed(3)", js_str!("-0.001")),
TestAction::assert_eq("(-0.001).toFixed(4)", js_str!("-0.0010")),
TestAction::assert_eq("(-1).toFixed(4)", js_str!("-1.0000")),
TestAction::assert_eq("(-1).toFixed(1)", js_str!("-1.0")),
TestAction::assert_eq("(-1).toFixed(0)", js_str!("-1")),
TestAction::assert_eq("(-1.1).toFixed(0)", js_str!("-1")),
TestAction::assert_eq("(-12.1).toFixed(0)", js_str!("-12")),
TestAction::assert_eq("(-1.12).toFixed(0)", js_str!("-1")),
TestAction::assert_eq("(-12.12).toFixed(0)", js_str!("-12")),
TestAction::assert_eq("(-0.0000006).toFixed(7)", js_str!("-0.0000006")),
TestAction::assert_eq("(-0.00000006).toFixed(8)", js_str!("-0.00000006")),
TestAction::assert_eq("(-0.00000006).toFixed(9)", js_str!("-0.000000060")),
TestAction::assert_eq("(-0.00000006).toFixed(10)", js_str!("-0.0000000600")),
TestAction::assert_eq("(-0).toFixed(0)", js_str!("0")),
TestAction::assert_eq("(-0).toFixed(1)", js_str!("0.0")),
TestAction::assert_eq("(-0).toFixed(2)", js_str!("0.00")),
TestAction::assert_eq("(0.00001).toFixed(5)", js_str!("0.00001")),
TestAction::assert_eq(
"(0.0000000000000000001).toFixed(20)",
js_string!("0.00000000000000000010"),
js_str!("0.00000000000000000010"),
),
TestAction::assert_eq("(0.00001).toFixed(17)", js_string!("0.00001000000000000")),
TestAction::assert_eq("(1).toFixed(17)", js_string!("1.00000000000000000")),
TestAction::assert_eq("(0.00001).toFixed(17)", js_str!("0.00001000000000000")),
TestAction::assert_eq("(1).toFixed(17)", js_str!("1.00000000000000000")),
TestAction::assert_eq(
"(100000000000000128).toFixed(1)",
js_string!("100000000000000128.0"),
js_str!("100000000000000128.0"),
),
TestAction::assert_eq(
"(10000000000000128).toFixed(2)",
js_string!("10000000000000128.00"),
js_str!("10000000000000128.00"),
),
TestAction::assert_eq(
"(10000000000000128).toFixed(20)",
js_string!("10000000000000128.00000000000000000000"),
js_str!("10000000000000128.00000000000000000000"),
),
TestAction::assert_eq("(-42).toFixed(3)", js_string!("-42.000")),
TestAction::assert_eq("(-42).toFixed(3)", js_str!("-42.000")),
TestAction::assert_eq(
"(-0.0000000000000000001).toFixed(20)",
js_string!("-0.00000000000000000010"),
js_str!("-0.00000000000000000010"),
),
TestAction::assert_eq(
"(0.123123123123123).toFixed(20)",
js_string!("0.12312312312312299889"),
js_str!("0.12312312312312299889"),
),
TestAction::assert_eq(
"(-1000000000000000128).toFixed()",
js_string!("-1000000000000000128"),
js_str!("-1000000000000000128"),
),
TestAction::assert_eq("(0).toFixed()", js_string!("0")),
TestAction::assert_eq("(0).toFixed()", js_str!("0")),
TestAction::assert_eq(
"(1000000000000000128).toFixed()",
js_string!("1000000000000000128"),
js_str!("1000000000000000128"),
),
TestAction::assert_eq("(1000).toFixed()", js_string!("1000")),
TestAction::assert_eq("(0.00001).toFixed()", js_string!("0")),
TestAction::assert_eq("(1000).toFixed()", js_str!("1000")),
TestAction::assert_eq("(0.00001).toFixed()", js_str!("0")),
// Test that we round up even when the last digit generated is even.
// dtoa does not do this in its original form.
TestAction::assert_eq("(0.5).toFixed(0)", js_string!("1")),
TestAction::assert_eq("(-0.5).toFixed(0)", js_string!("-1")),
TestAction::assert_eq("(1.25).toFixed(1)", js_string!("1.3")),
TestAction::assert_eq("(0.5).toFixed(0)", js_str!("1")),
TestAction::assert_eq("(-0.5).toFixed(0)", js_str!("-1")),
TestAction::assert_eq("(1.25).toFixed(1)", js_str!("1.3")),
// This is bizare, but Spidermonkey and KJS behave the same.
TestAction::assert_eq("(234.2040).toFixed(4)", js_string!("234.2040")),
TestAction::assert_eq("(234.2040506).toFixed(4)", js_string!("234.2041")),
TestAction::assert_eq("(234.2040).toFixed(4)", js_str!("234.2040")),
TestAction::assert_eq("(234.2040506).toFixed(4)", js_str!("234.2041")),
]);
}
@ -162,8 +160,8 @@ fn to_fixed() {
#[test]
fn issue_2609() {
run_test_actions([
TestAction::assert_eq("(1.25).toFixed(1)", js_string!("1.3")),
TestAction::assert_eq("(1.35).toFixed(1)", js_string!("1.4")),
TestAction::assert_eq("(1.25).toFixed(1)", js_str!("1.3")),
TestAction::assert_eq("(1.35).toFixed(1)", js_str!("1.4")),
]);
}
@ -172,10 +170,10 @@ fn to_locale_string() {
// TODO: We don't actually do any locale checking here
// To honor the spec we should print numbers according to user locale.
run_test_actions([
TestAction::assert_eq("Number().toLocaleString()", js_string!("0")),
TestAction::assert_eq("Number(5).toLocaleString()", js_string!("5")),
TestAction::assert_eq("Number('345600').toLocaleString()", js_string!("345600")),
TestAction::assert_eq("Number(-25).toLocaleString()", js_string!("-25")),
TestAction::assert_eq("Number().toLocaleString()", js_str!("0")),
TestAction::assert_eq("Number(5).toLocaleString()", js_str!("5")),
TestAction::assert_eq("Number('345600').toLocaleString()", js_str!("345600")),
TestAction::assert_eq("Number(-25).toLocaleString()", js_str!("-25")),
]);
}
@ -183,21 +181,21 @@ fn to_locale_string() {
fn to_precision() {
const ERROR: &str = "precision must be an integer at least 1 and no greater than 100";
run_test_actions([
TestAction::assert_eq("(1/0).toPrecision(3)", js_string!("Infinity")),
TestAction::assert_eq("Number().toPrecision()", js_string!("0")),
TestAction::assert_eq("Number().toPrecision(undefined)", js_string!("0")),
TestAction::assert_eq("(123456789).toPrecision(1)", js_string!("1e+8")),
TestAction::assert_eq("(123456789).toPrecision(4)", js_string!("1.235e+8")),
TestAction::assert_eq("(123456789).toPrecision(9)", js_string!("123456789")),
TestAction::assert_eq("(-123456789).toPrecision(4)", js_string!("-1.235e+8")),
TestAction::assert_eq("(1/0).toPrecision(3)", js_str!("Infinity")),
TestAction::assert_eq("Number().toPrecision()", js_str!("0")),
TestAction::assert_eq("Number().toPrecision(undefined)", js_str!("0")),
TestAction::assert_eq("(123456789).toPrecision(1)", js_str!("1e+8")),
TestAction::assert_eq("(123456789).toPrecision(4)", js_str!("1.235e+8")),
TestAction::assert_eq("(123456789).toPrecision(9)", js_str!("123456789")),
TestAction::assert_eq("(-123456789).toPrecision(4)", js_str!("-1.235e+8")),
TestAction::assert_eq(
"(123456789).toPrecision(50)",
js_string!("123456789.00000000000000000000000000000000000000000"),
js_str!("123456789.00000000000000000000000000000000000000000"),
),
TestAction::assert_eq("(0.1).toPrecision(4)", js_string!("0.1000")),
TestAction::assert_eq("(0.1).toPrecision(4)", js_str!("0.1000")),
TestAction::assert_eq(
"(1/3).toPrecision(60)",
js_string!("0.333333333333333314829616256247390992939472198486328125000000"),
js_str!("0.333333333333333314829616256247390992939472198486328125000000"),
),
TestAction::assert_native_error("(1).toPrecision(101)", JsNativeErrorKind::Range, ERROR),
TestAction::assert_native_error("(1).toPrecision(0)", JsNativeErrorKind::Range, ERROR),
@ -209,132 +207,126 @@ fn to_precision() {
#[test]
fn to_string() {
run_test_actions([
TestAction::assert_eq("Number(NaN).toString()", js_string!("NaN")),
TestAction::assert_eq("Number(1/0).toString()", js_string!("Infinity")),
TestAction::assert_eq("Number(-1/0).toString()", js_string!("-Infinity")),
TestAction::assert_eq("Number(0).toString()", js_string!("0")),
TestAction::assert_eq("Number(9).toString()", js_string!("9")),
TestAction::assert_eq("Number(90).toString()", js_string!("90")),
TestAction::assert_eq("Number(90.12).toString()", js_string!("90.12")),
TestAction::assert_eq("Number(0.1).toString()", js_string!("0.1")),
TestAction::assert_eq("Number(0.01).toString()", js_string!("0.01")),
TestAction::assert_eq("Number(0.0123).toString()", js_string!("0.0123")),
TestAction::assert_eq("Number(0.00001).toString()", js_string!("0.00001")),
TestAction::assert_eq("Number(0.000001).toString()", js_string!("0.000001")),
TestAction::assert_eq("Number(NaN).toString(16)", js_string!("NaN")),
TestAction::assert_eq("Number(1/0).toString(16)", js_string!("Infinity")),
TestAction::assert_eq("Number(-1/0).toString(16)", js_string!("-Infinity")),
TestAction::assert_eq("Number(0).toString(16)", js_string!("0")),
TestAction::assert_eq("Number(9).toString(16)", js_string!("9")),
TestAction::assert_eq("Number(90).toString(16)", js_string!("5a")),
TestAction::assert_eq("Number(90.12).toString(16)", js_string!("5a.1eb851eb852")),
TestAction::assert_eq("Number(0.1).toString(16)", js_string!("0.1999999999999a")),
TestAction::assert_eq("Number(0.01).toString(16)", js_string!("0.028f5c28f5c28f6")),
TestAction::assert_eq(
"Number(0.0123).toString(16)",
js_string!("0.032617c1bda511a"),
),
TestAction::assert_eq("Number(NaN).toString()", js_str!("NaN")),
TestAction::assert_eq("Number(1/0).toString()", js_str!("Infinity")),
TestAction::assert_eq("Number(-1/0).toString()", js_str!("-Infinity")),
TestAction::assert_eq("Number(0).toString()", js_str!("0")),
TestAction::assert_eq("Number(9).toString()", js_str!("9")),
TestAction::assert_eq("Number(90).toString()", js_str!("90")),
TestAction::assert_eq("Number(90.12).toString()", js_str!("90.12")),
TestAction::assert_eq("Number(0.1).toString()", js_str!("0.1")),
TestAction::assert_eq("Number(0.01).toString()", js_str!("0.01")),
TestAction::assert_eq("Number(0.0123).toString()", js_str!("0.0123")),
TestAction::assert_eq("Number(0.00001).toString()", js_str!("0.00001")),
TestAction::assert_eq("Number(0.000001).toString()", js_str!("0.000001")),
TestAction::assert_eq("Number(NaN).toString(16)", js_str!("NaN")),
TestAction::assert_eq("Number(1/0).toString(16)", js_str!("Infinity")),
TestAction::assert_eq("Number(-1/0).toString(16)", js_str!("-Infinity")),
TestAction::assert_eq("Number(0).toString(16)", js_str!("0")),
TestAction::assert_eq("Number(9).toString(16)", js_str!("9")),
TestAction::assert_eq("Number(90).toString(16)", js_str!("5a")),
TestAction::assert_eq("Number(90.12).toString(16)", js_str!("5a.1eb851eb852")),
TestAction::assert_eq("Number(0.1).toString(16)", js_str!("0.1999999999999a")),
TestAction::assert_eq("Number(0.01).toString(16)", js_str!("0.028f5c28f5c28f6")),
TestAction::assert_eq("Number(0.0123).toString(16)", js_str!("0.032617c1bda511a")),
TestAction::assert_eq(
"Number(111111111111111111111).toString(16)",
js_string!("605f9f6dd18bc8000"),
js_str!("605f9f6dd18bc8000"),
),
TestAction::assert_eq(
"Number(1111111111111111111111).toString(16)",
js_string!("3c3bc3a4a2f75c0000"),
js_str!("3c3bc3a4a2f75c0000"),
),
TestAction::assert_eq(
"Number(11111111111111111111111).toString(16)",
js_string!("25a55a46e5da9a00000"),
js_str!("25a55a46e5da9a00000"),
),
TestAction::assert_eq(
"Number(0.00001).toString(16)",
js_string!("0.0000a7c5ac471b4788"),
js_str!("0.0000a7c5ac471b4788"),
),
TestAction::assert_eq(
"Number(0.000001).toString(16)",
js_string!("0.000010c6f7a0b5ed8d"),
js_str!("0.000010c6f7a0b5ed8d"),
),
TestAction::assert_eq(
"Number(0.0000001).toString(16)",
js_string!("0.000001ad7f29abcaf48"),
js_str!("0.000001ad7f29abcaf48"),
),
TestAction::assert_eq(
"Number(0.00000012).toString(16)",
js_string!("0.000002036565348d256"),
js_str!("0.000002036565348d256"),
),
TestAction::assert_eq(
"Number(0.000000123).toString(16)",
js_string!("0.0000021047ee22aa466"),
js_str!("0.0000021047ee22aa466"),
),
TestAction::assert_eq(
"Number(0.00000001).toString(16)",
js_string!("0.0000002af31dc4611874"),
js_str!("0.0000002af31dc4611874"),
),
TestAction::assert_eq(
"Number(0.000000012).toString(16)",
js_string!("0.000000338a23b87483be"),
js_str!("0.000000338a23b87483be"),
),
TestAction::assert_eq(
"Number(0.0000000123).toString(16)",
js_string!("0.00000034d3fe36aaa0a2"),
js_str!("0.00000034d3fe36aaa0a2"),
),
TestAction::assert_eq("Number(-0).toString(16)", js_string!("0")),
TestAction::assert_eq("Number(-9).toString(16)", js_string!("-9")),
TestAction::assert_eq("Number(-0).toString(16)", js_str!("0")),
TestAction::assert_eq("Number(-9).toString(16)", js_str!("-9")),
//
TestAction::assert_eq("Number(-90).toString(16)", js_string!("-5a")),
TestAction::assert_eq("Number(-90.12).toString(16)", js_string!("-5a.1eb851eb852")),
TestAction::assert_eq("Number(-0.1).toString(16)", js_string!("-0.1999999999999a")),
TestAction::assert_eq(
"Number(-0.01).toString(16)",
js_string!("-0.028f5c28f5c28f6"),
),
TestAction::assert_eq("Number(-90).toString(16)", js_str!("-5a")),
TestAction::assert_eq("Number(-90.12).toString(16)", js_str!("-5a.1eb851eb852")),
TestAction::assert_eq("Number(-0.1).toString(16)", js_str!("-0.1999999999999a")),
TestAction::assert_eq("Number(-0.01).toString(16)", js_str!("-0.028f5c28f5c28f6")),
TestAction::assert_eq(
"Number(-0.0123).toString(16)",
js_string!("-0.032617c1bda511a"),
js_str!("-0.032617c1bda511a"),
),
TestAction::assert_eq(
"Number(-111111111111111111111).toString(16)",
js_string!("-605f9f6dd18bc8000"),
js_str!("-605f9f6dd18bc8000"),
),
TestAction::assert_eq(
"Number(-1111111111111111111111).toString(16)",
js_string!("-3c3bc3a4a2f75c0000"),
js_str!("-3c3bc3a4a2f75c0000"),
),
TestAction::assert_eq(
"Number(-11111111111111111111111).toString(16)",
js_string!("-25a55a46e5da9a00000"),
js_str!("-25a55a46e5da9a00000"),
),
TestAction::assert_eq(
"Number(-0.00001).toString(16)",
js_string!("-0.0000a7c5ac471b4788"),
js_str!("-0.0000a7c5ac471b4788"),
),
TestAction::assert_eq(
"Number(-0.000001).toString(16)",
js_string!("-0.000010c6f7a0b5ed8d"),
js_str!("-0.000010c6f7a0b5ed8d"),
),
TestAction::assert_eq(
"Number(-0.0000001).toString(16)",
js_string!("-0.000001ad7f29abcaf48"),
js_str!("-0.000001ad7f29abcaf48"),
),
TestAction::assert_eq(
"Number(-0.00000012).toString(16)",
js_string!("-0.000002036565348d256"),
js_str!("-0.000002036565348d256"),
),
TestAction::assert_eq(
"Number(-0.000000123).toString(16)",
js_string!("-0.0000021047ee22aa466"),
js_str!("-0.0000021047ee22aa466"),
),
TestAction::assert_eq(
"Number(-0.00000001).toString(16)",
js_string!("-0.0000002af31dc4611874"),
js_str!("-0.0000002af31dc4611874"),
),
TestAction::assert_eq(
"Number(-0.000000012).toString(16)",
js_string!("-0.000000338a23b87483be"),
js_str!("-0.000000338a23b87483be"),
),
TestAction::assert_eq(
"Number(-0.0000000123).toString(16)",
js_string!("-0.00000034d3fe36aaa0a2"),
js_str!("-0.00000034d3fe36aaa0a2"),
),
]);
}
@ -342,26 +334,26 @@ fn to_string() {
#[test]
fn num_to_string_exponential() {
run_test_actions([
TestAction::assert_eq("(0).toString()", js_string!("0")),
TestAction::assert_eq("(-0).toString()", js_string!("0")),
TestAction::assert_eq("(0).toString()", js_str!("0")),
TestAction::assert_eq("(-0).toString()", js_str!("0")),
TestAction::assert_eq(
"(111111111111111111111).toString()",
js_string!("111111111111111110000"),
js_str!("111111111111111110000"),
),
TestAction::assert_eq(
"(1111111111111111111111).toString()",
js_string!("1.1111111111111111e+21"),
js_str!("1.1111111111111111e+21"),
),
TestAction::assert_eq(
"(11111111111111111111111).toString()",
js_string!("1.1111111111111111e+22"),
),
TestAction::assert_eq("(0.0000001).toString()", js_string!("1e-7")),
TestAction::assert_eq("(0.00000012).toString()", js_string!("1.2e-7")),
TestAction::assert_eq("(0.000000123).toString()", js_string!("1.23e-7")),
TestAction::assert_eq("(0.00000001).toString()", js_string!("1e-8")),
TestAction::assert_eq("(0.000000012).toString()", js_string!("1.2e-8")),
TestAction::assert_eq("(0.0000000123).toString()", js_string!("1.23e-8")),
js_str!("1.1111111111111111e+22"),
),
TestAction::assert_eq("(0.0000001).toString()", js_str!("1e-7")),
TestAction::assert_eq("(0.00000012).toString()", js_str!("1.2e-7")),
TestAction::assert_eq("(0.000000123).toString()", js_str!("1.23e-7")),
TestAction::assert_eq("(0.00000001).toString()", js_str!("1e-8")),
TestAction::assert_eq("(0.000000012).toString()", js_str!("1.2e-8")),
TestAction::assert_eq("(0.0000000123).toString()", js_str!("1.23e-8")),
]);
}
@ -642,11 +634,11 @@ fn issue_2717() {
run_test_actions([
TestAction::assert_eq(
"(0.1600057092765239).toString(36)",
js_string!("0.5rd85dm1ixq"),
js_str!("0.5rd85dm1ixq"),
),
TestAction::assert_eq(
"(0.23046743672210102).toString(36)",
js_string!("0.8aoosla2phj"),
js_str!("0.8aoosla2phj"),
),
]);
}

49
core/engine/src/builtins/object/mod.rs

@ -13,8 +13,6 @@
//! [spec]: https://tc39.es/ecma262/#sec-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
use std::ops::Deref;
use super::{
error::ErrorObject, Array, BuiltInBuilder, BuiltInConstructor, Date, IntrinsicObject, RegExp,
};
@ -30,12 +28,13 @@ use crate::{
},
property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind},
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
symbol::JsSymbol,
value::JsValue,
Context, JsArgs, JsData, JsResult, JsString,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
use tap::{Conv, Pipe};
@ -63,7 +62,7 @@ impl IntrinsicObject for OrdinaryObject {
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.inherits(None)
.accessor(
utf16!("__proto__"),
js_string!("__proto__"),
Some(legacy_proto_getter),
Some(legacy_setter_proto),
Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
@ -574,42 +573,42 @@ impl OrdinaryObject {
// 4. If Desc has a [[Value]] field, then
if let Some(value) = desc.value() {
// a. Perform ! CreateDataPropertyOrThrow(obj, "value", Desc.[[Value]]).
obj.create_data_property_or_throw(utf16!("value"), value.clone(), context)
obj.create_data_property_or_throw(js_str!("value"), value.clone(), context)
.expect("CreateDataPropertyOrThrow cannot fail here");
}
// 5. If Desc has a [[Writable]] field, then
if let Some(writable) = desc.writable() {
// a. Perform ! CreateDataPropertyOrThrow(obj, "writable", Desc.[[Writable]]).
obj.create_data_property_or_throw(utf16!("writable"), writable, context)
obj.create_data_property_or_throw(js_str!("writable"), writable, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
}
// 6. If Desc has a [[Get]] field, then
if let Some(get) = desc.get() {
// a. Perform ! CreateDataPropertyOrThrow(obj, "get", Desc.[[Get]]).
obj.create_data_property_or_throw(utf16!("get"), get.clone(), context)
obj.create_data_property_or_throw(js_str!("get"), get.clone(), context)
.expect("CreateDataPropertyOrThrow cannot fail here");
}
// 7. If Desc has a [[Set]] field, then
if let Some(set) = desc.set() {
// a. Perform ! CreateDataPropertyOrThrow(obj, "set", Desc.[[Set]]).
obj.create_data_property_or_throw(utf16!("set"), set.clone(), context)
obj.create_data_property_or_throw(js_str!("set"), set.clone(), context)
.expect("CreateDataPropertyOrThrow cannot fail here");
}
// 8. If Desc has an [[Enumerable]] field, then
if let Some(enumerable) = desc.enumerable() {
// a. Perform ! CreateDataPropertyOrThrow(obj, "enumerable", Desc.[[Enumerable]]).
obj.create_data_property_or_throw(utf16!("enumerable"), enumerable, context)
obj.create_data_property_or_throw(js_str!("enumerable"), enumerable, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
}
// 9. If Desc has a [[Configurable]] field, then
if let Some(configurable) = desc.configurable() {
// a. Perform ! CreateDataPropertyOrThrow(obj, "configurable", Desc.[[Configurable]]).
obj.create_data_property_or_throw(utf16!("configurable"), configurable, context)
obj.create_data_property_or_throw(js_str!("configurable"), configurable, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
}
@ -825,11 +824,11 @@ impl OrdinaryObject {
pub fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. If the this value is undefined, return "[object Undefined]".
if this.is_undefined() {
return Ok(js_string!("[object Undefined]").into());
return Ok(js_str!("[object Undefined]").into());
}
// 2. If the this value is null, return "[object Null]".
if this.is_null() {
return Ok(js_string!("[object Null]").into());
return Ok(js_str!("[object Null]").into());
}
// 3. Let O be ! ToObject(this value).
let o = this.to_object(context).expect("toObject cannot fail here");
@ -837,37 +836,37 @@ impl OrdinaryObject {
// 4. Let isArray be ? IsArray(O).
// 5. If isArray is true, let builtinTag be "Array".
let builtin_tag = if o.is_array_abstract()? {
utf16!("Array")
js_str!("Array")
} else {
let o_borrow = o.borrow();
if o_borrow.is_arguments() {
// 6. Else if O has a [[ParameterMap]] internal slot, let builtinTag be "Arguments".
utf16!("Arguments")
js_str!("Arguments")
} else if o.is_callable() {
// 7. Else if O has a [[Call]] internal method, let builtinTag be "Function".
utf16!("Function")
js_str!("Function")
} else if o.is::<ErrorObject>() {
// 8. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error".
utf16!("Error")
js_str!("Error")
} else if o.is::<bool>() {
// 9. Else if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean".
utf16!("Boolean")
js_str!("Boolean")
} else if o.is::<f64>() {
// 10. Else if O has a [[NumberData]] internal slot, let builtinTag be "Number".
utf16!("Number")
js_str!("Number")
} else if o.is::<JsString>() {
// 11. Else if O has a [[StringData]] internal slot, let builtinTag be "String".
utf16!("String")
js_str!("String")
} else if o.is::<Date>() {
// 12. Else if O has a [[DateValue]] internal slot, let builtinTag be "Date".
utf16!("Date")
js_str!("Date")
} else if o.is::<RegExp>() {
// 13. Else if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp".
utf16!("RegExp")
js_str!("RegExp")
} else {
// 14. Else, let builtinTag be "Object".
utf16!("Object")
js_str!("Object")
}
};
@ -875,10 +874,10 @@ impl OrdinaryObject {
let tag = o.get(JsSymbol::to_string_tag(), context)?;
// 16. If Type(tag) is not String, set tag to builtinTag.
let tag_str = tag.as_string().map_or(builtin_tag, JsString::deref);
let tag_str = tag.as_string().map_or(builtin_tag, JsString::as_str);
// 17. Return the string-concatenation of "[object ", tag, and "]".
Ok(js_string!(utf16!("[object "), tag_str, utf16!("]")).into())
Ok(js_string!(js_str!("[object "), tag_str, js_str!("]")).into())
}
/// `Object.prototype.toLocaleString( [ reserved1 [ , reserved2 ] ] )`
@ -897,7 +896,7 @@ impl OrdinaryObject {
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Return ? Invoke(O, "toString").
this.invoke(utf16!("toString"), &[], context)
this.invoke(js_str!("toString"), &[], context)
}
/// `Object.prototype.hasOwnProperty( property )`

27
core/engine/src/builtins/object/tests.rs

@ -1,4 +1,5 @@
use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction};
use boa_macros::js_str;
use indoc::indoc;
#[test]
@ -137,21 +138,21 @@ fn object_to_string() {
"#}),
TestAction::assert_eq(
"Object.prototype.toString.call(undefined)",
js_string!("[object Undefined]"),
js_str!("[object Undefined]"),
),
TestAction::assert_eq(
"Object.prototype.toString.call(null)",
js_string!("[object Null]"),
js_str!("[object Null]"),
),
TestAction::assert_eq("[].toString()", js_string!("[object Array]")),
TestAction::assert_eq("(() => {}).toString()", js_string!("[object Function]")),
TestAction::assert_eq("(new Error('')).toString()", js_string!("[object Error]")),
TestAction::assert_eq("Boolean().toString()", js_string!("[object Boolean]")),
TestAction::assert_eq("Number(42).toString()", js_string!("[object Number]")),
TestAction::assert_eq("String('boa').toString()", js_string!("[object String]")),
TestAction::assert_eq("(new Date()).toString()", js_string!("[object Date]")),
TestAction::assert_eq("/boa/.toString()", js_string!("[object RegExp]")),
TestAction::assert_eq("({}).toString()", js_string!("[object Object]")),
TestAction::assert_eq("[].toString()", js_str!("[object Array]")),
TestAction::assert_eq("(() => {}).toString()", js_str!("[object Function]")),
TestAction::assert_eq("(new Error('')).toString()", js_str!("[object Error]")),
TestAction::assert_eq("Boolean().toString()", js_str!("[object Boolean]")),
TestAction::assert_eq("Number(42).toString()", js_str!("[object Number]")),
TestAction::assert_eq("String('boa').toString()", js_str!("[object String]")),
TestAction::assert_eq("(new Date()).toString()", js_str!("[object Date]")),
TestAction::assert_eq("/boa/.toString()", js_str!("[object RegExp]")),
TestAction::assert_eq("({}).toString()", js_str!("[object Object]")),
]);
}
@ -163,7 +164,7 @@ fn define_symbol_property() {
let sym = Symbol("key");
Object.defineProperty(obj, sym, { value: "val" });
"#}),
TestAction::assert_eq("obj[sym]", js_string!("val")),
TestAction::assert_eq("obj[sym]", js_str!("val")),
]);
}

4
core/engine/src/builtins/options.rs

@ -2,7 +2,7 @@
use std::{fmt, str::FromStr};
use crate::{object::JsObject, Context, JsNativeError, JsResult, JsString, JsValue};
use crate::{object::JsObject, string::JsStr, Context, JsNativeError, JsResult, JsString, JsValue};
/// A type used as an option parameter for [`get_option`].
pub(crate) trait OptionType: Sized {
@ -50,7 +50,7 @@ where
/// [spec]: https://tc39.es/ecma402/#sec-getoption
pub(crate) fn get_option<T: OptionType>(
options: &JsObject,
property: &[u16],
property: JsStr<'_>,
context: &mut Context,
) -> JsResult<Option<T>> {
// 1. Let value be ? Get(options, property).

38
core/engine/src/builtins/promise/mod.rs

@ -17,13 +17,13 @@ use crate::{
},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
symbol::JsSymbol,
value::JsValue,
Context, JsArgs, JsError, JsResult, JsString,
};
use boa_gc::{custom_trace, Finalize, Gc, GcRefCell, Trace};
use boa_macros::JsData;
use boa_macros::{js_str, JsData};
use boa_profiler::Profiler;
use std::{cell::Cell, rc::Rc};
use tap::{Conv, Pipe};
@ -768,7 +768,7 @@ impl Promise {
// s. Perform ? Invoke(nextPromise, "then", « onFulfilled, resultCapability.[[Reject]] »).
next_promise.invoke(
utf16!("then"),
js_str!("then"),
&[
on_fulfilled.into(),
result_capability.functions.reject.clone().into(),
@ -957,15 +957,15 @@ impl Promise {
// 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled").
obj.create_data_property_or_throw(
utf16!("status"),
js_string!("fulfilled"),
js_str!("status"),
js_str!("fulfilled"),
context,
)
.expect("cannot fail per spec");
// 11. Perform ! CreateDataPropertyOrThrow(obj, "value", x).
obj.create_data_property_or_throw(
utf16!("value"),
js_str!("value"),
args.get_or_undefined(0).clone(),
context,
)
@ -1047,15 +1047,15 @@ impl Promise {
// 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected").
obj.create_data_property_or_throw(
utf16!("status"),
js_string!("rejected"),
js_str!("status"),
js_str!("rejected"),
context,
)
.expect("cannot fail per spec");
// 11. Perform ! CreateDataPropertyOrThrow(obj, "reason", x).
obj.create_data_property_or_throw(
utf16!("reason"),
js_str!("reason"),
args.get_or_undefined(0).clone(),
context,
)
@ -1107,7 +1107,7 @@ impl Promise {
// ab. Perform ? Invoke(nextPromise, "then", « onFulfilled, onRejected »).
next_promise.invoke(
utf16!("then"),
js_str!("then"),
&[on_fulfilled.into(), on_rejected.into()],
context,
)?;
@ -1343,7 +1343,7 @@ impl Promise {
// s. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], onRejected »).
next_promise.invoke(
utf16!("then"),
js_str!("then"),
&[
result_capability.functions.resolve.clone().into(),
on_rejected.into(),
@ -1466,7 +1466,7 @@ impl Promise {
// i. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »).
next_promise.invoke(
utf16!("then"),
js_str!("then"),
&[
result_capability.functions.resolve.clone().into(),
result_capability.functions.reject.clone().into(),
@ -1621,7 +1621,7 @@ impl Promise {
let promise = this;
// 2. Return ? Invoke(promise, "then", « undefined, onRejected »).
promise.invoke(
utf16!("then"),
js_str!("then"),
&[JsValue::undefined(), on_rejected.clone()],
context,
)
@ -1667,7 +1667,7 @@ impl Promise {
// a. Let thenFinally be onFinally.
// b. Let catchFinally be onFinally.
// 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
let then = promise.get(utf16!("then"), context)?;
let then = promise.get(js_str!("then"), context)?;
return then.call(this, &[on_finally.clone(), on_finally.clone()], context);
};
@ -1675,7 +1675,7 @@ impl Promise {
Self::then_catch_finally_closures(c, on_finally, context);
// 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
let then = promise.get(utf16!("then"), context)?;
let then = promise.get(js_str!("then"), context)?;
then.call(this, &[then_finally.into(), catch_finally.into()], context)
}
@ -1730,7 +1730,7 @@ impl Promise {
let value_thunk = return_value.length(0).name("").build();
// v. Return ? Invoke(promise, "then", « valueThunk »).
promise.invoke(utf16!("then"), &[value_thunk.into()], context)
promise.invoke(js_str!("then"), &[value_thunk.into()], context)
},
FinallyCaptures {
on_finally: on_finally.clone(),
@ -1781,7 +1781,7 @@ impl Promise {
let thrower = throw_reason.length(0).name("").build();
// v. Return ? Invoke(promise, "then", « thrower »).
promise.invoke(utf16!("then"), &[thrower.into()], context)
promise.invoke(js_str!("then"), &[thrower.into()], context)
},
FinallyCaptures { on_finally, c },
),
@ -1986,7 +1986,7 @@ impl Promise {
context: &mut Context,
) -> JsResult<JsObject> {
// 1. Let promiseResolve be ? Get(promiseConstructor, "resolve").
let promise_resolve = promise_constructor.get(utf16!("resolve"), context)?;
let promise_resolve = promise_constructor.get(js_str!("resolve"), context)?;
// 2. If IsCallable(promiseResolve) is false, throw a TypeError exception.
promise_resolve.as_callable().cloned().ok_or_else(|| {
@ -2184,7 +2184,7 @@ impl Promise {
};
// 9. Let then be Completion(Get(resolution, "then")).
let then_action = match then.get(utf16!("then"), context) {
let then_action = match then.get(js_str!("then"), context) {
// 10. If then is an abrupt completion, then
Err(e) => {
// a. Perform RejectPromise(promise, then.[[Value]]).

33
core/engine/src/builtins/proxy/mod.rs

@ -26,11 +26,12 @@ use crate::{
},
property::{PropertyDescriptor, PropertyKey},
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
value::Type,
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_gc::{Finalize, GcRefCell, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
use rustc_hash::FxHashSet;
@ -233,12 +234,12 @@ impl Proxy {
// 6. Perform ! CreateDataPropertyOrThrow(result, "proxy", p).
result
.create_data_property_or_throw(utf16!("proxy"), p, context)
.create_data_property_or_throw(js_str!("proxy"), p, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
// 7. Perform ! CreateDataPropertyOrThrow(result, "revoke", revoker).
result
.create_data_property_or_throw(utf16!("revoke"), revoker, context)
.create_data_property_or_throw(js_str!("revoke"), revoker, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
// 8. Return result.
@ -266,7 +267,7 @@ pub(crate) fn proxy_exotic_get_prototype_of(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "getPrototypeOf").
let Some(trap) = handler.get_method(utf16!("getPrototypeOf"), context)? else {
let Some(trap) = handler.get_method(js_str!("getPrototypeOf"), context)? else {
// 6. If trap is undefined, then
// a. Return ? target.[[GetPrototypeOf]]().
return target.__get_prototype_of__(context);
@ -327,7 +328,7 @@ pub(crate) fn proxy_exotic_set_prototype_of(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "setPrototypeOf").
let Some(trap) = handler.get_method(utf16!("setPrototypeOf"), context)? else {
let Some(trap) = handler.get_method(js_str!("setPrototypeOf"), context)? else {
// 6. If trap is undefined, then
// a. Return ? target.[[SetPrototypeOf]](V).
return target.__set_prototype_of__(val, context);
@ -386,7 +387,7 @@ pub(crate) fn proxy_exotic_is_extensible(obj: &JsObject, context: &mut Context)
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "isExtensible").
let Some(trap) = handler.get_method(utf16!("isExtensible"), context)? else {
let Some(trap) = handler.get_method(js_str!("isExtensible"), context)? else {
// 6. If trap is undefined, then
// a. Return ? IsExtensible(target).
return target.is_extensible(context);
@ -431,7 +432,7 @@ pub(crate) fn proxy_exotic_prevent_extensions(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "preventExtensions").
let Some(trap) = handler.get_method(utf16!("preventExtensions"), context)? else {
let Some(trap) = handler.get_method(js_str!("preventExtensions"), context)? else {
// 6. If trap is undefined, then
// a. Return ? target.[[PreventExtensions]]().
return target.__prevent_extensions__(context);
@ -478,7 +479,7 @@ pub(crate) fn proxy_exotic_get_own_property(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "getOwnPropertyDescriptor").
let Some(trap) = handler.get_method(utf16!("getOwnPropertyDescriptor"), context)? else {
let Some(trap) = handler.get_method(js_str!("getOwnPropertyDescriptor"), context)? else {
// 6. If trap is undefined, then
// a. Return ? target.[[GetOwnProperty]](P).
return target.__get_own_property__(key, context);
@ -603,7 +604,7 @@ pub(crate) fn proxy_exotic_define_own_property(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "defineProperty").
let Some(trap) = handler.get_method(utf16!("defineProperty"), context)? else {
let Some(trap) = handler.get_method(js_str!("defineProperty"), context)? else {
// 6. If trap is undefined, then
// a. Return ? target.[[DefineOwnProperty]](P, Desc).
return target.__define_own_property__(key, desc, context);
@ -715,7 +716,7 @@ pub(crate) fn proxy_exotic_has_property(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "has").
let Some(trap) = handler.get_method(utf16!("has"), context)? else {
let Some(trap) = handler.get_method(js_str!("has"), context)? else {
// 6. If trap is undefined, then
// a. Return ? target.[[HasProperty]](P).
return target.has_property(key.clone(), context);
@ -783,7 +784,7 @@ pub(crate) fn proxy_exotic_get(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "get").
let Some(trap) = handler.get_method(utf16!("get"), context)? else {
let Some(trap) = handler.get_method(js_str!("get"), context)? else {
// 6. If trap is undefined, then
// a. Return ? target.[[Get]](P, Receiver).
return target.__get__(key, receiver, context);
@ -853,7 +854,7 @@ pub(crate) fn proxy_exotic_set(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "set").
let Some(trap) = handler.get_method(utf16!("set"), context)? else {
let Some(trap) = handler.get_method(js_str!("set"), context)? else {
// 6. If trap is undefined, then
// a. Return ? target.[[Set]](P, V, Receiver).
return target.__set__(key, value, receiver, context);
@ -933,7 +934,7 @@ pub(crate) fn proxy_exotic_delete(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "deleteProperty").
let Some(trap) = handler.get_method(utf16!("deleteProperty"), context)? else {
let Some(trap) = handler.get_method(js_str!("deleteProperty"), context)? else {
// 6. If trap is undefined, then
// a. Return ? target.[[Delete]](P).
return target.__delete__(key, context);
@ -998,7 +999,7 @@ pub(crate) fn proxy_exotic_own_property_keys(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "ownKeys").
let Some(trap) = handler.get_method(utf16!("ownKeys"), context)? else {
let Some(trap) = handler.get_method(js_str!("ownKeys"), context)? else {
// 6. If trap is undefined, then
// a. Return ? target.[[OwnPropertyKeys]]().
return target.__own_property_keys__(context);
@ -1132,7 +1133,7 @@ fn proxy_exotic_call(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "apply").
let Some(trap) = handler.get_method(utf16!("apply"), context)? else {
let Some(trap) = handler.get_method(js_str!("apply"), context)? else {
// 6. If trap is undefined, then
// a. Return ? Call(target, thisArgument, argumentsList).
return Ok(target.__call__(argument_count));
@ -1180,7 +1181,7 @@ fn proxy_exotic_construct(
assert!(target.is_constructor());
// 6. Let trap be ? GetMethod(handler, "construct").
let Some(trap) = handler.get_method(utf16!("construct"), context)? else {
let Some(trap) = handler.get_method(js_str!("construct"), context)? else {
// 7. If trap is undefined, then
// a. Return ? Construct(target, argumentsList, newTarget).
return Ok(target.__construct__(argument_count));

10
core/engine/src/builtins/reflect/tests.rs

@ -1,4 +1,5 @@
use crate::{js_string, run_test_actions, JsValue, TestAction};
use crate::{run_test_actions, JsValue, TestAction};
use boa_macros::js_str;
use indoc::indoc;
#[test]
@ -68,10 +69,7 @@ fn get_prototype_of() {
function F() { this.p = 42 };
let f = new F();
"#}),
TestAction::assert_eq(
"Reflect.getPrototypeOf(f).constructor.name",
js_string!("F"),
),
TestAction::assert_eq("Reflect.getPrototypeOf(f).constructor.name", js_str!("F")),
]);
}
@ -134,6 +132,6 @@ fn set_prototype_of() {
let obj = {}
Reflect.setPrototypeOf(obj, F);
"#}),
TestAction::assert_eq("Reflect.getPrototypeOf(obj).name", js_string!("F")),
TestAction::assert_eq("Reflect.getPrototypeOf(obj).name", js_str!("F")),
]);
}

199
core/engine/src/builtins/regexp/mod.rs

@ -17,12 +17,13 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject, CONSTRUCTOR},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16, CodePoint},
string::{common::StaticJsStrings, CodePoint, JsStrVariant},
symbol::JsSymbol,
value::JsValue,
Context, JsArgs, JsData, JsResult, JsString,
};
use boa_gc::{Finalize, Trace};
use boa_macros::{js_str, utf16};
use boa_parser::lexer::regex::RegExpFlags;
use boa_profiler::Profiler;
use regress::{Flags, Range, Regex};
@ -238,12 +239,12 @@ impl BuiltInConstructor for RegExp {
(p, f)
} else if let Some(pattern) = pattern_is_regexp {
// a. Let P be ? Get(pattern, "source").
let p = pattern.get(js_string!("source"), context)?;
let p = pattern.get(js_str!("source"), context)?;
// b. If flags is undefined, then
let f = if flags.is_undefined() {
// i. Let F be ? Get(pattern, "flags").
pattern.get(js_string!("flags"), context)?
pattern.get(js_str!("flags"), context)?
// c. Else,
} else {
// i. Let F be flags.
@ -636,54 +637,54 @@ impl RegExp {
};
// 3. Let codeUnits be a new empty List.
let mut code_units = Vec::new();
let mut code_units = String::new();
// 4. Let hasIndices be ToBoolean(? Get(R, "hasIndices")).
// 5. If hasIndices is true, append the code unit 0x0064 (LATIN SMALL LETTER D) to codeUnits.
if object.get(utf16!("hasIndices"), context)?.to_boolean() {
code_units.extend_from_slice(utf16!("d"));
if object.get(js_str!("hasIndices"), context)?.to_boolean() {
code_units.push('d');
}
// 6. Let global be ToBoolean(? Get(R, "global")).
// 7. If global is true, append the code unit 0x0067 (LATIN SMALL LETTER G) to codeUnits.
if object.get(utf16!("global"), context)?.to_boolean() {
code_units.extend_from_slice(utf16!("g"));
if object.get(js_str!("global"), context)?.to_boolean() {
code_units.push('g');
}
// 8. Let ignoreCase be ToBoolean(? Get(R, "ignoreCase")).
// 9. If ignoreCase is true, append the code unit 0x0069 (LATIN SMALL LETTER I) to codeUnits.
if object.get(utf16!("ignoreCase"), context)?.to_boolean() {
code_units.extend_from_slice(utf16!("i"));
if object.get(js_str!("ignoreCase"), context)?.to_boolean() {
code_units.push('i');
}
// 10. Let multiline be ToBoolean(? Get(R, "multiline")).
// 11. If multiline is true, append the code unit 0x006D (LATIN SMALL LETTER M) to codeUnits.
if object.get(utf16!("multiline"), context)?.to_boolean() {
code_units.extend_from_slice(utf16!("m"));
if object.get(js_str!("multiline"), context)?.to_boolean() {
code_units.push('m');
}
// 12. Let dotAll be ToBoolean(? Get(R, "dotAll")).
// 13. If dotAll is true, append the code unit 0x0073 (LATIN SMALL LETTER S) to codeUnits.
if object.get(utf16!("dotAll"), context)?.to_boolean() {
code_units.extend_from_slice(utf16!("s"));
if object.get(js_str!("dotAll"), context)?.to_boolean() {
code_units.push('s');
}
// 14. Let unicode be ToBoolean(? Get(R, "unicode")).
// 15. If unicode is true, append the code unit 0x0075 (LATIN SMALL LETTER U) to codeUnits.
if object.get(utf16!("unicode"), context)?.to_boolean() {
code_units.extend_from_slice(utf16!("u"));
if object.get(js_str!("unicode"), context)?.to_boolean() {
code_units.push('u');
}
// 16. Let unicodeSets be ToBoolean(? Get(R, "unicodeSets")).
// 17. If unicodeSets is true, append the code unit 0x0076 (LATIN SMALL LETTER V) to codeUnits.
if object.get(utf16!("unicodeSets"), context)?.to_boolean() {
code_units.extend_from_slice(utf16!("v"));
if object.get(js_str!("unicodeSets"), context)?.to_boolean() {
code_units.push('v');
}
// 18. Let sticky be ToBoolean(? Get(R, "sticky")).
// 19. If sticky is true, append the code unit 0x0079 (LATIN SMALL LETTER Y) to codeUnits.
if object.get(utf16!("sticky"), context)?.to_boolean() {
code_units.extend_from_slice(utf16!("y"));
if object.get(js_str!("sticky"), context)?.to_boolean() {
code_units.push('y');
}
// 20. Return the String value whose code units are the elements of the List codeUnits.
@ -724,7 +725,7 @@ impl RegExp {
this,
&JsValue::new(context.intrinsics().constructors().regexp().prototype()),
) {
Ok(JsValue::new(js_string!("(?:)")))
Ok(JsValue::new(js_str!("(?:)")))
} else {
Err(JsNativeError::typ()
.with_message("RegExp.prototype.source method called on incompatible value")
@ -752,7 +753,7 @@ impl RegExp {
/// [spec]: https://tc39.es/ecma262/#sec-escaperegexppattern
fn escape_pattern(src: &JsString, _flags: &JsString) -> JsValue {
if src.is_empty() {
js_string!("(?:)").into()
js_str!("(?:)").into()
} else {
let mut s = Vec::with_capacity(src.len());
let mut buf = [0; 2];
@ -863,7 +864,7 @@ impl RegExp {
// 2. Assert: Type(S) is String.
// 3. Let exec be ? Get(R, "exec").
let exec = this.get(utf16!("exec"), context)?;
let exec = this.get(js_str!("exec"), context)?;
// 4. If IsCallable(exec) is true, then
if let Some(exec) = exec.as_callable() {
@ -915,19 +916,21 @@ impl RegExp {
let length = input.len() as u64;
// 2. Let lastIndex be ℝ(? ToLength(? Get(R, "lastIndex"))).
let mut last_index = this.get(utf16!("lastIndex"), context)?.to_length(context)?;
let mut last_index = this
.get(js_str!("lastIndex"), context)?
.to_length(context)?;
// 3. Let flags be R.[[OriginalFlags]].
let flags = &rx.original_flags;
// 4. If flags contains "g", let global be true; else let global be false.
let global = flags.contains(&('g' as u16));
let global = flags.contains(b'g');
// 5. If flags contains "y", let sticky be true; else let sticky be false.
let sticky = flags.contains(&('y' as u16));
let sticky = flags.contains(b'y');
// 6. If flags contains "d", let hasIndices be true; else let hasIndices be false.
let has_indices = flags.contains(&('d' as u16));
let has_indices = flags.contains(b'd');
// 7. If global is false and sticky is false, set lastIndex to 0.
if !global && !sticky {
@ -938,7 +941,7 @@ impl RegExp {
let matcher = &rx.matcher;
// 9. If flags contains "u" or flags contains "v", let fullUnicode be true; else let fullUnicode be false.
let full_unicode = flags.contains(&('u' as u16)) || flags.contains(&('v' as u16));
let full_unicode = flags.contains(b'u') || flags.contains(b'v');
// NOTE: The following steps are take care of by regress:
//
@ -952,7 +955,7 @@ impl RegExp {
// i. If global is true or sticky is true, then
if global || sticky {
// 1. Perform ? Set(R, "lastIndex", +0𝔽, true).
this.set(utf16!("lastIndex"), 0, true, context)?;
this.set(js_str!("lastIndex"), 0, true, context)?;
}
// ii. Return null.
@ -961,10 +964,20 @@ impl RegExp {
// 13.b. Let inputIndex be the index into input of the character that was obtained from element lastIndex of S.
// 13.c. Let r be matcher(input, inputIndex).
let r: Option<regress::Match> = if full_unicode {
matcher.find_from_utf16(input, last_index as usize).next()
} else {
matcher.find_from_ucs2(input, last_index as usize).next()
let r: Option<regress::Match> = match (full_unicode, input.as_str().variant()) {
(true | false, JsStrVariant::Latin1(_)) => {
// TODO: Currently regress does not support latin1 encoding.
let input = input.to_vec();
// NOTE: We can use the faster ucs2 variant since there will never be two byte unicode.
matcher.find_from_ucs2(&input, last_index as usize).next()
}
(true, JsStrVariant::Utf16(input)) => {
matcher.find_from_utf16(input, last_index as usize).next()
}
(false, JsStrVariant::Utf16(input)) => {
matcher.find_from_ucs2(input, last_index as usize).next()
}
};
let Some(match_value) = r else {
@ -975,7 +988,7 @@ impl RegExp {
// 13.a.i. If global is true or sticky is true, then
if global || sticky {
// 1. Perform ? Set(R, "lastIndex", +0𝔽, true).
this.set(utf16!("lastIndex"), 0, true, context)?;
this.set(js_str!("lastIndex"), 0, true, context)?;
}
// MOVE: ii. Set lastIndex to AdvanceStringIndex(S, lastIndex, fullUnicode).
@ -994,7 +1007,7 @@ impl RegExp {
// NOTE: regress currently doesn't support the sticky flag so we have to emulate it.
if sticky && match_value.start() != last_index as usize {
// 1. Perform ? Set(R, "lastIndex", +0𝔽, true).
this.set(utf16!("lastIndex"), 0, true, context)?;
this.set(js_str!("lastIndex"), 0, true, context)?;
// 2. Return null.
return Ok(None);
@ -1012,7 +1025,7 @@ impl RegExp {
// 16. If global is true or sticky is true, then
if global || sticky {
// a. Perform ? Set(R, "lastIndex", 𝔽(e), true).
this.set(utf16!("lastIndex"), e, true, context)?;
this.set(js_str!("lastIndex"), e, true, context)?;
}
// 17. Let n be the number of elements in r's captures List.
@ -1026,11 +1039,11 @@ impl RegExp {
let a = Array::array_create(n + 1, None, context)?;
// 22. Perform ! CreateDataPropertyOrThrow(A, "index", 𝔽(lastIndex)).
a.create_data_property_or_throw(utf16!("index"), last_index, context)
a.create_data_property_or_throw(js_str!("index"), last_index, context)
.expect("this CreateDataPropertyOrThrow call must not fail");
// 23. Perform ! CreateDataPropertyOrThrow(A, "input", S).
a.create_data_property_or_throw(utf16!("input"), input.clone(), context)
a.create_data_property_or_throw(js_str!("input"), input.clone(), context)
.expect("this CreateDataPropertyOrThrow call must not fail");
// 24. Let match be the Match Record { [[StartIndex]]: lastIndex, [[EndIndex]]: e }.
@ -1051,7 +1064,7 @@ impl RegExp {
.expect("this CreateDataPropertyOrThrow call must not fail");
// 28. Let matchedSubstr be GetMatchString(S, match).
let matched_substr = js_string!(&input[(last_index as usize)..(e)]);
let matched_substr = input.get_expect((last_index as usize)..(e));
// 29. Perform ! CreateDataPropertyOrThrow(A, "0", matchedSubstr).
a.create_data_property_or_throw(0, matched_substr, context)
@ -1081,7 +1094,7 @@ impl RegExp {
for (name, range) in named_groups {
let name = js_string!(name);
if let Some(range) = range {
let value = js_string!(&input[range.clone()]);
let value = input.get_expect(range.clone());
groups
.create_data_property_or_throw(name.clone(), value, context)
@ -1126,11 +1139,11 @@ impl RegExp {
// 22.2.7.8 MakeMatchIndicesIndexPairArray ( S, indices, groupNames, hasGroups )
// 8. Perform ! CreateDataPropertyOrThrow(A, "groups", groups).
indices
.create_data_property_or_throw(utf16!("groups"), group_names, context)
.create_data_property_or_throw(js_str!("groups"), group_names, context)
.expect("this CreateDataPropertyOrThrow call must not fail");
// 32. Perform ! CreateDataPropertyOrThrow(A, "groups", groups).
a.create_data_property_or_throw(utf16!("groups"), groups, context)
a.create_data_property_or_throw(js_str!("groups"), groups, context)
.expect("this CreateDataPropertyOrThrow call must not fail");
// 27. For each integer i such that i ≥ 1 and i ≤ n, in ascending order, do
@ -1141,9 +1154,9 @@ impl RegExp {
// b. If captureI is undefined, let capturedValue be undefined.
// c. Else if fullUnicode is true, then
// d. Else,
let captured_value = capture
.clone()
.map_or_else(JsValue::undefined, |range| js_string!(&input[range]).into());
let captured_value = capture.clone().map_or_else(JsValue::undefined, |range| {
js_string!(input.get_expect(range)).into()
});
// e. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(i)), capturedValue).
a.create_data_property_or_throw(i, captured_value.clone(), context)
@ -1171,7 +1184,7 @@ impl RegExp {
// a. Let indicesArray be MakeMatchIndicesIndexPairArray(S, indices, groupNames, hasGroups).
// b. Perform ! CreateDataPropertyOrThrow(A, "indices", indicesArray).
if has_indices {
a.create_data_property_or_throw(utf16!("indices"), indices, context)
a.create_data_property_or_throw(js_str!("indices"), indices, context)
.expect("this CreateDataPropertyOrThrow call must not fail");
}
@ -1206,10 +1219,10 @@ impl RegExp {
let arg_str = args.get_or_undefined(0).to_string(context)?;
// 4. Let flags be ? ToString(? Get(rx, "flags")).
let flags = rx.get(utf16!("flags"), context)?.to_string(context)?;
let flags = rx.get(js_str!("flags"), context)?.to_string(context)?;
// 5. If flags does not contain "g", then
if !flags.contains(&103) {
if !flags.contains(b'g') {
// a. Return ? RegExpExec(rx, S).
return (Self::abstract_exec(rx, arg_str, context)?)
.map_or_else(|| Ok(JsValue::null()), |v| Ok(v.into()));
@ -1218,10 +1231,10 @@ impl RegExp {
// 6. Else,
// a. If flags contains "u" or flags contains "v", let fullUnicode be true. Otherwise, let fullUnicode be false.
let full_unicode = flags.contains(&117) || flags.contains(&118);
let full_unicode = flags.contains(b'u') || flags.contains(b'v');
// b. Perform ? Set(rx, "lastIndex", +0𝔽, true).
rx.set(utf16!("lastIndex"), 0, true, context)?;
rx.set(js_str!("lastIndex"), 0, true, context)?;
// c. Let A be ! ArrayCreate(0).
let a = Array::array_create(0, None, context).expect("this ArrayCreate call must not fail");
@ -1247,13 +1260,18 @@ impl RegExp {
// 3. If matchStr is the empty String, then
if match_str.is_empty() {
// a. Let thisIndex be ℝ(? ToLength(? Get(rx, "lastIndex"))).
let this_index = rx.get(utf16!("lastIndex"), context)?.to_length(context)?;
let this_index = rx.get(js_str!("lastIndex"), context)?.to_length(context)?;
// b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode).
let next_index = advance_string_index(&arg_str, this_index, full_unicode);
// c. Perform ? Set(rx, "lastIndex", 𝔽(nextIndex), true).
rx.set(utf16!("lastIndex"), JsValue::new(next_index), true, context)?;
rx.set(
js_str!("lastIndex"),
JsValue::new(next_index),
true,
context,
)?;
}
// 4. Set n to n + 1.
@ -1293,14 +1311,14 @@ impl RegExp {
})?;
// 3. Let pattern be ? ToString(? Get(R, "source")).
let pattern = regexp.get(utf16!("source"), context)?.to_string(context)?;
let pattern = regexp.get(js_str!("source"), context)?.to_string(context)?;
// 4. Let flags be ? ToString(? Get(R, "flags")).
let flags = regexp.get(utf16!("flags"), context)?.to_string(context)?;
let flags = regexp.get(js_str!("flags"), context)?.to_string(context)?;
// 5. Let result be the string-concatenation of "/", pattern, "/", and flags.
// 6. Return result.
Ok(js_string!(utf16!("/"), &pattern, utf16!("/"), &flags).into())
Ok(js_string!(js_str!("/"), &pattern, js_str!("/"), &flags).into())
}
/// `RegExp.prototype[ @@matchAll ]( string )`
@ -1332,26 +1350,26 @@ impl RegExp {
let c = regexp.species_constructor(StandardConstructors::regexp, context)?;
// 5. Let flags be ? ToString(? Get(R, "flags")).
let flags = regexp.get(utf16!("flags"), context)?.to_string(context)?;
let flags = regexp.get(js_str!("flags"), context)?.to_string(context)?;
// 6. Let matcher be ? Construct(C, « R, flags »).
let matcher = c.construct(&[this.clone(), flags.clone().into()], Some(&c), context)?;
// 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")).
let last_index = regexp
.get(utf16!("lastIndex"), context)?
.get(js_str!("lastIndex"), context)?
.to_length(context)?;
// 8. Perform ? Set(matcher, "lastIndex", lastIndex, true).
matcher.set(utf16!("lastIndex"), last_index, true, context)?;
matcher.set(js_str!("lastIndex"), last_index, true, context)?;
// 9. If flags contains "g", let global be true.
// 10. Else, let global be false.
let global = flags.contains(&('g' as u16));
let global = flags.contains(b'g');
// 11. If flags contains "u", let fullUnicode be true.
// 12. Else, let fullUnicode be false.
let unicode = flags.contains(&('u' as u16));
let unicode = flags.contains(b'u');
// 13. Return ! CreateRegExpStringIterator(matcher, S, global, fullUnicode).
Ok(RegExpStringIterator::create_regexp_string_iterator(
@ -1414,18 +1432,18 @@ impl RegExp {
};
// 7. Let flags be ? ToString(? Get(rx, "flags")).
let flags = rx.get(utf16!("flags"), context)?.to_string(context)?;
let flags = rx.get(js_str!("flags"), context)?.to_string(context)?;
// 8. If flags contains "g", let global be true. Otherwise, let global be false.
let global = flags.as_slice().contains(&u16::from(b'g'));
let global = flags.contains(b'g');
// 9. If global is true, then
let full_unicode = if global {
// a. If flags contains "u", let fullUnicode be true. Otherwise, let fullUnicode be false.
let full_unicode = flags.contains(&u16::from(b'u'));
let full_unicode = flags.contains(b'u');
// b. Perform ? Set(rx, "lastIndex", +0𝔽, true).
rx.set(utf16!("lastIndex"), 0, true, context)?;
rx.set(js_str!("lastIndex"), 0, true, context)?;
full_unicode
} else {
@ -1467,13 +1485,18 @@ impl RegExp {
// 2. If matchStr is the empty String, then
if match_str.is_empty() {
// a. Let thisIndex be ℝ(? ToLength(? Get(rx, "lastIndex"))).
let this_index = rx.get(utf16!("lastIndex"), context)?.to_length(context)?;
let this_index = rx.get(js_str!("lastIndex"), context)?.to_length(context)?;
// b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode).
let next_index = advance_string_index(&s, this_index, full_unicode);
// c. Perform ? Set(rx, "lastIndex", 𝔽(nextIndex), true).
rx.set(utf16!("lastIndex"), JsValue::new(next_index), true, context)?;
rx.set(
js_str!("lastIndex"),
JsValue::new(next_index),
true,
context,
)?;
}
}
@ -1502,7 +1525,7 @@ impl RegExp {
// e. Let position be ? ToIntegerOrInfinity(? Get(result, "index")).
let position = result
.get(utf16!("index"), context)?
.get(js_str!("index"), context)?
.to_integer_or_infinity(context)?;
// f. Set position to the result of clamping position between 0 and lengthS.
@ -1534,7 +1557,7 @@ impl RegExp {
}
// j. Let namedCaptures be ? Get(result, "groups").
let mut named_captures = result.get(utf16!("groups"), context)?;
let mut named_captures = result.get(js_str!("groups"), context)?;
let replacement = match replace_value {
// k. If functionalReplace is true, then
@ -1587,8 +1610,8 @@ impl RegExp {
// In such cases, the corresponding substitution is ignored.
// ii. Set accumulatedResult to the string-concatenation of accumulatedResult, the substring of S from nextSourcePosition to position, and replacement.
accumulated_result.extend_from_slice(&s[next_source_position..position]);
accumulated_result.extend_from_slice(&replacement);
accumulated_result.extend(s.get_expect(next_source_position..position).iter());
accumulated_result.extend(replacement.iter());
// iii. Set nextSourcePosition to position + matchLength.
next_source_position = position + match_length;
@ -1597,11 +1620,15 @@ impl RegExp {
// 16. If nextSourcePosition ≥ lengthS, return accumulatedResult.
if next_source_position >= length_s {
return Ok(js_string!(accumulated_result).into());
return Ok(js_string!(&accumulated_result[..]).into());
}
// 17. Return the string-concatenation of accumulatedResult and the substring of S from nextSourcePosition.
Ok(js_string!(&accumulated_result[..], &s[next_source_position..]).into())
Ok(js_string!(
&JsString::from(&accumulated_result[..]),
s.get_expect(next_source_position..)
)
.into())
}
/// `RegExp.prototype[ @@search ]( string )`
@ -1630,31 +1657,31 @@ impl RegExp {
let arg_str = args.get_or_undefined(0).to_string(context)?;
// 4. Let previousLastIndex be ? Get(rx, "lastIndex").
let previous_last_index = rx.get(utf16!("lastIndex"), context)?;
let previous_last_index = rx.get(js_str!("lastIndex"), context)?;
// 5. If SameValue(previousLastIndex, +0𝔽) is false, then
if !JsValue::same_value(&previous_last_index, &JsValue::new(0)) {
// a. Perform ? Set(rx, "lastIndex", +0𝔽, true).
rx.set(utf16!("lastIndex"), 0, true, context)?;
rx.set(js_str!("lastIndex"), 0, true, context)?;
}
// 6. Let result be ? RegExpExec(rx, S).
let result = Self::abstract_exec(rx, arg_str, context)?;
// 7. Let currentLastIndex be ? Get(rx, "lastIndex").
let current_last_index = rx.get(utf16!("lastIndex"), context)?;
let current_last_index = rx.get(js_str!("lastIndex"), context)?;
// 8. If SameValue(currentLastIndex, previousLastIndex) is false, then
if !JsValue::same_value(&current_last_index, &previous_last_index) {
// a. Perform ? Set(rx, "lastIndex", previousLastIndex, true).
rx.set(utf16!("lastIndex"), previous_last_index, true, context)?;
rx.set(js_str!("lastIndex"), previous_last_index, true, context)?;
}
// 9. If result is null, return -1𝔽.
// 10. Return ? Get(result, "index").
result.map_or_else(
|| Ok(JsValue::new(-1)),
|result| result.get(utf16!("index"), context),
|result| result.get(js_str!("index"), context),
)
}
@ -1687,18 +1714,18 @@ impl RegExp {
let constructor = rx.species_constructor(StandardConstructors::regexp, context)?;
// 5. Let flags be ? ToString(? Get(rx, "flags")).
let flags = rx.get(utf16!("flags"), context)?.to_string(context)?;
let flags = rx.get(js_str!("flags"), context)?.to_string(context)?;
// 6. If flags contains "u", let unicodeMatching be true.
// 7. Else, let unicodeMatching be false.
let unicode = flags.contains(&('u' as u16));
let unicode = flags.contains(b'u');
// 8. If flags contains "y", let newFlags be flags.
// 9. Else, let newFlags be the string-concatenation of flags and "y".
let new_flags = if flags.contains(&('y' as u16)) {
let new_flags = if flags.contains(b'y') {
flags
} else {
js_string!(&flags, utf16!("y"))
js_string!(&flags, js_str!("y"))
};
// 10. Let splitter be ? Construct(C, « rx, newFlags »).
@ -1756,7 +1783,7 @@ impl RegExp {
// 19. Repeat, while q < size,
while q < size {
// a. Perform ? Set(splitter, "lastIndex", 𝔽(q), true).
splitter.set(utf16!("lastIndex"), JsValue::new(q), true, context)?;
splitter.set(js_str!("lastIndex"), JsValue::new(q), true, context)?;
// b. Let z be ? RegExpExec(splitter, S).
let result = Self::abstract_exec(&splitter, arg_str.clone(), context)?;
@ -1766,7 +1793,7 @@ impl RegExp {
if let Some(result) = result {
// i. Let e be ℝ(? ToLength(? Get(splitter, "lastIndex"))).
let mut e = splitter
.get(utf16!("lastIndex"), context)?
.get(js_str!("lastIndex"), context)?
.to_length(context)?;
// ii. Set e to min(e, size).
@ -1778,7 +1805,7 @@ impl RegExp {
q = advance_string_index(&arg_str, q, unicode);
} else {
// 1. Let T be the substring of S from p to q.
let arg_str_substring = js_string!(&arg_str[p as usize..q as usize]);
let arg_str_substring = arg_str.get_expect(p as usize..q as usize);
// 2. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(lengthA)), T).
a.create_data_property_or_throw(length_a, arg_str_substring, context)
@ -1829,7 +1856,7 @@ impl RegExp {
}
// 20. Let T be the substring of S from p to size.
let arg_str_substring = js_string!(&arg_str[p as usize..size as usize]);
let arg_str_substring = arg_str.get_expect(p as usize..size as usize);
// 21. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(lengthA)), T).
a.create_data_property_or_throw(length_a, arg_str_substring, context)
@ -1890,7 +1917,7 @@ impl RegExp {
.expect("already checked that the object was a RegExp") = regexp;
}
this.set(utf16!("lastIndex"), 0, true, context)?;
this.set(js_str!("lastIndex"), 0, true, context)?;
Ok(this.into())
}

6
core/engine/src/builtins/regexp/regexp_string_iterator.rs

@ -18,11 +18,11 @@ use crate::{
object::JsObject,
property::Attribute,
realm::Realm,
string::utf16,
symbol::JsSymbol,
Context, JsData, JsResult, JsString, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
use regexp::{advance_string_index, RegExp};
@ -160,7 +160,7 @@ impl RegExpStringIterator {
// 1. Let thisIndex be ℝ(? ToLength(? Get(R, "lastIndex"))).
let this_index = iterator
.matcher
.get(utf16!("lastIndex"), context)?
.get(js_str!("lastIndex"), context)?
.to_length(context)?;
// 2. Let nextIndex be ! AdvanceStringIndex(S, thisIndex, fullUnicode).
@ -170,7 +170,7 @@ impl RegExpStringIterator {
// 3. Perform ? Set(R, "lastIndex", 𝔽(nextIndex), true).
iterator
.matcher
.set(utf16!("lastIndex"), next_index, true, context)?;
.set(js_str!("lastIndex"), next_index, true, context)?;
}
// vi. Perform ? Yield(match).

17
core/engine/src/builtins/regexp/tests.rs

@ -2,6 +2,7 @@ use crate::{
js_string, native_function::NativeFunctionObject, run_test_actions, JsNativeErrorKind,
JsObject, JsValue, TestAction,
};
use boa_macros::js_str;
use indoc::indoc;
#[test]
@ -36,7 +37,7 @@ fn species() {
// return-value
TestAction::assert("Object.is(accessor.call(thisVal), thisVal)"),
// symbol-species-name
TestAction::assert_eq("name.value", js_string!("get [Symbol.species]")),
TestAction::assert_eq("name.value", js_str!("get [Symbol.species]")),
TestAction::assert("!name.enumerable"),
TestAction::assert("!name.writable"),
TestAction::assert("name.configurable"),
@ -65,7 +66,7 @@ fn flags() {
TestAction::assert("!re_gi.dotAll"),
TestAction::assert("!re_gi.unicode"),
TestAction::assert("!re_gi.sticky"),
TestAction::assert_eq("re_gi.flags", js_string!("gi")),
TestAction::assert_eq("re_gi.flags", js_str!("gi")),
//
TestAction::assert("!re_sm.global"),
TestAction::assert("!re_sm.ignoreCase"),
@ -73,7 +74,7 @@ fn flags() {
TestAction::assert("re_sm.dotAll"),
TestAction::assert("!re_sm.unicode"),
TestAction::assert("!re_sm.sticky"),
TestAction::assert_eq("re_sm.flags", js_string!("ms")),
TestAction::assert_eq("re_sm.flags", js_str!("ms")),
//
TestAction::assert("!re_u.global"),
TestAction::assert("!re_u.ignoreCase"),
@ -81,7 +82,7 @@ fn flags() {
TestAction::assert("!re_u.dotAll"),
TestAction::assert("re_u.unicode"),
TestAction::assert("!re_u.sticky"),
TestAction::assert_eq("re_u.flags", js_string!("u")),
TestAction::assert_eq("re_u.flags", js_str!("u")),
]);
}
@ -114,7 +115,7 @@ fn exec() {
TestAction::assert_eq("result.index", 4),
TestAction::assert_eq(
"result.input",
js_string!("The Quick Brown Fox Jumps Over The Lazy Dog"),
js_str!("The Quick Brown Fox Jumps Over The Lazy Dog"),
),
]);
}
@ -138,8 +139,8 @@ fn no_panic_on_parse_fail() {
#[test]
fn to_string() {
run_test_actions([
TestAction::assert_eq("(new RegExp('a+b+c')).toString()", js_string!("/a+b+c/")),
TestAction::assert_eq("(new RegExp('bar', 'g')).toString()", js_string!("/bar/g")),
TestAction::assert_eq("(new RegExp('a+b+c')).toString()", js_str!("/a+b+c/")),
TestAction::assert_eq("(new RegExp('bar', 'g')).toString()", js_str!("/bar/g")),
TestAction::assert_eq(r"(new RegExp('\\n', 'g')).toString()", js_string!(r"/\n/g")),
TestAction::assert_eq(r"/\n/g.toString()", js_string!(r"/\n/g")),
TestAction::assert_eq(r"/,\;/.toString()", js_string!(r"/,\;/")),
@ -164,7 +165,7 @@ fn search() {
TestAction::assert("!length.writable"),
TestAction::assert("length.configurable"),
// name
TestAction::assert_eq("name.value", js_string!("[Symbol.search]")),
TestAction::assert_eq("name.value", js_str!("[Symbol.search]")),
TestAction::assert("!name.enumerable"),
TestAction::assert("!name.writable"),
TestAction::assert("name.configurable"),

11
core/engine/src/builtins/set/mod.rs

@ -26,10 +26,11 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject},
property::{Attribute, PropertyNameKind},
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
symbol::JsSymbol,
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_macros::js_str;
use boa_profiler::Profiler;
use num_traits::Zero;
@ -71,18 +72,18 @@ impl IntrinsicObject for Set {
.method(Self::for_each, js_string!("forEach"), 1)
.method(Self::has, js_string!("has"), 1)
.property(
utf16!("keys"),
js_string!("keys"),
values_function.clone(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.accessor(
utf16!("size"),
js_string!("size"),
Some(size_getter),
None,
Attribute::CONFIGURABLE,
)
.property(
utf16!("values"),
js_string!("values"),
values_function.clone(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
@ -138,7 +139,7 @@ impl BuiltInConstructor for Set {
}
// 5. Let adder be ? Get(set, "add").
let adder = set.get(utf16!("add"), context)?;
let adder = set.get(js_str!("add"), context)?;
// 6. If IsCallable(adder) is false, throw a TypeError exception.
let adder = adder.as_callable().ok_or_else(|| {

212
core/engine/src/builtins/string/mod.rs

@ -17,12 +17,12 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject},
property::{Attribute, PropertyDescriptor},
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::{CodePoint, Utf16Trim},
string::{common::StaticJsStrings, CodePoint},
symbol::JsSymbol,
value::IntegerOrInfinity,
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_macros::{js_str, utf16};
use boa_profiler::Profiler;
use icu_normalizer::{ComposingNormalizer, DecomposingNormalizer};
use std::cmp::{max, min};
@ -32,6 +32,9 @@ use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject};
mod string_iterator;
pub(crate) use string_iterator::StringIterator;
#[cfg(feature = "annex-b")]
pub use crate::JsStr;
/// The set of normalizers required for the `String.prototype.normalize` function.
#[derive(Debug)]
pub(crate) struct StringNormalizers {
@ -70,6 +73,23 @@ pub(crate) const fn is_trimmable_whitespace(c: char) -> bool {
)
}
/// Helper function to check if a `u8` latin1 character is trimmable.
pub(crate) const fn is_trimmable_whitespace_latin1(c: u8) -> bool {
// The rust implementation of `trim` does not regard the same characters whitespace as ecma standard does
//
// Rust uses \p{White_Space} by default, which also includes:
// `\u{0085}' (next line)
// And does not include:
// '\u{FEFF}' (zero width non-breaking space)
// Explicit whitespace: https://tc39.es/ecma262/#sec-white-space
matches!(
c,
0x09 | 0x0B | 0x0C | 0x20 | 0xA0 |
// Line terminators: https://tc39.es/ecma262/#sec-line-terminators
0x0A | 0x0D
)
}
/// JavaScript `String` implementation.
#[derive(Debug, Clone, Copy)]
pub(crate) struct String;
@ -382,7 +402,7 @@ impl String {
let cooked = args.get_or_undefined(0).to_object(context)?;
// 3. Let raw be ? ToObject(? Get(cooked, "raw")).
let raw = cooked.get(utf16!("raw"), context)?.to_object(context)?;
let raw = cooked.get(js_str!("raw"), context)?.to_object(context)?;
// 4. Let literalSegments be ? LengthOfArrayLike(raw).
let literal_segments = raw.length_of_array_like(context)?;
@ -407,13 +427,13 @@ impl String {
let next_seg = raw.get(next_key, context)?.to_string(context)?;
// c. Append the code unit elements of nextSeg to the end of stringElements.
string_elements.extend(next_seg.iter().copied());
string_elements.extend(next_seg.iter());
// d. If nextIndex + 1 = literalSegments, then
if next_index + 1 == literal_segments {
// i. Return the String value whose code units are the elements in the List stringElements.
// If stringElements has no elements, the empty String is returned.
return Ok(js_string!(string_elements).into());
return Ok(js_string!(&string_elements[..]).into());
}
// e. If nextIndex < numberOfSubstitutions, let next be substitutions[nextIndex].
@ -429,7 +449,7 @@ impl String {
let next_sub = next.to_string(context)?;
// h. Append the code unit elements of nextSub to the end of stringElements.
string_elements.extend(next_sub.iter().copied());
string_elements.extend(next_sub.iter());
// i. Set nextIndex to nextIndex + 1.
next_index += 1;
@ -461,7 +481,7 @@ impl String {
}
// 3. Return result.
Ok(js_string!(result).into())
Ok(js_string!(&result[..]).into())
}
/// `String.prototype.toString ( )`
@ -511,7 +531,7 @@ impl String {
// 6. Return the substring of S from position to position + 1.
IntegerOrInfinity::Integer(i) if i >= 0 && i < string.len() as i64 => {
let i = i as usize;
Ok(js_string!(&string[i..=i]).into())
Ok(js_string!(string.get_expect(i..=i)).into())
}
// 5. If position < 0 or position ≥ size, return the empty String.
_ => Ok(js_string!().into()),
@ -553,7 +573,7 @@ impl String {
};
// 8. Return the substring of S from k to k + 1.
Ok(js_string!(&s[k..=k]).into())
Ok(js_string!(s.get_expect(k..=k)).into())
}
/// `String.prototype.codePointAt( index )`
@ -629,9 +649,11 @@ impl String {
match position {
// 4. Let size be the length of S.
IntegerOrInfinity::Integer(i) if i >= 0 && i < string.len() as i64 => {
IntegerOrInfinity::Integer(i) if i >= 0 => {
// 6. Return the Number value for the numeric value of the code unit at index position within the String S.
Ok(u32::from(string[i as usize]).into())
Ok(string
.get(i as usize)
.map_or_else(JsValue::nan, JsValue::from))
}
// 5. If position < 0 or position ≥ size, return NaN.
_ => Ok(JsValue::nan()),
@ -708,14 +730,14 @@ impl String {
return Ok(js_string!().into());
}
let n = n as usize;
let mut result = Vec::with_capacity(n * len);
let mut result = Vec::with_capacity(n);
std::iter::repeat(&string[..])
std::iter::repeat(string.as_str())
.take(n)
.for_each(|s| result.extend_from_slice(s));
.for_each(|s| result.push(s));
// 6. Return the String value that is made from n copies of S appended together.
Ok(js_string!(result).into())
Ok(JsString::concat_array(&result).into())
}
// 5. If n is 0, return the empty String.
IntegerOrInfinity::Integer(0) => Ok(js_string!().into()),
@ -790,7 +812,7 @@ impl String {
Ok(js_string!().into())
} else {
// 13. Return the substring of S from from to to.
Ok(js_string!(&string[from..to]).into())
Ok(js_string!(string.get_expect(from..to)).into())
}
}
@ -859,7 +881,7 @@ impl String {
// 14. Return ! SameValueNonNumeric(substring, searchStr).
// `SameValueNonNumeric` forwards to `==`, so directly check
// equality to avoid converting to `JsValue`
Ok(JsValue::new(search_string == string[start..end]))
Ok(JsValue::new(search_string == string.get_expect(start..end)))
}
}
@ -922,7 +944,7 @@ impl String {
// 14. Return ! SameValueNonNumeric(substring, searchStr).
// `SameValueNonNumeric` forwards to `==`, so directly check
// equality to avoid converting to `JsValue`
Ok(JsValue::new(search_str == string[start..end]))
Ok(JsValue::new(search_str == string.get_expect(start..end)))
} else {
// 12. If start < 0, return false.
Ok(false.into())
@ -973,7 +995,7 @@ impl String {
// 10. Let index be ! StringIndexOf(S, searchStr, start).
// 11. If index is not -1, return true.
// 12. Return false.
Ok(string.index_of(&search_str, start).is_some().into())
Ok(string.index_of(search_str.as_str(), start).is_some().into())
}
/// `String.prototype.replace( regexp|substr, newSubstr|function )`
@ -1042,12 +1064,12 @@ impl String {
// 8. Let position be ! StringIndexOf(string, searchString, 0).
// 9. If position is -1, return string.
let Some(position) = string.index_of(&search_string, 0) else {
let Some(position) = string.index_of(search_string.as_str(), 0) else {
return Ok(string.into());
};
// 10. Let preserved be the substring of string from 0 to position.
let preserved = &string[..position];
let preserved = JsString::from(string.get_expect(..position));
let replacement = match replace_value {
// 11. If functionalReplace is true, then
@ -1081,7 +1103,12 @@ impl String {
};
// 13. Return the string-concatenation of preserved, replacement, and the substring of string from position + searchLength.
Ok(js_string!(preserved, &replacement, &string[position + search_length..]).into())
Ok(js_string!(
&preserved,
&replacement,
&JsString::from(string.get_expect(position + search_length..))
)
.into())
}
/// `22.1.3.18 String.prototype.replaceAll ( searchValue, replaceValue )`
@ -1117,13 +1144,13 @@ impl String {
// b. If isRegExp is true, then
if let Some(obj) = RegExp::is_reg_exp(search_value, context)? {
// i. Let flags be ? Get(searchValue, "flags").
let flags = obj.get(utf16!("flags"), context)?;
let flags = obj.get(js_str!("flags"), context)?;
// ii. Perform ? RequireObjectCoercible(flags).
flags.require_object_coercible()?;
// iii. If ? ToString(flags) does not contain "g", throw a TypeError exception.
if !flags.to_string(context)?.contains(&u16::from(b'g')) {
if !flags.to_string(context)?.contains(b'g') {
return Err(JsNativeError::typ()
.with_message(
"String.prototype.replaceAll called with a non-global RegExp argument",
@ -1167,7 +1194,7 @@ impl String {
let mut match_positions = Vec::new();
// 10. Let position be ! StringIndexOf(string, searchString, 0).
let mut position = string.index_of(&search_string, 0);
let mut position = string.index_of(search_string.as_str(), 0);
// 11. Repeat, while position is not -1,
while let Some(p) = position {
@ -1175,7 +1202,7 @@ impl String {
match_positions.push(p);
// b. Set position to ! StringIndexOf(string, searchString, position + advanceBy).
position = string.index_of(&search_string, p + advance_by);
position = string.index_of(search_string.as_str(), p + advance_by);
}
// 12. Let endOfLastMatch be 0.
@ -1187,7 +1214,7 @@ impl String {
// 14. For each element p of matchPositions, do
for p in match_positions {
// a. Let preserved be the substring of string from endOfLastMatch to p.
let preserved = &string[end_of_last_match..p];
let preserved = string.get_expect(end_of_last_match..p);
// c. Else,
let replacement = match replace {
@ -1222,8 +1249,8 @@ impl String {
};
// d. Set result to the string-concatenation of result, preserved, and replacement.
result.extend_from_slice(preserved);
result.extend_from_slice(&replacement);
result.extend(preserved.iter());
result.extend(replacement.iter());
// e. Set endOfLastMatch to p + searchLength.
end_of_last_match = p + search_length;
@ -1232,11 +1259,11 @@ impl String {
// 15. If endOfLastMatch < the length of string, then
if end_of_last_match < string.len() {
// a. Set result to the string-concatenation of result and the substring of string from endOfLastMatch.
result.extend_from_slice(&string[end_of_last_match..]);
result.extend(string.get_expect(end_of_last_match..).iter());
}
// 16. Return result.
Ok(js_string!(result).into())
Ok(js_string!(&result[..]).into())
}
/// `String.prototype.indexOf( searchValue[, fromIndex] )`
@ -1278,7 +1305,7 @@ impl String {
// 8. Return 𝔽(! StringIndexOf(S, searchStr, start)).
Ok(string
.index_of(&search_str, start)
.index_of(search_str.as_str(), start)
.map_or(-1, |i| i as i64)
.into())
}
@ -1368,10 +1395,10 @@ impl String {
// 11. For each non-negative integer i starting with start such that i ≤ len - searchLen, in descending order, do
for i in (0..=min(start, end)).rev() {
// a. Let candidate be the substring of S from i to i + searchLen.
let candidate = &string[i..i + search_len];
let candidate = string.get_expect(i..i + search_len);
// b. If candidate is the same sequence of code units as searchStr, return 𝔽(i).
if candidate == &search_str {
if candidate == search_str {
return Ok(i.into());
}
}
@ -1431,6 +1458,9 @@ impl String {
.expect("constructor must return a `Collator` object")
.collator();
let s = s.iter().collect::<Vec<_>>();
let that_value = that_value.iter().collect::<Vec<_>>();
collator.compare_utf16(&s, &that_value) as i8
}
@ -1544,15 +1574,15 @@ impl String {
}
};
let truncated_string_filler = filler.repeat(repetitions as usize);
let truncated_string_filler = &truncated_string_filler[..fill_len as usize];
let truncated_string_filler = filler.to_vec().repeat(repetitions as usize);
let truncated_string_filler = JsString::from(&truncated_string_filler[..fill_len as usize]);
// 10. If placement is start, return the string-concatenation of truncatedStringFiller and S.
if placement == Placement::Start {
Ok(js_string!(truncated_string_filler, &string).into())
Ok(js_string!(&truncated_string_filler, &string).into())
} else {
// 11. Else, return the string-concatenation of S and truncatedStringFiller.
Ok(js_string!(&string, truncated_string_filler).into())
Ok(js_string!(&string, &truncated_string_filler).into())
}
}
@ -1876,7 +1906,7 @@ impl String {
let to = max(final_start, final_end);
// 10. Return the substring of S from from to to.
Ok(js_string!(&string[from..to]).into())
Ok(js_string!(string.get_expect(from..to)).into())
}
/// `String.prototype.split ( separator, limit )`
@ -1946,9 +1976,10 @@ impl String {
// b. Let codeUnits be a List consisting of the sequence of code units that are the elements of head.
let head = this_str
.get(..lim)
.unwrap_or(&this_str[..])
.unwrap_or(this_str.as_str())
.iter()
.map(|code| js_string!(std::slice::from_ref(code)).into());
.map(|code| js_string!(std::slice::from_ref(&code)).into());
// c. Return ! CreateArrayFromList(codeUnits).
return Ok(Array::create_array_from_list(head, context).into());
}
@ -1965,13 +1996,13 @@ impl String {
let mut i = 0;
// 13. Let j be ! StringIndexOf(S, R, 0).
let mut j = this_str.index_of(&separator_str, 0);
let mut j = this_str.index_of(separator_str.as_str(), 0);
// 14. Repeat, while j is not -1
while let Some(index) = j {
// a. Let T be the substring of S from i to j.
// b. Append T as the last element of substrings.
substrings.push(js_string!(&this_str[i..index]));
substrings.push(this_str.get_expect(i..index).into());
// c. If the number of elements of substrings is lim, return ! CreateArrayFromList(substrings).
if substrings.len() == lim {
@ -1985,12 +2016,12 @@ impl String {
i = index + separator_length;
// e. Set j to ! StringIndexOf(S, R, i).
j = this_str.index_of(&separator_str, i);
j = this_str.index_of(separator_str.as_str(), i);
}
// 15. Let T be the substring of S from i.
// 16. Append T to substrings.
substrings.push(js_string!(&this_str[i..]));
substrings.push(JsString::from(this_str.get_expect(i..)));
// 17. Return ! CreateArrayFromList(substrings).
Ok(
@ -2045,13 +2076,13 @@ impl String {
// b. If isRegExp is true, then
if let Some(regexp) = RegExp::is_reg_exp(regexp, context)? {
// i. Let flags be ? Get(regexp, "flags").
let flags = regexp.get(utf16!("flags"), context)?;
let flags = regexp.get(js_str!("flags"), context)?;
// ii. Perform ? RequireObjectCoercible(flags).
flags.require_object_coercible()?;
// iii. If ? ToString(flags) does not contain "g", throw a TypeError exception.
if !flags.to_string(context)?.contains(&u16::from(b'g')) {
if !flags.to_string(context)?.contains(b'g') {
return Err(JsNativeError::typ()
.with_message(
"String.prototype.matchAll called with a non-global RegExp argument",
@ -2071,7 +2102,7 @@ impl String {
let s = o.to_string(context)?;
// 4. Let rx be ? RegExpCreate(regexp, "g").
let rx = RegExp::create(regexp, &JsValue::new(js_string!("g")), context)?;
let rx = RegExp::create(regexp, &JsValue::new(js_str!("g")), context)?;
// 5. Return ? Invoke(rx, @@matchAll, « S »).
rx.invoke(JsSymbol::match_all(), &[JsValue::new(s)], context)
@ -2115,10 +2146,10 @@ impl String {
&JsValue::Undefined => Normalization::Nfc,
// 4. Else, let f be ? ToString(form).
f => match f.to_string(context)? {
ntype if &ntype == utf16!("NFC") => Normalization::Nfc,
ntype if &ntype == utf16!("NFD") => Normalization::Nfd,
ntype if &ntype == utf16!("NFKC") => Normalization::Nfkc,
ntype if &ntype == utf16!("NFKD") => Normalization::Nfkd,
ntype if &ntype == "NFC" => Normalization::Nfc,
ntype if &ntype == "NFD" => Normalization::Nfd,
ntype if &ntype == "NFKC" => Normalization::Nfkc,
ntype if &ntype == "NFKD" => Normalization::Nfkd,
// 5. If f is not one of "NFC", "NFD", "NFKC", or "NFKD", throw a RangeError exception.
_ => {
return Err(JsNativeError::range()
@ -2147,6 +2178,8 @@ impl String {
}
};
let s = s.iter().collect::<Vec<_>>();
let result = match normalization {
Normalization::Nfc => normalizers.nfc.normalize_utf16(&s),
Normalization::Nfd => normalizers.nfd.normalize_utf16(&s),
@ -2155,7 +2188,7 @@ impl String {
};
// 7. Return ns.
Ok(js_string!(result).into())
Ok(js_string!(&result[..]).into())
}
/// `String.prototype.search( regexp )`
@ -2276,7 +2309,7 @@ impl String {
// 11. Return the substring of S from intStart to intEnd.
if let Some(substr) = s.get(int_start..int_end) {
Ok(js_string!(substr).into())
Ok(substr.into())
} else {
Ok(js_string!().into())
}
@ -2290,8 +2323,8 @@ impl String {
/// [spec]: https://tc39.es/ecma262/#sec-createhtml
pub(crate) fn create_html(
string: &JsValue,
tag: &[u16],
attribute_and_value: Option<(&[u16], &JsValue)>,
tag: JsStr<'_>,
attribute_and_value: Option<(JsStr<'_>, &JsValue)>,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let str be ? RequireObjectCoercible(string).
@ -2301,7 +2334,7 @@ impl String {
let s = str.to_string(context)?;
// 3. Let p1 be the string-concatenation of "<" and tag.
let mut p1 = JsString::concat_array(&[utf16!("<"), tag]);
let mut p1 = js_string!(js_str!("<"), tag);
// 4. If attribute is not the empty String, then
if let Some((attribute, value)) = attribute_and_value {
@ -2312,7 +2345,7 @@ impl String {
// of the code unit 0x0022 (QUOTATION MARK) in V has been replaced with the six
// code unit sequence "&quot;".
let mut escaped_v = Vec::with_capacity(v.len());
for c in v.as_slice().iter().copied() {
for c in &v {
if c == 0x0022 {
escaped_v.extend(utf16!("&quot;"));
continue;
@ -2328,27 +2361,20 @@ impl String {
// the code unit 0x0022 (QUOTATION MARK)
// escapedV
// the code unit 0x0022 (QUOTATION MARK)
p1 = JsString::concat_array(&[
p1.as_slice(),
utf16!(" "),
p1 = js_string!(
&p1,
js_str!(" "),
attribute,
utf16!("=\""),
escaped_v.as_slice(),
utf16!("\""),
]);
js_str!("=\""),
&JsString::from(&escaped_v[..]),
js_str!("\"")
);
}
// 5. Let p2 be the string-concatenation of p1 and ">".
// 6. Let p3 be the string-concatenation of p2 and S.
// 7. Let p4 be the string-concatenation of p3, "</", tag, and ">".
let p4 = JsString::concat_array(&[
p1.as_slice(),
utf16!(">"),
s.as_slice(),
utf16!("</"),
tag,
utf16!(">"),
]);
let p4 = js_string!(&p1, js_str!(">"), &s, js_str!("</"), tag, js_str!(">"));
// 8. Return p4.
Ok(p4.into())
@ -2372,7 +2398,7 @@ impl String {
// 1. Let S be the this value.
let s = this;
// 2. Return ? CreateHTML(S, "a", "name", name).
Self::create_html(s, utf16!("a"), Some((utf16!("name"), name)), context)
Self::create_html(s, js_str!("a"), Some((js_str!("name"), name)), context)
}
/// `String.prototype.big( )`
@ -2387,7 +2413,7 @@ impl String {
// 1. Let S be the this value.
let s = this;
// 2. Return ? CreateHTML(S, "big", "", "").
Self::create_html(s, utf16!("big"), None, context)
Self::create_html(s, js_str!("big"), None, context)
}
/// `String.prototype.blink( )`
@ -2402,7 +2428,7 @@ impl String {
// 1. Let S be the this value.
let s = this;
// 2. Return ? CreateHTML(S, "blink", "", "").
Self::create_html(s, utf16!("blink"), None, context)
Self::create_html(s, js_str!("blink"), None, context)
}
/// `String.prototype.bold( )`
@ -2417,7 +2443,7 @@ impl String {
// 1. Let S be the this value.
let s = this;
// 2. Return ? CreateHTML(S, "b", "", "").
Self::create_html(s, utf16!("b"), None, context)
Self::create_html(s, js_str!("b"), None, context)
}
/// `String.prototype.fixed( )`
@ -2432,7 +2458,7 @@ impl String {
// 1. Let S be the this value.
let s = this;
// 2. Return ? CreateHTML(S, "big", "", "").
Self::create_html(s, utf16!("tt"), None, context)
Self::create_html(s, js_str!("tt"), None, context)
}
/// `String.prototype.fontcolor( color )`
@ -2453,7 +2479,7 @@ impl String {
// 1. Let S be the this value.
let s = this;
// 2. Return ? CreateHTML(S, "font", "color", color).
Self::create_html(s, utf16!("font"), Some((utf16!("color"), color)), context)
Self::create_html(s, js_str!("font"), Some((js_str!("color"), color)), context)
}
/// `String.prototype.fontsize( size )`
@ -2474,7 +2500,7 @@ impl String {
// 1. Let S be the this value.
let s = this;
// 2. Return ? CreateHTML(S, "font", "size", size).
Self::create_html(s, utf16!("font"), Some((utf16!("size"), size)), context)
Self::create_html(s, js_str!("font"), Some((js_str!("size"), size)), context)
}
/// `String.prototype.italics( )`
@ -2493,7 +2519,7 @@ impl String {
// 1. Let S be the this value.
let s = this;
// 2. Return ? CreateHTML(S, "i", "", "").
Self::create_html(s, utf16!("i"), None, context)
Self::create_html(s, js_str!("i"), None, context)
}
/// `String.prototype.link( url )`
@ -2514,7 +2540,7 @@ impl String {
// 1. Let S be the this value.
let s = this;
// 2. Return ? CreateHTML(S, "a", "href", url).
Self::create_html(s, utf16!("a"), Some((utf16!("href"), url)), context)
Self::create_html(s, js_str!("a"), Some((js_str!("href"), url)), context)
}
/// `String.prototype.small( )`
@ -2529,7 +2555,7 @@ impl String {
// 1. Let S be the this value.
let s = this;
// 2. Return ? CreateHTML(S, "small", "", "").
Self::create_html(s, utf16!("small"), None, context)
Self::create_html(s, js_str!("small"), None, context)
}
/// `String.prototype.strike( )`
@ -2548,7 +2574,7 @@ impl String {
// 1. Let S be the this value.
let s = this;
// 2. Return ? CreateHTML(S, "strike", "", "").
Self::create_html(s, utf16!("strike"), None, context)
Self::create_html(s, js_str!("strike"), None, context)
}
/// `String.prototype.sub( )`
@ -2563,7 +2589,7 @@ impl String {
// 1. Let S be the this value.
let s = this;
// 2. Return ? CreateHTML(S, "sub", "", "").
Self::create_html(s, utf16!("sub"), None, context)
Self::create_html(s, js_str!("sub"), None, context)
}
/// `String.prototype.sup( )`
@ -2578,7 +2604,7 @@ impl String {
// 1. Let S be the this value.
let s = this;
// 2. Return ? CreateHTML(S, "sup", "", "").
Self::create_html(s, utf16!("sup"), None, context)
Self::create_html(s, js_str!("sup"), None, context)
}
}
@ -2639,19 +2665,19 @@ pub(crate) fn get_substitution(
// $&
Some(CodePoint::Unicode('&')) => {
// matched
result.extend_from_slice(matched);
result.extend(matched.iter());
}
// $`
Some(CodePoint::Unicode('`')) => {
// The replacement is the substring of str from 0 to position.
result.extend_from_slice(&str[..position]);
result.extend(str.get_expect(..position).iter());
}
// $'
Some(CodePoint::Unicode('\'')) => {
// If tailPos ≥ stringLength, the replacement is the empty String.
// Otherwise the replacement is the substring of str from tailPos.
if tail_pos < str_length {
result.extend_from_slice(&str[tail_pos..]);
result.extend(str.get_expect(tail_pos..).iter());
}
}
// $nn
@ -2698,7 +2724,7 @@ pub(crate) fn get_substitution(
// a. Let refReplacement be capture.
if let Some(capture) = captures.get(index - 1) {
if let Some(s) = capture.as_string() {
result.extend_from_slice(s);
result.extend(s.iter());
}
}
@ -2742,14 +2768,14 @@ pub(crate) fn get_substitution(
// d. Else,
} else {
// i. Let groupName be the enclosed substring.
let group_name = js_string!(group_name);
let group_name = js_string!(&group_name[..]);
// ii. Let capture be ? Get(namedCaptures, groupName).
let capture = named_captures.get(group_name, context)?;
// iii. If capture is undefined, replace the text through > with the empty String.
// iv. Otherwise, replace the text through > with ? ToString(capture).
if !capture.is_undefined() {
result.extend_from_slice(&capture.to_string(context)?);
result.extend(capture.to_string(context)?.iter());
}
}
}
@ -2768,5 +2794,5 @@ pub(crate) fn get_substitution(
}
// 11. Return result.
Ok(js_string!(result))
Ok(js_string!(&result[..]))
}

110
core/engine/src/builtins/string/tests.rs

@ -1,3 +1,4 @@
use boa_macros::js_str;
use indoc::indoc;
use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction};
@ -56,11 +57,11 @@ fn concat() {
"#}),
TestAction::assert_eq(
"hello.concat(world, nice)",
js_string!("Hello, world! Have a nice day."),
js_str!("Hello, world! Have a nice day."),
),
TestAction::assert_eq(
"hello + world + nice",
js_string!("Hello, world! Have a nice day."),
js_str!("Hello, world! Have a nice day."),
),
]);
}
@ -72,10 +73,7 @@ fn generic_concat() {
Number.prototype.concat = String.prototype.concat;
let number = new Number(100);
"#}),
TestAction::assert_eq(
"number.concat(' - 50', ' = 50')",
js_string!("100 - 50 = 50"),
),
TestAction::assert_eq("number.concat(' - 50', ' = 50')", js_str!("100 - 50 = 50")),
]);
}
@ -100,8 +98,8 @@ fn repeat() {
TestAction::assert_eq("empty.repeat(1)", js_string!()),
TestAction::assert_eq("en.repeat(0)", js_string!()),
TestAction::assert_eq("zh.repeat(0)", js_string!()),
TestAction::assert_eq("en.repeat(1)", js_string!("english")),
TestAction::assert_eq("zh.repeat(2)", js_string!("中文中文")),
TestAction::assert_eq("en.repeat(1)", js_str!("english")),
TestAction::assert_eq("zh.repeat(2)", js_str!("中文中文")),
]);
}
@ -140,9 +138,9 @@ fn repeat_generic() {
run_test_actions([
TestAction::run("Number.prototype.repeat = String.prototype.repeat;"),
TestAction::assert_eq("(0).repeat(0)", js_string!()),
TestAction::assert_eq("(1).repeat(1)", js_string!("1")),
TestAction::assert_eq("(1).repeat(5)", js_string!("11111")),
TestAction::assert_eq("(12).repeat(3)", js_string!("121212")),
TestAction::assert_eq("(1).repeat(1)", js_str!("1")),
TestAction::assert_eq("(1).repeat(5)", js_str!("11111")),
TestAction::assert_eq("(12).repeat(3)", js_str!("121212")),
]);
}
@ -152,7 +150,7 @@ fn replace() {
indoc! {r#"
"abc".replace("a", "2")
"#},
js_string!("2bc"),
js_str!("2bc"),
)]);
}
@ -162,7 +160,7 @@ fn replace_no_match() {
indoc! {r#"
"abc".replace(/d/, "$&$&")
"#},
js_string!("abc"),
js_str!("abc"),
)]);
}
@ -172,7 +170,7 @@ fn replace_with_capture_groups() {
indoc! {r#"
"John Smith".replace(/(\w+)\s(\w+)/, '$2, $1')
"#},
js_string!("Smith, John"),
js_str!("Smith, John"),
)]);
}
@ -183,7 +181,7 @@ fn replace_with_tenth_capture_group() {
var re = /(\d)(\d)(\d)(\d)(\d)(\d)(\d)(\d)(\d)(\d)/;
"0123456789".replace(re, '$10')
"#},
js_string!("9"),
js_str!("9"),
)]);
}
@ -199,11 +197,11 @@ fn replace_substitutions() {
var end = a.replace(re, " $' ");
var no_sub = a.replace(re, " $_ ");
"#}),
TestAction::assert_eq("a.replace(re, \" $$ \")", js_string!("one $ three")),
TestAction::assert_eq("a.replace(re, \"$&$&\")", js_string!("one two two three")),
TestAction::assert_eq("a.replace(re, \" $` \")", js_string!("one one three")),
TestAction::assert_eq("a.replace(re, \" $' \")", js_string!("one three three")),
TestAction::assert_eq("a.replace(re, \" $_ \")", js_string!("one $_ three")),
TestAction::assert_eq("a.replace(re, \" $$ \")", js_str!("one $ three")),
TestAction::assert_eq("a.replace(re, \"$&$&\")", js_str!("one two two three")),
TestAction::assert_eq("a.replace(re, \" $` \")", js_str!("one one three")),
TestAction::assert_eq("a.replace(re, \" $' \")", js_str!("one three three")),
TestAction::assert_eq("a.replace(re, \" $_ \")", js_str!("one $_ three")),
]);
}
@ -222,11 +220,11 @@ fn replace_with_function() {
"#}),
TestAction::assert_eq(
"\"ecmascript is cool\".replace(/c(o)(o)(l)/, replacer)",
js_string!("ecmascript is awesome!"),
js_str!("ecmascript is awesome!"),
),
TestAction::assert_eq("p1", js_string!("o")),
TestAction::assert_eq("p2", js_string!("o")),
TestAction::assert_eq("p3", js_string!("l")),
TestAction::assert_eq("p1", js_str!("o")),
TestAction::assert_eq("p2", js_str!("o")),
TestAction::assert_eq("p3", js_str!("l")),
TestAction::assert_eq("length", 14),
]);
}
@ -329,7 +327,7 @@ fn match_all_one() {
)
"#}),
TestAction::assert_eq("m1.value.index", 0),
TestAction::assert_eq("m1.value.input", js_string!("test1test2")),
TestAction::assert_eq("m1.value.input", js_str!("test1test2")),
TestAction::assert_eq("m1.value.groups", JsValue::undefined()),
TestAction::assert(indoc! {r#"
arrayEquals(
@ -338,7 +336,7 @@ fn match_all_one() {
)
"#}),
TestAction::assert_eq("m2.value.index", 5),
TestAction::assert_eq("m2.value.input", js_string!("test1test2")),
TestAction::assert_eq("m2.value.input", js_str!("test1test2")),
TestAction::assert_eq("m2.value.groups", JsValue::undefined()),
TestAction::assert_eq("m3.value", JsValue::undefined()),
]);
@ -366,7 +364,7 @@ fn match_all_two() {
)
"#}),
TestAction::assert_eq("m1.value.index", 6),
TestAction::assert_eq("m1.value.input", js_string!("table football, foosball")),
TestAction::assert_eq("m1.value.input", js_str!("table football, foosball")),
TestAction::assert_eq("m1.value.groups", JsValue::undefined()),
TestAction::assert(indoc! {r#"
arrayEquals(
@ -375,7 +373,7 @@ fn match_all_two() {
)
"#}),
TestAction::assert_eq("m2.value.index", 16),
TestAction::assert_eq("m2.value.input", js_string!("table football, foosball")),
TestAction::assert_eq("m2.value.input", js_str!("table football, foosball")),
TestAction::assert_eq("m2.value.groups", JsValue::undefined()),
TestAction::assert_eq("m3.value", JsValue::undefined()),
]);
@ -401,7 +399,7 @@ fn test_match() {
TestAction::assert_eq("result1.index", 4),
TestAction::assert_eq(
"result1.input",
js_string!("The Quick Brown Fox Jumps Over The Lazy Dog"),
js_str!("The Quick Brown Fox Jumps Over The Lazy Dog"),
),
TestAction::assert(indoc! {r#"
arrayEquals(
@ -418,7 +416,7 @@ fn test_match() {
TestAction::assert_eq("result3.index", 0),
TestAction::assert_eq(
"result3.input",
js_string!("The Quick Brown Fox Jumps Over The Lazy Dog"),
js_str!("The Quick Brown Fox Jumps Over The Lazy Dog"),
),
TestAction::assert(indoc! {r#"
arrayEquals(
@ -432,30 +430,30 @@ fn test_match() {
#[test]
fn trim() {
run_test_actions([
TestAction::assert_eq(r"'Hello'.trim()", js_string!("Hello")),
TestAction::assert_eq(r"' \nHello'.trim()", js_string!("Hello")),
TestAction::assert_eq(r"'Hello \n\r'.trim()", js_string!("Hello")),
TestAction::assert_eq(r"' Hello '.trim()", js_string!("Hello")),
TestAction::assert_eq(r"'Hello'.trim()", js_str!("Hello")),
TestAction::assert_eq(r"' \nHello'.trim()", js_str!("Hello")),
TestAction::assert_eq(r"'Hello \n\r'.trim()", js_str!("Hello")),
TestAction::assert_eq(r"' Hello '.trim()", js_str!("Hello")),
]);
}
#[test]
fn trim_start() {
run_test_actions([
TestAction::assert_eq(r"'Hello'.trimStart()", js_string!("Hello")),
TestAction::assert_eq(r"' \nHello'.trimStart()", js_string!("Hello")),
TestAction::assert_eq(r"'Hello \n\r'.trimStart()", js_string!("Hello \n\r")),
TestAction::assert_eq(r"' Hello '.trimStart()", js_string!("Hello ")),
TestAction::assert_eq(r"'Hello'.trimStart()", js_str!("Hello")),
TestAction::assert_eq(r"' \nHello'.trimStart()", js_str!("Hello")),
TestAction::assert_eq(r"'Hello \n\r'.trimStart()", js_str!("Hello \n\r")),
TestAction::assert_eq(r"' Hello '.trimStart()", js_str!("Hello ")),
]);
}
#[test]
fn trim_end() {
run_test_actions([
TestAction::assert_eq(r"'Hello'.trimEnd()", js_string!("Hello")),
TestAction::assert_eq(r"' \nHello'.trimEnd()", js_string!(" \nHello")),
TestAction::assert_eq(r"'Hello \n\r'.trimEnd()", js_string!("Hello")),
TestAction::assert_eq(r"' Hello '.trimEnd()", js_string!(" Hello")),
TestAction::assert_eq(r"'Hello'.trimEnd()", js_str!("Hello")),
TestAction::assert_eq(r"' \nHello'.trimEnd()", js_str!(" \nHello")),
TestAction::assert_eq(r"'Hello \n\r'.trimEnd()", js_str!("Hello")),
TestAction::assert_eq(r"' Hello '.trimEnd()", js_str!(" Hello")),
]);
}
@ -578,7 +576,7 @@ fn split_with_symbol_split_method() {
sep_a[Symbol.split] = function(s, limit) { return s + limit.toString(); };
'hello'.split(sep_a, 10)
"#},
js_string!("hello10"),
js_str!("hello10"),
),
TestAction::assert(indoc! {r#"
let sep_b = {};
@ -752,10 +750,10 @@ fn last_index_non_integer_position_argument() {
fn char_at() {
run_test_actions([
TestAction::assert_eq("'abc'.charAt(-1)", js_string!()),
TestAction::assert_eq("'abc'.charAt(1)", js_string!("b")),
TestAction::assert_eq("'abc'.charAt(1)", js_str!("b")),
TestAction::assert_eq("'abc'.charAt(9)", js_string!()),
TestAction::assert_eq("'abc'.charAt()", js_string!("a")),
TestAction::assert_eq("'abc'.charAt(null)", js_string!("a")),
TestAction::assert_eq("'abc'.charAt()", js_str!("a")),
TestAction::assert_eq("'abc'.charAt(null)", js_str!("a")),
TestAction::assert_eq(r"'\uDBFF'.charAt(0)", js_string!(&[0xDBFFu16])),
]);
}
@ -794,10 +792,10 @@ fn code_point_at() {
#[test]
fn slice() {
run_test_actions([
TestAction::assert_eq("'abc'.slice()", js_string!("abc")),
TestAction::assert_eq("'abc'.slice(1)", js_string!("bc")),
TestAction::assert_eq("'abc'.slice(-1)", js_string!("c")),
TestAction::assert_eq("'abc'.slice(0, 9)", js_string!("abc")),
TestAction::assert_eq("'abc'.slice()", js_str!("abc")),
TestAction::assert_eq("'abc'.slice(1)", js_str!("bc")),
TestAction::assert_eq("'abc'.slice(-1)", js_str!("c")),
TestAction::assert_eq("'abc'.slice(0, 9)", js_str!("abc")),
TestAction::assert_eq("'abc'.slice(9, 10)", js_string!()),
]);
}
@ -844,8 +842,8 @@ fn unicode_iter() {
fn string_get_property() {
run_test_actions([
TestAction::assert_eq("'abc'[-1]", JsValue::undefined()),
TestAction::assert_eq("'abc'[1]", js_string!("b")),
TestAction::assert_eq("'abc'[2]", js_string!("c")),
TestAction::assert_eq("'abc'[1]", js_str!("b")),
TestAction::assert_eq("'abc'[2]", js_str!("c")),
TestAction::assert_eq("'abc'[3]", JsValue::undefined()),
TestAction::assert_eq("'abc'['foo']", JsValue::undefined()),
TestAction::assert_eq("'😀'[0]", js_string!(&[0xD83D])),
@ -866,9 +864,9 @@ fn search() {
fn from_code_point() {
// Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint
run_test_actions([
TestAction::assert_eq("String.fromCodePoint(42)", js_string!("*")),
TestAction::assert_eq("String.fromCodePoint(65, 90)", js_string!("AZ")),
TestAction::assert_eq("String.fromCodePoint(0x404)", js_string!("Є")),
TestAction::assert_eq("String.fromCodePoint(42)", js_str!("*")),
TestAction::assert_eq("String.fromCodePoint(65, 90)", js_str!("AZ")),
TestAction::assert_eq("String.fromCodePoint(0x404)", js_str!("Є")),
TestAction::assert_eq(
"String.fromCodePoint(0x2f804)",
js_string!(&[0xD87E, 0xDC04]),
@ -884,7 +882,7 @@ fn from_code_point() {
),
TestAction::assert_eq(
"String.fromCodePoint(9731, 9733, 9842, 0x4F60)",
js_string!("☃★♲你"),
js_str!("☃★♲你"),
),
TestAction::assert_native_error(
"String.fromCodePoint('_')",

10
core/engine/src/builtins/symbol/mod.rs

@ -64,8 +64,8 @@ impl GlobalSymbolRegistry {
}
fn get_or_create_symbol(&self, key: &JsString) -> JsResult<JsSymbol> {
let slice = &**key;
if let Some(symbol) = self.keys.get(slice) {
let slice = key.iter().collect::<Vec<_>>();
if let Some(symbol) = self.keys.get(&slice[..]) {
return Ok(symbol.clone());
}
@ -73,8 +73,10 @@ impl GlobalSymbolRegistry {
JsNativeError::range()
.with_message("reached the maximum number of symbols that can be created")
})?;
self.keys.insert(slice.into(), symbol.clone());
self.symbols.insert(symbol.clone(), slice.into());
self.keys
.insert(slice.clone().into_boxed_slice(), symbol.clone());
self.symbols
.insert(symbol.clone(), slice.into_boxed_slice());
Ok(symbol)
}

5
core/engine/src/builtins/symbol/tests.rs

@ -1,4 +1,5 @@
use crate::{js_string, run_test_actions, JsValue, TestAction};
use crate::{run_test_actions, JsValue, TestAction};
use boa_macros::js_str;
use indoc::indoc;
#[test]
@ -12,7 +13,7 @@ fn call_symbol_and_check_return_type() {
fn print_symbol_expect_description() {
run_test_actions([TestAction::assert_eq(
"String(Symbol('Hello'))",
js_string!("Symbol(Hello)"),
js_str!("Symbol(Hello)"),
)]);
}

22
core/engine/src/builtins/temporal/calendar/mod.rs

@ -18,10 +18,11 @@ use crate::{
object::internal_methods::get_prototype_from_constructor,
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{custom_trace, Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
use temporal_rs::{
components::calendar::{
@ -75,7 +76,12 @@ impl IntrinsicObject for Calendar {
Self::NAME,
Attribute::CONFIGURABLE,
)
.accessor(utf16!("id"), Some(get_id), None, Attribute::CONFIGURABLE)
.accessor(
js_string!("id"),
Some(get_id),
None,
Attribute::CONFIGURABLE,
)
.static_method(Self::from, js_string!("from"), 1)
.method(Self::date_from_fields, js_string!("dateFromFields"), 2)
.method(
@ -244,7 +250,7 @@ impl Calendar {
};
// 8. Let overflow be ? ToTemporalOverflow(options).
let overflow = get_option(&options, utf16!("overflow"), context)?
let overflow = get_option(&options, js_str!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain);
// NOTE: implement the below on the calenar itself
@ -327,7 +333,7 @@ impl Calendar {
};
// 7. Let overflow be ? ToTemporalOverflow(options).
let overflow = get_option::<ArithmeticOverflow>(&options, utf16!("overflow"), context)?
let overflow = get_option::<ArithmeticOverflow>(&options, js_str!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain);
let result = calendar
@ -403,7 +409,7 @@ impl Calendar {
};
// 8. Let overflow be ? ToTemporalOverflow(options).
let overflow = get_option(&options, utf16!("overflow"), context)?
let overflow = get_option(&options, js_str!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain);
let result = calendar
@ -439,7 +445,7 @@ impl Calendar {
let options_obj = get_options_object(options)?;
// 7. Let overflow be ? ToTemporalOverflow(options).
let overflow = get_option(&options_obj, utf16!("overflow"), context)?
let overflow = get_option(&options_obj, js_str!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain);
// 8. Let balanceResult be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]],
@ -476,7 +482,7 @@ impl Calendar {
// 8. If largestUnit is "auto", set largestUnit to "day".
let largest_unit = super::options::get_temporal_unit(
&options,
utf16!("largestUnit"),
js_str!("largestUnit"),
TemporalUnitGroup::Date,
None,
context,
@ -1033,7 +1039,7 @@ pub(crate) fn get_temporal_calendar_slot_value_with_default(
}
// 2. Let calendarLike be ? Get(item, "calendar").
let calendar_like = item.get(utf16!("calendar"), context)?;
let calendar_like = item.get(js_str!("calendar"), context)?;
// 3. Return ? ToTemporalCalendarSlotValue(calendarLike, "iso8601").
to_temporal_calendar_slot_value(&calendar_like, context)

46
core/engine/src/builtins/temporal/calendar/object.rs

@ -13,7 +13,7 @@ use crate::{
Context, JsObject, JsString, JsValue,
};
use boa_macros::utf16;
use boa_macros::js_str;
use num_traits::ToPrimitive;
use plain_date::PlainDate;
use plain_date_time::PlainDateTime;
@ -41,7 +41,7 @@ impl CalendarProtocol for JsObject {
context: &mut Context,
) -> TemporalResult<Date<Self>> {
let method = self
.get(utf16!("dateFromFields"), context)
.get(js_str!("dateFromFields"), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let fields = JsObject::from_temporal_fields(fields, context)
@ -51,7 +51,7 @@ impl CalendarProtocol for JsObject {
overflow_obj
.create_data_property_or_throw(
utf16!("overflow"),
js_str!("overflow"),
JsString::from(overflow.to_string()),
context,
)
@ -88,7 +88,7 @@ impl CalendarProtocol for JsObject {
context: &mut Context,
) -> TemporalResult<YearMonth<JsObject>> {
let method = self
.get(utf16!("yearMonthFromFields"), context)
.get(js_str!("yearMonthFromFields"), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let fields = JsObject::from_temporal_fields(fields, context)
@ -98,7 +98,7 @@ impl CalendarProtocol for JsObject {
overflow_obj
.create_data_property_or_throw(
utf16!("overflow"),
js_str!("overflow"),
JsString::from(overflow.to_string()),
context,
)
@ -137,7 +137,7 @@ impl CalendarProtocol for JsObject {
context: &mut Context,
) -> TemporalResult<MonthDay<JsObject>> {
let method = self
.get(utf16!("yearMonthFromFields"), context)
.get(js_str!("yearMonthFromFields"), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let fields = JsObject::from_temporal_fields(fields, context)
@ -147,7 +147,7 @@ impl CalendarProtocol for JsObject {
overflow_obj
.create_data_property_or_throw(
utf16!("overflow"),
js_str!("overflow"),
JsString::from(overflow.to_string()),
context,
)
@ -227,7 +227,7 @@ impl CalendarProtocol for JsObject {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(utf16!("year")), context)
.get(PropertyKey::from(js_str!("year")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
@ -269,7 +269,7 @@ impl CalendarProtocol for JsObject {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(utf16!("month")), context)
.get(PropertyKey::from(js_str!("month")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
@ -311,7 +311,7 @@ impl CalendarProtocol for JsObject {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(utf16!("monthCode")), context)
.get(PropertyKey::from(js_str!("monthCode")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
@ -338,7 +338,7 @@ impl CalendarProtocol for JsObject {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(utf16!("day")), context)
.get(PropertyKey::from(js_str!("day")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
@ -380,7 +380,7 @@ impl CalendarProtocol for JsObject {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(utf16!("dayOfWeek")), context)
.get(PropertyKey::from(js_str!("dayOfWeek")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
@ -424,7 +424,7 @@ impl CalendarProtocol for JsObject {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(utf16!("dayOfYear")), context)
.get(PropertyKey::from(js_str!("dayOfYear")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
@ -468,7 +468,7 @@ impl CalendarProtocol for JsObject {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(utf16!("weekOfYear")), context)
.get(PropertyKey::from(js_str!("weekOfYear")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
@ -512,7 +512,7 @@ impl CalendarProtocol for JsObject {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(utf16!("yearOfWeek")), context)
.get(PropertyKey::from(js_str!("yearOfWeek")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
@ -549,7 +549,7 @@ impl CalendarProtocol for JsObject {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(utf16!("daysInWeek")), context)
.get(PropertyKey::from(js_str!("daysInWeek")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
@ -593,7 +593,7 @@ impl CalendarProtocol for JsObject {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(utf16!("daysInMonth")), context)
.get(PropertyKey::from(js_str!("daysInMonth")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
.as_callable()
@ -638,7 +638,7 @@ impl CalendarProtocol for JsObject {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(utf16!("daysInYear")), context)
.get(PropertyKey::from(js_str!("daysInYear")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
@ -682,7 +682,7 @@ impl CalendarProtocol for JsObject {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(utf16!("monthsInYear")), context)
.get(PropertyKey::from(js_str!("monthsInYear")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
@ -728,7 +728,7 @@ impl CalendarProtocol for JsObject {
let date_like = date_like_to_object(date_like, context)?;
let method = self
.get(PropertyKey::from(utf16!("inLeapYear")), context)
.get(PropertyKey::from(js_str!("inLeapYear")), context)
.expect("method must exist on a object that implements the CalendarProtocol.");
let val = method
@ -753,7 +753,7 @@ impl CalendarProtocol for JsObject {
);
let method = self
.get(PropertyKey::from(utf16!("fields")), context)
.get(PropertyKey::from(js_str!("fields")), context)
.expect("method must exist on an object that implements the CalendarProtocol.");
let result = method
@ -799,7 +799,7 @@ impl CalendarProtocol for JsObject {
.map_err(|e| TemporalError::general(e.to_string()))?;
let method = self
.get(PropertyKey::from(utf16!("mergeFields")), context)
.get(PropertyKey::from(js_str!("mergeFields")), context)
.expect("method must exist on an object that implements the CalendarProtocol.");
let value = method
@ -824,7 +824,7 @@ impl CalendarProtocol for JsObject {
fn identifier(&self, context: &mut Context) -> TemporalResult<String> {
let identifier = self
.__get__(
&PropertyKey::from(utf16!("id")),
&PropertyKey::from(js_str!("id")),
self.clone().into(),
&mut context.into(),
)

59
core/engine/src/builtins/temporal/duration/mod.rs

@ -10,10 +10,11 @@ use crate::{
object::internal_methods::get_prototype_from_constructor,
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
use temporal_rs::{
components::Duration as InnerDuration,
@ -108,73 +109,73 @@ impl IntrinsicObject for Duration {
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.accessor(
utf16!("years"),
js_string!("years"),
Some(get_years),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("months"),
js_string!("months"),
Some(get_months),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("weeks"),
js_string!("weeks"),
Some(get_weeks),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("days"),
js_string!("days"),
Some(get_days),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("hours"),
js_string!("hours"),
Some(get_hours),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("minutes"),
js_string!("minutes"),
Some(get_minutes),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("seconds"),
js_string!("seconds"),
Some(get_seconds),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("milliseconds"),
js_string!("milliseconds"),
Some(get_milliseconds),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("microseconds"),
js_string!("microseconds"),
Some(get_microseconds),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("nanoseconds"),
js_string!("nanoseconds"),
Some(get_nanoseconds),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("sign"),
js_string!("sign"),
Some(get_sign),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("blank"),
js_string!("blank"),
Some(is_blank),
None,
Attribute::CONFIGURABLE,
@ -623,7 +624,7 @@ impl Duration {
let new_round_to = JsObject::with_null_proto();
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString).
new_round_to.create_data_property_or_throw(
utf16!("smallestUnit"),
js_str!("smallestUnit"),
param_string,
context,
)?;
@ -644,7 +645,7 @@ impl Duration {
// 9. Let largestUnit be ? GetTemporalUnit(roundTo, "largestUnit", datetime, undefined, « "auto" »).
let largest_unit = get_temporal_unit(
&round_to,
utf16!("largestUnit"),
js_str!("largestUnit"),
TemporalUnitGroup::DateTime,
Some([TemporalUnit::Auto].into()),
context,
@ -661,12 +662,12 @@ impl Duration {
// 14. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
let rounding_mode =
get_option::<TemporalRoundingMode>(&round_to, utf16!("roundingMode"), context)?;
get_option::<TemporalRoundingMode>(&round_to, js_str!("roundingMode"), context)?;
// 15. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", datetime, undefined).
let smallest_unit = get_temporal_unit(
&round_to,
utf16!("smallestUnit"),
js_str!("smallestUnit"),
TemporalUnitGroup::DateTime,
None,
context,
@ -725,7 +726,7 @@ impl Duration {
let total_of = JsObject::with_null_proto();
// c. Perform ! CreateDataPropertyOrThrow(totalOf, "unit", paramString).
total_of.create_data_property_or_throw(
utf16!("unit"),
js_str!("unit"),
param_string.clone(),
context,
)?;
@ -748,7 +749,7 @@ impl Duration {
// 10. Let unit be ? GetTemporalUnit(totalOf, "unit", datetime, required).
let _unit = get_temporal_unit(
&total_of,
utf16!("unit"),
js_str!("unit"),
TemporalUnitGroup::DateTime,
None,
context,
@ -905,70 +906,70 @@ pub(crate) fn to_temporal_partial_duration(
// 3. NOTE: The following steps read properties and perform independent validation in alphabetical order.
// 4. Let days be ? Get(temporalDurationLike, "days").
let days = unknown_object.get(utf16!("days"), context)?;
let days = unknown_object.get(js_str!("days"), context)?;
if !days.is_undefined() {
// 5. If days is not undefined, set result.[[Days]] to ? ToIntegerIfIntegral(days).
result.set_days(f64::from(to_integer_if_integral(&days, context)?));
}
// 6. Let hours be ? Get(temporalDurationLike, "hours").
let hours = unknown_object.get(utf16!("hours"), context)?;
let hours = unknown_object.get(js_str!("hours"), context)?;
// 7. If hours is not undefined, set result.[[Hours]] to ? ToIntegerIfIntegral(hours).
if !hours.is_undefined() {
result.set_hours(f64::from(to_integer_if_integral(&hours, context)?));
}
// 8. Let microseconds be ? Get(temporalDurationLike, "microseconds").
let microseconds = unknown_object.get(utf16!("microseconds"), context)?;
let microseconds = unknown_object.get(js_str!("microseconds"), context)?;
// 9. If microseconds is not undefined, set result.[[Microseconds]] to ? ToIntegerIfIntegral(microseconds).
if !microseconds.is_undefined() {
result.set_microseconds(f64::from(to_integer_if_integral(&microseconds, context)?));
}
// 10. Let milliseconds be ? Get(temporalDurationLike, "milliseconds").
let milliseconds = unknown_object.get(utf16!("milliseconds"), context)?;
let milliseconds = unknown_object.get(js_str!("milliseconds"), context)?;
// 11. If milliseconds is not undefined, set result.[[Milliseconds]] to ? ToIntegerIfIntegral(milliseconds).
if !milliseconds.is_undefined() {
result.set_milliseconds(f64::from(to_integer_if_integral(&milliseconds, context)?));
}
// 12. Let minutes be ? Get(temporalDurationLike, "minutes").
let minutes = unknown_object.get(utf16!("minutes"), context)?;
let minutes = unknown_object.get(js_str!("minutes"), context)?;
// 13. If minutes is not undefined, set result.[[Minutes]] to ? ToIntegerIfIntegral(minutes).
if !minutes.is_undefined() {
result.set_minutes(f64::from(to_integer_if_integral(&minutes, context)?));
}
// 14. Let months be ? Get(temporalDurationLike, "months").
let months = unknown_object.get(utf16!("months"), context)?;
let months = unknown_object.get(js_str!("months"), context)?;
// 15. If months is not undefined, set result.[[Months]] to ? ToIntegerIfIntegral(months).
if !months.is_undefined() {
result.set_months(f64::from(to_integer_if_integral(&months, context)?));
}
// 16. Let nanoseconds be ? Get(temporalDurationLike, "nanoseconds").
let nanoseconds = unknown_object.get(utf16!("nanoseconds"), context)?;
let nanoseconds = unknown_object.get(js_str!("nanoseconds"), context)?;
// 17. If nanoseconds is not undefined, set result.[[Nanoseconds]] to ? ToIntegerIfIntegral(nanoseconds).
if !nanoseconds.is_undefined() {
result.set_nanoseconds(f64::from(to_integer_if_integral(&nanoseconds, context)?));
}
// 18. Let seconds be ? Get(temporalDurationLike, "seconds").
let seconds = unknown_object.get(utf16!("seconds"), context)?;
let seconds = unknown_object.get(js_str!("seconds"), context)?;
// 19. If seconds is not undefined, set result.[[Seconds]] to ? ToIntegerIfIntegral(seconds).
if !seconds.is_undefined() {
result.set_seconds(f64::from(to_integer_if_integral(&seconds, context)?));
}
// 20. Let weeks be ? Get(temporalDurationLike, "weeks").
let weeks = unknown_object.get(utf16!("weeks"), context)?;
let weeks = unknown_object.get(js_str!("weeks"), context)?;
// 21. If weeks is not undefined, set result.[[Weeks]] to ? ToIntegerIfIntegral(weeks).
if !weeks.is_undefined() {
result.set_weeks(f64::from(to_integer_if_integral(&weeks, context)?));
}
// 22. Let years be ? Get(temporalDurationLike, "years").
let years = unknown_object.get(utf16!("years"), context)?;
let years = unknown_object.get(js_str!("years"), context)?;
// 23. If years is not undefined, set result.[[Years]] to ? ToIntegerIfIntegral(years).
if !years.is_undefined() {
result.set_years(f64::from(to_integer_if_integral(&years, context)?));

35
core/engine/src/builtins/temporal/instant/mod.rs

@ -14,11 +14,12 @@ use crate::{
object::internal_methods::get_prototype_from_constructor,
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsBigInt, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol,
JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
use temporal_rs::{
components::Instant as InnerInstant,
@ -64,25 +65,25 @@ impl IntrinsicObject for Instant {
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("epochSeconds"),
js_str!("epochSeconds"),
Some(get_seconds),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("epochMilliseconds"),
js_str!("epochMilliseconds"),
Some(get_millis),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("epochMicroseconds"),
js_str!("epochMicroseconds"),
Some(get_micros),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("epochNanoseconds"),
js_str!("epochNanoseconds"),
Some(get_nanos),
None,
Attribute::CONFIGURABLE,
@ -285,10 +286,10 @@ impl Instant {
// Fetch the necessary options.
let options = get_options_object(args.get_or_undefined(1))?;
let mode = get_option::<TemporalRoundingMode>(&options, utf16!("roundingMode"), context)?;
let increment = get_option::<f64>(&options, utf16!("roundingIncrement"), context)?;
let smallest_unit = get_option::<TemporalUnit>(&options, utf16!("smallestUnit"), context)?;
let largest_unit = get_option::<TemporalUnit>(&options, utf16!("largestUnit"), context)?;
let mode = get_option::<TemporalRoundingMode>(&options, js_str!("roundingMode"), context)?;
let increment = get_option::<f64>(&options, js_str!("roundingIncrement"), context)?;
let smallest_unit = get_option::<TemporalUnit>(&options, js_str!("smallestUnit"), context)?;
let largest_unit = get_option::<TemporalUnit>(&options, js_str!("largestUnit"), context)?;
let result = instant
.inner
.until(&other, mode, increment, smallest_unit, largest_unit)?;
@ -313,10 +314,10 @@ impl Instant {
// 3. Return ? DifferenceTemporalInstant(since, instant, other, options).
let other = to_temporal_instant(args.get_or_undefined(0))?;
let options = get_options_object(args.get_or_undefined(1))?;
let mode = get_option::<TemporalRoundingMode>(&options, utf16!("roundingMode"), context)?;
let increment = get_option::<f64>(&options, utf16!("roundingIncrement"), context)?;
let smallest_unit = get_option::<TemporalUnit>(&options, utf16!("smallestUnit"), context)?;
let largest_unit = get_option::<TemporalUnit>(&options, utf16!("largestUnit"), context)?;
let mode = get_option::<TemporalRoundingMode>(&options, js_str!("roundingMode"), context)?;
let increment = get_option::<f64>(&options, js_str!("roundingIncrement"), context)?;
let smallest_unit = get_option::<TemporalUnit>(&options, js_str!("smallestUnit"), context)?;
let largest_unit = get_option::<TemporalUnit>(&options, js_str!("largestUnit"), context)?;
let result = instant
.inner
.since(&other, mode, increment, smallest_unit, largest_unit)?;
@ -353,7 +354,7 @@ impl Instant {
let new_round_to = JsObject::with_null_proto();
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString).
new_round_to.create_data_property_or_throw(
utf16!("smallestUnit"),
js_str!("smallestUnit"),
param_string,
context,
)?;
@ -370,16 +371,16 @@ impl Instant {
// alphabetical order (ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode").
// 7. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo).
let rounding_increment =
get_option::<f64>(&round_to, utf16!("roundingIncrement"), context)?;
get_option::<f64>(&round_to, js_str!("roundingIncrement"), context)?;
// 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
let rounding_mode =
get_option::<TemporalRoundingMode>(&round_to, utf16!("roundingMode"), context)?;
get_option::<TemporalRoundingMode>(&round_to, js_str!("roundingMode"), context)?;
// 9. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit"), time, required).
let smallest_unit = get_temporal_unit(
&round_to,
utf16!("smallestUnit"),
js_str!("smallestUnit"),
TemporalUnitGroup::Time,
None,
context,

4
core/engine/src/builtins/temporal/mod.rs

@ -37,7 +37,7 @@ use crate::{
value::Type,
Context, JsBigInt, JsError, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_macros::utf16;
use boa_macros::js_str;
use boa_profiler::Profiler;
use temporal_rs::{
components::{Date as TemporalDate, ZonedDateTime as TemporalZonedDateTime},
@ -252,7 +252,7 @@ pub(crate) fn to_relative_temporal_object(
options: &JsObject,
context: &mut Context,
) -> RelativeTemporalObjectResult {
let relative_to = options.get(PropertyKey::from(utf16!("relativeTo")), context)?;
let relative_to = options.get(PropertyKey::from(js_str!("relativeTo")), context)?;
let plain_date = match relative_to {
JsValue::String(relative_to_str) => Some(relative_to_str.into()),
JsValue::Object(relative_to_obj) => Some(relative_to_obj.into()),

6
core/engine/src/builtins/temporal/options.rs

@ -10,7 +10,9 @@
use crate::{
builtins::options::{get_option, ParsableOptionType},
js_string, Context, JsNativeError, JsObject, JsResult,
js_string,
string::JsStr,
Context, JsNativeError, JsObject, JsResult,
};
use temporal_rs::options::{
ArithmeticOverflow, DurationOverflow, InstantDisambiguation, OffsetDisambiguation,
@ -58,7 +60,7 @@ pub(crate) fn get_temporal_rounding_increment(
#[inline]
pub(crate) fn get_temporal_unit(
options: &JsObject,
key: &[u16],
key: JsStr<'_>,
unit_group: TemporalUnitGroup,
extra_values: Option<Vec<TemporalUnit>>,
context: &mut Context,

38
core/engine/src/builtins/temporal/plain_date/mod.rs

@ -13,10 +13,11 @@ use crate::{
object::internal_methods::get_prototype_from_constructor,
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
use temporal_rs::{
components::{
@ -125,80 +126,85 @@ impl IntrinsicObject for PlainDate {
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("calendarId"),
js_string!("calendarId"),
Some(get_calendar_id),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("year"),
js_string!("year"),
Some(get_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("month"),
js_string!("month"),
Some(get_month),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("monthCode"),
js_string!("monthCode"),
Some(get_month_code),
None,
Attribute::CONFIGURABLE,
)
.accessor(utf16!("day"), Some(get_day), None, Attribute::CONFIGURABLE)
.accessor(
utf16!("dayOfWeek"),
js_string!("day"),
Some(get_day),
None,
Attribute::CONFIGURABLE,
)
.accessor(
js_string!("dayOfWeek"),
Some(get_day_of_week),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("dayOfYear"),
js_string!("dayOfYear"),
Some(get_day_of_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("weekOfYear"),
js_string!("weekOfYear"),
Some(get_week_of_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("yearOfWeek"),
js_string!("yearOfWeek"),
Some(get_year_of_week),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("daysInWeek"),
js_string!("daysInWeek"),
Some(get_days_in_week),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("daysInMonth"),
js_string!("daysInMonth"),
Some(get_days_in_month),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("daysInYear"),
js_string!("daysInYear"),
Some(get_days_in_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("monthsInYear"),
js_string!("monthsInYear"),
Some(get_months_in_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("inLeapYear"),
js_string!("inLeapYear"),
Some(get_in_leap_year),
None,
Attribute::CONFIGURABLE,
@ -643,7 +649,7 @@ pub(crate) fn to_temporal_date(
// c. If item has an [[InitializedTemporalDateTime]] internal slot, then
} else if let Some(date_time) = object.downcast_ref::<PlainDateTime>() {
// i. Perform ? ToTemporalOverflow(options).
let _o = get_option(&options_obj, utf16!("overflow"), context)?
let _o = get_option(&options_obj, js_str!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain);
let date = InnerDate::from_datetime(date_time.inner());

43
core/engine/src/builtins/temporal/plain_date_time/mod.rs

@ -11,10 +11,11 @@ use crate::{
object::internal_methods::get_prototype_from_constructor,
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
#[cfg(test)]
@ -152,116 +153,116 @@ impl IntrinsicObject for PlainDateTime {
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("calendarId"),
js_str!("calendarId"),
Some(get_calendar_id),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("year"),
js_str!("year"),
Some(get_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("month"),
js_str!("month"),
Some(get_month),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("monthCode"),
js_str!("monthCode"),
Some(get_month_code),
None,
Attribute::CONFIGURABLE,
)
.accessor(utf16!("day"), Some(get_day), None, Attribute::CONFIGURABLE)
.accessor(js_str!("day"), Some(get_day), None, Attribute::CONFIGURABLE)
.accessor(
utf16!("hour"),
js_str!("hour"),
Some(get_hour),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("minute"),
js_str!("minute"),
Some(get_minute),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("second"),
js_str!("second"),
Some(get_second),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("millisecond"),
js_str!("millisecond"),
Some(get_millisecond),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("microsecond"),
js_str!("microsecond"),
Some(get_microsecond),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("nanosecond"),
js_str!("nanosecond"),
Some(get_nanosecond),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("dayOfWeek"),
js_str!("dayOfWeek"),
Some(get_day_of_week),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("dayOfYear"),
js_str!("dayOfYear"),
Some(get_day_of_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("weekOfYear"),
js_str!("weekOfYear"),
Some(get_week_of_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("yearOfWeek"),
js_str!("yearOfWeek"),
Some(get_year_of_week),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("daysInWeek"),
js_str!("daysInWeek"),
Some(get_days_in_week),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("daysInMonth"),
js_str!("daysInMonth"),
Some(get_days_in_month),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("daysInYear"),
js_str!("daysInYear"),
Some(get_days_in_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("monthsInYear"),
js_str!("monthsInYear"),
Some(get_months_in_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("inLeapYear"),
js_str!("inLeapYear"),
Some(get_in_leap_year),
None,
Attribute::CONFIGURABLE,

39
core/engine/src/builtins/temporal/plain_time/mod.rs

@ -14,7 +14,7 @@ use crate::{
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::utf16;
use boa_macros::js_str;
use boa_profiler::Profiler;
use temporal_rs::{
components::Time,
@ -71,33 +71,38 @@ impl IntrinsicObject for PlainTime {
Self::NAME,
Attribute::CONFIGURABLE,
)
.accessor(utf16!("hour"), Some(get_hour), None, Attribute::default())
.accessor(
utf16!("minute"),
js_string!("hour"),
Some(get_hour),
None,
Attribute::default(),
)
.accessor(
js_string!("minute"),
Some(get_minute),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("second"),
js_string!("second"),
Some(get_second),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("millisecond"),
js_string!("millisecond"),
Some(get_millisecond),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("microsecond"),
js_string!("microsecond"),
Some(get_microsecond),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("nanosecond"),
js_string!("nanosecond"),
Some(get_nanosecond),
None,
Attribute::CONFIGURABLE,
@ -345,7 +350,7 @@ impl PlainTime {
let new_round_to = JsObject::with_null_proto();
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString).
new_round_to.create_data_property_or_throw(
utf16!("smallestUnit"),
js_str!("smallestUnit"),
param_string,
context,
)?;
@ -361,16 +366,16 @@ impl PlainTime {
// 6. NOTE: The following steps read options and perform independent validation in alphabetical order (ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode").
// 7. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo).
let rounding_increment =
get_option::<f64>(&round_to, utf16!("roundingIncrement"), context)?;
get_option::<f64>(&round_to, js_str!("roundingIncrement"), context)?;
// 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
let rounding_mode =
get_option::<TemporalRoundingMode>(&round_to, utf16!("roundingMode"), context)?;
get_option::<TemporalRoundingMode>(&round_to, js_str!("roundingMode"), context)?;
// 9. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", time, required).
let smallest_unit = get_temporal_unit(
&round_to,
utf16!("smallestUnit"),
js_str!("smallestUnit"),
TemporalUnitGroup::Time,
None,
context,
@ -404,29 +409,29 @@ impl PlainTime {
let fields = JsObject::with_object_proto(context.intrinsics());
// 4. Perform ! CreateDataPropertyOrThrow(fields, "isoHour", 𝔽(temporalTime.[[ISOHour]])).
fields.create_data_property_or_throw(utf16!("isoHour"), time.inner.hour(), context)?;
fields.create_data_property_or_throw(js_str!("isoHour"), time.inner.hour(), context)?;
// 5. Perform ! CreateDataPropertyOrThrow(fields, "isoMicrosecond", 𝔽(temporalTime.[[ISOMicrosecond]])).
fields.create_data_property_or_throw(
utf16!("isoMicrosecond"),
js_str!("isoMicrosecond"),
time.inner.microsecond(),
context,
)?;
// 6. Perform ! CreateDataPropertyOrThrow(fields, "isoMillisecond", 𝔽(temporalTime.[[ISOMillisecond]])).
fields.create_data_property_or_throw(
utf16!("isoMillisecond"),
js_str!("isoMillisecond"),
time.inner.millisecond(),
context,
)?;
// 7. Perform ! CreateDataPropertyOrThrow(fields, "isoMinute", 𝔽(temporalTime.[[ISOMinute]])).
fields.create_data_property_or_throw(utf16!("isoMinute"), time.inner.minute(), context)?;
fields.create_data_property_or_throw(js_str!("isoMinute"), time.inner.minute(), context)?;
// 8. Perform ! CreateDataPropertyOrThrow(fields, "isoNanosecond", 𝔽(temporalTime.[[ISONanosecond]])).
fields.create_data_property_or_throw(
utf16!("isoNanosecond"),
js_str!("isoNanosecond"),
time.inner.nanosecond(),
context,
)?;
// 9. Perform ! CreateDataPropertyOrThrow(fields, "isoSecond", 𝔽(temporalTime.[[ISOSecond]])).
fields.create_data_property_or_throw(utf16!("isoSecond"), time.inner.second(), context)?;
fields.create_data_property_or_throw(js_str!("isoSecond"), time.inner.second(), context)?;
// 10. Return fields.
Ok(fields.into())

18
core/engine/src/builtins/temporal/plain_year_month/mod.rs

@ -7,7 +7,7 @@ use crate::{
object::internal_methods::get_prototype_from_constructor,
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{Finalize, Trace};
@ -98,49 +98,49 @@ impl IntrinsicObject for PlainYearMonth {
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("calendarId"),
js_string!("calendarId"),
Some(get_calendar_id),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("year"),
js_string!("year"),
Some(get_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("month"),
js_string!("month"),
Some(get_month),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("monthCode"),
js_string!("monthCode"),
Some(get_month_code),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("daysInMonth"),
js_string!("daysInMonth"),
Some(get_days_in_month),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("daysInYear"),
js_string!("daysInYear"),
Some(get_days_in_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("monthsInYear"),
js_string!("monthsInYear"),
Some(get_months_in_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("inLeapYear"),
js_string!("inLeapYear"),
Some(get_in_leap_year),
None,
Attribute::CONFIGURABLE,

10
core/engine/src/builtins/temporal/tests.rs

@ -1,4 +1,6 @@
use crate::{js_string, run_test_actions, JsValue, TestAction};
use boa_macros::js_str;
use crate::{run_test_actions, JsValue, TestAction};
// Temporal Object tests.
@ -8,9 +10,9 @@ fn temporal_object() {
run_test_actions([
TestAction::assert_eq(
"Object.prototype.toString.call(Temporal)",
js_string!("[object Temporal]"),
js_str!("[object Temporal]"),
),
TestAction::assert_eq("String(Temporal)", js_string!("[object Temporal]")),
TestAction::assert_eq("String(Temporal)", js_str!("[object Temporal]")),
TestAction::assert_eq("Object.keys(Temporal).length === 0", true),
]);
}
@ -22,7 +24,7 @@ fn now_object() {
TestAction::assert_eq("Object.isExtensible(Temporal.Now)", true),
TestAction::assert_eq(
"Object.prototype.toString.call(Temporal.Now)",
js_string!("[object Temporal.Now]"),
js_str!("[object Temporal.Now]"),
),
TestAction::assert_eq(
"Object.getPrototypeOf(Temporal.Now) === Object.prototype",

7
core/engine/src/builtins/temporal/time_zone/custom.rs

@ -1,7 +1,8 @@
//! A custom `TimeZone` object.
use crate::{property::PropertyKey, string::utf16, Context, JsObject, JsValue};
use crate::{property::PropertyKey, Context, JsObject, JsValue};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use num_bigint::BigInt;
use temporal_rs::{
components::{tz::TzProtocol, Instant},
@ -18,7 +19,7 @@ impl TzProtocol for JsCustomTimeZone {
fn get_offset_nanos_for(&self, context: &mut Context) -> TemporalResult<BigInt> {
let method = self
.tz
.get(utf16!("getOffsetNanosFor"), context)
.get(js_str!("getOffsetNanosFor"), context)
.expect("Method must exist for the custom calendar to be valid.");
let result = method
@ -45,7 +46,7 @@ impl TzProtocol for JsCustomTimeZone {
let ident = self
.tz
.__get__(
&PropertyKey::from(utf16!("id")),
&PropertyKey::from(js_str!("id")),
JsValue::undefined(),
&mut context.into(),
)

4
core/engine/src/builtins/temporal/time_zone/mod.rs

@ -11,7 +11,7 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, CONSTRUCTOR},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{custom_trace, Finalize, Trace};
@ -95,7 +95,7 @@ impl IntrinsicObject for TimeZone {
realm.intrinsics().constructors().time_zone().prototype(),
Attribute::default(),
)
.accessor(utf16!("id"), Some(get_id), None, Attribute::default())
.accessor(js_string!("id"), Some(get_id), None, Attribute::default())
.build();
}

20
core/engine/src/builtins/typed_array/builtin.rs

@ -3,7 +3,7 @@ use std::{
sync::atomic::{self, Ordering},
};
use boa_macros::utf16;
use boa_macros::{js_str, utf16};
use num_traits::Zero;
use super::{
@ -79,19 +79,19 @@ impl IntrinsicObject for BuiltinTypedArray {
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.accessor(
utf16!("buffer"),
js_string!("buffer"),
Some(get_buffer),
None,
Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.accessor(
utf16!("byteLength"),
js_string!("byteLength"),
Some(get_byte_length),
None,
Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.accessor(
utf16!("byteOffset"),
js_string!("byteOffset"),
Some(get_byte_offset),
None,
Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
@ -1214,7 +1214,7 @@ impl BuiltinTypedArray {
for k in 0..len {
// a. If k > 0, set R to the string-concatenation of R and sep.
if k > 0 {
r.extend_from_slice(&sep);
r.extend(sep.iter());
}
// b. Let element be ! Get(O, ! ToString(𝔽(k))).
@ -1223,12 +1223,12 @@ impl BuiltinTypedArray {
// c. If element is undefined, let next be the empty String; otherwise, let next be ! ToString(element).
// d. Set R to the string-concatenation of R and next.
if !element.is_undefined() {
r.extend_from_slice(&element.to_string(context)?);
r.extend(element.to_string(context)?.iter());
}
}
// 9. Return R.
Ok(js_string!(r).into())
Ok(js_string!(&r[..]).into())
}
/// `%TypedArray%.prototype.keys ( )`
@ -2493,7 +2493,7 @@ impl BuiltinTypedArray {
if is_fixed_len || !next_element.is_undefined() {
let s = next_element
.invoke(
utf16!("toLocaleString"),
js_str!("toLocaleString"),
&[
args.get_or_undefined(0).clone(),
args.get_or_undefined(1).clone(),
@ -2502,11 +2502,11 @@ impl BuiltinTypedArray {
)?
.to_string(context)?;
r.extend_from_slice(&s);
r.extend(s.iter());
};
}
Ok(js_string!(r).into())
Ok(js_string!(&r[..]).into())
}
/// `%TypedArray%.prototype.values ( )`

5
core/engine/src/builtins/typed_array/mod.rs

@ -29,6 +29,7 @@ use crate::{
Context, JsArgs, JsResult, JsString,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
mod builtin;
@ -74,12 +75,12 @@ impl<T: TypedArrayMarker> IntrinsicObject for T {
Attribute::CONFIGURABLE,
)
.property(
js_string!("BYTES_PER_ELEMENT"),
js_str!("BYTES_PER_ELEMENT"),
std::mem::size_of::<T::Element>(),
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
)
.static_property(
js_string!("BYTES_PER_ELEMENT"),
js_str!("BYTES_PER_ELEMENT"),
std::mem::size_of::<T::Element>(),
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
)

4
core/engine/src/builtins/typed_array/object.rs

@ -16,7 +16,7 @@ use crate::{
Context, JsNativeError, JsResult, JsString, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::utf16;
use boa_macros::js_str;
use super::{is_valid_integer_index, TypedArrayKind};
@ -263,7 +263,7 @@ impl TypedArray {
/// [spec]: https://tc39.es/ecma262/#sec-canonicalnumericindexstring
fn canonical_numeric_index_string(argument: &JsString) -> Option<f64> {
// 1. If argument is "-0", return -0𝔽.
if argument == utf16!("-0") {
if argument == &js_str!("-0") {
return Some(-0.0);
}

26
core/engine/src/builtins/uri/mod.rs

@ -305,11 +305,11 @@ where
loop {
// a. If k = strLen, return R.
if k == str_len {
return Ok(js_string!(r));
return Ok(js_string!(&r[..]));
}
// b. Let C be the code unit at index k within string.
let c = string[k];
let c = string.get_expect(k);
// c. If C is in unescapedSet, then
if unescaped_set(c) {
@ -380,11 +380,11 @@ where
loop {
// a. If k = strLen, return R.
if k == str_len {
return Ok(js_string!(r));
return Ok(js_string!(&r[..]));
}
// b. Let C be the code unit at index k within string.
let c = string[k];
let c = string.get_expect(k);
// c. If C is not the code unit 0x0025 (PERCENT SIGN), then
#[allow(clippy::if_not_else)]
@ -406,9 +406,10 @@ where
// iii. If the code units at index (k + 1) and (k + 2) within string do not represent
// hexadecimal digits, throw a URIError exception.
// iv. Let B be the 8-bit value represented by the two hexadecimal digits at index (k + 1) and (k + 2).
let b = decode_hex_byte(string[k + 1], string[k + 2]).ok_or_else(|| {
JsNativeError::uri().with_message("invalid hexadecimal digit found")
})?;
let b = decode_hex_byte(string.get_expect(k + 1), string.get_expect(k + 2))
.ok_or_else(|| {
JsNativeError::uri().with_message("invalid hexadecimal digit found")
})?;
// v. Set k to k + 2.
k += 2;
@ -428,7 +429,7 @@ where
} else {
// 3. Else,
// a. Let S be the substring of string from start to k + 1.
Vec::from(&string[start..=k])
string.get_expect(start..=k).to_vec()
}
} else {
// viii. Else,
@ -456,7 +457,7 @@ where
k += 1;
// b. If the code unit at index k within string is not the code unit 0x0025 (PERCENT SIGN), throw a URIError exception.
if string[k] != 0x0025 {
if string.get_expect(k) != 0x0025 {
return Err(JsNativeError::uri()
.with_message("escape characters must be preceded with a % sign")
.into());
@ -464,9 +465,10 @@ where
// c. If the code units at index (k + 1) and (k + 2) within string do not represent hexadecimal digits, throw a URIError exception.
// d. Let B be the 8-bit value represented by the two hexadecimal digits at index (k + 1) and (k + 2).
let b = decode_hex_byte(string[k + 1], string[k + 2]).ok_or_else(|| {
JsNativeError::uri().with_message("invalid hexadecimal digit found")
})?;
let b = decode_hex_byte(string.get_expect(k + 1), string.get_expect(k + 2))
.ok_or_else(|| {
JsNativeError::uri().with_message("invalid hexadecimal digit found")
})?;
// e. Set k to k + 2.
k += 2;

5
core/engine/src/builtins/weak_map/mod.rs

@ -17,11 +17,12 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, ErasedVTableObject, JsObject},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
symbol::JsSymbol,
Context, JsArgs, JsNativeError, JsResult, JsString, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
type NativeWeakMap = boa_gc::WeakMap<ErasedVTableObject, JsValue>;
@ -100,7 +101,7 @@ impl BuiltInConstructor for WeakMap {
}
// 5. Let adder be ? Get(map, "set").
let adder = map.get(utf16!("set"), context)?;
let adder = map.get(js_str!("set"), context)?;
// 6. If IsCallable(adder) is false, throw a TypeError exception.
if !adder.is_callable() {

5
core/engine/src/builtins/weak_set/mod.rs

@ -14,11 +14,12 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, ErasedVTableObject, JsObject},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
string::common::StaticJsStrings,
symbol::JsSymbol,
Context, JsArgs, JsNativeError, JsResult, JsString, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
type NativeWeakSet = boa_gc::WeakMap<ErasedVTableObject, ()>;
@ -96,7 +97,7 @@ impl BuiltInConstructor for WeakSet {
}
// 5. Let adder be ? Get(set, "add").
let adder = weak_set.get(utf16!("add"), context)?;
let adder = weak_set.get(js_str!("add"), context)?;
// 6. If IsCallable(adder) is false, throw a TypeError exception.
let adder = adder

6
core/engine/src/bytecompiler/declarations.rs

@ -18,7 +18,7 @@ use boa_ast::{
visitor::NodeRef,
Declaration, Script, StatementListItem,
};
use boa_interner::Sym;
use boa_interner::{JStrRef, Sym};
#[cfg(feature = "annex-b")]
use boa_ast::operations::annex_b_function_declarations_names;
@ -223,9 +223,11 @@ pub(crate) fn eval_declaration_instantiation_context(
let private_identifiers = private_identifiers
.into_iter()
.map(|ident| {
// TODO: Replace JStrRef with JsStr this would eliminate the to_vec call.
let ident = ident.to_vec();
context
.interner()
.get(ident.as_slice())
.get(JStrRef::Utf16(&ident))
.expect("string should be in interner")
})
.collect();

20
core/engine/src/bytecompiler/expression/mod.rs

@ -4,7 +4,7 @@ mod object_literal;
mod unary;
mod update;
use super::{Access, Callable, NodeKind, Operand};
use super::{Access, Callable, NodeKind, Operand, ToJsString};
use crate::{
bytecompiler::{ByteCompiler, Literal},
vm::{GeneratorResumeKind, Opcode},
@ -22,9 +22,9 @@ use boa_ast::{
impl ByteCompiler<'_> {
fn compile_literal(&mut self, lit: &AstLiteral, use_expr: bool) {
match lit {
AstLiteral::String(v) => self.emit_push_literal(Literal::String(
self.interner().resolve_expect(*v).into_common(false),
)),
AstLiteral::String(v) => {
self.emit_push_literal(Literal::String(v.to_js_string(self.interner())));
}
AstLiteral::Int(v) => self.emit_push_integer(*v),
AstLiteral::Num(v) => self.emit_push_rational(*v),
AstLiteral::BigInt(v) => {
@ -58,9 +58,9 @@ impl ByteCompiler<'_> {
fn compile_template_literal(&mut self, template_literal: &TemplateLiteral, use_expr: bool) {
for element in template_literal.elements() {
match element {
TemplateElement::String(s) => self.emit_push_literal(Literal::String(
self.interner().resolve_expect(*s).into_common(false),
)),
TemplateElement::String(s) => {
self.emit_push_literal(Literal::String(s.to_js_string(self.interner())));
}
TemplateElement::Expr(expr) => {
self.compile_expr(expr, true);
}
@ -268,14 +268,12 @@ impl ByteCompiler<'_> {
for (cooked, raw) in template.cookeds().iter().zip(template.raws()) {
if let Some(cooked) = cooked {
self.emit_push_literal(Literal::String(
self.interner().resolve_expect(*cooked).into_common(false),
cooked.to_js_string(self.interner()),
));
} else {
self.emit_opcode(Opcode::PushUndefined);
}
self.emit_push_literal(Literal::String(
self.interner().resolve_expect(*raw).into_common(false),
));
self.emit_push_literal(Literal::String(raw.to_js_string(self.interner())));
}
self.emit(

18
core/engine/src/bytecompiler/mod.rs

@ -21,7 +21,7 @@ use crate::{
BindingOpcode, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind, Handler,
InlineCache, Opcode, VaryingOperandKind,
},
JsBigInt, JsString,
JsBigInt, JsStr, JsString,
};
use boa_ast::{
declaration::{Binding, LexicalDeclaration, VarDeclaration},
@ -55,7 +55,15 @@ pub(crate) trait ToJsString {
impl ToJsString for Sym {
fn to_js_string(&self, interner: &Interner) -> JsString {
js_string!(interner.resolve_expect(*self).utf16())
// TODO: Identify latin1 encodeable strings during parsing to avoid this check.
let string = interner.resolve_expect(*self).utf16();
for c in string {
if u8::try_from(*c).is_err() {
return js_string!(string);
}
}
let string = string.iter().map(|c| *c as u8).collect::<Vec<_>>();
js_string!(JsStr::latin1(&string))
}
}
@ -392,9 +400,9 @@ impl<'ctx> ByteCompiler<'ctx> {
return *index;
}
let string = self.interner().resolve_expect(name.sym()).utf16();
let index = self.constants.len() as u32;
self.constants.push(Constant::String(js_string!(string)));
let string = name.to_js_string(self.interner());
self.constants.push(Constant::String(string));
self.names_map.insert(name, index);
index
}
@ -744,7 +752,7 @@ impl<'ctx> ByteCompiler<'ctx> {
}
fn resolve_identifier_expect(&self, identifier: Identifier) -> JsString {
js_string!(self.interner().resolve_expect(identifier.sym()).utf16())
identifier.to_js_string(self.interner())
}
fn access_get(&mut self, access: Access<'_>, use_expr: bool) {

12
core/engine/src/class.rs

@ -10,7 +10,7 @@
//! # property::Attribute,
//! # class::{Class, ClassBuilder},
//! # Context, JsResult, JsValue,
//! # JsArgs, Source, JsObject, js_string,
//! # JsArgs, Source, JsObject, js_str, js_string,
//! # JsNativeError, JsData,
//! # };
//! # use boa_gc::{Finalize, Trace};
@ -56,7 +56,7 @@
//! let age = args.get_or_undefined(1).to_number(context)?;
//!
//! // Roughly equivalent to `this.age = Number(age)`.
//! instance.set(js_string!("age"), age, true, context)?;
//! instance.set(js_str!("age"), age, true, context)?;
//!
//! Ok(())
//! }
@ -70,9 +70,9 @@
//! if let Some(object) = this.as_object() {
//! if let Some(animal) = object.downcast_ref::<Animal>() {
//! return Ok(match &*animal {
//! Self::Cat => js_string!("meow"),
//! Self::Dog => js_string!("woof"),
//! Self::Other => js_string!(r"¯\_(ツ)_/¯"),
//! Self::Cat => js_str!("meow"),
//! Self::Dog => js_str!("woof"),
//! Self::Other => js_str!(r"¯\_(ツ)_/¯"),
//! }.into());
//! }
//! }
@ -96,7 +96,7 @@
//!
//! assert_eq!(
//! result.as_string().unwrap(),
//! &js_string!("My pet is 3 years old. Right, buddy? - woof!")
//! &js_str!("My pet is 3 years old. Right, buddy? - woof!")
//! );
//! }
//! ```

19
core/engine/src/context/intrinsics.rs

@ -1,6 +1,7 @@
//! Data structures that contain intrinsic objects and constructors.
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use crate::{
builtins::{iterable::IteratorPrototypes, uri::UriFunctions, Array, OrdinaryObject},
@ -1394,7 +1395,7 @@ impl ObjectTemplates {
let ordinary_object =
ObjectTemplate::with_prototype(root_shape, constructors.object().prototype());
let mut array = ObjectTemplate::new(root_shape);
let length_property_key: PropertyKey = js_string!("length").into();
let length_property_key: PropertyKey = js_str!("length").into();
array.property(
length_property_key.clone(),
Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE,
@ -1419,7 +1420,7 @@ impl ObjectTemplates {
let mut regexp = regexp_without_proto.clone();
regexp.set_prototype(constructors.regexp().prototype());
let name_property_key: PropertyKey = js_string!("name").into();
let name_property_key: PropertyKey = js_str!("name").into();
let mut function = ObjectTemplate::new(root_shape);
function.property(
length_property_key.clone(),
@ -1478,7 +1479,7 @@ impl ObjectTemplates {
// [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false,
// [[Configurable]]: false }).
unmapped_arguments.accessor(
js_string!("callee").into(),
js_str!("callee").into(),
true,
true,
Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
@ -1487,17 +1488,17 @@ impl ObjectTemplates {
// 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor {
// [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).
mapped_arguments.property(
js_string!("callee").into(),
js_str!("callee").into(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
let mut iterator_result = ordinary_object.clone();
iterator_result.property(
js_string!("value").into(),
js_str!("value").into(),
Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE,
);
iterator_result.property(
js_string!("done").into(),
js_str!("done").into(),
Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE,
);
@ -1509,11 +1510,11 @@ impl ObjectTemplates {
with_resolvers
// 4. Perform ! CreateDataPropertyOrThrow(obj, "promise", promiseCapability.[[Promise]]).
.property(js_string!("promise").into(), Attribute::all())
.property(js_str!("promise").into(), Attribute::all())
// 5. Perform ! CreateDataPropertyOrThrow(obj, "resolve", promiseCapability.[[Resolve]]).
.property(js_string!("resolve").into(), Attribute::all())
.property(js_str!("resolve").into(), Attribute::all())
// 6. Perform ! CreateDataPropertyOrThrow(obj, "reject", promiseCapability.[[Reject]]).
.property(js_string!("reject").into(), Attribute::all());
.property(js_str!("reject").into(), Attribute::all());
with_resolvers
};

16
core/engine/src/context/mod.rs

@ -51,7 +51,7 @@ thread_local! {
///
/// ```rust
/// use boa_engine::{
/// js_string,
/// js_str,
/// object::ObjectInitializer,
/// property::{Attribute, PropertyDescriptor},
/// Context, Source,
@ -73,10 +73,10 @@ thread_local! {
///
/// // Create an object that can be used in eval calls.
/// let arg = ObjectInitializer::new(&mut context)
/// .property(js_string!("x"), 12, Attribute::READONLY)
/// .property(js_str!("x"), 12, Attribute::READONLY)
/// .build();
/// context
/// .register_global_property(js_string!("arg"), arg, Attribute::all())
/// .register_global_property(js_str!("arg"), arg, Attribute::all())
/// .expect("property shouldn't exist");
///
/// let value = context.eval(Source::from_bytes("test(arg)")).unwrap();
@ -211,7 +211,7 @@ impl Context {
/// # Example
/// ```
/// use boa_engine::{
/// js_string,
/// js_str,
/// object::ObjectInitializer,
/// property::{Attribute, PropertyDescriptor},
/// Context,
@ -221,19 +221,19 @@ impl Context {
///
/// context
/// .register_global_property(
/// js_string!("myPrimitiveProperty"),
/// js_str!("myPrimitiveProperty"),
/// 10,
/// Attribute::all(),
/// )
/// .expect("property shouldn't exist");
///
/// let object = ObjectInitializer::new(&mut context)
/// .property(js_string!("x"), 0, Attribute::all())
/// .property(js_string!("y"), 1, Attribute::all())
/// .property(js_str!("x"), 0, Attribute::all())
/// .property(js_str!("y"), 1, Attribute::all())
/// .build();
/// context
/// .register_global_property(
/// js_string!("myObjectProperty"),
/// js_str!("myObjectProperty"),
/// object,
/// Attribute::all(),
/// )

22
core/engine/src/error.rs

@ -8,10 +8,10 @@ use crate::{
object::JsObject,
property::PropertyDescriptor,
realm::Realm,
string::utf16,
Context, JsString, JsValue,
};
use boa_gc::{custom_trace, Finalize, Trace};
use boa_macros::js_str;
use thiserror::Error;
/// The error type returned by all operations related
@ -30,11 +30,11 @@ use thiserror::Error;
/// # Examples
///
/// ```rust
/// # use boa_engine::{JsError, JsNativeError, JsNativeErrorKind, JsValue, js_string};
/// let cause = JsError::from_opaque(js_string!("error!").into());
/// # use boa_engine::{JsError, JsNativeError, JsNativeErrorKind, JsValue, js_str};
/// let cause = JsError::from_opaque(js_str!("error!").into());
///
/// assert!(cause.as_opaque().is_some());
/// assert_eq!(cause.as_opaque().unwrap(), &JsValue::from(js_string!("error!")));
/// assert_eq!(cause.as_opaque().unwrap(), &JsValue::from(js_str!("error!")));
///
/// let native_error: JsError = JsNativeError::typ()
/// .with_message("invalid type!")
@ -270,7 +270,7 @@ impl JsError {
ErrorObject::Syntax => JsNativeErrorKind::Syntax,
ErrorObject::Uri => JsNativeErrorKind::Uri,
ErrorObject::Aggregate => {
let errors = obj.get(utf16!("errors"), context).map_err(|e| {
let errors = obj.get(js_str!("errors"), context).map_err(|e| {
TryNativeError::InaccessibleProperty {
property: "errors",
source: e,
@ -878,7 +878,7 @@ impl JsNativeError {
/// # Examples
///
/// ```rust
/// # use boa_engine::{Context, JsError, JsNativeError, js_string};
/// # use boa_engine::{Context, JsError, JsNativeError, js_str};
/// # use boa_engine::builtins::error::ErrorObject;
/// let context = &mut Context::default();
///
@ -887,8 +887,8 @@ impl JsNativeError {
///
/// assert!(error_obj.is::<ErrorObject>());
/// assert_eq!(
/// error_obj.get(js_string!("message"), context).unwrap(),
/// js_string!("error!").into()
/// error_obj.get(js_str!("message"), context).unwrap(),
/// js_str!("error!").into()
/// )
/// ```
///
@ -941,14 +941,14 @@ impl JsNativeError {
JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, tag);
o.create_non_enumerable_data_property_or_throw(
js_string!("message"),
js_str!("message"),
js_string!(&**message),
context,
);
if let Some(cause) = cause {
o.create_non_enumerable_data_property_or_throw(
js_string!("cause"),
js_str!("cause"),
cause.to_opaque(context),
context,
);
@ -961,7 +961,7 @@ impl JsNativeError {
.collect::<Vec<_>>();
let errors = Array::create_array_from_list(errors, context);
o.define_property_or_throw(
js_string!("errors"),
js_str!("errors"),
PropertyDescriptor::builder()
.configurable(true)
.enumerable(false)

11
core/engine/src/lib.rs

@ -77,7 +77,6 @@
compile_error!("Boa requires a lock free `AtomicUsize` in order to work properly.");
extern crate self as boa_engine;
extern crate static_assertions as sa;
pub use boa_ast as ast;
pub use boa_gc as gc;
@ -104,10 +103,11 @@ pub mod symbol;
pub mod value;
pub mod vm;
pub(crate) mod tagged;
mod host_defined;
mod small_map;
mod sys;
mod tagged;
#[cfg(test)]
mod tests;
@ -123,12 +123,12 @@ pub mod prelude {
native_function::NativeFunction,
object::{JsData, JsObject, NativeObject},
script::Script,
string::JsString,
string::{JsStr, JsString},
symbol::JsSymbol,
value::JsValue,
};
pub use boa_gc::{Finalize, Trace};
pub use boa_macros::JsData;
pub use boa_macros::{js_str, JsData};
pub use boa_parser::Source;
}
@ -138,6 +138,9 @@ use std::result::Result as StdResult;
#[doc(inline)]
pub use prelude::*;
#[doc(inline)]
pub use boa_parser::Source;
/// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`)
pub type JsResult<T> = StdResult<T, JsError>;

6
core/engine/src/module/source.rs

@ -12,7 +12,7 @@ use boa_ast::{
};
use boa_gc::{Finalize, Gc, GcRefCell, Trace};
use boa_interner::Interner;
use boa_macros::utf16;
use boa_macros::js_str;
use indexmap::IndexSet;
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
@ -506,7 +506,7 @@ impl SourceTextModule {
// c. For each element n of starNames, do
for n in requested_module.get_exported_names(export_star_set, interner) {
// i. If SameValue(n, "default") is false, then
if &n != utf16!("default") {
if n != js_str!("default") {
// 1. If exportedNames does not contain n, then
// a. Append n to exportedNames.
exported_names.insert(n);
@ -583,7 +583,7 @@ impl SourceTextModule {
}
// 7. If SameValue(exportName, "default") is true, then
if &export_name.clone() == utf16!("default") {
if export_name == &js_str!("default") {
// a. Assert: A default export was not explicitly defined by this module.
// b. Return null.
// c. NOTE: A default export cannot be provided by an export * from "mod" declaration.

4
core/engine/src/object/builtins/jsdate.rs

@ -18,7 +18,7 @@ use time::{format_description::well_known::Rfc3339, OffsetDateTime};
///
/// ```
/// use boa_engine::{
/// js_string, object::builtins::JsDate, Context, JsResult, JsValue,
/// js_str, object::builtins::JsDate, Context, JsResult, JsValue,
/// };
///
/// fn main() -> JsResult<()> {
@ -31,7 +31,7 @@ use time::{format_description::well_known::Rfc3339, OffsetDateTime};
///
/// assert_eq!(
/// date.to_date_string(context)?,
/// JsValue::from(js_string!("Mon Dec 04 1995"))
/// JsValue::from(js_str!("Mon Dec 04 1995"))
/// );
///
/// Ok(())

68
core/engine/src/object/builtins/jsmap.rs

@ -4,12 +4,12 @@ use crate::{
builtins::Map,
error::JsNativeError,
object::{JsFunction, JsMapIterator, JsObject, JsObjectType},
string::utf16,
value::TryFromJs,
Context, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use std::ops::Deref;
/// `JsMap` provides a wrapper for Boa's implementation of the ECMAScript `Map` object.
@ -20,7 +20,7 @@ use std::ops::Deref;
/// ```
/// # use boa_engine::{
/// # object::builtins::JsMap,
/// # Context, JsValue, JsResult, js_string
/// # Context, JsValue, JsResult, js_str
/// # };
/// # fn main() -> JsResult<()> {
/// // Create default `Context`
@ -30,8 +30,8 @@ use std::ops::Deref;
/// let map = JsMap::new(context);
///
/// // Set key-value pairs for the `JsMap`.
/// map.set(js_string!("Key-1"), js_string!("Value-1"), context)?;
/// map.set(js_string!("Key-2"), 10, context)?;
/// map.set(js_str!("Key-1"), js_str!("Value-1"), context)?;
/// map.set(js_str!("Key-2"), 10, context)?;
///
/// assert_eq!(map.get_size(context)?, 2.into());
/// # Ok(())
@ -42,7 +42,7 @@ use std::ops::Deref;
/// ```
/// # use boa_engine::{
/// # object::builtins::{JsArray, JsMap},
/// # Context, JsValue, JsResult, js_string
/// # Context, JsValue, JsResult, js_str
/// # };
/// # fn main() -> JsResult<()> {
/// // Create a default `Context`
@ -53,8 +53,8 @@ use std::ops::Deref;
///
/// // Create a `[key, value]` pair of JsValues
/// let vec_one: Vec<JsValue> = vec![
/// js_string!("first-key").into(),
/// js_string!("first-value").into()
/// js_str!("first-key").into(),
/// js_str!("first-value").into()
/// ];
///
/// // We create an push our `[key, value]` pair onto our array as a `JsArray`
@ -64,8 +64,8 @@ use std::ops::Deref;
/// let js_iterable_map = JsMap::from_js_iterable(&js_array.into(), context)?;
///
/// assert_eq!(
/// js_iterable_map.get(js_string!("first-key"), context)?,
/// js_string!("first-value").into()
/// js_iterable_map.get(js_str!("first-key"), context)?,
/// js_str!("first-value").into()
/// );
///
/// # Ok(())
@ -102,7 +102,7 @@ impl JsMap {
/// ```
/// # use boa_engine::{
/// # object::builtins::{JsArray, JsMap},
/// # Context, JsResult, JsValue, js_string
/// # Context, JsResult, JsValue, js_str
/// # };
/// # fn main() -> JsResult<()> {
/// # // Create a default `Context`
@ -112,8 +112,8 @@ impl JsMap {
///
/// // Create a `[key, value]` pair of JsValues and add it to the `JsArray` as a `JsArray`
/// let vec_one: Vec<JsValue> = vec![
/// js_string!("first-key").into(),
/// js_string!("first-value").into()
/// js_str!("first-key").into(),
/// js_str!("first-value").into()
/// ];
/// js_array.push(JsArray::from_iter(vec_one, context), context)?;
///
@ -129,7 +129,7 @@ impl JsMap {
// Let adder be Get(map, "set") per spec. This action should not fail with default map.
let adder = map
.get(utf16!("set"), context)
.get(js_str!("set"), context)
.expect("creating a map with the default prototype must not fail");
let _completion_record = add_entries_from_iterable(&map, iterable, &adder, context)?;
@ -223,18 +223,18 @@ impl JsMap {
/// ```
/// # use boa_engine::{
/// # object::builtins::JsMap,
/// # Context, JsValue, JsResult, js_string
/// # Context, JsValue, JsResult, js_str
/// # };
/// # fn main() -> JsResult<()> {
/// # let context = &mut Context::default();
/// let js_map = JsMap::new(context);
///
/// js_map.set(js_string!("foo"), js_string!("bar"), context)?;
/// js_map.set(js_str!("foo"), js_str!("bar"), context)?;
/// js_map.set(2, 4, context)?;
///
/// assert_eq!(
/// js_map.get(js_string!("foo"), context)?,
/// js_string!("bar").into()
/// js_map.get(js_str!("foo"), context)?,
/// js_str!("bar").into()
/// );
/// assert_eq!(js_map.get(2, context)?, 4.into());
/// # Ok(())
@ -259,13 +259,13 @@ impl JsMap {
/// ```
/// # use boa_engine::{
/// # object::builtins::JsMap,
/// # Context, JsValue, JsResult, js_string
/// # Context, JsValue, JsResult, js_str
/// # };
/// # fn main() -> JsResult<()> {
/// # let context = &mut Context::default();
/// let js_map = JsMap::new(context);
///
/// js_map.set(js_string!("foo"), js_string!("bar"), context)?;
/// js_map.set(js_str!("foo"), js_str!("bar"), context)?;
///
/// let map_size = js_map.get_size(context)?;
///
@ -285,19 +285,19 @@ impl JsMap {
/// ```
/// # use boa_engine::{
/// # object::builtins::JsMap,
/// # Context, JsValue, JsResult, js_string
/// # Context, JsValue, JsResult, js_str
/// # };
/// # fn main() -> JsResult<()> {
/// # let context = &mut Context::default();
/// let js_map = JsMap::new(context);
/// js_map.set(js_string!("foo"), js_string!("bar"), context)?;
/// js_map.set(js_string!("hello"), js_string!("world"), context)?;
/// js_map.set(js_str!("foo"), js_str!("bar"), context)?;
/// js_map.set(js_str!("hello"), js_str!("world"), context)?;
///
/// js_map.delete(js_string!("foo"), context)?;
/// js_map.delete(js_str!("foo"), context)?;
///
/// assert_eq!(js_map.get_size(context)?, 1.into());
/// assert_eq!(
/// js_map.get(js_string!("foo"), context)?,
/// js_map.get(js_str!("foo"), context)?,
/// JsValue::undefined()
/// );
/// # Ok(())
@ -317,16 +317,16 @@ impl JsMap {
/// ```
/// # use boa_engine::{
/// # object::builtins::JsMap,
/// # Context, JsValue, JsResult, js_string
/// # Context, JsValue, JsResult, js_str
/// # };
/// # fn main() -> JsResult<()> {
/// # let context = &mut Context::default();
/// let js_map = JsMap::new(context);
/// js_map.set(js_string!("foo"), js_string!("bar"), context)?;
/// js_map.set(js_str!("foo"), js_str!("bar"), context)?;
///
/// let retrieved_value = js_map.get(js_string!("foo"), context)?;
/// let retrieved_value = js_map.get(js_str!("foo"), context)?;
///
/// assert_eq!(retrieved_value, js_string!("bar").into());
/// assert_eq!(retrieved_value, js_str!("bar").into());
/// # Ok(())
/// # }
/// ```
@ -344,13 +344,13 @@ impl JsMap {
/// ```
/// # use boa_engine::{
/// # object::builtins::JsMap,
/// # Context, JsValue, JsResult, js_string
/// # Context, JsValue, JsResult, js_str
/// # };
/// # fn main() -> JsResult<()> {
/// # let context = &mut Context::default();
/// let js_map = JsMap::new(context);
/// js_map.set(js_string!("foo"), js_string!("bar"), context)?;
/// js_map.set(js_string!("hello"), js_string!("world"), context)?;
/// js_map.set(js_str!("foo"), js_str!("bar"), context)?;
/// js_map.set(js_str!("hello"), js_str!("world"), context)?;
///
/// js_map.clear(context)?;
///
@ -370,14 +370,14 @@ impl JsMap {
/// ```
/// # use boa_engine::{
/// # object::builtins::JsMap,
/// # Context, JsValue, JsResult, js_string
/// # Context, JsValue, JsResult, js_str
/// # };
/// # fn main() -> JsResult<()> {
/// # let context = &mut Context::default();
/// let js_map = JsMap::new(context);
/// js_map.set(js_string!("foo"), js_string!("bar"), context)?;
/// js_map.set(js_str!("foo"), js_str!("bar"), context)?;
///
/// let has_key = js_map.has(js_string!("foo"), context)?;
/// let has_key = js_map.has(js_str!("foo"), context)?;
///
/// assert_eq!(has_key, true.into());
/// # Ok(())

12
core/engine/src/object/builtins/jspromise.rs

@ -25,7 +25,7 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace};
/// ```
/// # use boa_engine::{
/// # builtins::promise::PromiseState,
/// # js_string,
/// # js_str,
/// # object::{builtins::JsPromise, FunctionObjectBuilder},
/// # property::Attribute,
/// # Context, JsArgs, JsError, JsValue, NativeFunction,
@ -35,14 +35,14 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace};
/// let context = &mut Context::default();
///
/// context.register_global_property(
/// js_string!("finally"),
/// js_str!("finally"),
/// false,
/// Attribute::all(),
/// );
///
/// let promise = JsPromise::new(
/// |resolvers, context| {
/// let result = js_string!("hello world!").into();
/// let result = js_str!("hello world!").into();
/// resolvers.resolve.call(
/// &JsValue::undefined(),
/// &[result],
@ -75,7 +75,7 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace};
/// .finally(
/// NativeFunction::from_fn_ptr(|_, _, context| {
/// context.global_object().clone().set(
/// js_string!("finally"),
/// js_str!("finally"),
/// JsValue::from(true),
/// true,
/// context,
@ -90,14 +90,14 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace};
///
/// assert_eq!(
/// promise.state(),
/// PromiseState::Fulfilled(js_string!("hello world!").into())
/// PromiseState::Fulfilled(js_str!("hello world!").into())
/// );
///
/// assert_eq!(
/// context
/// .global_object()
/// .clone()
/// .get(js_string!("finally"), context)?,
/// .get(js_str!("finally"), context)?,
/// JsValue::from(true)
/// );
///

34
core/engine/src/object/builtins/jsproxy.rs

@ -1,16 +1,14 @@
//! A Rust API wrapper for the `Proxy` Builtin ECMAScript Object
use boa_gc::{Finalize, Trace};
use super::JsFunction;
use crate::{
builtins::Proxy,
js_str,
native_function::{NativeFunction, NativeFunctionPointer},
object::{FunctionObjectBuilder, JsObject, JsObjectType},
string::utf16,
value::TryFromJs,
Context, JsNativeError, JsResult, JsValue,
};
use super::JsFunction;
use boa_gc::{Finalize, Trace};
/// `JsProxy` provides a wrapper for Boa's implementation of the ECMAScript `Proxy` object
///
@ -405,7 +403,7 @@ impl JsProxyBuilder {
.length(3)
.build();
handler
.create_data_property_or_throw(utf16!("apply"), f, context)
.create_data_property_or_throw(js_str!("apply"), f, context)
.expect("new object should be writable");
}
if let Some(construct) = self.construct {
@ -414,7 +412,7 @@ impl JsProxyBuilder {
.length(3)
.build();
handler
.create_data_property_or_throw(utf16!("construct"), f, context)
.create_data_property_or_throw(js_str!("construct"), f, context)
.expect("new object should be writable");
}
if let Some(define_property) = self.define_property {
@ -425,7 +423,7 @@ impl JsProxyBuilder {
.length(3)
.build();
handler
.create_data_property_or_throw(utf16!("defineProperty"), f, context)
.create_data_property_or_throw(js_str!("defineProperty"), f, context)
.expect("new object should be writable");
}
if let Some(delete_property) = self.delete_property {
@ -436,7 +434,7 @@ impl JsProxyBuilder {
.length(2)
.build();
handler
.create_data_property_or_throw(utf16!("deleteProperty"), f, context)
.create_data_property_or_throw(js_str!("deleteProperty"), f, context)
.expect("new object should be writable");
}
if let Some(get) = self.get {
@ -444,7 +442,7 @@ impl JsProxyBuilder {
.length(3)
.build();
handler
.create_data_property_or_throw(utf16!("get"), f, context)
.create_data_property_or_throw(js_str!("get"), f, context)
.expect("new object should be writable");
}
if let Some(get_own_property_descriptor) = self.get_own_property_descriptor {
@ -455,7 +453,7 @@ impl JsProxyBuilder {
.length(2)
.build();
handler
.create_data_property_or_throw(utf16!("getOwnPropertyDescriptor"), f, context)
.create_data_property_or_throw(js_str!("getOwnPropertyDescriptor"), f, context)
.expect("new object should be writable");
}
if let Some(get_prototype_of) = self.get_prototype_of {
@ -466,7 +464,7 @@ impl JsProxyBuilder {
.length(1)
.build();
handler
.create_data_property_or_throw(utf16!("getPrototypeOf"), f, context)
.create_data_property_or_throw(js_str!("getPrototypeOf"), f, context)
.expect("new object should be writable");
}
if let Some(has) = self.has {
@ -474,7 +472,7 @@ impl JsProxyBuilder {
.length(2)
.build();
handler
.create_data_property_or_throw(utf16!("has"), f, context)
.create_data_property_or_throw(js_str!("has"), f, context)
.expect("new object should be writable");
}
if let Some(is_extensible) = self.is_extensible {
@ -485,7 +483,7 @@ impl JsProxyBuilder {
.length(1)
.build();
handler
.create_data_property_or_throw(utf16!("isExtensible"), f, context)
.create_data_property_or_throw(js_str!("isExtensible"), f, context)
.expect("new object should be writable");
}
if let Some(own_keys) = self.own_keys {
@ -494,7 +492,7 @@ impl JsProxyBuilder {
.length(1)
.build();
handler
.create_data_property_or_throw(utf16!("ownKeys"), f, context)
.create_data_property_or_throw(js_str!("ownKeys"), f, context)
.expect("new object should be writable");
}
if let Some(prevent_extensions) = self.prevent_extensions {
@ -505,7 +503,7 @@ impl JsProxyBuilder {
.length(1)
.build();
handler
.create_data_property_or_throw(utf16!("preventExtensions"), f, context)
.create_data_property_or_throw(js_str!("preventExtensions"), f, context)
.expect("new object should be writable");
}
if let Some(set) = self.set {
@ -513,7 +511,7 @@ impl JsProxyBuilder {
.length(4)
.build();
handler
.create_data_property_or_throw(utf16!("set"), f, context)
.create_data_property_or_throw(js_str!("set"), f, context)
.expect("new object should be writable");
}
if let Some(set_prototype_of) = self.set_prototype_of {
@ -524,7 +522,7 @@ impl JsProxyBuilder {
.length(2)
.build();
handler
.create_data_property_or_throw(utf16!("setPrototypeOf"), f, context)
.create_data_property_or_throw(js_str!("setPrototypeOf"), f, context)
.expect("new object should be writable");
}

3
core/engine/src/object/internal_methods/string.rs

@ -1,5 +1,4 @@
use crate::{
js_string,
object::{JsData, JsObject},
property::{PropertyDescriptor, PropertyKey},
Context, JsResult, JsString,
@ -158,7 +157,7 @@ fn string_get_own_property(obj: &JsObject, key: &PropertyKey) -> Option<Property
// 10. Let len be the length of str.
// 11. If ℝ(index) < 0 or len ≤ ℝ(index), return undefined.
// 12. Let resultStr be the String value of length 1, containing one code unit from str, specifically the code unit at index ℝ(index).
let result_str = js_string!(string.get(pos..=pos)?);
let result_str = string.get(pos..=pos)?;
// 13. Return the PropertyDescriptor { [[Value]]: resultStr, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false }.
let desc = PropertyDescriptor::builder()

32
core/engine/src/object/jsobject.rs

@ -17,11 +17,11 @@ use crate::{
error::JsNativeError,
js_string,
property::{PropertyDescriptor, PropertyKey},
string::utf16,
value::PreferredType,
Context, JsResult, JsString, JsValue,
};
use boa_gc::{self, Finalize, Gc, GcBox, GcRefCell, Trace};
use boa_macros::js_str;
use std::{
cell::RefCell,
collections::HashMap,
@ -337,9 +337,9 @@ impl JsObject {
// 4. Else,
// a. Let methodNames be « "valueOf", "toString" ».
let method_names = if hint == PreferredType::String {
[utf16!("toString"), utf16!("valueOf")]
[js_str!("toString"), js_str!("valueOf")]
} else {
[utf16!("valueOf"), utf16!("toString")]
[js_str!("valueOf"), js_str!("toString")]
};
// 5. For each name in methodNames in List order, do
@ -379,41 +379,41 @@ impl JsObject {
// 3. Let hasEnumerable be ? HasProperty(Obj, "enumerable").
// 4. If hasEnumerable is true, then ...
if self.has_property(utf16!("enumerable"), context)? {
if self.has_property(js_str!("enumerable"), context)? {
// a. Let enumerable be ! ToBoolean(? Get(Obj, "enumerable")).
// b. Set desc.[[Enumerable]] to enumerable.
desc = desc.enumerable(self.get(utf16!("enumerable"), context)?.to_boolean());
desc = desc.enumerable(self.get(js_str!("enumerable"), context)?.to_boolean());
}
// 5. Let hasConfigurable be ? HasProperty(Obj, "configurable").
// 6. If hasConfigurable is true, then ...
if self.has_property(utf16!("configurable"), context)? {
if self.has_property(js_str!("configurable"), context)? {
// a. Let configurable be ! ToBoolean(? Get(Obj, "configurable")).
// b. Set desc.[[Configurable]] to configurable.
desc = desc.configurable(self.get(utf16!("configurable"), context)?.to_boolean());
desc = desc.configurable(self.get(js_str!("configurable"), context)?.to_boolean());
}
// 7. Let hasValue be ? HasProperty(Obj, "value").
// 8. If hasValue is true, then ...
if self.has_property(utf16!("value"), context)? {
if self.has_property(js_str!("value"), context)? {
// a. Let value be ? Get(Obj, "value").
// b. Set desc.[[Value]] to value.
desc = desc.value(self.get(utf16!("value"), context)?);
desc = desc.value(self.get(js_str!("value"), context)?);
}
// 9. Let hasWritable be ? HasProperty(Obj, ).
// 10. If hasWritable is true, then ...
if self.has_property(utf16!("writable"), context)? {
if self.has_property(js_str!("writable"), context)? {
// a. Let writable be ! ToBoolean(? Get(Obj, "writable")).
// b. Set desc.[[Writable]] to writable.
desc = desc.writable(self.get(utf16!("writable"), context)?.to_boolean());
desc = desc.writable(self.get(js_str!("writable"), context)?.to_boolean());
}
// 11. Let hasGet be ? HasProperty(Obj, "get").
// 12. If hasGet is true, then
let get = if self.has_property(utf16!("get"), context)? {
let get = if self.has_property(js_str!("get"), context)? {
// a. Let getter be ? Get(Obj, "get").
let getter = self.get(utf16!("get"), context)?;
let getter = self.get(js_str!("get"), context)?;
// b. If IsCallable(getter) is false and getter is not undefined, throw a TypeError exception.
// todo: extract IsCallable to be callable from Value
if !getter.is_undefined() && getter.as_object().map_or(true, |o| !o.is_callable()) {
@ -429,9 +429,9 @@ impl JsObject {
// 13. Let hasSet be ? HasProperty(Obj, "set").
// 14. If hasSet is true, then
let set = if self.has_property(utf16!("set"), context)? {
let set = if self.has_property(js_str!("set"), context)? {
// 14.a. Let setter be ? Get(Obj, "set").
let setter = self.get(utf16!("set"), context)?;
let setter = self.get(js_str!("set"), context)?;
// 14.b. If IsCallable(setter) is false and setter is not undefined, throw a TypeError exception.
// todo: extract IsCallable to be callable from Value
if !setter.is_undefined() && setter.as_object().map_or(true, |o| !o.is_callable()) {
@ -940,7 +940,7 @@ impl<T: NativeObject + ?Sized> Debug for JsObject<T> {
if self.is_callable() {
let name_prop = obj
.properties()
.get(&PropertyKey::String(JsString::from("name")));
.get(&PropertyKey::String(js_string!("name")));
let name = match name_prop {
None => JsString::default(),
Some(prop) => prop

11
core/engine/src/object/mod.rs

@ -2,6 +2,7 @@
//!
//! For the builtin object wrappers, please see [`object::builtins`][builtins] for implementors.
use boa_macros::js_str;
pub use jsobject::{RecursionLimiter, Ref, RefMut};
pub use operations::IntegrityLevel;
pub use property_map::*;
@ -22,8 +23,8 @@ use crate::{
native_function::{NativeFunction, NativeFunctionObject},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
string::{common::StaticJsStrings, utf16},
Context, JsString, JsSymbol, JsValue,
string::common::StaticJsStrings,
Context, JsStr, JsString, JsSymbol, JsValue,
};
use boa_gc::{Finalize, Trace};
@ -52,10 +53,10 @@ pub use jsobject::*;
pub(crate) trait JsObjectType: Into<JsValue> + Into<JsObject> {}
/// Const `constructor`, usually set on prototypes as a key to point to their respective constructor object.
pub const CONSTRUCTOR: &[u16] = utf16!("constructor");
pub const CONSTRUCTOR: JsStr<'_> = js_str!("constructor");
/// Const `prototype`, usually set on constructors as a key to point to their respective prototype object.
pub const PROTOTYPE: &[u16] = utf16!("prototype");
pub const PROTOTYPE: JsStr<'_> = js_str!("prototype");
/// Common field names.
@ -1017,7 +1018,7 @@ impl<'ctx> ConstructorBuilder<'ctx> {
};
constructor.insert(StaticJsStrings::LENGTH, length);
constructor.insert(utf16!("name"), name);
constructor.insert(js_str!("name"), name);
if let Some(proto) = self.custom_prototype.take() {
constructor.set_prototype(proto);

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save