Browse Source

Rewrite some patterns with let-else and ok_or_else (#2404)

This Pull Request updates the codebase to the newest version of rustc (1.65.0).

It changes the following:

- Bumps `rust-version` to 1.65.0.
- Rewrites some snippets to use the new let else, ok_or_else and some other utils.
- Removes the `rustdoc::missing_doc_code_examples` allow lint from our codebase. (Context: https://github.com/rust-lang/rust/pull/101732)
pull/2406/head
José Julián Espina 2 years ago
parent
commit
91235c77fe
  1. 2
      Cargo.toml
  2. 6
      boa_ast/src/lib.rs
  3. 11
      boa_engine/src/bigint.rs
  4. 8
      boa_engine/src/builtins/array/mod.rs
  5. 62
      boa_engine/src/builtins/array_buffer/mod.rs
  6. 128
      boa_engine/src/builtins/date/mod.rs
  7. 10
      boa_engine/src/builtins/error/mod.rs
  8. 4
      boa_engine/src/builtins/eval/mod.rs
  9. 12
      boa_engine/src/builtins/function/mod.rs
  10. 8
      boa_engine/src/builtins/iterable/async_from_sync_iterator.rs
  11. 23
      boa_engine/src/builtins/iterable/mod.rs
  12. 10
      boa_engine/src/builtins/map/mod.rs
  13. 4
      boa_engine/src/builtins/object/mod.rs
  14. 12
      boa_engine/src/builtins/promise/mod.rs
  15. 28
      boa_engine/src/builtins/reflect/mod.rs
  16. 37
      boa_engine/src/builtins/regexp/mod.rs
  17. 23
      boa_engine/src/builtins/set/mod.rs
  18. 4
      boa_engine/src/builtins/string/mod.rs
  19. 4
      boa_engine/src/builtins/uri/mod.rs
  20. 15
      boa_engine/src/class.rs
  21. 1
      boa_engine/src/lib.rs
  22. 4
      boa_engine/src/object/internal_methods/arguments.rs
  23. 5
      boa_engine/src/object/internal_methods/global.rs
  24. 5
      boa_engine/src/object/internal_methods/mod.rs
  25. 52
      boa_engine/src/object/internal_methods/proxy.rs
  26. 27
      boa_engine/src/object/operations.rs
  27. 4
      boa_engine/src/string/mod.rs
  28. 6
      boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs
  29. 12
      boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs
  30. 44
      boa_engine/src/syntax/parser/statement/declaration/lexical.rs
  31. 44
      boa_engine/src/syntax/parser/statement/variable/mod.rs
  32. 22
      boa_engine/src/vm/opcode/iteration/for_await.rs
  33. 1
      boa_interner/src/lib.rs
  34. 1
      boa_tester/src/main.rs
  35. 3
      boa_unicode/src/lib.rs
  36. 3
      boa_wasm/src/lib.rs

2
Cargo.toml

@ -16,7 +16,7 @@ members = [
[workspace.package]
edition = "2021"
version = "0.16.0"
rust-version = "1.64"
rust-version = "1.65"
authors = ["boa-dev"]
repository = "https://github.com/boa-dev/boa"
license = "Unlicense/MIT"

6
boa_ast/src/lib.rs

@ -49,11 +49,7 @@
nonstandard_style,
missing_docs
)]
#![allow(
clippy::module_name_repetitions,
clippy::too_many_lines,
rustdoc::missing_doc_code_examples
)]
#![allow(clippy::module_name_repetitions, clippy::too_many_lines)]
mod position;
mod punctuator;

11
boa_engine/src/bigint.rs

@ -149,13 +149,10 @@ impl JsBigInt {
#[inline]
pub fn pow(x: &Self, y: &Self) -> JsResult<Self> {
let y = if let Some(y) = y.inner.to_biguint() {
y
} else {
return Err(JsNativeError::range()
.with_message("BigInt negative exponent")
.into());
};
let y = y
.inner
.to_biguint()
.ok_or_else(|| JsNativeError::range().with_message("BigInt negative exponent"))?;
let num_bits = (x.inner.bits() as f64
* y.to_f64().expect("Unable to convert from BigUInt to f64"))

8
boa_engine/src/builtins/array/mod.rs

@ -300,9 +300,7 @@ impl Array {
/// by `Array.prototype.concat`.
fn is_concat_spreadable(o: &JsValue, context: &mut Context) -> JsResult<bool> {
// 1. If Type(O) is not Object, return false.
let o = if let Some(o) = o.as_object() {
o
} else {
let Some(o) = o.as_object() else {
return Ok(false);
};
@ -461,9 +459,7 @@ impl Array {
let next = iterator_record.step(context)?;
// iv. If next is false, then
let next = if let Some(next) = next {
next
} else {
let Some(next) = next else {
// 1. Perform ? Set(A, "length", 𝔽(k), true).
a.set("length", k, true, context)?;

62
boa_engine/src/builtins/array_buffer/mod.rs

@ -148,33 +148,25 @@ impl ArrayBuffer {
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
let obj = if let Some(obj) = this.as_object() {
obj
} else {
return Err(JsNativeError::typ()
.with_message("ArrayBuffer.byteLength called with non-object value")
.into());
};
let obj = this.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("ArrayBuffer.byteLength called with non-object value")
})?;
let obj = obj.borrow();
let o = if let Some(o) = obj.as_array_buffer() {
o
} else {
return Err(JsNativeError::typ()
.with_message("ArrayBuffer.byteLength called with invalid object")
.into());
};
let buf = obj.as_array_buffer().ok_or_else(|| {
JsNativeError::typ().with_message("ArrayBuffer.byteLength called with invalid object")
})?;
// TODO: Shared Array Buffer
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
// 4. If IsDetachedBuffer(O) is true, return +0𝔽.
if Self::is_detached_buffer(o) {
if Self::is_detached_buffer(buf) {
return Ok(0.into());
}
// 5. Let length be O.[[ArrayBufferByteLength]].
// 6. Return 𝔽(length).
Ok(o.array_buffer_byte_length.into())
Ok(buf.array_buffer_byte_length.into())
}
/// `25.1.5.3 ArrayBuffer.prototype.slice ( start, end )`
@ -186,34 +178,26 @@ impl ArrayBuffer {
fn slice(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
let obj = if let Some(obj) = this.as_object() {
obj
} else {
return Err(JsNativeError::typ()
.with_message("ArrayBuffer.slice called with non-object value")
.into());
};
let obj = this.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("ArrayBuffer.slice called with non-object value")
})?;
let obj_borrow = obj.borrow();
let o = if let Some(o) = obj_borrow.as_array_buffer() {
o
} else {
return Err(JsNativeError::typ()
.with_message("ArrayBuffer.slice called with invalid object")
.into());
};
let buf = obj_borrow.as_array_buffer().ok_or_else(|| {
JsNativeError::typ().with_message("ArrayBuffer.slice called with invalid object")
})?;
// TODO: Shared Array Buffer
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
// 4. If IsDetachedBuffer(O) is true, throw a TypeError exception.
if Self::is_detached_buffer(o) {
if Self::is_detached_buffer(buf) {
return Err(JsNativeError::typ()
.with_message("ArrayBuffer.slice called with detached buffer")
.into());
}
// 5. Let len be O.[[ArrayBufferByteLength]].
let len = o.array_buffer_byte_length as i64;
let len = buf.array_buffer_byte_length as i64;
// 6. Let relativeStart be ? ToIntegerOrInfinity(start).
let relative_start = args.get_or_undefined(0).to_integer_or_infinity(context)?;
@ -298,14 +282,14 @@ impl ArrayBuffer {
// 22. NOTE: Side-effects of the above steps may have detached O.
// 23. If IsDetachedBuffer(O) is true, throw a TypeError exception.
if Self::is_detached_buffer(o) {
if Self::is_detached_buffer(buf) {
return Err(JsNativeError::typ()
.with_message("ArrayBuffer detached while ArrayBuffer.slice was running")
.into());
}
// 24. Let fromBuf be O.[[ArrayBufferData]].
let from_buf = o
let from_buf = buf
.array_buffer_data
.as_ref()
.expect("ArrayBuffer cannot be detached here");
@ -389,13 +373,9 @@ impl ArrayBuffer {
// 2. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception.
// 3. Let srcBlock be srcBuffer.[[ArrayBufferData]].
let src_block = if let Some(b) = &self.array_buffer_data {
b
} else {
return Err(JsNativeError::syntax()
.with_message("Cannot clone detached array buffer")
.into());
};
let src_block = self.array_buffer_data.as_deref().ok_or_else(|| {
JsNativeError::syntax().with_message("Cannot clone detached array buffer")
})?;
{
// 4. Let targetBlock be targetBuffer.[[ArrayBufferData]].

128
boa_engine/src/builtins/date/mod.rs

@ -508,13 +508,9 @@ impl Date {
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. If Type(O) is not Object, throw a TypeError exception.
let o = if let Some(o) = this.as_object() {
o
} else {
return Err(JsNativeError::typ()
.with_message("Date.prototype[@@toPrimitive] called on non object")
.into());
};
let o = this.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("Date.prototype[@@toPrimitive] called on non object")
})?;
let hint = args.get_or_undefined(0);
@ -908,25 +904,13 @@ impl Date {
}
// 3. Let y be ? ToNumber(year).
let y = args
.get(0)
.cloned()
.unwrap_or_default()
.to_number(context)?;
let y = args.get_or_undefined(0).to_number(context)?;
// 4. If month is not present, let m be MonthFromTime(t); otherwise, let m be ? ToNumber(month).
let m = if let Some(m) = args.get(1) {
Some(m.to_number(context)?)
} else {
None
};
let m = args.get(1).map(|v| v.to_number(context)).transpose()?;
// 5. If date is not present, let dt be DateFromTime(t); otherwise, let dt be ? ToNumber(date).
let dt = if let Some(dt) = args.get(2) {
Some(dt.to_number(context)?)
} else {
None
};
let dt = args.get(2).map(|v| v.to_number(context)).transpose()?;
// 6. Let newDate be MakeDate(MakeDay(y, m, dt), TimeWithinDay(t)).
t.set_components(false, Some(y), m, dt, None, None, None, None);
@ -965,25 +949,13 @@ impl Date {
.to_number(context)?;
// 3. If min is not present, let m be MinFromTime(t); otherwise, let m be ? ToNumber(min).
let m = if let Some(m) = args.get(1) {
Some(m.to_number(context)?)
} else {
None
};
let m = args.get(1).map(|v| v.to_number(context)).transpose()?;
// 4. If sec is not present, let s be SecFromTime(t); otherwise, let s be ? ToNumber(sec).
let sec = if let Some(sec) = args.get(2) {
Some(sec.to_number(context)?)
} else {
None
};
let sec = args.get(2).map(|v| v.to_number(context)).transpose()?;
// 5. If ms is not present, let milli be msFromTime(t); otherwise, let milli be ? ToNumber(ms).
let milli = if let Some(milli) = args.get(3) {
Some(milli.to_number(context)?)
} else {
None
};
let milli = args.get(3).map(|v| v.to_number(context)).transpose()?;
// 6. Let date be MakeDate(Day(t), MakeTime(h, m, s, milli)).
t.set_components(false, None, None, None, Some(h), m, sec, milli);
@ -1062,18 +1034,10 @@ impl Date {
.to_number(context)?;
// 3. If sec is not present, let s be SecFromTime(t); otherwise, let s be ? ToNumber(sec).
let s = if let Some(s) = args.get(1) {
Some(s.to_number(context)?)
} else {
None
};
let s = args.get(1).map(|v| v.to_number(context)).transpose()?;
// 4. If ms is not present, let milli be msFromTime(t); otherwise, let milli be ? ToNumber(ms).
let milli = if let Some(milli) = args.get(2) {
Some(milli.to_number(context)?)
} else {
None
};
let milli = args.get(2).map(|v| v.to_number(context)).transpose()?;
// 5. Let date be MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)).
t.set_components(false, None, None, None, None, Some(m), s, milli);
@ -1110,11 +1074,7 @@ impl Date {
.to_number(context)?;
// 3. If date is not present, let dt be DateFromTime(t); otherwise, let dt be ? ToNumber(date).
let dt = if let Some(date) = args.get(1) {
Some(date.to_number(context)?)
} else {
None
};
let dt = args.get(1).map(|v| v.to_number(context)).transpose()?;
// 4. Let newDate be MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t)).
t.set_components(false, None, Some(m), dt, None, None, None, None);
@ -1155,11 +1115,7 @@ impl Date {
.to_number(context)?;
// 3. If ms is not present, let milli be msFromTime(t); otherwise, let milli be ? ToNumber(ms).
let milli = if let Some(milli) = args.get(1) {
Some(milli.to_number(context)?)
} else {
None
};
let milli = args.get(1).map(|v| v.to_number(context)).transpose()?;
// 4. Let date be MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)).
t.set_components(false, None, None, None, None, None, Some(s), milli);
@ -1341,18 +1297,10 @@ impl Date {
.to_number(context)?;
// 4. If month is not present, let m be MonthFromTime(t); otherwise, let m be ? ToNumber(month).
let m = if let Some(m) = args.get(1) {
Some(m.to_number(context)?)
} else {
None
};
let m = args.get(1).map(|v| v.to_number(context)).transpose()?;
// 5. If date is not present, let dt be DateFromTime(t); otherwise, let dt be ? ToNumber(date).
let dt = if let Some(dt) = args.get(2) {
Some(dt.to_number(context)?)
} else {
None
};
let dt = args.get(2).map(|v| v.to_number(context)).transpose()?;
// 6. Let newDate be MakeDate(MakeDay(y, m, dt), TimeWithinDay(t)).
t.set_components(true, Some(y), m, dt, None, None, None, None);
@ -1395,25 +1343,13 @@ impl Date {
.to_number(context)?;
// 3. If min is not present, let m be MinFromTime(t); otherwise, let m be ? ToNumber(min).
let m = if let Some(m) = args.get(1) {
Some(m.to_number(context)?)
} else {
None
};
let m = args.get(1).map(|v| v.to_number(context)).transpose()?;
// 4. If sec is not present, let s be SecFromTime(t); otherwise, let s be ? ToNumber(sec).
let sec = if let Some(s) = args.get(2) {
Some(s.to_number(context)?)
} else {
None
};
let sec = args.get(2).map(|v| v.to_number(context)).transpose()?;
// 5. If ms is not present, let milli be msFromTime(t); otherwise, let milli be ? ToNumber(ms).
let ms = if let Some(ms) = args.get(3) {
Some(ms.to_number(context)?)
} else {
None
};
let ms = args.get(3).map(|v| v.to_number(context)).transpose()?;
// 6. Let newDate be MakeDate(Day(t), MakeTime(h, m, s, milli)).
t.set_components(true, None, None, None, Some(h), m, sec, ms);
@ -1493,21 +1429,13 @@ impl Date {
// 3. If sec is not present, let s be SecFromTime(t).
// 4. Else,
let s = if let Some(s) = args.get(1) {
// a. Let s be ? ToNumber(sec).
Some(s.to_number(context)?)
} else {
None
};
let s = args.get(1).map(|v| v.to_number(context)).transpose()?;
// 5. If ms is not present, let milli be msFromTime(t).
// 6. Else,
let milli = if let Some(ms) = args.get(2) {
// a. Let milli be ? ToNumber(ms).
Some(ms.to_number(context)?)
} else {
None
};
let milli = args.get(2).map(|v| v.to_number(context)).transpose()?;
// 7. Let date be MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)).
t.set_components(true, None, None, None, None, Some(m), s, milli);
@ -1549,12 +1477,8 @@ impl Date {
// 3. If date is not present, let dt be DateFromTime(t).
// 4. Else,
let dt = if let Some(dt) = args.get(1) {
// a. Let dt be ? ToNumber(date).
Some(dt.to_number(context)?)
} else {
None
};
let dt = args.get(1).map(|v| v.to_number(context)).transpose()?;
// 5. Let newDate be MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t)).
t.set_components(true, None, Some(m), dt, None, None, None, None);
@ -1596,12 +1520,8 @@ impl Date {
// 3. If ms is not present, let milli be msFromTime(t).
// 4. Else,
let milli = if let Some(milli) = args.get(1) {
// a. Let milli be ? ToNumber(ms).
Some(milli.to_number(context)?)
} else {
None
};
let milli = args.get(1).map(|v| v.to_number(context)).transpose()?;
// 5. Let date be MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)).
t.set_components(true, None, None, None, None, None, Some(s), milli);
@ -1841,9 +1761,7 @@ impl Date {
// This method is implementation-defined and discouraged, so we just require the same format as the string
// constructor.
let date = if let Some(arg) = args.get(0) {
arg
} else {
let Some(date) = args.get(0) else {
return Ok(JsValue::nan());
};

10
boa_engine/src/builtins/error/mod.rs

@ -170,14 +170,10 @@ impl Error {
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
let o = if let Some(o) = this.as_object() {
o
// 2. If Type(O) is not Object, throw a TypeError exception.
} else {
return Err(JsNativeError::typ()
.with_message("'this' is not an Object")
.into());
};
let o = this
.as_object()
.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)?;

4
boa_engine/src/builtins/eval/mod.rs

@ -77,9 +77,7 @@ impl Eval {
debug_assert!(direct || !strict);
// 2. If Type(x) is not String, return x.
let x = if let Some(x) = x.as_string() {
x.clone()
} else {
let Some(x) = x.as_string() else {
return Ok(x.clone());
};

12
boa_engine/src/builtins/function/mod.rs

@ -841,15 +841,9 @@ impl BuiltInFunctionObject {
}
};
let name = if let Some(name) = name {
if name.is_empty() {
"anonymous".into()
} else {
name
}
} else {
"anonymous".into()
};
let name = name
.filter(|n| !n.is_empty())
.unwrap_or_else(|| "anonymous".into());
match function {
Function::Native { .. } | Function::Closure { .. } | Function::Ordinary { .. } => {

8
boa_engine/src/builtins/iterable/async_from_sync_iterator.rs

@ -233,10 +233,8 @@ impl AsyncFromSyncIterator {
// 10. IfAbruptRejectPromise(result, promiseCapability).
if_abrupt_reject_promise!(result, promise_capability, context);
let Some(result) = result.as_object() else {
// 11. If Type(result) is not Object, then
let result = if let Some(result) = result.as_object() {
result
} else {
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
promise_capability
.reject()
@ -333,10 +331,8 @@ impl AsyncFromSyncIterator {
// 10. IfAbruptRejectPromise(result, promiseCapability).
if_abrupt_reject_promise!(result, promise_capability, context);
let Some(result) = result.as_object() else {
// 11. If Type(result) is not Object, then
let result = if let Some(result) = result.as_object() {
result
} else {
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
promise_capability
.reject()

23
boa_engine/src/builtins/iterable/mod.rs

@ -353,13 +353,9 @@ impl IteratorRecord {
// Note: We check if iteratorRecord.[[NextMethod]] is callable here.
// This check would happen in `Call` according to the spec, but we do not implement call for `JsValue`.
let next_method = if let Some(next_method) = self.next_method.as_callable() {
next_method
} else {
return Err(JsNativeError::typ()
.with_message("iterable next method not a function")
.into());
};
let next_method = self.next_method.as_callable().ok_or_else(|| {
JsNativeError::typ().with_message("iterable next method not a function")
})?;
let result = if let Some(value) = value {
// 2. Else,
@ -373,13 +369,14 @@ impl IteratorRecord {
// 3. If Type(result) is not Object, throw a TypeError exception.
// 4. Return result.
if let Some(o) = result.as_object() {
Ok(IteratorResult { object: o.clone() })
} else {
Err(JsNativeError::typ()
result
.as_object()
.map(|o| IteratorResult { object: o.clone() })
.ok_or_else(|| {
JsNativeError::typ()
.with_message("next value should be an object")
.into())
}
.into()
})
}
/// `IteratorStep ( iteratorRecord )`

10
boa_engine/src/builtins/map/mod.rs

@ -540,16 +540,14 @@ pub(crate) fn add_entries_from_iterable(
// b. If next is false, return target.
// c. Let nextItem be ? IteratorValue(next).
let next_item = if let Some(next) = next {
next.value(context)?
} else {
let Some(next_item) = next else {
return Ok(target.clone().into());
};
let next_item = if let Some(obj) = next_item.as_object() {
obj
let next_item = next_item.value(context)?;
let Some(next_item) = next_item.as_object() else {
// d. If Type(nextItem) is not Object, then
} else {
// i. Let error be ThrowCompletion(a newly created TypeError object).
let err = Err(JsNativeError::typ()
.with_message("cannot get key and value from primitive item of `iterable`")

4
boa_engine/src/builtins/object/mod.rs

@ -635,9 +635,7 @@ impl Object {
}
};
let obj = if let Some(obj) = o.as_object() {
obj
} else {
let Some(obj) = o.as_object() else {
// 3. If Type(O) is not Object, return O.
return Ok(o);
};

12
boa_engine/src/builtins/promise/mod.rs

@ -1268,10 +1268,7 @@ impl Promise {
return Ok(JsValue::Undefined);
}
let then = if let Some(resolution) = resolution.as_object() {
// 9. Let then be Completion(Get(resolution, "then")).
resolution.get("then", context)
} else {
let Some(then) = resolution.as_object() else {
// 8. If Type(resolution) is not Object, then
// a. Perform FulfillPromise(promise, resolution).
promise
@ -1284,7 +1281,8 @@ impl Promise {
return Ok(JsValue::Undefined);
};
let then_action = match then {
// 9. Let then be Completion(Get(resolution, "then")).
let then_action = match then.get("then", context) {
// 10. If then is an abrupt completion, then
Err(e) => {
// a. Perform RejectPromise(promise, then.[[Value]]).
@ -1741,9 +1739,7 @@ impl Promise {
let promise = this;
// 2. If Type(promise) is not Object, throw a TypeError exception.
let promise_obj = if let Some(p) = promise.as_object() {
p
} else {
let Some(promise_obj) = promise.as_object() else {
return Err(JsNativeError::typ()
.with_message("finally called with a non-object promise")
.into());

28
boa_engine/src/builtins/reflect/mod.rs

@ -113,13 +113,9 @@ impl Reflect {
let new_target = if let Some(new_target) = args.get(2) {
// 3. Else if IsConstructor(newTarget) is false, throw a TypeError exception.
if let Some(new_target) = new_target.as_constructor() {
new_target
} else {
return Err(JsNativeError::typ()
.with_message("newTarget must be a constructor")
.into());
}
new_target.as_constructor().ok_or_else(|| {
JsNativeError::typ().with_message("newTarget must be a constructor")
})?
} else {
// 2. If newTarget is not present, set newTarget to target.
target
@ -206,12 +202,11 @@ impl Reflect {
// 2. Let key be ? ToPropertyKey(propertyKey).
let key = args.get_or_undefined(1).to_property_key(context)?;
// 3. If receiver is not present, then
let receiver = if let Some(receiver) = args.get(2).cloned() {
receiver
} else {
// 3.a. Set receiver to target.
target.clone().into()
};
let receiver = args
.get(2)
.cloned()
.unwrap_or_else(|| target.clone().into());
// 4. Return ? target.[[Get]](key, receiver).
target.__get__(&key, receiver, context)
}
@ -369,11 +364,10 @@ impl Reflect {
.ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?;
let key = args.get_or_undefined(1).to_property_key(context)?;
let value = args.get_or_undefined(2);
let receiver = if let Some(receiver) = args.get(3).cloned() {
receiver
} else {
target.clone().into()
};
let receiver = args
.get(3)
.cloned()
.unwrap_or_else(|| target.clone().into());
Ok(target
.__set__(key, value.clone(), receiver, context)?
.into())

37
boa_engine/src/builtins/regexp/mod.rs

@ -1045,13 +1045,10 @@ impl RegExp {
) -> JsResult<JsValue> {
// 1. Let rx be the this value.
// 2. If Type(rx) is not Object, throw a TypeError exception.
let rx = if let Some(rx) = this.as_object() {
rx
} else {
return Err(JsNativeError::typ()
let rx = this.as_object().ok_or_else(|| {
JsNativeError::typ()
.with_message("RegExp.prototype.match method called on incompatible value")
.into());
};
})?;
// 3. Let S be ? ToString(string).
let arg_str = args.get_or_undefined(0).to_string(context)?;
@ -1235,15 +1232,11 @@ impl RegExp {
) -> JsResult<JsValue> {
// 1. Let rx be the this value.
// 2. If Type(rx) is not Object, throw a TypeError exception.
let rx = if let Some(rx) = this.as_object() {
rx
} else {
return Err(JsNativeError::typ()
.with_message(
let rx = this.as_object().ok_or_else(|| {
JsNativeError::typ().with_message(
"RegExp.prototype[Symbol.replace] method called on incompatible value",
)
.into());
};
})?;
// 3. Let S be ? ToString(string).
let arg_str = args.get_or_undefined(0).to_string(context)?;
@ -1457,13 +1450,10 @@ impl RegExp {
) -> JsResult<JsValue> {
// 1. Let rx be the this value.
// 2. If Type(rx) is not Object, throw a TypeError exception.
let rx = if let Some(rx) = this.as_object() {
rx
} else {
return Err(JsNativeError::typ()
let rx = this.as_object().ok_or_else(|| {
JsNativeError::typ()
.with_message("RegExp.prototype[Symbol.search] method called on incompatible value")
.into());
};
})?;
// 3. Let S be ? ToString(string).
let arg_str = args.get_or_undefined(0).to_string(context)?;
@ -1515,13 +1505,10 @@ impl RegExp {
) -> JsResult<JsValue> {
// 1. Let rx be the this value.
// 2. If Type(rx) is not Object, throw a TypeError exception.
let rx = if let Some(rx) = this.as_object() {
rx
} else {
return Err(JsNativeError::typ()
let rx = this.as_object().ok_or_else(|| {
JsNativeError::typ()
.with_message("RegExp.prototype.split method called on incompatible value")
.into());
};
})?;
// 3. Let S be ? ToString(string).
let arg_str = args.get_or_undefined(0).to_string(context)?;

23
boa_engine/src/builtins/set/mod.rs

@ -281,21 +281,16 @@ impl Set {
pub(crate) fn delete(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
let value = args.get_or_undefined(0);
let res = if let Some(object) = this.as_object() {
if let Some(set) = object.borrow_mut().as_set_mut() {
set.delete(value)
} else {
return Err(JsNativeError::typ()
.with_message("'this' is not a Set")
.into());
}
} else {
return Err(JsNativeError::typ()
.with_message("'this' is not a Set")
.into());
};
let mut object = this
.as_object()
.map(JsObject::borrow_mut)
.ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Set"))?;
let set = object
.as_set_mut()
.ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Set"))?;
Ok(res.into())
Ok(set.delete(value).into())
}
/// `Set.prototype.entries( )`

4
boa_engine/src/builtins/string/mod.rs

@ -952,9 +952,7 @@ impl String {
// 8. Let position be ! StringIndexOf(string, searchString, 0).
// 9. If position is -1, return string.
let position = if let Some(p) = this_str.index_of(&search_str, 0) {
p
} else {
let Some(position) = this_str.index_of(&search_str, 0) else {
return Ok(this_str.into());
};

4
boa_engine/src/builtins/uri/mod.rs

@ -253,9 +253,7 @@ where
let cp = string.code_point_at(k);
// ii. If cp.[[IsUnpairedSurrogate]] is true, throw a URIError exception.
let ch = if let CodePoint::Unicode(ch) = cp {
ch
} else {
let CodePoint::Unicode(ch) = cp else {
return Err(JsNativeError::uri()
.with_message("trying to encode an invalid string")
.into());

15
boa_engine/src/class.rs

@ -113,10 +113,8 @@ impl<T: Class> ClassConstructor for T {
.into());
}
let class_constructor = context.global_object().clone().get(T::NAME, context)?;
let class_constructor = if let JsValue::Object(ref obj) = class_constructor {
obj
} else {
let class = context.global_object().clone().get(T::NAME, context)?;
let JsValue::Object(ref class_constructor) = class else {
return Err(JsNativeError::typ()
.with_message(format!(
"invalid constructor for native class `{}` ",
@ -124,10 +122,8 @@ impl<T: Class> ClassConstructor for T {
))
.into());
};
let class_prototype =
if let JsValue::Object(ref obj) = class_constructor.get(PROTOTYPE, context)? {
obj.clone()
} else {
let JsValue::Object(ref class_prototype) = class_constructor.get(PROTOTYPE, context)? else {
return Err(JsNativeError::typ()
.with_message(format!(
"invalid default prototype for native class `{}`",
@ -138,14 +134,13 @@ impl<T: Class> ClassConstructor for T {
let prototype = this
.as_object()
.cloned()
.map(|obj| {
obj.get(PROTOTYPE, context)
.map(|val| val.as_object().cloned())
})
.transpose()?
.flatten()
.unwrap_or(class_prototype);
.unwrap_or_else(|| class_prototype.clone());
let native_instance = Self::constructor(this, args, context)?;
let object_instance = JsObject::from_proto_and_data(

1
boa_engine/src/lib.rs

@ -72,7 +72,6 @@
// Ignore because `write!(string, ...)` instead of `string.push_str(&format!(...))` can fail.
// We only use it in `ToInternedString` where performance is not an issue.
clippy::format_push_string,
rustdoc::missing_doc_code_examples
)]
extern crate static_assertions as sa;

4
boa_engine/src/object/internal_methods/arguments.rs

@ -30,9 +30,7 @@ pub(crate) fn arguments_exotic_get_own_property(
) -> JsResult<Option<PropertyDescriptor>> {
// 1. Let desc be OrdinaryGetOwnProperty(args, P).
// 2. If desc is undefined, return desc.
let desc = if let Some(desc) = super::ordinary_get_own_property(obj, key, context)? {
desc
} else {
let Some(desc) = super::ordinary_get_own_property(obj, key, context)? else {
return Ok(None);
};

5
boa_engine/src/object/internal_methods/global.rs

@ -501,11 +501,8 @@ pub(crate) fn validate_and_apply_property_descriptor(
);
// 1. Assert: If O is not undefined, then IsPropertyKey(P) is true.
let mut current = if let Some(own) = current {
own
}
let Some(mut current) = current else {
// 2. If current is undefined, then
else {
// a. If extensible is false, return false.
if !extensible {
return false;

5
boa_engine/src/object/internal_methods/mod.rs

@ -794,11 +794,8 @@ pub(crate) fn validate_and_apply_property_descriptor(
Profiler::global().start_event("Object::validate_and_apply_property_descriptor", "object");
// 1. Assert: If O is not undefined, then IsPropertyKey(P) is true.
let mut current = if let Some(own) = current {
own
}
let Some(mut current) = current else {
// 2. If current is undefined, then
else {
// a. If extensible is false, return false.
if !extensible {
return false;

52
boa_engine/src/object/internal_methods/proxy.rs

@ -65,10 +65,8 @@ pub(crate) fn proxy_exotic_get_prototype_of(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "getPrototypeOf").
let trap = if let Some(trap) = handler.get_method("getPrototypeOf", context)? {
trap
let Some(trap) = handler.get_method("getPrototypeOf", context)? else {
// 6. If trap is undefined, then
} else {
// a. Return ? target.[[GetPrototypeOf]]().
return target.__get_prototype_of__(context);
};
@ -130,10 +128,8 @@ pub(crate) fn proxy_exotic_set_prototype_of(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "setPrototypeOf").
let trap = if let Some(trap) = handler.get_method("setPrototypeOf", context)? {
trap
let Some(trap) = handler.get_method("setPrototypeOf", context)? else {
// 6. If trap is undefined, then
} else {
// a. Return ? target.[[SetPrototypeOf]](V).
return target.__set_prototype_of__(val, context);
};
@ -193,10 +189,8 @@ pub(crate) fn proxy_exotic_is_extensible(obj: &JsObject, context: &mut Context)
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "isExtensible").
let trap = if let Some(trap) = handler.get_method("isExtensible", context)? {
trap
let Some(trap) = handler.get_method("isExtensible", context)? else {
// 6. If trap is undefined, then
} else {
// a. Return ? IsExtensible(target).
return target.is_extensible(context);
};
@ -242,10 +236,8 @@ pub(crate) fn proxy_exotic_prevent_extensions(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "preventExtensions").
let trap = if let Some(trap) = handler.get_method("preventExtensions", context)? {
trap
let Some(trap) = handler.get_method("preventExtensions", context)? else {
// 6. If trap is undefined, then
} else {
// a. Return ? target.[[PreventExtensions]]().
return target.__prevent_extensions__(context);
};
@ -291,10 +283,8 @@ pub(crate) fn proxy_exotic_get_own_property(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "getOwnPropertyDescriptor").
let trap = if let Some(trap) = handler.get_method("getOwnPropertyDescriptor", context)? {
trap
let Some(trap) = handler.get_method("getOwnPropertyDescriptor", context)? else {
// 6. If trap is undefined, then
} else {
// a. Return ? target.[[GetOwnProperty]](P).
return target.__get_own_property__(key, context);
};
@ -418,10 +408,8 @@ pub(crate) fn proxy_exotic_define_own_property(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "defineProperty").
let trap = if let Some(trap) = handler.get_method("defineProperty", context)? {
trap
let Some(trap) = handler.get_method("defineProperty", context)? else {
// 6. If trap is undefined, then
} else {
// a. Return ? target.[[DefineOwnProperty]](P, Desc).
return target.__define_own_property__(key, desc, context);
};
@ -532,10 +520,8 @@ pub(crate) fn proxy_exotic_has_property(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "has").
let trap = if let Some(trap) = handler.get_method("has", context)? {
trap
let Some(trap) = handler.get_method("has", context)? else {
// 6. If trap is undefined, then
} else {
// a. Return ? target.[[HasProperty]](P).
return target.has_property(key.clone(), context);
};
@ -601,10 +587,8 @@ pub(crate) fn proxy_exotic_get(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "get").
let trap = if let Some(trap) = handler.get_method("get", context)? {
trap
let Some(trap) = handler.get_method("get", context)? else {
// 6. If trap is undefined, then
} else {
// a. Return ? target.[[Get]](P, Receiver).
return target.__get__(key, receiver, context);
};
@ -673,10 +657,8 @@ pub(crate) fn proxy_exotic_set(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "set").
let trap = if let Some(trap) = handler.get_method("set", context)? {
trap
let Some(trap) = handler.get_method("set", context)? else {
// 6. If trap is undefined, then
} else {
// a. Return ? target.[[Set]](P, V, Receiver).
return target.__set__(key, value, receiver, context);
};
@ -757,10 +739,8 @@ pub(crate) fn proxy_exotic_delete(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "deleteProperty").
let trap = if let Some(trap) = handler.get_method("deleteProperty", context)? {
trap
let Some(trap) = handler.get_method("deleteProperty", context)? else {
// 6. If trap is undefined, then
} else {
// a. Return ? target.[[Delete]](P).
return target.__delete__(key, context);
};
@ -826,10 +806,8 @@ pub(crate) fn proxy_exotic_own_property_keys(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "ownKeys").
let trap = if let Some(trap) = handler.get_method("ownKeys", context)? {
trap
let Some(trap) = handler.get_method("ownKeys", context)? else {
// 6. If trap is undefined, then
} else {
// a. Return ? target.[[OwnPropertyKeys]]().
return target.__own_property_keys__(context);
};
@ -964,10 +942,8 @@ fn proxy_exotic_call(
.try_data()?;
// 5. Let trap be ? GetMethod(handler, "apply").
let trap = if let Some(trap) = handler.get_method("apply", context)? {
trap
let Some(trap) = handler.get_method("apply", context)? else {
// 6. If trap is undefined, then
} else {
// a. Return ? Call(target, thisArgument, argumentsList).
return target.call(this, args, context);
};
@ -1009,10 +985,8 @@ fn proxy_exotic_construct(
assert!(target.is_constructor());
// 6. Let trap be ? GetMethod(handler, "construct").
let trap = if let Some(trap) = handler.get_method("construct", context)? {
trap
let Some(trap) = handler.get_method("construct", context)? else {
// 7. If trap is undefined, then
} else {
// a. Return ? Construct(target, argumentsList, newTarget).
return target.construct(args, Some(new_target), context);
};

27
boa_engine/src/object/operations.rs

@ -502,13 +502,9 @@ impl JsObject {
}
// 4. If Type(C) is not Object, throw a TypeError exception.
let c = if let Some(c) = c.as_object() {
c
} else {
return Err(JsNativeError::typ()
.with_message("property 'constructor' is not an object")
.into());
};
let c = c.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("property 'constructor' is not an object")
})?;
// 5. Let S be ? Get(C, @@species).
let s = c.get(WellKnownSymbols::species(), context)?;
@ -801,9 +797,7 @@ impl JsValue {
context: &mut Context,
) -> JsResult<bool> {
// 1. If IsCallable(C) is false, return false.
let function = if let Some(function) = function.as_callable() {
function
} else {
let Some(function) = function.as_callable() else {
return Ok(false);
};
@ -818,9 +812,7 @@ impl JsValue {
);
}
let mut object = if let Some(obj) = object.as_object() {
obj.clone()
} else {
let Some(mut object) = object.as_object().cloned() else {
// 3. If Type(O) is not Object, return false.
return Ok(false);
};
@ -828,14 +820,11 @@ impl JsValue {
// 4. Let P be ? Get(C, "prototype").
let prototype = function.get("prototype", context)?;
let prototype = if let Some(obj) = prototype.as_object() {
obj
} else {
// 5. If Type(P) is not Object, throw a TypeError exception.
return Err(JsNativeError::typ()
let prototype = prototype.as_object().ok_or_else(|| {
JsNativeError::typ()
.with_message("function has non-object prototype in instanceof check")
.into());
};
})?;
// 6. Repeat,
loop {

4
boa_engine/src/string/mod.rs

@ -452,9 +452,7 @@ impl JsString {
pub(crate) fn to_number(&self) -> f64 {
// 1. Let text be ! StringToCodePoints(str).
// 2. Let literal be ParseText(text, StringNumericLiteral).
let string = if let Ok(string) = self.to_std_string() {
string
} else {
let Ok(string) = self.to_std_string() else {
// 3. If literal is a List of errors, return NaN.
return f64::NAN;
};

6
boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs

@ -216,11 +216,7 @@ where
let (class_element_name, method) =
AsyncMethod::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
let property_name = if let property::ClassElementName::PropertyName(property_name) =
class_element_name
{
property_name
} else {
let property::ClassElementName::PropertyName(property_name) = class_element_name else {
return Err(ParseError::general(
"private identifiers not allowed in object literal",
position,

12
boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs

@ -143,14 +143,13 @@ fn parse_callable_declaration<R: Read, C: CallableDeclaration>(
cursor: &mut Cursor<R>,
interner: &mut Interner,
) -> Result<(Identifier, FormalParameterList, StatementList), ParseError> {
let next_token = cursor.peek(0, interner)?;
let name = if let Some(token) = next_token {
match token.kind() {
let next_token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
let name = match next_token.kind() {
TokenKind::Punctuator(Punctuator::OpenParen) => {
if !c.is_default() {
return Err(ParseError::unexpected(
token.to_string(interner),
token.span(),
next_token.to_string(interner),
next_token.span(),
c.error_context(),
));
}
@ -158,9 +157,6 @@ fn parse_callable_declaration<R: Read, C: CallableDeclaration>(
}
_ => BindingIdentifier::new(c.name_allow_yield(), c.name_allow_await())
.parse(cursor, interner)?,
}
} else {
return Err(ParseError::AbruptEnd);
};
// Early Error: If BindingIdentifier is present and the source code matching BindingIdentifier is strict mode code,

44
boa_engine/src/syntax/parser/statement/declaration/lexical.rs

@ -261,22 +261,17 @@ where
let bindings = ObjectBindingPattern::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
let init = if let Some(t) = cursor.peek(0, interner)? {
if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) {
let init = if cursor
.peek(0, interner)?
.filter(|t| *t.kind() == TokenKind::Punctuator(Punctuator::Assign))
.is_some()
{
Some(
Initializer::new(
None,
self.allow_in,
self.allow_yield,
self.allow_await,
)
Initializer::new(None, self.allow_in, self.allow_yield, self.allow_await)
.parse(cursor, interner)?,
)
} else {
None
}
} else {
None
};
let declaration = Pattern::Object(bindings.into());
@ -294,22 +289,17 @@ where
let bindings = ArrayBindingPattern::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
let init = if let Some(t) = cursor.peek(0, interner)? {
if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) {
let init = if cursor
.peek(0, interner)?
.filter(|t| *t.kind() == TokenKind::Punctuator(Punctuator::Assign))
.is_some()
{
Some(
Initializer::new(
None,
self.allow_in,
self.allow_yield,
self.allow_await,
)
Initializer::new(None, self.allow_in, self.allow_yield, self.allow_await)
.parse(cursor, interner)?,
)
} else {
None
}
} else {
None
};
let declaration = Pattern::Array(bindings.into());
@ -334,8 +324,11 @@ where
)));
}
let init = if let Some(t) = cursor.peek(0, interner)? {
if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) {
let init = if cursor
.peek(0, interner)?
.filter(|t| *t.kind() == TokenKind::Punctuator(Punctuator::Assign))
.is_some()
{
Some(
Initializer::new(
Some(ident),
@ -347,9 +340,6 @@ where
)
} else {
None
}
} else {
None
};
Ok(Variable::from_identifier(ident, init))
}

44
boa_engine/src/syntax/parser/statement/variable/mod.rs

@ -170,22 +170,17 @@ where
let bindings = ObjectBindingPattern::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
let init = if let Some(t) = cursor.peek(0, interner)? {
if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) {
let init = if cursor
.peek(0, interner)?
.filter(|t| *t.kind() == TokenKind::Punctuator(Punctuator::Assign))
.is_some()
{
Some(
Initializer::new(
None,
self.allow_in,
self.allow_yield,
self.allow_await,
)
Initializer::new(None, self.allow_in, self.allow_yield, self.allow_await)
.parse(cursor, interner)?,
)
} else {
None
}
} else {
None
};
Ok(Variable::from_pattern(bindings.into(), init))
@ -194,22 +189,17 @@ where
let bindings = ArrayBindingPattern::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
let init = if let Some(t) = cursor.peek(0, interner)? {
if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) {
let init = if cursor
.peek(0, interner)?
.filter(|t| *t.kind() == TokenKind::Punctuator(Punctuator::Assign))
.is_some()
{
Some(
Initializer::new(
None,
self.allow_in,
self.allow_yield,
self.allow_await,
)
Initializer::new(None, self.allow_in, self.allow_yield, self.allow_await)
.parse(cursor, interner)?,
)
} else {
None
}
} else {
None
};
Ok(Variable::from_pattern(bindings.into(), init))
@ -218,17 +208,17 @@ where
let ident = BindingIdentifier::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
let init = if let Some(t) = cursor.peek(0, interner)? {
if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) {
let init = if cursor
.peek(0, interner)?
.filter(|t| *t.kind() == TokenKind::Punctuator(Punctuator::Assign))
.is_some()
{
Some(
Initializer::new(Some(ident), true, self.allow_yield, self.allow_await)
.parse(cursor, interner)?,
)
} else {
None
}
} else {
None
};
Ok(Variable::from_identifier(ident, init))
}

22
boa_engine/src/vm/opcode/iteration/for_await.rs

@ -23,13 +23,9 @@ impl Operation for ForAwaitOfLoopIterate {
.as_boolean()
.expect("iterator [[Done]] was not a boolean");
let next_method = context.vm.pop();
let next_method_object = if let Some(object) = next_method.as_callable() {
object
} else {
return Err(JsNativeError::typ()
.with_message("iterable next method not a function")
.into());
};
let next_method_object = next_method.as_callable().ok_or_else(|| {
JsNativeError::typ().with_message("iterable next method not a function")
})?;
let iterator = context.vm.pop();
let next_result = next_method_object.call(&iterator, &[], context)?;
context.vm.push(iterator);
@ -54,13 +50,11 @@ impl Operation for ForAwaitOfLoopNext {
let address = context.vm.read::<u32>();
let next_result = context.vm.pop();
let next_result = if let Some(next_result) = next_result.as_object() {
IteratorResult::new(next_result.clone())
} else {
return Err(JsNativeError::typ()
.with_message("next value should be an object")
.into());
};
let next_result = next_result
.as_object()
.cloned()
.map(IteratorResult::new)
.ok_or_else(|| JsNativeError::typ().with_message("next value should be an object"))?;
if next_result.complete(context)? {
context.vm.frame_mut().pc = address as usize;

1
boa_interner/src/lib.rs

@ -69,7 +69,6 @@
clippy::let_unit_value,
// TODO deny once false positive is fixed (https://github.com/rust-lang/rust-clippy/issues/9626).
clippy::trait_duplication_in_bounds,
rustdoc::missing_doc_code_examples,
)]
extern crate static_assertions as sa;

1
boa_tester/src/main.rs

@ -60,7 +60,6 @@
clippy::missing_errors_doc,
clippy::as_conversions,
clippy::let_unit_value,
rustdoc::missing_doc_code_examples
)]
mod exec;

3
boa_unicode/src/lib.rs

@ -63,8 +63,7 @@
clippy::must_use_candidate,
clippy::missing_errors_doc,
clippy::as_conversions,
clippy::let_unit_value,
rustdoc::missing_doc_code_examples
clippy::let_unit_value
)]
mod tables;

3
boa_wasm/src/lib.rs

@ -55,8 +55,7 @@
clippy::must_use_candidate,
clippy::missing_errors_doc,
clippy::as_conversions,
clippy::let_unit_value,
rustdoc::missing_doc_code_examples
clippy::let_unit_value
)]
use boa_engine::Context;

Loading…
Cancel
Save