1#[cfg(test)]
6mod tests;
7
8use crate::{
9 builtins::{
10 number::{f64_to_int32, f64_to_uint32},
11 Number,
12 },
13 object::{JsObject, Object, ObjectData},
14 property::{PropertyDescriptor, PropertyKey},
15 symbol::{JsSymbol, WellKnownSymbols},
16 BoaProfiler, Context, JsBigInt, JsResult, JsString,
17};
18use gc::{Finalize, Trace};
19use std::{
20 collections::HashSet,
21 convert::TryFrom,
22 fmt::{self, Display},
23 str::FromStr,
24};
25
26mod conversions;
27pub(crate) mod display;
28mod equality;
29mod hash;
30mod operations;
31mod r#type;
32
33pub use conversions::*;
34pub use display::ValueDisplay;
35pub use equality::*;
36pub use hash::*;
37pub use operations::*;
38pub use r#type::Type;
39
40#[derive(Trace, Finalize, Debug, Clone)]
42pub enum JsValue {
43 Null,
45 Undefined,
47 Boolean(bool),
49 String(JsString),
51 Rational(f64),
53 Integer(i32),
55 BigInt(JsBigInt),
57 Object(JsObject),
59 Symbol(JsSymbol),
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum IntegerOrInfinity {
66 Integer(i64),
67 PositiveInfinity,
68 NegativeInfinity,
69}
70
71impl JsValue {
72 #[inline]
74 pub fn new<T>(value: T) -> Self
75 where
76 T: Into<Self>,
77 {
78 value.into()
79 }
80
81 #[inline]
83 pub fn undefined() -> Self {
84 Self::Undefined
85 }
86
87 #[inline]
89 pub fn null() -> Self {
90 Self::Null
91 }
92
93 #[inline]
95 pub fn nan() -> Self {
96 Self::Rational(f64::NAN)
97 }
98
99 #[inline]
101 pub fn positive_inifnity() -> Self {
102 Self::Rational(f64::INFINITY)
103 }
104
105 #[inline]
107 pub fn negative_inifnity() -> Self {
108 Self::Rational(f64::NEG_INFINITY)
109 }
110
111 pub(crate) fn new_object(context: &Context) -> Self {
113 let _timer = BoaProfiler::global().start_event("new_object", "value");
114 context.construct_object().into()
115 }
116
117 #[inline]
119 pub fn is_object(&self) -> bool {
120 matches!(self, Self::Object(_))
121 }
122
123 #[inline]
124 pub fn as_object(&self) -> Option<JsObject> {
125 match *self {
126 Self::Object(ref o) => Some(o.clone()),
127 _ => None,
128 }
129 }
130
131 #[inline]
133 pub fn is_symbol(&self) -> bool {
134 matches!(self, Self::Symbol(_))
135 }
136
137 pub fn as_symbol(&self) -> Option<JsSymbol> {
138 match self {
139 Self::Symbol(symbol) => Some(symbol.clone()),
140 _ => None,
141 }
142 }
143
144 #[inline]
146 pub fn is_function(&self) -> bool {
147 matches!(self, Self::Object(o) if o.is_function())
148 }
149
150 #[inline]
152 pub fn is_undefined(&self) -> bool {
153 matches!(self, Self::Undefined)
154 }
155
156 #[inline]
158 pub fn is_null(&self) -> bool {
159 matches!(self, Self::Null)
160 }
161
162 #[inline]
164 pub fn is_null_or_undefined(&self) -> bool {
165 matches!(self, Self::Null | Self::Undefined)
166 }
167
168 #[inline]
170 pub fn is_double(&self) -> bool {
171 matches!(self, Self::Rational(_))
172 }
173
174 #[inline]
176 #[allow(clippy::float_cmp)]
177 pub fn is_integer(&self) -> bool {
178 let is_racional_intiger = |n: f64| n == ((n as i32) as f64);
181
182 match *self {
183 Self::Integer(_) => true,
184 Self::Rational(n) if is_racional_intiger(n) => true,
185 _ => false,
186 }
187 }
188
189 #[inline]
191 pub fn is_number(&self) -> bool {
192 matches!(self, Self::Rational(_) | Self::Integer(_))
193 }
194
195 #[inline]
196 pub fn as_number(&self) -> Option<f64> {
197 match *self {
198 Self::Integer(integer) => Some(integer.into()),
199 Self::Rational(rational) => Some(rational),
200 _ => None,
201 }
202 }
203
204 #[inline]
206 pub fn is_string(&self) -> bool {
207 matches!(self, Self::String(_))
208 }
209
210 #[inline]
212 pub fn as_string(&self) -> Option<&JsString> {
213 match self {
214 Self::String(ref string) => Some(string),
215 _ => None,
216 }
217 }
218
219 #[inline]
221 pub fn is_boolean(&self) -> bool {
222 matches!(self, Self::Boolean(_))
223 }
224
225 #[inline]
226 pub fn as_boolean(&self) -> Option<bool> {
227 match self {
228 Self::Boolean(boolean) => Some(*boolean),
229 _ => None,
230 }
231 }
232
233 #[inline]
235 pub fn is_bigint(&self) -> bool {
236 matches!(self, Self::BigInt(_))
237 }
238
239 #[inline]
241 pub fn as_bigint(&self) -> Option<&JsBigInt> {
242 match self {
243 Self::BigInt(bigint) => Some(bigint),
244 _ => None,
245 }
246 }
247
248 pub fn to_boolean(&self) -> bool {
255 match *self {
256 Self::Undefined | Self::Null => false,
257 Self::Symbol(_) | Self::Object(_) => true,
258 Self::String(ref s) if !s.is_empty() => true,
259 Self::Rational(n) if n != 0.0 && !n.is_nan() => true,
260 Self::Integer(n) if n != 0 => true,
261 Self::BigInt(ref n) if !n.is_zero() => true,
262 Self::Boolean(v) => v,
263 _ => false,
264 }
265 }
266
267 pub(crate) fn get_property<Key>(&self, key: Key) -> Option<PropertyDescriptor>
271 where
272 Key: Into<PropertyKey>,
273 {
274 let key = key.into();
275 let _timer = BoaProfiler::global().start_event("Value::get_property", "value");
276 match self {
277 Self::Object(ref object) => {
278 let property = object.borrow().properties().get(&key).cloned();
280 if property.is_some() {
281 return property;
282 }
283
284 object.borrow().prototype_instance().get_property(key)
285 }
286 _ => None,
287 }
288 }
289
290 pub(crate) fn get_field<K>(&self, key: K, context: &mut Context) -> JsResult<Self>
293 where
294 K: Into<PropertyKey>,
295 {
296 let _timer = BoaProfiler::global().start_event("Value::get_field", "value");
297 if let Self::Object(ref obj) = *self {
298 obj.clone()
299 .__get__(&key.into(), obj.clone().into(), context)
300 } else {
301 Ok(JsValue::undefined())
302 }
303 }
304
305 #[inline]
314 pub(crate) fn set_field<K, V>(
315 &self,
316 key: K,
317 value: V,
318 throw: bool,
319 context: &mut Context,
320 ) -> JsResult<JsValue>
321 where
322 K: Into<PropertyKey>,
323 V: Into<JsValue>,
324 {
325 let key = key.into();
332 let value = value.into();
333 let _timer = BoaProfiler::global().start_event("Value::set_field", "value");
334 if let Self::Object(ref obj) = *self {
335 let success = obj
337 .clone()
338 .__set__(key, value.clone(), obj.clone().into(), context)?;
339
340 if !success && throw {
343 return Err(context.construct_type_error("Cannot assign value to property"));
344 } else {
345 return Ok(value);
346 }
347 }
348 Ok(value)
349 }
350
351 #[inline]
353 pub fn set_data(&self, data: ObjectData) {
354 if let Self::Object(ref obj) = *self {
355 obj.borrow_mut().data = data;
356 }
357 }
358
359 #[inline]
361 pub(crate) fn set_property<K, P>(&self, key: K, property: P)
362 where
363 K: Into<PropertyKey>,
364 P: Into<PropertyDescriptor>,
365 {
366 if let Some(object) = self.as_object() {
367 object.insert(key.into(), property.into());
368 }
369 }
370
371 pub fn to_primitive(
375 &self,
376 context: &mut Context,
377 preferred_type: PreferredType,
378 ) -> JsResult<JsValue> {
379 if let JsValue::Object(obj) = self {
382 if let Some(exotic_to_prim) =
383 obj.get_method(context, WellKnownSymbols::to_primitive())?
384 {
385 let hint = match preferred_type {
386 PreferredType::String => "string",
387 PreferredType::Number => "number",
388 PreferredType::Default => "default",
389 }
390 .into();
391 let result = exotic_to_prim.call(self, &[hint], context)?;
392 return if result.is_object() {
393 Err(context.construct_type_error("Symbol.toPrimitive cannot return an object"))
394 } else {
395 Ok(result)
396 };
397 }
398
399 let mut hint = preferred_type;
400
401 if hint == PreferredType::Default {
402 hint = PreferredType::Number;
403 };
404
405 obj.ordinary_to_primitive(context, hint)
407 } else {
408 Ok(self.clone())
410 }
411 }
412
413 pub fn to_bigint(&self, context: &mut Context) -> JsResult<JsBigInt> {
417 match self {
418 JsValue::Null => Err(context.construct_type_error("cannot convert null to a BigInt")),
419 JsValue::Undefined => {
420 Err(context.construct_type_error("cannot convert undefined to a BigInt"))
421 }
422 JsValue::String(ref string) => {
423 if let Some(value) = JsBigInt::from_string(string) {
424 Ok(value)
425 } else {
426 Err(context.construct_syntax_error(format!(
427 "cannot convert string '{}' to bigint primitive",
428 string
429 )))
430 }
431 }
432 JsValue::Boolean(true) => Ok(JsBigInt::one()),
433 JsValue::Boolean(false) => Ok(JsBigInt::zero()),
434 JsValue::Integer(num) => Ok(JsBigInt::new(*num)),
435 JsValue::Rational(num) => {
436 if let Ok(bigint) = JsBigInt::try_from(*num) {
437 return Ok(bigint);
438 }
439 Err(context.construct_type_error(format!(
440 "The number {} cannot be converted to a BigInt because it is not an integer",
441 num
442 )))
443 }
444 JsValue::BigInt(b) => Ok(b.clone()),
445 JsValue::Object(_) => {
446 let primitive = self.to_primitive(context, PreferredType::Number)?;
447 primitive.to_bigint(context)
448 }
449 JsValue::Symbol(_) => {
450 Err(context.construct_type_error("cannot convert Symbol to a BigInt"))
451 }
452 }
453 }
454
455 #[inline]
467 pub fn display(&self) -> ValueDisplay<'_> {
468 ValueDisplay { value: self }
469 }
470
471 pub fn to_string(&self, context: &mut Context) -> JsResult<JsString> {
475 match self {
476 JsValue::Null => Ok("null".into()),
477 JsValue::Undefined => Ok("undefined".into()),
478 JsValue::Boolean(boolean) => Ok(boolean.to_string().into()),
479 JsValue::Rational(rational) => Ok(Number::to_native_string(*rational).into()),
480 JsValue::Integer(integer) => Ok(integer.to_string().into()),
481 JsValue::String(string) => Ok(string.clone()),
482 JsValue::Symbol(_) => {
483 Err(context.construct_type_error("can't convert symbol to string"))
484 }
485 JsValue::BigInt(ref bigint) => Ok(bigint.to_string().into()),
486 JsValue::Object(_) => {
487 let primitive = self.to_primitive(context, PreferredType::String)?;
488 primitive.to_string(context)
489 }
490 }
491 }
492
493 pub fn to_object(&self, context: &mut Context) -> JsResult<JsObject> {
499 match self {
500 JsValue::Undefined | JsValue::Null => {
501 Err(context.construct_type_error("cannot convert 'null' or 'undefined' to object"))
502 }
503 JsValue::Boolean(boolean) => {
504 let prototype = context.standard_objects().boolean_object().prototype();
505 Ok(JsObject::new(Object::with_prototype(
506 prototype.into(),
507 ObjectData::boolean(*boolean),
508 )))
509 }
510 JsValue::Integer(integer) => {
511 let prototype = context.standard_objects().number_object().prototype();
512 Ok(JsObject::new(Object::with_prototype(
513 prototype.into(),
514 ObjectData::number(f64::from(*integer)),
515 )))
516 }
517 JsValue::Rational(rational) => {
518 let prototype = context.standard_objects().number_object().prototype();
519 Ok(JsObject::new(Object::with_prototype(
520 prototype.into(),
521 ObjectData::number(*rational),
522 )))
523 }
524 JsValue::String(ref string) => {
525 let prototype = context.standard_objects().string_object().prototype();
526
527 let object = JsObject::new(Object::with_prototype(
528 prototype.into(),
529 ObjectData::string(string.clone()),
530 ));
531 object.insert_property(
533 "length",
534 PropertyDescriptor::builder()
535 .value(string.encode_utf16().count())
536 .writable(false)
537 .enumerable(false)
538 .configurable(false),
539 );
540 Ok(object)
541 }
542 JsValue::Symbol(ref symbol) => {
543 let prototype = context.standard_objects().symbol_object().prototype();
544 Ok(JsObject::new(Object::with_prototype(
545 prototype.into(),
546 ObjectData::symbol(symbol.clone()),
547 )))
548 }
549 JsValue::BigInt(ref bigint) => {
550 let prototype = context.standard_objects().bigint_object().prototype();
551 Ok(JsObject::new(Object::with_prototype(
552 prototype.into(),
553 ObjectData::big_int(bigint.clone()),
554 )))
555 }
556 JsValue::Object(jsobject) => Ok(jsobject.clone()),
557 }
558 }
559
560 pub fn to_property_key(&self, context: &mut Context) -> JsResult<PropertyKey> {
564 Ok(match self {
565 JsValue::String(string) => string.clone().into(),
567 JsValue::Symbol(symbol) => symbol.clone().into(),
568 _ => match self.to_primitive(context, PreferredType::String)? {
570 JsValue::String(ref string) => string.clone().into(),
571 JsValue::Symbol(ref symbol) => symbol.clone().into(),
572 primitive => primitive.to_string(context)?.into(),
573 },
574 })
575 }
576
577 pub fn to_numeric(&self, context: &mut Context) -> JsResult<Numeric> {
581 let primitive = self.to_primitive(context, PreferredType::Number)?;
582 if let Some(bigint) = primitive.as_bigint() {
583 return Ok(bigint.clone().into());
584 }
585 Ok(self.to_number(context)?.into())
586 }
587
588 pub fn to_u32(&self, context: &mut Context) -> JsResult<u32> {
594 if let JsValue::Integer(number) = *self {
596 return Ok(number as u32);
597 }
598 let number = self.to_number(context)?;
599
600 Ok(f64_to_uint32(number))
601 }
602
603 pub fn to_i32(&self, context: &mut Context) -> JsResult<i32> {
607 if let JsValue::Integer(number) = *self {
609 return Ok(number);
610 }
611 let number = self.to_number(context)?;
612
613 Ok(f64_to_int32(number))
614 }
615
616 pub fn to_index(&self, context: &mut Context) -> JsResult<usize> {
620 if self.is_undefined() {
621 return Ok(0);
622 }
623
624 let integer_index = self.to_integer(context)?;
625
626 if integer_index < 0.0 {
627 return Err(context.construct_range_error("Integer index must be >= 0"));
628 }
629
630 if integer_index > Number::MAX_SAFE_INTEGER {
631 return Err(
632 context.construct_range_error("Integer index must be less than 2**(53) - 1")
633 );
634 }
635
636 Ok(integer_index as usize)
637 }
638
639 pub fn to_length(&self, context: &mut Context) -> JsResult<usize> {
643 let len = self.to_integer(context)?;
645
646 if len < 0.0 {
648 return Ok(0);
649 }
650
651 Ok(len.min(Number::MAX_SAFE_INTEGER) as usize)
653 }
654
655 pub fn to_integer(&self, context: &mut Context) -> JsResult<f64> {
659 let number = self.to_number(context)?;
661
662 if !number.is_finite() {
664 if number.is_nan() {
666 return Ok(0.0);
667 }
668 return Ok(number);
669 }
670
671 Ok(number.trunc() + 0.0) }
676
677 pub fn to_number(&self, context: &mut Context) -> JsResult<f64> {
683 match *self {
684 JsValue::Null => Ok(0.0),
685 JsValue::Undefined => Ok(f64::NAN),
686 JsValue::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
687 JsValue::String(ref string) => Ok(string.string_to_number()),
688 JsValue::Rational(number) => Ok(number),
689 JsValue::Integer(integer) => Ok(f64::from(integer)),
690 JsValue::Symbol(_) => {
691 Err(context.construct_type_error("argument must not be a symbol"))
692 }
693 JsValue::BigInt(_) => {
694 Err(context.construct_type_error("argument must not be a bigint"))
695 }
696 JsValue::Object(_) => {
697 let primitive = self.to_primitive(context, PreferredType::Number)?;
698 primitive.to_number(context)
699 }
700 }
701 }
702
703 pub fn to_numeric_number(&self, context: &mut Context) -> JsResult<f64> {
709 let primitive = self.to_primitive(context, PreferredType::Number)?;
710 if let Some(bigint) = primitive.as_bigint() {
711 return Ok(bigint.to_f64());
712 }
713 primitive.to_number(context)
714 }
715
716 #[inline]
728 pub fn require_object_coercible(&self, context: &mut Context) -> JsResult<&JsValue> {
729 if self.is_null_or_undefined() {
730 Err(context.construct_type_error("cannot convert null or undefined to Object"))
731 } else {
732 Ok(self)
733 }
734 }
735
736 #[inline]
737 pub fn to_property_descriptor(&self, context: &mut Context) -> JsResult<PropertyDescriptor> {
738 match self {
740 JsValue::Object(ref obj) => obj.to_property_descriptor(context),
741 _ => Err(context
742 .construct_type_error("Cannot construct a property descriptor from a non-object")),
743 }
744 }
745
746 pub fn to_integer_or_infinity(&self, context: &mut Context) -> JsResult<IntegerOrInfinity> {
750 let number = self.to_number(context)?;
752
753 if number.is_nan() || number == 0.0 || number == -0.0 {
755 Ok(IntegerOrInfinity::Integer(0))
756 } else if number.is_infinite() && number.is_sign_positive() {
757 Ok(IntegerOrInfinity::PositiveInfinity)
759 } else if number.is_infinite() && number.is_sign_negative() {
760 Ok(IntegerOrInfinity::NegativeInfinity)
762 } else {
763 let integer = number.abs().floor();
765 let integer = integer.min(Number::MAX_SAFE_INTEGER) as i64;
766
767 if number < 0.0 {
770 Ok(IntegerOrInfinity::Integer(-integer))
771 } else {
772 Ok(IntegerOrInfinity::Integer(integer))
773 }
774 }
775 }
776
777 pub fn type_of(&self) -> JsString {
785 match *self {
786 Self::Rational(_) | Self::Integer(_) => "number",
787 Self::String(_) => "string",
788 Self::Boolean(_) => "boolean",
789 Self::Symbol(_) => "symbol",
790 Self::Null => "object",
791 Self::Undefined => "undefined",
792 Self::BigInt(_) => "bigint",
793 Self::Object(ref object) => {
794 if object.is_callable() {
795 "function"
796 } else {
797 "object"
798 }
799 }
800 }
801 .into()
802 }
803
804 pub(crate) fn is_array(&self, _context: &mut Context) -> JsResult<bool> {
811 if let Some(object) = self.as_object() {
813 Ok(object.is_array())
820 } else {
821 Ok(false)
822 }
823 }
824}
825
826impl Default for JsValue {
827 fn default() -> Self {
828 Self::Undefined
829 }
830}
831
832#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
834pub enum PreferredType {
835 String,
836 Number,
837 Default,
838}
839
840#[derive(Debug, Clone, PartialEq, PartialOrd)]
842pub enum Numeric {
843 Number(f64),
845 BigInt(JsBigInt),
847}
848
849impl From<f64> for Numeric {
850 #[inline]
851 fn from(value: f64) -> Self {
852 Self::Number(value)
853 }
854}
855
856impl From<i32> for Numeric {
857 #[inline]
858 fn from(value: i32) -> Self {
859 Self::Number(value.into())
860 }
861}
862
863impl From<i16> for Numeric {
864 #[inline]
865 fn from(value: i16) -> Self {
866 Self::Number(value.into())
867 }
868}
869
870impl From<i8> for Numeric {
871 #[inline]
872 fn from(value: i8) -> Self {
873 Self::Number(value.into())
874 }
875}
876
877impl From<u32> for Numeric {
878 #[inline]
879 fn from(value: u32) -> Self {
880 Self::Number(value.into())
881 }
882}
883
884impl From<u16> for Numeric {
885 #[inline]
886 fn from(value: u16) -> Self {
887 Self::Number(value.into())
888 }
889}
890
891impl From<u8> for Numeric {
892 #[inline]
893 fn from(value: u8) -> Self {
894 Self::Number(value.into())
895 }
896}
897
898impl From<JsBigInt> for Numeric {
899 #[inline]
900 fn from(value: JsBigInt) -> Self {
901 Self::BigInt(value)
902 }
903}
904
905impl From<Numeric> for JsValue {
906 fn from(value: Numeric) -> Self {
907 match value {
908 Numeric::Number(number) => Self::new(number),
909 Numeric::BigInt(bigint) => Self::new(bigint),
910 }
911 }
912}