Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Implement async function and await #2158

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions boa_engine/src/builtins/async_function/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//! This module implements the global `AsyncFunction` object.
//!
//! More information:
//! - [ECMAScript reference][spec]
//! - [MDN documentation][mdn]
//!
//! [spec]: https://tc39.es/ecma262/#sec-async-function-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction

use crate::{
builtins::{
function::{ConstructorKind, Function},
BuiltIn,
},
object::ObjectData,
property::PropertyDescriptor,
symbol::WellKnownSymbols,
Context, JsResult, JsValue,
};
use boa_profiler::Profiler;

#[derive(Debug, Clone, Copy)]
pub struct AsyncFunction;

impl BuiltIn for AsyncFunction {
const NAME: &'static str = "AsyncFunction";

fn init(context: &mut Context) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init");

let prototype = &context
.intrinsics()
.constructors()
.async_function()
.prototype;
let constructor = &context
.intrinsics()
.constructors()
.async_function()
.constructor;

constructor.set_prototype(Some(
context.intrinsics().constructors().function().constructor(),
));
let property = PropertyDescriptor::builder()
.value(1)
.writable(false)
.enumerable(false)
.configurable(true);
constructor.borrow_mut().insert("length", property);
let property = PropertyDescriptor::builder()
.value(Self::NAME)
.writable(false)
.enumerable(false)
.configurable(true);
constructor.borrow_mut().insert("name", property);
let property = PropertyDescriptor::builder()
.value(prototype.clone())
.writable(false)
.enumerable(false)
.configurable(false);
constructor.borrow_mut().insert("prototype", property);
constructor.borrow_mut().data = ObjectData::function(Function::Native {
function: Self::constructor,
constructor: Some(ConstructorKind::Base),
});

prototype.set_prototype(Some(
context.intrinsics().constructors().function().prototype(),
));
let property = PropertyDescriptor::builder()
.value(constructor.clone())
.writable(false)
.enumerable(false)
.configurable(true);
prototype.borrow_mut().insert("constructor", property);
let property = PropertyDescriptor::builder()
.value(Self::NAME)
.writable(false)
.enumerable(false)
.configurable(true);
prototype
.borrow_mut()
.insert(WellKnownSymbols::to_string_tag(), property);

None
}
}

impl AsyncFunction {
/// `AsyncFunction ( p1, p2, … , pn, body )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-async-function-constructor-arguments
fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
crate::builtins::function::BuiltInFunctionObject::create_dynamic_function(
new_target, args, true, context,
)
.map(Into::into)
}
}
58 changes: 51 additions & 7 deletions boa_engine/src/builtins/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ use crate::{
object::{ConstructorBuilder, FunctionBuilder, JsFunction, PrivateElement, Ref, RefMut},
property::{Attribute, PropertyDescriptor, PropertyKey},
symbol::WellKnownSymbols,
syntax::{ast::node::FormalParameterList, Parser},
syntax::{
ast::node::{FormalParameterList, StatementList},
Parser,
},
value::IntegerOrInfinity,
Context, JsResult, JsString, JsValue,
};
Expand All @@ -38,6 +41,8 @@ use std::{
};
use tap::{Conv, Pipe};

use super::promise::PromiseCapability;

pub(crate) mod arguments;
#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -232,6 +237,11 @@ pub enum Function {
/// The `[[PrivateMethods]]` internal slot.
private_methods: Vec<(Sym, PrivateElement)>,
},
Async {
code: Gc<crate::vm::CodeBlock>,
environments: DeclarativeEnvironmentStack,
promise_capability: PromiseCapability,
},
Generator {
code: Gc<crate::vm::CodeBlock>,
environments: DeclarativeEnvironmentStack,
Expand All @@ -252,6 +262,11 @@ unsafe impl Trace for Function {
mark(elem);
}
}
Self::Async { code, environments, promise_capability } => {
mark(code);
mark(environments);
mark(promise_capability);
}
Self::Generator { code, environments } => {
mark(code);
mark(environments);
Expand All @@ -273,7 +288,7 @@ impl Function {
Self::Native { constructor, .. } | Self::Closure { constructor, .. } => {
constructor.is_some()
}
Self::Generator { .. } => false,
Self::Generator { .. } | Self::Async { .. } => false,
Self::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical),
}
}
Expand Down Expand Up @@ -350,6 +365,18 @@ impl Function {
private_methods.push((name, method));
}
}

/// Returns the promise capability if the function is an async function.
pub(crate) fn get_promise_capability(&self) -> Option<&PromiseCapability> {
if let Self::Async {
promise_capability, ..
} = self
{
Some(promise_capability)
} else {
None
}
}
}

/// Creates a new member function of a `Object` or `prototype`.
Expand Down Expand Up @@ -433,7 +460,7 @@ impl BuiltInFunctionObject {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
Self::create_dynamic_function(new_target, args, context).map(Into::into)
Self::create_dynamic_function(new_target, args, false, context).map(Into::into)
}

/// `CreateDynamicFunction ( constructor, newTarget, kind, args )`
Expand All @@ -442,9 +469,10 @@ impl BuiltInFunctionObject {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createdynamicfunction
fn create_dynamic_function(
pub(crate) fn create_dynamic_function(
new_target: &JsValue,
args: &[JsValue],
r#async: bool,
context: &mut Context,
) -> JsResult<JsObject> {
let prototype =
Expand All @@ -462,7 +490,7 @@ impl BuiltInFunctionObject {
parameters.push(')');

let parameters = match Parser::new(parameters.as_bytes())
.parse_formal_parameters(context.interner_mut(), false, false)
.parse_formal_parameters(context.interner_mut(), false, r#async)
{
Ok(parameters) => parameters,
Err(e) => {
Expand All @@ -479,7 +507,7 @@ impl BuiltInFunctionObject {
let body = match Parser::new(body_arg.as_bytes()).parse_function_body(
context.interner_mut(),
false,
false,
r#async,
) {
Ok(statement_list) => statement_list,
Err(e) => {
Expand Down Expand Up @@ -548,7 +576,23 @@ impl BuiltInFunctionObject {
)?;

let environments = context.realm.environments.pop_to_global();
let function_object = crate::vm::create_function_object(code, context);
let function_object = crate::vm::create_function_object(code, r#async, context);
context.realm.environments.extend(environments);

Ok(function_object)
} else if r#async {
let code = crate::bytecompiler::ByteCompiler::compile_function_code(
crate::bytecompiler::FunctionKind::Expression,
Some(Sym::EMPTY_STRING),
&FormalParameterList::empty(),
&StatementList::default(),
false,
false,
context,
)?;

let environments = context.realm.environments.pop_to_global();
let function_object = crate::vm::create_function_object(code, r#async, context);
context.realm.environments.extend(environments);

Ok(function_object)
Expand Down
5 changes: 4 additions & 1 deletion boa_engine/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pub mod array;
pub mod array_buffer;
pub mod async_function;
pub mod bigint;
pub mod boolean;
pub mod dataview;
Expand Down Expand Up @@ -38,6 +39,7 @@ pub mod intl;

pub(crate) use self::{
array::{array_iterator::ArrayIterator, Array},
async_function::AsyncFunction,
bigint::BigInt,
boolean::Boolean,
dataview::DataView,
Expand Down Expand Up @@ -185,7 +187,8 @@ pub fn init(context: &mut Context) {
Reflect,
Generator,
GeneratorFunction,
Promise
Promise,
AsyncFunction
};

#[cfg(feature = "intl")]
Expand Down
27 changes: 23 additions & 4 deletions boa_engine/src/builtins/promise/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ enum ReactionType {
}

#[derive(Debug, Clone, Trace, Finalize)]
struct PromiseCapability {
pub struct PromiseCapability {
promise: JsObject,
resolve: JsFunction,
reject: JsFunction,
Expand All @@ -99,7 +99,7 @@ impl PromiseCapability {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-newpromisecapability
fn new(c: &JsValue, context: &mut Context) -> JsResult<Self> {
pub(crate) fn new(c: &JsValue, context: &mut Context) -> JsResult<Self> {
#[derive(Debug, Clone, Trace, Finalize)]
struct RejectResolve {
reject: JsValue,
Expand Down Expand Up @@ -194,6 +194,21 @@ impl PromiseCapability {
}
}
}

/// Returns the promise object.
pub(crate) fn promise(&self) -> &JsObject {
&self.promise
}

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

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

impl BuiltIn for Promise {
Expand Down Expand Up @@ -1878,7 +1893,7 @@ impl Promise {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-performpromisethen
fn perform_promise_then(
pub(crate) fn perform_promise_then(
&mut self,
on_fulfilled: &JsValue,
on_rejected: &JsValue,
Expand Down Expand Up @@ -2000,7 +2015,11 @@ impl Promise {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-promise-resolve
fn promise_resolve(c: JsObject, x: JsValue, context: &mut Context) -> JsResult<JsValue> {
pub(crate) fn promise_resolve(
c: JsObject,
x: JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If IsPromise(x) is true, then
if let Some(x) = x.as_promise() {
// a. Let xConstructor be ? Get(x, "constructor").
Expand Down
Loading