Skip to content

Commit

Permalink
Handle early errors for declarations in StatementList (#1175)
Browse files Browse the repository at this point in the history
* Merge {Let,Const,Var}DeclList

* Rustfmt

* Handle early errors for declarations in StatementList
  • Loading branch information
0x7D2B authored and Razican committed May 22, 2021
1 parent 639fce1 commit d9c5744
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 7 deletions.
1 change: 1 addition & 0 deletions boa/src/syntax/ast/node/declaration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ impl Executable for DeclarationList {
}
continue;
}

match &self {
Const(_) => context.create_immutable_binding(
decl.name().to_owned(),
Expand Down
30 changes: 29 additions & 1 deletion boa/src/syntax/ast/node/statement_list/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
syntax::ast::node::Node,
BoaProfiler, Context, Result, Value,
};
use std::{fmt, ops::Deref, rc::Rc};
use std::{collections::HashSet, fmt, ops::Deref, rc::Rc};

#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -55,6 +55,34 @@ impl StatementList {
}
Ok(())
}

pub fn lexically_declared_names(&self) -> HashSet<&str> {
let mut set = HashSet::new();
for stmt in self.items() {
if let Node::LetDeclList(decl_list) | Node::ConstDeclList(decl_list) = stmt {
for decl in decl_list.as_ref() {
if !set.insert(decl.name()) {
// It is a Syntax Error if the LexicallyDeclaredNames of StatementList contains any duplicate entries.
// https://tc39.es/ecma262/#sec-block-static-semantics-early-errors
unreachable!("Redeclaration of {}", decl.name());
}
}
}
}
set
}

pub fn var_declared_names(&self) -> HashSet<&str> {
let mut set = HashSet::new();
for stmt in self.items() {
if let Node::VarDeclList(decl_list) = stmt {
for decl in decl_list.as_ref() {
set.insert(decl.name());
}
}
}
set
}
}

impl Executable for StatementList {
Expand Down
21 changes: 20 additions & 1 deletion boa/src/syntax/parser/expression/assignment/arrow_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
//! [spec]: https://tc39.es/ecma262/#sec-arrow-function-definitions
use super::AssignmentExpression;
use crate::syntax::lexer::TokenKind;
use crate::{
syntax::{
ast::{
node::{ArrowFunctionDecl, FormalParameter, Node, Return, StatementList},
Punctuator,
},
lexer::{Error as LexError, Position, TokenKind},
parser::{
error::{ErrorContext, ParseError, ParseResult},
function::{FormalParameters, FunctionBody},
Expand Down Expand Up @@ -90,6 +90,25 @@ where

cursor.expect(TokenKind::Punctuator(Punctuator::Arrow), "arrow function")?;
let body = ConciseBody::new(self.allow_in).parse(cursor)?;

// It is a Syntax Error if any element of the BoundNames of ArrowParameters
// also occurs in the LexicallyDeclaredNames of ConciseBody.
// https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors
{
let lexically_declared_names = body.lexically_declared_names();
for param in params.as_ref() {
if lexically_declared_names.contains(param.name()) {
return Err(ParseError::lex(LexError::Syntax(
format!("Redeclaration of formal parameter `{}`", param.name()).into(),
match cursor.peek(0)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}

Ok(ArrowFunctionDecl::new(params, body))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ mod tests;
use crate::{
syntax::{
ast::{node::AsyncFunctionExpr, Keyword, Punctuator},
lexer::TokenKind,
lexer::{Error as LexError, Position, TokenKind},
parser::{
function::{FormalParameters, FunctionBody},
statement::BindingIdentifier,
Expand Down Expand Up @@ -74,6 +74,24 @@ where

cursor.expect(Punctuator::CloseBlock, "async function expression")?;

// 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 params.as_ref() {
if lexically_declared_names.contains(param.name()) {
return Err(ParseError::lex(LexError::Syntax(
format!("Redeclaration of formal parameter `{}`", param.name()).into(),
match cursor.peek(0)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}

Ok(AsyncFunctionExpr::new(name, params, body))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ mod tests;
use crate::{
syntax::{
ast::{node::FunctionExpr, Keyword, Punctuator},
lexer::TokenKind,
lexer::{Error as LexError, Position, TokenKind},
parser::{
function::{FormalParameters, FunctionBody},
statement::BindingIdentifier,
Expand Down Expand Up @@ -69,6 +69,24 @@ where

cursor.expect(Punctuator::CloseBlock, "function expression")?;

// 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 params.as_ref() {
if lexically_declared_names.contains(param.name()) {
return Err(ParseError::lex(LexError::Syntax(
format!("Redeclaration of formal parameter `{}`", param.name()).into(),
match cursor.peek(0)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}

Ok(FunctionExpr::new(name, params, body))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use crate::syntax::{
ast::{node::AsyncFunctionDecl, Keyword, Punctuator},
lexer::TokenKind,
parser::{
function::FormalParameters, function::FunctionBody, statement::BindingIdentifier,
function::FormalParameters,
function::FunctionBody,
statement::{BindingIdentifier, LexError, Position},
AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser,
},
};
Expand Down Expand Up @@ -84,6 +86,24 @@ where

cursor.expect(Punctuator::CloseBlock, "async function declaration")?;

// 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 params.as_ref() {
if lexically_declared_names.contains(param.name()) {
return Err(ParseError::lex(LexError::Syntax(
format!("Redeclaration of formal parameter `{}`", param.name()).into(),
match cursor.peek(0)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}

Ok(AsyncFunctionDecl::new(name, params, body))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ mod tests;
use crate::syntax::{
ast::{node::FunctionDecl, Keyword, Punctuator},
parser::{
function::FormalParameters, function::FunctionBody, statement::BindingIdentifier,
function::FormalParameters,
function::FunctionBody,
statement::{BindingIdentifier, LexError, Position},
AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser,
},
};
Expand Down Expand Up @@ -65,6 +67,24 @@ where

cursor.expect(Punctuator::CloseBlock, "function declaration")?;

// 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 params.as_ref() {
if lexically_declared_names.contains(param.name()) {
return Err(ParseError::lex(LexError::Syntax(
format!("Redeclaration of formal parameter `{}`", param.name()).into(),
match cursor.peek(0)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}

Ok(FunctionDecl::new(name, params, body))
}
}
50 changes: 49 additions & 1 deletion boa/src/syntax/parser/statement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,14 @@ use super::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser
use crate::{
syntax::{
ast::{node, Keyword, Node, Punctuator},
lexer::{Error as LexError, InputElement, TokenKind},
lexer::{Error as LexError, InputElement, Position, TokenKind},
parser::expression::await_expr::AwaitExpression,
},
BoaProfiler,
};
use labelled_stm::LabelledStatement;

use std::collections::HashSet;
use std::io::Read;

/// Statement parsing.
Expand Down Expand Up @@ -289,6 +290,53 @@ where
while cursor.next_if(Punctuator::Semicolon)?.is_some() {}
}

// Handle any redeclarations
// https://tc39.es/ecma262/#sec-block-static-semantics-early-errors
{
let mut lexically_declared_names: HashSet<&str> = HashSet::new();
let mut var_declared_names: HashSet<&str> = HashSet::new();

// TODO: Use more helpful positions in errors when spans are added to Nodes
for item in &items {
match item {
Node::LetDeclList(decl_list) | Node::ConstDeclList(decl_list) => {
for decl in decl_list.as_ref() {
// if name in VarDeclaredNames or can't be added to
// LexicallyDeclaredNames, raise an error
if var_declared_names.contains(decl.name())
|| !lexically_declared_names.insert(decl.name())
{
return Err(ParseError::lex(LexError::Syntax(
format!("Redeclaration of variable `{}`", decl.name()).into(),
match cursor.peek(0)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
Node::VarDeclList(decl_list) => {
for decl in decl_list.as_ref() {
// if name in LexicallyDeclaredNames, raise an error
if lexically_declared_names.contains(decl.name()) {
return Err(ParseError::lex(LexError::Syntax(
format!("Redeclaration of variable `{}`", decl.name()).into(),
match cursor.peek(0)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
// otherwise, add to VarDeclaredNames
var_declared_names.insert(decl.name());
}
}
_ => (),
}
}
}

items.sort_by(Node::hoistable_order);

Ok(items.into())
Expand Down

0 comments on commit d9c5744

Please sign in to comment.