Skip to content

Commit

Permalink
Introduce experimental features
Browse files Browse the repository at this point in the history
  • Loading branch information
jedel1043 committed Sep 27, 2023
1 parent ae41479 commit bc0ea58
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 41 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: tarpaulin
args: --workspace --features intl --ignore-tests --engine llvm --out xml
args: --workspace --features annex-b,intl,experimental --ignore-tests --engine llvm --out xml
- name: Upload to codecov.io
uses: codecov/codecov-action@v3

Expand Down Expand Up @@ -62,9 +62,9 @@ jobs:
- name: Install latest nextest
uses: taiki-e/install-action@nextest
- name: Test with nextest
run: cargo nextest run --profile ci --cargo-profile ci --features intl
run: cargo nextest run --profile ci --cargo-profile ci --features annex-b,intl,experimental
- name: Test docs
run: cargo test --doc --profile ci --features intl
run: cargo test --doc --profile ci --all-features

msrv:
name: Minimum supported Rust version
Expand Down
2 changes: 1 addition & 1 deletion boa_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ phf = { workspace = true, features = ["macros"] }
pollster.workspace = true

[features]
default = ["boa_engine/annex-b", "boa_engine/intl"]
default = ["boa_engine/annex-b", "boa_engine/experimental", "boa_engine/intl"]

[target.x86_64-unknown-linux-gnu.dependencies]
jemallocator.workspace = true
Expand Down
3 changes: 3 additions & 0 deletions boa_engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ trace = []
# Enable Boa's additional ECMAScript features for web browsers.
annex-b = ["boa_parser/annex-b"]

# Enable experimental features, like Stage 3 proposals.
experimental = []

[dependencies]
boa_interner.workspace = true
boa_gc = { workspace = true, features = [ "thinvec" ] }
Expand Down
110 changes: 85 additions & 25 deletions boa_engine/src/builtins/promise/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,22 @@ pub enum OperationType {
/// the resolution value.
///
/// [`Promise()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Finalize)]
pub struct ResolvingFunctions {
/// The `resolveFunc` parameter of the executor passed to `Promise()`.
pub resolve: JsFunction,
/// The `rejectFunc` parameter of the executor passed to `Promise()`.
pub reject: JsFunction,
}

// Manually implementing `Trace` to allow destructuring.
unsafe impl Trace for ResolvingFunctions {
custom_trace!(this, {
mark(&this.resolve);
mark(&this.reject);
});
}

// ==================== Private API ====================

/// `IfAbruptRejectPromise ( value, capability )`
Expand Down Expand Up @@ -155,16 +163,21 @@ pub(crate) use if_abrupt_reject_promise;
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-promisecapability-records
#[derive(Debug, Clone, Trace, Finalize)]
#[derive(Debug, Clone, Finalize)]
pub(crate) struct PromiseCapability {
/// The `[[Promise]]` field.
promise: JsObject,

/// The `[[Resolve]]` field.
resolve: JsFunction,
/// The resolving functions,
functions: ResolvingFunctions,
}

/// The `[[Reject]]` field.
reject: JsFunction,
// SAFETY: manually implementing `Trace` to allow destructuring.
unsafe impl Trace for PromiseCapability {
custom_trace!(this, {
mark(&this.promise);
mark(&this.functions);
});
}

/// The internal `PromiseReaction` data type.
Expand Down Expand Up @@ -297,8 +310,7 @@ impl PromiseCapability {
// 10. Return promiseCapability.
Ok(Self {
promise,
resolve,
reject,
functions: ResolvingFunctions { resolve, reject },
})
}

Expand All @@ -309,12 +321,12 @@ impl PromiseCapability {

/// Returns the resolve function.
pub(crate) const fn resolve(&self) -> &JsFunction {
&self.resolve
&self.functions.resolve
}

/// Returns the reject function.
pub(crate) const fn reject(&self) -> &JsFunction {
&self.reject
&self.functions.reject
}
}

Expand All @@ -326,7 +338,7 @@ impl IntrinsicObject for Promise {
.name("get [Symbol.species]")
.build();

BuiltInBuilder::from_standard_constructor::<Self>(realm)
let builder = BuiltInBuilder::from_standard_constructor::<Self>(realm)
.static_method(Self::all, "all", 1)
.static_method(Self::all_settled, "allSettled", 1)
.static_method(Self::any, "any", 1)
Expand All @@ -347,8 +359,13 @@ impl IntrinsicObject for Promise {
JsSymbol::to_string_tag(),
Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.build();
);

#[cfg(feature = "experimental")]
let builder =
builder.static_method(Self::with_resolvers, crate::js_string!("withResolvers"), 0);

builder.build();
}

fn get(intrinsics: &Intrinsics) -> JsObject {
Expand Down Expand Up @@ -447,6 +464,42 @@ impl Promise {
&self.state
}

/// [`Promise.withResolvers ( )`][spec]
///
/// Creates a new promise that is pending, and returns that promise plus the resolve and reject
/// functions associated with it.
///
/// [spec]: https://tc39.es/proposal-promise-with-resolvers/#sec-promise.withResolvers
#[cfg(feature = "experimental")]
pub(crate) fn with_resolvers(
this: &JsValue,
_args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. Let C be the this value.
let c = this.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("Promise.all() called on a non-object")
})?;

// 2. Let promiseCapability be ? NewPromiseCapability(C).
let PromiseCapability {
promise,
functions: ResolvingFunctions { resolve, reject },
} = PromiseCapability::new(c, context)?;

// 3. Let obj be OrdinaryObjectCreate(%Object.prototype%).
// 4. Perform ! CreateDataPropertyOrThrow(obj, "promise", promiseCapability.[[Promise]]).
// 5. Perform ! CreateDataPropertyOrThrow(obj, "resolve", promiseCapability.[[Resolve]]).
// 6. Perform ! CreateDataPropertyOrThrow(obj, "reject", promiseCapability.[[Reject]]).
let obj = context.intrinsics().templates().with_resolvers().create(
ObjectData::ordinary(),
vec![promise.into(), resolve.into(), reject.into()],
);

// 7. Return obj.
Ok(obj.into())
}

/// `Promise.all ( iterable )`
///
/// More information:
Expand Down Expand Up @@ -563,7 +616,7 @@ impl Promise {
);

// 2. Perform ? Call(resultCapability.[[Resolve]], undefined, « valuesArray »).
result_capability.resolve.call(
result_capability.functions.resolve.call(
&JsValue::undefined(),
&[values_array.into()],
context,
Expand Down Expand Up @@ -646,7 +699,7 @@ impl Promise {
already_called: Rc::new(Cell::new(false)),
index,
values: values.clone(),
capability_resolve: result_capability.resolve.clone(),
capability_resolve: result_capability.functions.resolve.clone(),
remaining_elements_count: remaining_elements_count.clone(),
},
),
Expand All @@ -662,7 +715,10 @@ impl Promise {
// s. Perform ? Invoke(nextPromise, "then", « onFulfilled, resultCapability.[[Reject]] »).
next_promise.invoke(
utf16!("then"),
&[on_fulfilled.into(), result_capability.reject.clone().into()],
&[
on_fulfilled.into(),
result_capability.functions.reject.clone().into(),
],
context,
)?;

Expand Down Expand Up @@ -787,7 +843,7 @@ impl Promise {
);

// 2. Perform ? Call(resultCapability.[[Resolve]], undefined, « valuesArray »).
result_capability.resolve.call(
result_capability.functions.resolve.call(
&JsValue::undefined(),
&[values_array.into()],
context,
Expand Down Expand Up @@ -887,7 +943,7 @@ impl Promise {
already_called: Rc::new(Cell::new(false)),
index,
values: values.clone(),
capability: result_capability.resolve.clone(),
capability: result_capability.functions.resolve.clone(),
remaining_elements: remaining_elements_count.clone(),
},
),
Expand Down Expand Up @@ -973,7 +1029,7 @@ impl Promise {
already_called: Rc::new(Cell::new(false)),
index,
values: values.clone(),
capability: result_capability.resolve.clone(),
capability: result_capability.functions.resolve.clone(),
remaining_elements: remaining_elements_count.clone(),
},
),
Expand Down Expand Up @@ -1208,7 +1264,7 @@ impl Promise {
already_called: Rc::new(Cell::new(false)),
index,
errors: errors.clone(),
capability_reject: result_capability.reject.clone(),
capability_reject: result_capability.functions.reject.clone(),
remaining_elements_count: remaining_elements_count.clone(),
},
),
Expand All @@ -1224,7 +1280,10 @@ impl Promise {
// s. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], onRejected »).
next_promise.invoke(
utf16!("then"),
&[result_capability.resolve.clone().into(), on_rejected.into()],
&[
result_capability.functions.resolve.clone().into(),
on_rejected.into(),
],
context,
)?;

Expand Down Expand Up @@ -1344,8 +1403,8 @@ impl Promise {
next_promise.invoke(
utf16!("then"),
&[
result_capability.resolve.clone().into(),
result_capability.reject.clone().into(),
result_capability.functions.resolve.clone().into(),
result_capability.functions.reject.clone().into(),
],
context,
)?;
Expand Down Expand Up @@ -1388,6 +1447,7 @@ impl Promise {

// 3. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »).
promise_capability
.functions
.reject
.call(&JsValue::undefined(), &[e], context)?;

Expand Down Expand Up @@ -1453,6 +1513,7 @@ impl Promise {

// 3. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »).
promise_capability
.functions
.resolve
.call(&JsValue::Undefined, &[x], context)?;

Expand Down Expand Up @@ -2221,8 +2282,7 @@ fn new_promise_reaction_job(
// g. Assert: promiseCapability is a PromiseCapability Record.
let PromiseCapability {
promise: _,
resolve,
reject,
functions: ResolvingFunctions { resolve, reject },
} = &promise_capability_record;

match handler_result {
Expand Down
Loading

0 comments on commit bc0ea58

Please sign in to comment.