Skip to content

Commit

Permalink
Allow BindingPatterns as CatchParameter (#1628)
Browse files Browse the repository at this point in the history
  • Loading branch information
lowr authored Oct 5, 2021
1 parent df836f1 commit 916c9d8
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 35 deletions.
10 changes: 9 additions & 1 deletion boa/src/syntax/ast/node/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
gc::{Finalize, Trace},
BoaProfiler, Context, JsResult, JsValue,
};
use std::fmt;
use std::{collections::HashSet, fmt};

#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -45,6 +45,14 @@ impl Block {
self.statements.items()
}

pub(crate) fn lexically_declared_names(&self) -> HashSet<&str> {
self.statements.lexically_declared_names()
}

pub(crate) fn var_declared_named(&self) -> HashSet<&str> {
self.statements.var_declared_names()
}

/// Implements the display formatting with indentation.
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result {
writeln!(f, "{{")?;
Expand Down
50 changes: 35 additions & 15 deletions boa/src/syntax/ast/node/try_node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
},
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::{Block, Identifier, Node},
syntax::ast::node::{Block, Declaration, Node},
BoaProfiler, Context, JsResult, JsValue,
};
use std::fmt;
Expand Down Expand Up @@ -100,13 +100,33 @@ impl Executable for Try {
let res = self.block().run(context).map_or_else(
|err| {
if let Some(catch) = self.catch() {
{
let env = context.get_current_environment();
context.push_environment(DeclarativeEnvironmentRecord::new(Some(env)));

if let Some(param) = catch.parameter() {
context.create_mutable_binding(param, false, VariableScope::Block)?;
context.initialize_binding(param, err)?;
let env = context.get_current_environment();
context.push_environment(DeclarativeEnvironmentRecord::new(Some(env)));

if let Some(param) = catch.parameter() {
match param {
Declaration::Identifier { ident, init } => {
debug_assert!(init.is_none());

context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?;
context.initialize_binding(ident.as_ref(), err)?;
}
Declaration::Pattern(pattern) => {
debug_assert!(pattern.init().is_none());

for (ident, value) in pattern.run(Some(err), context)? {
context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?;
context.initialize_binding(ident.as_ref(), value)?;
}
}
}
}

Expand Down Expand Up @@ -147,27 +167,27 @@ impl From<Try> for Node {
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct Catch {
parameter: Option<Identifier>,
parameter: Option<Box<Declaration>>,
block: Block,
}

impl Catch {
/// Creates a new catch block.
pub(in crate::syntax) fn new<OI, I, B>(parameter: OI, block: B) -> Self
pub(in crate::syntax) fn new<OD, D, B>(parameter: OD, block: B) -> Self
where
OI: Into<Option<I>>,
I: Into<Identifier>,
OD: Into<Option<D>>,
D: Into<Declaration>,
B: Into<Block>,
{
Self {
parameter: parameter.into().map(I::into),
parameter: parameter.into().map(|d| Box::new(d.into())),
block: block.into(),
}
}

/// Gets the parameter of the catch block.
pub fn parameter(&self) -> Option<&str> {
self.parameter.as_ref().map(Identifier::as_ref)
pub fn parameter(&self) -> Option<&Declaration> {
self.parameter.as_deref()
}

/// Retrieves the catch execution block.
Expand Down
32 changes: 32 additions & 0 deletions boa/src/syntax/ast/node/try_node/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,38 @@ fn catch_binding() {
assert_eq!(&exec(scenario), "20");
}

#[test]
fn catch_binding_pattern_object() {
let scenario = r#"
let a = 10;
try {
throw {
n: 30,
};
} catch ({ n }) {
a = n;
}
a;
"#;
assert_eq!(&exec(scenario), "30");
}

#[test]
fn catch_binding_pattern_array() {
let scenario = r#"
let a = 10;
try {
throw [20, 30];
} catch ([, n]) {
a = n;
}
a;
"#;
assert_eq!(&exec(scenario), "30");
}

#[test]
fn catch_binding_finally() {
let scenario = r#"
Expand Down
92 changes: 80 additions & 12 deletions boa/src/syntax/parser/statement/try_stm/catch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ use crate::{
syntax::{
ast::{
node::{self, Identifier},
Keyword, Punctuator,
Keyword, Position, Punctuator,
},
lexer::TokenKind,
parser::{
statement::{block::Block, BindingIdentifier},
statement::{
block::Block, ArrayBindingPattern, BindingIdentifier, ObjectBindingPattern,
},
AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser,
},
},
BoaProfiler,
};

use rustc_hash::FxHashSet;
use std::io::Read;

/// Catch parsing
Expand Down Expand Up @@ -57,17 +61,63 @@ where
let catch_param = if cursor.next_if(Punctuator::OpenParen)?.is_some() {
let catch_param =
CatchParameter::new(self.allow_yield, self.allow_await).parse(cursor)?;

cursor.expect(Punctuator::CloseParen, "catch in try statement")?;
Some(catch_param)
} else {
None
};

let mut set = FxHashSet::default();
let idents = match &catch_param {
Some(node::Declaration::Identifier { ident, .. }) => vec![ident.as_ref()],
Some(node::Declaration::Pattern(p)) => p.idents(),
_ => vec![],
};

// It is a Syntax Error if BoundNames of CatchParameter contains any duplicate elements.
// https://tc39.es/ecma262/#sec-variablestatements-in-catch-blocks
for ident in idents {
if !set.insert(ident) {
// FIXME: pass correct position once #1295 lands
return Err(ParseError::general(
"duplicate identifier",
Position::new(1, 1),
));
}
}

// Catch block
Ok(node::Catch::new::<_, Identifier, _>(
catch_param,
Block::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?,
))
let catch_block =
Block::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?;

// It is a Syntax Error if any element of the BoundNames of CatchParameter also occurs in the LexicallyDeclaredNames of Block.
// It is a Syntax Error if any element of the BoundNames of CatchParameter also occurs in the VarDeclaredNames of Block.
// https://tc39.es/ecma262/#sec-try-statement-static-semantics-early-errors

// FIXME: `lexically_declared_names` only holds part of LexicallyDeclaredNames of the
// Block e.g. function names are *not* included but should be.
let lexically_declared_names = catch_block.lexically_declared_names();
let var_declared_names = catch_block.var_declared_named();

for ident in set {
// FIXME: pass correct position once #1295 lands
if lexically_declared_names.contains(ident) {
return Err(ParseError::general(
"identifier redeclared",
Position::new(1, 1),
));
}
if var_declared_names.contains(ident) {
return Err(ParseError::general(
"identifier redeclared",
Position::new(1, 1),
));
}
}

let catch_node = node::Catch::new::<_, node::Declaration, _>(catch_param, catch_block);
Ok(catch_node)
}
}

Expand Down Expand Up @@ -103,12 +153,30 @@ impl<R> TokenParser<R> for CatchParameter
where
R: Read,
{
type Output = Identifier;
type Output = node::Declaration;

fn parse(self, cursor: &mut Cursor<R>) -> Result<Identifier, ParseError> {
// TODO: should accept BindingPattern
BindingIdentifier::new(self.allow_yield, self.allow_await)
.parse(cursor)
.map(Identifier::from)
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
let token = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?;

match token.kind() {
TokenKind::Punctuator(Punctuator::OpenBlock) => {
let pat = ObjectBindingPattern::new(true, self.allow_yield, self.allow_await)
.parse(cursor)?;

Ok(node::Declaration::new_with_object_pattern(pat, None))
}
TokenKind::Punctuator(Punctuator::OpenBracket) => {
let pat = ArrayBindingPattern::new(true, self.allow_yield, self.allow_await)
.parse(cursor)?;
Ok(node::Declaration::new_with_array_pattern(pat, None))
}
TokenKind::Identifier(_) => {
let ident = BindingIdentifier::new(self.allow_yield, self.allow_await)
.parse(cursor)
.map(Identifier::from)?;
Ok(node::Declaration::new_with_identifier(ident, None))
}
_ => Err(ParseError::unexpected(token.clone(), None)),
}
}
}
Loading

0 comments on commit 916c9d8

Please sign in to comment.