Skip to content

Commit

Permalink
Implement new.target expression (#2299)
Browse files Browse the repository at this point in the history
This Pull Request changes the following:

- Implement `new.target` expression
  • Loading branch information
raskad committed Sep 25, 2022
1 parent 573ac14 commit c58a899
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 5 deletions.
5 changes: 5 additions & 0 deletions boa_engine/src/bytecompiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,11 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::Pop);
}
}
Node::NewTarget => {
if use_expr {
self.emit_opcode(Opcode::PushNewTarget);
}
}
_ => unreachable!(),
}
Ok(())
Expand Down
6 changes: 6 additions & 0 deletions boa_engine/src/syntax/ast/node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@ pub enum Node {
/// A call of the super constructor. [More information](./super_call/struct.SuperCall.html).
SuperCall(SuperCall),

/// The `new.target` pseudo-property expression.
NewTarget,

/// A FormalParameterList.
///
/// This is only used in the parser itself.
Expand Down Expand Up @@ -364,6 +367,7 @@ impl Node {
Self::ClassDecl(ref decl) => decl.to_indented_string(interner, indentation),
Self::ClassExpr(ref expr) => expr.to_indented_string(interner, indentation),
Self::SuperCall(ref super_call) => super_call.to_interned_string(interner),
Self::NewTarget => "new.target".to_owned(),
Self::FormalParameterList(_) => unreachable!(),
}
}
Expand Down Expand Up @@ -1248,6 +1252,7 @@ impl Node {
}
}
Node::Yield(_) if symbol == ContainsSymbol::YieldExpression => return true,
Node::NewTarget if symbol == ContainsSymbol::NewTarget => return true,
_ => {}
}
false
Expand All @@ -1261,6 +1266,7 @@ pub(crate) enum ContainsSymbol {
SuperCall,
YieldExpression,
AwaitExpression,
NewTarget,
}

impl ToInternedString for Node {
Expand Down
16 changes: 15 additions & 1 deletion boa_engine/src/syntax/parser/expression/left_hand_side/member.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,21 @@ where
));
}
TokenKind::Keyword((Keyword::New, false)) => {
let _next = cursor.next(interner).expect("new keyword disappeared");
cursor.next(interner).expect("token disappeared");

if cursor.next_if(Punctuator::Dot, interner)?.is_some() {
let token = cursor.next(interner)?.ok_or(ParseError::AbruptEnd)?;
match token.kind() {
TokenKind::Identifier(Sym::TARGET) => return Ok(Node::NewTarget),
_ => {
return Err(ParseError::general(
"unexpected private identifier",
token.span().start(),
));
}
}
}

let lhs = self.parse(cursor, interner)?;
let args = match cursor.peek(0, interner)? {
Some(next) if next.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) => {
Expand Down
31 changes: 27 additions & 4 deletions boa_engine/src/syntax/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ impl<R> Parser<R> {
where
R: Read,
{
let (in_method, in_derived_constructor) = if let Some(function_env) = context
let (in_function, in_method, in_derived_constructor) = if let Some(function_env) = context
.realm
.environments
.get_this_environment()
Expand All @@ -156,20 +156,22 @@ impl<R> Parser<R> {
let has_super_binding = function_env_borrow.has_super_binding();
let function_object = function_env_borrow.function_object().borrow();
(
true,
has_super_binding,
function_object
.as_function()
.expect("must be function object")
.is_derived_constructor(),
)
} else {
(false, false)
(false, false, false)
};

let statement_list = Script::new(direct).parse(&mut self.cursor, context)?;

let mut contains_super_property = false;
let mut contains_super_call = false;
let mut contains_new_target = false;
if direct {
for node in statement_list.items() {
if !contains_super_property && node.contains(ContainsSymbol::SuperProperty) {
Expand All @@ -179,9 +181,19 @@ impl<R> Parser<R> {
if !contains_super_call && node.contains(ContainsSymbol::SuperCall) {
contains_super_call = true;
}

if !contains_new_target && node.contains(ContainsSymbol::NewTarget) {
contains_new_target = true;
}
}
}

if !in_function && contains_new_target {
return Err(ParseError::general(
"invalid new.target usage",
Position::new(1, 1),
));
}
if !in_method && contains_super_property {
return Err(ParseError::general(
"invalid super usage",
Expand Down Expand Up @@ -365,10 +377,11 @@ where
let body = self::statement::StatementList::new(false, false, false, &[])
.parse(cursor, interner)?;

// It is a Syntax Error if StatementList Contains super unless the source text containing super is eval code that is being processed by a direct eval.
// Additional early error rules for super within direct eval are defined in 19.2.1.1.
if !self.direct_eval {
for node in body.items() {
// It is a Syntax Error if StatementList Contains super unless the source text containing super is eval
// code that is being processed by a direct eval.
// Additional early error rules for super within direct eval are defined in 19.2.1.1.
if node.contains(ContainsSymbol::SuperCall)
|| node.contains(ContainsSymbol::SuperProperty)
{
Expand All @@ -377,6 +390,16 @@ where
Position::new(1, 1),
));
}

// It is a Syntax Error if StatementList Contains NewTarget unless the source text containing NewTarget
// is eval code that is being processed by a direct eval.
// Additional early error rules for NewTarget in direct eval are defined in 19.2.1.1.
if node.contains(ContainsSymbol::NewTarget) {
return Err(ParseError::general(
"invalid new.target usage",
Position::new(1, 1),
));
}
}
}

Expand Down
1 change: 1 addition & 0 deletions boa_engine/src/vm/code_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ impl CodeBlock {
| Opcode::PushClassField
| Opcode::SuperCallDerived
| Opcode::Await
| Opcode::PushNewTarget
| Opcode::CallEvalSpread
| Opcode::CallSpread
| Opcode::NewSpread
Expand Down
16 changes: 16 additions & 0 deletions boa_engine/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2501,6 +2501,22 @@ impl Context {
self.vm.push(JsValue::undefined());
return Ok(ShouldExit::Await);
}
Opcode::PushNewTarget => {
if let Some(env) = self
.realm
.environments
.get_this_environment()
.as_function_slots()
{
if let Some(new_target) = env.borrow().new_target() {
self.vm.push(new_target.clone());
} else {
self.vm.push(JsValue::undefined());
}
} else {
self.vm.push(JsValue::undefined());
}
}
}

Ok(ShouldExit::False)
Expand Down
9 changes: 9 additions & 0 deletions boa_engine/src/vm/opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,13 @@ pub enum Opcode {
/// Stack: promise **=>**
Await,

/// Push the current new target to the stack.
///
/// Operands:
///
/// Stack: **=>** new_target
PushNewTarget,

/// No-operation instruction, does nothing.
///
/// Operands:
Expand Down Expand Up @@ -1364,6 +1371,7 @@ impl Opcode {
Self::GeneratorNext => "GeneratorNext",
Self::AsyncGeneratorNext => "AsyncGeneratorNext",
Self::Await => "Await",
Self::PushNewTarget => "PushNewTarget",
Self::GeneratorNextDelegate => "GeneratorNextDelegate",
Self::Nop => "Nop",
}
Expand Down Expand Up @@ -1509,6 +1517,7 @@ impl Opcode {
Self::Yield => "INST - Yield",
Self::GeneratorNext => "INST - GeneratorNext",
Self::AsyncGeneratorNext => "INST - AsyncGeneratorNext",
Self::PushNewTarget => "INST - PushNewTarget",
Self::Await => "INST - Await",
Self::GeneratorNextDelegate => "INST - GeneratorNextDelegate",
Self::Nop => "INST - Nop",
Expand Down
4 changes: 4 additions & 0 deletions boa_interner/src/sym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ impl Sym {
/// Symbol for the `"of"` string.
pub const OF: Self = unsafe { Self::new_unchecked(27) };

/// Symbol for the `"target"` string.
pub const TARGET: Self = unsafe { Self::new_unchecked(28) };

/// Creates a new [`Sym`] from the provided `value`, or returns `None` if `index` is zero.
#[inline]
pub(super) fn new(value: usize) -> Option<Self> {
Expand Down Expand Up @@ -161,6 +164,7 @@ pub(super) static COMMON_STRINGS: phf::OrderedSet<&'static str> = {
"false",
"async",
"of",
"target",
};
// A `COMMON_STRINGS` of size `usize::MAX` would cause an overflow on our `Interner`
sa::const_assert!(COMMON_STRINGS.len() < usize::MAX);
Expand Down

0 comments on commit c58a899

Please sign in to comment.