use std::collections::BTreeMap;
use boa_cat::PromiseState;
use boa_cat::Value;
use boa_cat::fuel::Fuel;
use boa_cat::heap::Heap;
use boa_cat::outcome::{EvalResult, Outcome};
use boa_cat::value::Object;
#[must_use]
pub fn build(heap: Heap) -> (Value, Heap) {
let mut props = BTreeMap::new();
let _ = props.insert("resolve".to_owned(), Value::Native(resolve_impl));
let _ = props.insert("reject".to_owned(), Value::Native(reject_impl));
let _ = props.insert("all".to_owned(), Value::Native(all_impl));
let _ = props.insert("race".to_owned(), Value::Native(race_impl));
let _ = props.insert("allSettled".to_owned(), Value::Native(all_settled_impl));
let _ = props.insert("any".to_owned(), Value::Native(any_impl));
let (id, heap) = heap.alloc_object(Object::from_properties(props));
(Value::Object(id), heap)
}
#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
fn resolve_impl(args: Vec<Value>, _this: Value, heap: Heap, fuel: Fuel) -> EvalResult {
let value = args.first().cloned().unwrap_or(Value::Undefined);
match value {
Value::Promise(_) => Ok((Outcome::Normal(value), heap, fuel)),
Value::Undefined
| Value::Null
| Value::Boolean(_)
| Value::Number(_)
| Value::String(_)
| Value::Object(_)
| Value::Function(_)
| Value::Native(_) => {
let (id, heap) = heap.alloc_promise(PromiseState::Resolved(value));
Ok((Outcome::Normal(Value::Promise(id)), heap, fuel))
}
}
}
#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
fn reject_impl(args: Vec<Value>, _this: Value, heap: Heap, fuel: Fuel) -> EvalResult {
let value = args.first().cloned().unwrap_or(Value::Undefined);
let (id, heap) = heap.alloc_promise(PromiseState::Rejected(value));
Ok((Outcome::Normal(Value::Promise(id)), heap, fuel))
}
#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
fn all_impl(args: Vec<Value>, _this: Value, heap: Heap, fuel: Fuel) -> EvalResult {
let array = args.first().cloned().unwrap_or(Value::Undefined);
let entries = array_entries(&array, &heap);
match settle_all(&entries, &heap) {
SettleAll::AllResolved(values) => {
let (array_value, heap) = build_array_object(values, heap);
let (id, heap) = heap.alloc_promise(PromiseState::Resolved(array_value));
Ok((Outcome::Normal(Value::Promise(id)), heap, fuel))
}
SettleAll::FirstRejected(value) => {
let (id, heap) = heap.alloc_promise(PromiseState::Rejected(value));
Ok((Outcome::Normal(Value::Promise(id)), heap, fuel))
}
SettleAll::SomePending => Ok((
Outcome::Throw(type_error(
"Promise.all over Pending inputs is unsupported in this engine's synchronous model",
)),
heap,
fuel,
)),
}
}
#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
fn race_impl(args: Vec<Value>, _this: Value, heap: Heap, fuel: Fuel) -> EvalResult {
let array = args.first().cloned().unwrap_or(Value::Undefined);
let entries = array_entries(&array, &heap);
let first_settled = entries
.into_iter()
.find_map(|value| classify_entry(&value, &heap));
match first_settled {
Some(Settled::Resolved(v)) => {
let (id, heap) = heap.alloc_promise(PromiseState::Resolved(v));
Ok((Outcome::Normal(Value::Promise(id)), heap, fuel))
}
Some(Settled::Rejected(v)) => {
let (id, heap) = heap.alloc_promise(PromiseState::Rejected(v));
Ok((Outcome::Normal(Value::Promise(id)), heap, fuel))
}
None => Ok((
Outcome::Throw(type_error(
"Promise.race over Pending-only inputs is unsupported in this engine's synchronous model",
)),
heap,
fuel,
)),
}
}
#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
fn all_settled_impl(args: Vec<Value>, _this: Value, heap: Heap, fuel: Fuel) -> EvalResult {
let array = args.first().cloned().unwrap_or(Value::Undefined);
let entries = array_entries(&array, &heap);
let any_pending = entries
.iter()
.any(|value| classify_entry(value, &heap).is_none());
if any_pending {
Ok((
Outcome::Throw(type_error(
"Promise.allSettled over Pending inputs is unsupported in this engine's synchronous model",
)),
heap,
fuel,
))
} else {
let classified: Vec<Settled> = entries
.into_iter()
.filter_map(|v| classify_entry(&v, &heap))
.collect();
let (records, heap) =
classified
.into_iter()
.fold((Vec::new(), heap), |(acc, heap), settled| {
let (record, heap) = build_settled_record(settled, heap);
let extended: Vec<Value> =
acc.into_iter().chain(std::iter::once(record)).collect();
(extended, heap)
});
let (array_value, heap) = build_array_object(records, heap);
let (id, heap) = heap.alloc_promise(PromiseState::Resolved(array_value));
Ok((Outcome::Normal(Value::Promise(id)), heap, fuel))
}
}
#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
fn any_impl(args: Vec<Value>, _this: Value, heap: Heap, fuel: Fuel) -> EvalResult {
let array = args.first().cloned().unwrap_or(Value::Undefined);
let entries = array_entries(&array, &heap);
let any_pending = entries
.iter()
.any(|value| classify_entry(value, &heap).is_none());
if any_pending {
Ok((
Outcome::Throw(type_error(
"Promise.any over Pending inputs is unsupported in this engine's synchronous model",
)),
heap,
fuel,
))
} else {
let classified: Vec<Settled> = entries
.into_iter()
.filter_map(|v| classify_entry(&v, &heap))
.collect();
let first_fulfilled = classified.iter().find_map(|s| match s {
Settled::Resolved(v) => Some(v.clone()),
Settled::Rejected(_) => None,
});
if let Some(value) = first_fulfilled {
let (id, heap) = heap.alloc_promise(PromiseState::Resolved(value));
Ok((Outcome::Normal(Value::Promise(id)), heap, fuel))
} else {
aggregate_rejection(&classified, heap, fuel)
}
}
}
fn build_settled_record(settled: Settled, heap: Heap) -> (Value, Heap) {
let mut props = BTreeMap::new();
match settled {
Settled::Resolved(value) => {
let _ = props.insert("status".to_owned(), Value::String("fulfilled".to_owned()));
let _ = props.insert("value".to_owned(), value);
}
Settled::Rejected(value) => {
let _ = props.insert("status".to_owned(), Value::String("rejected".to_owned()));
let _ = props.insert("reason".to_owned(), value);
}
}
let (id, heap) = heap.alloc_object(Object::from_properties(props));
(Value::Object(id), heap)
}
#[allow(clippy::unnecessary_wraps)] fn aggregate_rejection(classified: &[Settled], heap: Heap, fuel: Fuel) -> EvalResult {
let reasons: Vec<Value> = classified
.iter()
.map(|s| match s {
Settled::Resolved(v) | Settled::Rejected(v) => v.clone(),
})
.collect();
let (errors_array, heap) = build_array_object(reasons, heap);
let mut props = BTreeMap::new();
let _ = props.insert(
"name".to_owned(),
Value::String("AggregateError".to_owned()),
);
let _ = props.insert(
"message".to_owned(),
Value::String("All promises were rejected".to_owned()),
);
let _ = props.insert("errors".to_owned(), errors_array);
let (error_id, heap) = heap.alloc_object(Object::from_properties(props));
let (promise_id, heap) = heap.alloc_promise(PromiseState::Rejected(Value::Object(error_id)));
Ok((Outcome::Normal(Value::Promise(promise_id)), heap, fuel))
}
enum SettleAll {
AllResolved(Vec<Value>),
FirstRejected(Value),
SomePending,
}
#[derive(Clone)]
enum Settled {
Resolved(Value),
Rejected(Value),
}
fn settle_all(entries: &[Value], heap: &Heap) -> SettleAll {
let folded = entries.iter().try_fold(Vec::<Value>::new(), |acc, value| {
match classify_entry(value, heap) {
Some(Settled::Resolved(v)) => Ok(acc.into_iter().chain(std::iter::once(v)).collect()),
Some(Settled::Rejected(v)) => Err(Some(v)),
None => Err(None),
}
});
match folded {
Ok(values) => SettleAll::AllResolved(values),
Err(Some(rejected)) => SettleAll::FirstRejected(rejected),
Err(None) => SettleAll::SomePending,
}
}
fn classify_entry(value: &Value, heap: &Heap) -> Option<Settled> {
match value {
Value::Promise(id) => match heap.promise(*id) {
Some(PromiseState::Resolved(v)) => Some(Settled::Resolved(v.clone())),
Some(PromiseState::Rejected(v)) => Some(Settled::Rejected(v.clone())),
Some(PromiseState::Pending(_)) | None => None,
},
Value::Undefined
| Value::Null
| Value::Boolean(_)
| Value::Number(_)
| Value::String(_)
| Value::Object(_)
| Value::Function(_)
| Value::Native(_) => Some(Settled::Resolved(value.clone())),
}
}
fn array_entries(value: &Value, heap: &Heap) -> Vec<Value> {
let id_opt = match value {
Value::Object(id) => Some(*id),
Value::Undefined
| Value::Null
| Value::Boolean(_)
| Value::Number(_)
| Value::String(_)
| Value::Function(_)
| Value::Native(_)
| Value::Promise(_) => None,
};
id_opt
.and_then(|id| heap.object(id))
.map(|object| {
let length = match object.get("length") {
Some(Value::Number(n)) if n.is_finite() && *n >= 0.0 => *n as u32,
Some(_) | None => 0,
};
(0..length)
.map(|i| {
object
.get(&format!("{i}"))
.cloned()
.unwrap_or(Value::Undefined)
})
.collect()
})
.unwrap_or_default()
}
fn build_array_object(values: Vec<Value>, heap: Heap) -> (Value, Heap) {
let length = u32::try_from(values.len()).unwrap_or(u32::MAX);
let map: BTreeMap<String, Value> = values
.into_iter()
.enumerate()
.map(|(i, v)| (format!("{i}"), v))
.chain(std::iter::once((
"length".to_owned(),
Value::Number(f64::from(length)),
)))
.collect();
let (id, heap) = heap.alloc_object(Object::from_properties(map));
(Value::Object(id), heap)
}
fn type_error(message: &str) -> Value {
Value::String(format!("TypeError: {message}"))
}