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 Function constructor #2090

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion boa_engine/src/builtins/error/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ impl TypeError {

pub(crate) fn create_throw_type_error(context: &mut Context) -> JsObject {
fn throw_type_error(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
context.throw_type_error("invalid type")
context.throw_type_error("'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them")
}

let function = JsObject::from_proto_and_data(
Expand Down
169 changes: 159 additions & 10 deletions boa_engine/src/builtins/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ use crate::{
object::{ConstructorBuilder, FunctionBuilder, Ref, RefMut},
property::{Attribute, PropertyDescriptor, PropertyKey},
symbol::WellKnownSymbols,
syntax::{ast::node::FormalParameterList, Parser},
value::IntegerOrInfinity,
Context, JsResult, JsString, JsValue,
};
use boa_gc::{self, Finalize, Gc, Trace};
use boa_interner::Sym;
use boa_profiler::Profiler;
use dyn_clone::DynClone;
use std::{
Expand Down Expand Up @@ -275,23 +277,152 @@ pub struct BuiltInFunctionObject;
impl BuiltInFunctionObject {
pub const LENGTH: usize = 1;

/// `Function ( p1, p2, … , pn, body )`
///
/// The apply() method invokes self with the first argument as the `this` value
/// and the rest of the arguments provided as an array (or an array-like object).
///
/// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-function-p1-p2-pn-body
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function
fn constructor(
new_target: &JsValue,
_: &[JsValue],
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
Self::create_dynamic_function(new_target, args, context).map(Into::into)
}

/// `CreateDynamicFunction ( constructor, newTarget, kind, args )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createdynamicfunction
fn create_dynamic_function(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsObject> {
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::function, context)?;
if let Some((body_arg, args)) = args.split_last() {
let parameters =
if args.is_empty() {
FormalParameterList::empty()
} else {
let mut parameters = Vec::with_capacity(args.len());
for arg in args {
parameters.push(arg.to_string(context)?);
}
let mut parameters = parameters.join(",");
parameters.push(')');

let parameters = match Parser::new(parameters.as_bytes())
.parse_formal_parameters(context.interner_mut(), false, false)
{
Ok(parameters) => parameters,
Err(e) => {
return context.throw_syntax_error(format!(
"failed to parse function parameters: {e}"
))
}
};
parameters
};

let body_arg = body_arg.to_string(context)?;

let body = match Parser::new(body_arg.as_bytes()).parse_function_body(
context.interner_mut(),
false,
false,
) {
Ok(statement_list) => statement_list,
Err(e) => {
return context
.throw_syntax_error(format!("failed to parse function body: {e}"))
}
};

// Early Error: If BindingIdentifier is present and the source text matched by BindingIdentifier is strict mode code,
// it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments".
if body.strict() {
for parameter in parameters.parameters.iter() {
for name in parameter.names() {
if name == Sym::ARGUMENTS || name == Sym::EVAL {
return context.throw_syntax_error(
" Unexpected 'eval' or 'arguments' in strict mode",
);
}
}
}
}

// Early Error: If the source code matching FormalParameters is strict mode code,
// the Early Error rules for UniqueFormalParameters : FormalParameters are applied.
if (body.strict()) && parameters.has_duplicates() {
return context
.throw_syntax_error("Duplicate parameter name not allowed in this context");
}

// Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of GeneratorBody is true
// and IsSimpleParameterList of FormalParameters is false.
if body.strict() && !parameters.is_simple() {
return context.throw_syntax_error(
"Illegal 'use strict' directive in function with non-simple parameter list",
);
}

let this = JsObject::from_proto_and_data(
prototype,
ObjectData::function(Function::Native {
function: |_, _, _| Ok(JsValue::undefined()),
constructor: true,
}),
);
// It is a Syntax Error if any element of the BoundNames of FormalParameters
// also occurs in the LexicallyDeclaredNames of FunctionBody.
// https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors
{
let lexically_declared_names = body.lexically_declared_names();
for param in parameters.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names
.iter()
.any(|(name, _)| *name == param_name)
{
return context.throw_syntax_error(format!(
"Redeclaration of formal parameter `{}`",
context.interner().resolve_expect(param_name)
));
}
}
}
}

Ok(this.into())
let code = crate::bytecompiler::ByteCompiler::compile_function_code(
crate::bytecompiler::FunctionKind::Expression,
Some(Sym::EMPTY_STRING),
&parameters,
&body,
false,
false,
context,
)?;

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

Ok(function_object)
} else {
let this = JsObject::from_proto_and_data(
prototype,
ObjectData::function(Function::Native {
function: |_, _, _| Ok(JsValue::undefined()),
constructor: true,
}),
);

Ok(this)
}
}

/// `Function.prototype.apply ( thisArg, argArray )`
Expand Down Expand Up @@ -523,18 +654,36 @@ impl BuiltIn for BuiltInFunctionObject {
.constructor(false)
.build();

let throw_type_error = context.intrinsics().objects().throw_type_error();

ConstructorBuilder::with_standard_constructor(
context,
Self::constructor,
context.intrinsics().constructors().function().clone(),
)
.name(Self::NAME)
.length(Self::LENGTH)
.method(Self::apply, "apply", 1)
.method(Self::apply, "apply", 2)
.method(Self::bind, "bind", 1)
.method(Self::call, "call", 1)
.method(Self::to_string, "toString", 0)
.property(symbol_has_instance, has_instance, Attribute::default())
.property_descriptor(
"caller",
PropertyDescriptor::builder()
.get(throw_type_error.clone())
.set(throw_type_error.clone())
.enumerable(false)
.configurable(true),
)
.property_descriptor(
"arguments",
PropertyDescriptor::builder()
.get(throw_type_error.clone())
.set(throw_type_error)
.enumerable(false)
.configurable(true),
)
.build()
.conv::<JsValue>()
.pipe(Some)
Expand Down
125 changes: 74 additions & 51 deletions boa_engine/src/bytecompiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
object::{MethodDefinition, PropertyDefinition, PropertyName},
operator::assign::AssignTarget,
template::TemplateElement,
Class, Declaration, GetConstField, GetField,
Class, Declaration, FormalParameterList, GetConstField, GetField, StatementList,
},
op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp},
Const, Node,
Expand Down Expand Up @@ -1866,54 +1866,17 @@ impl<'b> ByteCompiler<'b> {
Ok(())
}

pub(crate) fn function(&mut self, function: &Node, use_expr: bool) -> JsResult<()> {
#[derive(Debug, Clone, Copy, PartialEq)]
enum FunctionKind {
Declaration,
Expression,
Arrow,
}

let (kind, name, parameters, body, generator) = match function {
Node::FunctionDecl(function) => (
FunctionKind::Declaration,
Some(function.name()),
function.parameters(),
function.body(),
false,
),
Node::GeneratorDecl(generator) => (
FunctionKind::Declaration,
Some(generator.name()),
generator.parameters(),
generator.body(),
true,
),
Node::FunctionExpr(function) => (
FunctionKind::Expression,
function.name(),
function.parameters(),
function.body(),
false,
),
Node::GeneratorExpr(generator) => (
FunctionKind::Expression,
generator.name(),
generator.parameters(),
generator.body(),
true,
),
Node::ArrowFunctionDecl(function) => (
FunctionKind::Arrow,
function.name(),
function.params(),
function.body(),
false,
),
_ => unreachable!(),
};

let strict = body.strict() || self.code_block.strict;
/// Compile a function statement list and it's parameters into bytecode.
pub(crate) fn compile_function_code(
kind: FunctionKind,
name: Option<Sym>,
parameters: &FormalParameterList,
body: &StatementList,
generator: bool,
strict: bool,
context: &mut Context,
) -> JsResult<Gc<CodeBlock>> {
let strict = strict || body.strict();
let length = parameters.length();
let mut code = CodeBlock::new(name.unwrap_or(Sym::EMPTY_STRING), length, strict, true);

Expand All @@ -1932,7 +1895,7 @@ impl<'b> ByteCompiler<'b> {
names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(),
jump_info: Vec::new(),
context: self.context,
context,
};

compiler.context.push_compile_time_environment(true);
Expand Down Expand Up @@ -2023,7 +1986,59 @@ impl<'b> ByteCompiler<'b> {
compiler.emit(Opcode::PushUndefined, &[]);
compiler.emit(Opcode::Return, &[]);

let code = Gc::new(compiler.finish());
Ok(Gc::new(compiler.finish()))
}

/// Compile a function AST Node into bytecode.
pub(crate) fn function(&mut self, function: &Node, use_expr: bool) -> JsResult<()> {
let (kind, name, parameters, body, generator) = match function {
Node::FunctionDecl(function) => (
FunctionKind::Declaration,
Some(function.name()),
function.parameters(),
function.body(),
false,
),
Node::GeneratorDecl(generator) => (
FunctionKind::Declaration,
Some(generator.name()),
generator.parameters(),
generator.body(),
true,
),
Node::FunctionExpr(function) => (
FunctionKind::Expression,
function.name(),
function.parameters(),
function.body(),
false,
),
Node::GeneratorExpr(generator) => (
FunctionKind::Expression,
generator.name(),
generator.parameters(),
generator.body(),
true,
),
Node::ArrowFunctionDecl(function) => (
FunctionKind::Arrow,
function.name(),
function.params(),
function.body(),
false,
),
_ => unreachable!(),
};

let code = Self::compile_function_code(
kind,
name,
parameters,
body,
generator,
self.code_block.strict,
self.context,
)?;

let index = self.code_block.functions.len() as u32;
self.code_block.functions.push(code);
Expand Down Expand Up @@ -2906,3 +2921,11 @@ impl<'b> ByteCompiler<'b> {
Ok(())
}
}

/// `FunctionKind` describes how a function has been defined in the source code.
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum FunctionKind {
Razican marked this conversation as resolved.
Show resolved Hide resolved
Declaration,
Expression,
Arrow,
}
Loading