Browse Source

Implement scope analysis and local variables (#3988)

* Remove unnecessary compile time environment clones

* Remove compile time environments from every runtime DeclarativeEnvironment

* Implement scope analysis and local variables

* fix docs and fuzzer errors

* Apply suggestions

* Align `parse_script` and `parse_module` arguments

* Fix fuzzer
pull/4003/head
raskad 2 months ago committed by GitHub
parent
commit
aa09d98787
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      Cargo.lock
  2. 6
      cli/src/main.rs
  3. 2
      core/ast/Cargo.toml
  4. 31
      core/ast/src/expression/literal/object.rs
  5. 35
      core/ast/src/function/arrow_function.rs
  6. 35
      core/ast/src/function/async_arrow_function.rs
  7. 74
      core/ast/src/function/async_function.rs
  8. 73
      core/ast/src/function/async_generator.rs
  9. 262
      core/ast/src/function/class.rs
  10. 76
      core/ast/src/function/generator.rs
  11. 90
      core/ast/src/function/mod.rs
  12. 85
      core/ast/src/function/ordinary_function.rs
  13. 31
      core/ast/src/lib.rs
  14. 159
      core/ast/src/operations.rs
  15. 529
      core/ast/src/scope.rs
  16. 1992
      core/ast/src/scope_analyzer.rs
  17. 78
      core/ast/src/source.rs
  18. 21
      core/ast/src/statement/block.rs
  19. 37
      core/ast/src/statement/iteration/for_in_loop.rs
  20. 75
      core/ast/src/statement/iteration/for_loop.rs
  21. 37
      core/ast/src/statement/iteration/for_of_loop.rs
  22. 2
      core/ast/src/statement/iteration/mod.rs
  23. 28
      core/ast/src/statement/switch.rs
  24. 30
      core/ast/src/statement/try.rs
  25. 15
      core/ast/src/statement/with.rs
  26. 2
      core/ast/src/statement_list.rs
  27. 7
      core/ast/src/visitor.rs
  28. 2
      core/engine/Cargo.toml
  29. 70
      core/engine/src/builtins/eval/mod.rs
  30. 1
      core/engine/src/builtins/function/arguments.rs
  31. 43
      core/engine/src/builtins/function/mod.rs
  32. 7
      core/engine/src/builtins/json/mod.rs
  33. 101
      core/engine/src/bytecompiler/class.rs
  34. 656
      core/engine/src/bytecompiler/declarations.rs
  35. 25
      core/engine/src/bytecompiler/env.rs
  36. 31
      core/engine/src/bytecompiler/expression/assign.rs
  37. 2
      core/engine/src/bytecompiler/expression/object_literal.rs
  38. 8
      core/engine/src/bytecompiler/expression/unary.rs
  39. 31
      core/engine/src/bytecompiler/expression/update.rs
  40. 40
      core/engine/src/bytecompiler/function.rs
  41. 346
      core/engine/src/bytecompiler/mod.rs
  42. 2
      core/engine/src/bytecompiler/module.rs
  43. 22
      core/engine/src/bytecompiler/statement/block.rs
  44. 191
      core/engine/src/bytecompiler/statement/loop.rs
  45. 22
      core/engine/src/bytecompiler/statement/switch.rs
  46. 17
      core/engine/src/bytecompiler/statement/try.rs
  47. 8
      core/engine/src/bytecompiler/statement/with.rs
  48. 219
      core/engine/src/environments/compile.rs
  49. 10
      core/engine/src/environments/mod.rs
  50. 19
      core/engine/src/environments/runtime/declarative/function.rs
  51. 35
      core/engine/src/environments/runtime/declarative/mod.rs
  52. 18
      core/engine/src/environments/runtime/declarative/module.rs
  53. 263
      core/engine/src/environments/runtime/mod.rs
  54. 6
      core/engine/src/module/mod.rs
  55. 10
      core/engine/src/module/namespace.rs
  56. 84
      core/engine/src/module/source.rs
  57. 37
      core/engine/src/module/synthetic.rs
  58. 20
      core/engine/src/realm.rs
  59. 14
      core/engine/src/script.rs
  60. 11
      core/engine/src/vm/call_frame/mod.rs
  61. 58
      core/engine/src/vm/code_block.rs
  62. 10
      core/engine/src/vm/flowgraph/mod.rs
  63. 66
      core/engine/src/vm/opcode/call/mod.rs
  64. 4
      core/engine/src/vm/opcode/define/mod.rs
  65. 89
      core/engine/src/vm/opcode/locals/mod.rs
  66. 31
      core/engine/src/vm/opcode/mod.rs
  67. 37
      core/engine/src/vm/opcode/push/environment.rs
  68. 14
      core/engine/src/vm/opcode/set/name.rs
  69. 2
      core/engine/src/vm/runtime_limits.rs
  70. 60
      core/parser/src/parser/expression/assignment/arrow_function.rs
  71. 36
      core/parser/src/parser/expression/assignment/async_arrow_function.rs
  72. 18
      core/parser/src/parser/expression/primary/async_function_expression/tests.rs
  73. 22
      core/parser/src/parser/expression/primary/async_generator_expression/tests.rs
  74. 27
      core/parser/src/parser/expression/primary/function_expression/tests.rs
  75. 12
      core/parser/src/parser/expression/primary/generator_expression/tests.rs
  76. 13
      core/parser/src/parser/expression/primary/object_initializer/mod.rs
  77. 6
      core/parser/src/parser/expression/tests.rs
  78. 8
      core/parser/src/parser/function/mod.rs
  79. 102
      core/parser/src/parser/function/tests.rs
  80. 31
      core/parser/src/parser/mod.rs
  81. 12
      core/parser/src/parser/statement/block/tests.rs
  82. 101
      core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs
  83. 19
      core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs
  84. 61
      core/parser/src/parser/statement/iteration/for_statement.rs
  85. 3
      core/parser/src/parser/tests/format/mod.rs
  86. 17
      core/parser/src/parser/tests/mod.rs
  87. 3
      examples/src/bin/commuter_visitor.rs
  88. 3
      examples/src/bin/symbol_visitor.rs
  89. 8
      tests/fuzz/fuzz_targets/parser-idempotency.rs

1
Cargo.lock generated

@ -315,6 +315,7 @@ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"boa_interner", "boa_interner",
"boa_macros", "boa_macros",
"boa_string",
"indexmap", "indexmap",
"num-bigint", "num-bigint",
"rustc-hash 2.0.0", "rustc-hash 2.0.0",

6
cli/src/main.rs

@ -190,8 +190,9 @@ where
let mut parser = boa_parser::Parser::new(Source::from_bytes(src)); let mut parser = boa_parser::Parser::new(Source::from_bytes(src));
let dump = let dump =
if args.module { if args.module {
let scope = context.realm().scope().clone();
let module = parser let module = parser
.parse_module(context.interner_mut()) .parse_module(&scope, context.interner_mut())
.map_err(|e| format!("Uncaught SyntaxError: {e}"))?; .map_err(|e| format!("Uncaught SyntaxError: {e}"))?;
match arg { match arg {
@ -202,8 +203,9 @@ where
DumpFormat::Debug => format!("{module:#?}"), DumpFormat::Debug => format!("{module:#?}"),
} }
} else { } else {
let scope = context.realm().scope().clone();
let mut script = parser let mut script = parser
.parse_script(context.interner_mut()) .parse_script(&scope, context.interner_mut())
.map_err(|e| format!("Uncaught SyntaxError: {e}"))?; .map_err(|e| format!("Uncaught SyntaxError: {e}"))?;
if args.optimize { if args.optimize {

2
core/ast/Cargo.toml

@ -11,12 +11,14 @@ repository.workspace = true
rust-version.workspace = true rust-version.workspace = true
[features] [features]
annex-b = []
serde = ["dep:serde", "boa_interner/serde", "bitflags/serde", "num-bigint/serde"] serde = ["dep:serde", "boa_interner/serde", "bitflags/serde", "num-bigint/serde"]
arbitrary = ["dep:arbitrary", "boa_interner/arbitrary", "num-bigint/arbitrary"] arbitrary = ["dep:arbitrary", "boa_interner/arbitrary", "num-bigint/arbitrary"]
[dependencies] [dependencies]
boa_interner.workspace = true boa_interner.workspace = true
boa_macros.workspace = true boa_macros.workspace = true
boa_string.workspace = true
rustc-hash = { workspace = true, features = ["std"] } rustc-hash = { workspace = true, features = ["std"] }
bitflags.workspace = true bitflags.workspace = true
num-bigint.workspace = true num-bigint.workspace = true

31
core/ast/src/expression/literal/object.rs

@ -8,8 +8,10 @@ use crate::{
}, },
function::{FormalParameterList, FunctionBody}, function::{FormalParameterList, FunctionBody},
join_nodes, join_nodes,
operations::{contains, ContainsSymbol},
pattern::{ObjectPattern, ObjectPatternElement}, pattern::{ObjectPattern, ObjectPatternElement},
property::{MethodDefinitionKind, PropertyName}, property::{MethodDefinitionKind, PropertyName},
scope::FunctionScopes,
try_break, try_break,
visitor::{VisitWith, Visitor, VisitorMut}, visitor::{VisitWith, Visitor, VisitorMut},
}; };
@ -401,27 +403,35 @@ impl VisitWith for PropertyDefinition {
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ObjectMethodDefinition { pub struct ObjectMethodDefinition {
name: PropertyName, pub(crate) name: PropertyName,
parameters: FormalParameterList, pub(crate) parameters: FormalParameterList,
body: FunctionBody, pub(crate) body: FunctionBody,
pub(crate) contains_direct_eval: bool,
kind: MethodDefinitionKind, kind: MethodDefinitionKind,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scopes: FunctionScopes,
} }
impl ObjectMethodDefinition { impl ObjectMethodDefinition {
/// Creates a new object method definition. /// Creates a new object method definition.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new( pub fn new(
name: PropertyName, name: PropertyName,
parameters: FormalParameterList, parameters: FormalParameterList,
body: FunctionBody, body: FunctionBody,
kind: MethodDefinitionKind, kind: MethodDefinitionKind,
) -> Self { ) -> Self {
let contains_direct_eval = contains(&parameters, ContainsSymbol::DirectEval)
|| contains(&body, ContainsSymbol::DirectEval);
Self { Self {
name, name,
parameters, parameters,
body, body,
contains_direct_eval,
kind, kind,
scopes: FunctionScopes::default(),
} }
} }
@ -452,6 +462,13 @@ impl ObjectMethodDefinition {
pub const fn kind(&self) -> MethodDefinitionKind { pub const fn kind(&self) -> MethodDefinitionKind {
self.kind self.kind
} }
/// Gets the scopes of the object method definition.
#[inline]
#[must_use]
pub const fn scopes(&self) -> &FunctionScopes {
&self.scopes
}
} }
impl ToIndentedString for ObjectMethodDefinition { impl ToIndentedString for ObjectMethodDefinition {
@ -467,7 +484,7 @@ impl ToIndentedString for ObjectMethodDefinition {
}; };
let name = self.name.to_interned_string(interner); let name = self.name.to_interned_string(interner);
let parameters = join_nodes(interner, self.parameters.as_ref()); let parameters = join_nodes(interner, self.parameters.as_ref());
let body = block_to_string(self.body.statements(), interner, indent_n + 1); let body = block_to_string(&self.body.statements, interner, indent_n + 1);
format!("{indentation}{prefix}{name}({parameters}) {body},\n") format!("{indentation}{prefix}{name}({parameters}) {body},\n")
} }
} }
@ -479,7 +496,7 @@ impl VisitWith for ObjectMethodDefinition {
{ {
try_break!(visitor.visit_property_name(&self.name)); try_break!(visitor.visit_property_name(&self.name));
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_script(&self.body) visitor.visit_function_body(&self.body)
} }
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -488,6 +505,6 @@ impl VisitWith for ObjectMethodDefinition {
{ {
try_break!(visitor.visit_property_name_mut(&mut self.name)); try_break!(visitor.visit_property_name_mut(&mut self.name));
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_script_mut(&mut self.body) visitor.visit_function_body_mut(&mut self.body)
} }
} }

35
core/ast/src/function/arrow_function.rs

@ -1,3 +1,5 @@
use crate::operations::{contains, ContainsSymbol};
use crate::scope::FunctionScopes;
use crate::try_break; use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{ use crate::{
@ -23,34 +25,42 @@ use super::{FormalParameterList, FunctionBody};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ArrowFunction { pub struct ArrowFunction {
pub(crate) name: Option<Identifier>, pub(crate) name: Option<Identifier>,
parameters: FormalParameterList, pub(crate) parameters: FormalParameterList,
body: FunctionBody, pub(crate) body: FunctionBody,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scopes: FunctionScopes,
} }
impl ArrowFunction { impl ArrowFunction {
/// Creates a new `ArrowFunctionDecl` AST Expression. /// Creates a new `ArrowFunctionDecl` AST Expression.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new( pub fn new(
name: Option<Identifier>, name: Option<Identifier>,
params: FormalParameterList, parameters: FormalParameterList,
body: FunctionBody, body: FunctionBody,
) -> Self { ) -> Self {
let contains_direct_eval = contains(&parameters, ContainsSymbol::DirectEval)
|| contains(&body, ContainsSymbol::DirectEval);
Self { Self {
name, name,
parameters: params, parameters,
body, body,
contains_direct_eval,
scopes: FunctionScopes::default(),
} }
} }
/// Gets the name of the function declaration. /// Gets the name of the arrow function.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn name(&self) -> Option<Identifier> { pub const fn name(&self) -> Option<Identifier> {
self.name self.name
} }
/// Sets the name of the function declaration. /// Sets the name of the arrow function.
#[inline] #[inline]
pub fn set_name(&mut self, name: Option<Identifier>) { pub fn set_name(&mut self, name: Option<Identifier>) {
self.name = name; self.name = name;
@ -69,6 +79,13 @@ impl ArrowFunction {
pub const fn body(&self) -> &FunctionBody { pub const fn body(&self) -> &FunctionBody {
&self.body &self.body
} }
/// Returns the scopes of the arrow function.
#[inline]
#[must_use]
pub const fn scopes(&self) -> &FunctionScopes {
&self.scopes
}
} }
impl ToIndentedString for ArrowFunction { impl ToIndentedString for ArrowFunction {
@ -102,7 +119,7 @@ impl VisitWith for ArrowFunction {
try_break!(visitor.visit_identifier(ident)); try_break!(visitor.visit_identifier(ident));
} }
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_script(&self.body) visitor.visit_function_body(&self.body)
} }
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -113,6 +130,6 @@ impl VisitWith for ArrowFunction {
try_break!(visitor.visit_identifier_mut(ident)); try_break!(visitor.visit_identifier_mut(ident));
} }
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_script_mut(&mut self.body) visitor.visit_function_body_mut(&mut self.body)
} }
} }

35
core/ast/src/function/async_arrow_function.rs

@ -1,6 +1,8 @@
use std::ops::ControlFlow; use std::ops::ControlFlow;
use super::{FormalParameterList, FunctionBody}; use super::{FormalParameterList, FunctionBody};
use crate::operations::{contains, ContainsSymbol};
use crate::scope::FunctionScopes;
use crate::try_break; use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{ use crate::{
@ -23,52 +25,67 @@ use boa_interner::{Interner, ToIndentedString};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct AsyncArrowFunction { pub struct AsyncArrowFunction {
pub(crate) name: Option<Identifier>, pub(crate) name: Option<Identifier>,
parameters: FormalParameterList, pub(crate) parameters: FormalParameterList,
body: FunctionBody, pub(crate) body: FunctionBody,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scopes: FunctionScopes,
} }
impl AsyncArrowFunction { impl AsyncArrowFunction {
/// Creates a new `AsyncArrowFunction` AST Expression. /// Creates a new `AsyncArrowFunction` AST Expression.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new( pub fn new(
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: FunctionBody, body: FunctionBody,
) -> Self { ) -> Self {
let contains_direct_eval = contains(&parameters, ContainsSymbol::DirectEval)
|| contains(&body, ContainsSymbol::DirectEval);
Self { Self {
name, name,
parameters, parameters,
body, body,
contains_direct_eval,
scopes: FunctionScopes::default(),
} }
} }
/// Gets the name of the function declaration. /// Gets the name of the async arrow function.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn name(&self) -> Option<Identifier> { pub const fn name(&self) -> Option<Identifier> {
self.name self.name
} }
/// Sets the name of the function declaration. /// Sets the name of the async arrow function.
#[inline] #[inline]
pub fn set_name(&mut self, name: Option<Identifier>) { pub fn set_name(&mut self, name: Option<Identifier>) {
self.name = name; self.name = name;
} }
/// Gets the list of parameters of the arrow function. /// Gets the list of parameters of the async arrow function.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn parameters(&self) -> &FormalParameterList { pub const fn parameters(&self) -> &FormalParameterList {
&self.parameters &self.parameters
} }
/// Gets the body of the arrow function. /// Gets the body of the async arrow function.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn body(&self) -> &FunctionBody { pub const fn body(&self) -> &FunctionBody {
&self.body &self.body
} }
/// Returns the scopes of the async arrow function.
#[inline]
#[must_use]
pub const fn scopes(&self) -> &FunctionScopes {
&self.scopes
}
} }
impl ToIndentedString for AsyncArrowFunction { impl ToIndentedString for AsyncArrowFunction {
@ -102,7 +119,7 @@ impl VisitWith for AsyncArrowFunction {
try_break!(visitor.visit_identifier(ident)); try_break!(visitor.visit_identifier(ident));
} }
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_script(&self.body) visitor.visit_function_body(&self.body)
} }
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -113,6 +130,6 @@ impl VisitWith for AsyncArrowFunction {
try_break!(visitor.visit_identifier_mut(ident)); try_break!(visitor.visit_identifier_mut(ident));
} }
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_script_mut(&mut self.body) visitor.visit_function_body_mut(&mut self.body)
} }
} }

74
core/ast/src/function/async_function.rs

@ -4,7 +4,10 @@ use super::{FormalParameterList, FunctionBody};
use crate::{ use crate::{
block_to_string, block_to_string,
expression::{Expression, Identifier}, expression::{Expression, Identifier},
join_nodes, try_break, join_nodes,
operations::{contains, ContainsSymbol},
scope::{FunctionScopes, Scope},
try_break,
visitor::{VisitWith, Visitor, VisitorMut}, visitor::{VisitWith, Visitor, VisitorMut},
Declaration, Declaration,
}; };
@ -24,23 +27,27 @@ use core::ops::ControlFlow;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct AsyncFunctionDeclaration { pub struct AsyncFunctionDeclaration {
name: Identifier, name: Identifier,
parameters: FormalParameterList, pub(crate) parameters: FormalParameterList,
body: FunctionBody, pub(crate) body: FunctionBody,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scopes: FunctionScopes,
} }
impl AsyncFunctionDeclaration { impl AsyncFunctionDeclaration {
/// Creates a new async function declaration. /// Creates a new async function declaration.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new( pub fn new(name: Identifier, parameters: FormalParameterList, body: FunctionBody) -> Self {
name: Identifier, let contains_direct_eval = contains(&parameters, ContainsSymbol::DirectEval)
parameters: FormalParameterList, || contains(&body, ContainsSymbol::DirectEval);
body: FunctionBody,
) -> Self {
Self { Self {
name, name,
parameters, parameters,
body, body,
contains_direct_eval,
scopes: FunctionScopes::default(),
} }
} }
@ -64,6 +71,13 @@ impl AsyncFunctionDeclaration {
pub const fn body(&self) -> &FunctionBody { pub const fn body(&self) -> &FunctionBody {
&self.body &self.body
} }
/// Gets the scopes of the async function declaration.
#[inline]
#[must_use]
pub const fn scopes(&self) -> &FunctionScopes {
&self.scopes
}
} }
impl ToIndentedString for AsyncFunctionDeclaration { impl ToIndentedString for AsyncFunctionDeclaration {
@ -72,7 +86,7 @@ impl ToIndentedString for AsyncFunctionDeclaration {
"async function {}({}) {}", "async function {}({}) {}",
interner.resolve_expect(self.name.sym()), interner.resolve_expect(self.name.sym()),
join_nodes(interner, self.parameters.as_ref()), join_nodes(interner, self.parameters.as_ref()),
block_to_string(self.body.statements(), interner, indentation) block_to_string(&self.body.statements, interner, indentation)
) )
} }
} }
@ -84,7 +98,7 @@ impl VisitWith for AsyncFunctionDeclaration {
{ {
try_break!(visitor.visit_identifier(&self.name)); try_break!(visitor.visit_identifier(&self.name));
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_script(&self.body) visitor.visit_function_body(&self.body)
} }
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -93,7 +107,7 @@ impl VisitWith for AsyncFunctionDeclaration {
{ {
try_break!(visitor.visit_identifier_mut(&mut self.name)); try_break!(visitor.visit_identifier_mut(&mut self.name));
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_script_mut(&mut self.body) visitor.visit_function_body_mut(&mut self.body)
} }
} }
@ -117,26 +131,38 @@ impl From<AsyncFunctionDeclaration> for Declaration {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct AsyncFunctionExpression { pub struct AsyncFunctionExpression {
pub(crate) name: Option<Identifier>, pub(crate) name: Option<Identifier>,
parameters: FormalParameterList, pub(crate) parameters: FormalParameterList,
body: FunctionBody, pub(crate) body: FunctionBody,
has_binding_identifier: bool, pub(crate) has_binding_identifier: bool,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) name_scope: Option<Scope>,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scopes: FunctionScopes,
} }
impl AsyncFunctionExpression { impl AsyncFunctionExpression {
/// Creates a new async function expression. /// Creates a new async function expression.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new( pub fn new(
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: FunctionBody, body: FunctionBody,
has_binding_identifier: bool, has_binding_identifier: bool,
) -> Self { ) -> Self {
let contains_direct_eval = contains(&parameters, ContainsSymbol::DirectEval)
|| contains(&body, ContainsSymbol::DirectEval);
Self { Self {
name, name,
parameters, parameters,
body, body,
has_binding_identifier, has_binding_identifier,
name_scope: None,
contains_direct_eval,
scopes: FunctionScopes::default(),
} }
} }
@ -167,6 +193,20 @@ impl AsyncFunctionExpression {
pub const fn has_binding_identifier(&self) -> bool { pub const fn has_binding_identifier(&self) -> bool {
self.has_binding_identifier self.has_binding_identifier
} }
/// Gets the name scope of the async function expression.
#[inline]
#[must_use]
pub const fn name_scope(&self) -> Option<&Scope> {
self.name_scope.as_ref()
}
/// Gets the scopes of the async function expression.
#[inline]
#[must_use]
pub const fn scopes(&self) -> &FunctionScopes {
&self.scopes
}
} }
impl ToIndentedString for AsyncFunctionExpression { impl ToIndentedString for AsyncFunctionExpression {
@ -210,7 +250,7 @@ impl VisitWith for AsyncFunctionExpression {
try_break!(visitor.visit_identifier(ident)); try_break!(visitor.visit_identifier(ident));
} }
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_script(&self.body) visitor.visit_function_body(&self.body)
} }
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -221,6 +261,6 @@ impl VisitWith for AsyncFunctionExpression {
try_break!(visitor.visit_identifier_mut(ident)); try_break!(visitor.visit_identifier_mut(ident));
} }
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_script_mut(&mut self.body) visitor.visit_function_body_mut(&mut self.body)
} }
} }

73
core/ast/src/function/async_generator.rs

@ -1,4 +1,6 @@
//! Async Generator Expression //! Async Generator Expression
use crate::operations::{contains, ContainsSymbol};
use crate::scope::{FunctionScopes, Scope};
use crate::try_break; use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{ use crate::{
@ -24,23 +26,27 @@ use super::{FormalParameterList, FunctionBody};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct AsyncGeneratorDeclaration { pub struct AsyncGeneratorDeclaration {
name: Identifier, name: Identifier,
parameters: FormalParameterList, pub(crate) parameters: FormalParameterList,
body: FunctionBody, pub(crate) body: FunctionBody,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scopes: FunctionScopes,
} }
impl AsyncGeneratorDeclaration { impl AsyncGeneratorDeclaration {
/// Creates a new async generator declaration. /// Creates a new async generator declaration.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new( pub fn new(name: Identifier, parameters: FormalParameterList, body: FunctionBody) -> Self {
name: Identifier, let contains_direct_eval = contains(&parameters, ContainsSymbol::DirectEval)
parameters: FormalParameterList, || contains(&body, ContainsSymbol::DirectEval);
body: FunctionBody,
) -> Self {
Self { Self {
name, name,
parameters, parameters,
body, body,
contains_direct_eval,
scopes: FunctionScopes::default(),
} }
} }
@ -64,6 +70,13 @@ impl AsyncGeneratorDeclaration {
pub const fn body(&self) -> &FunctionBody { pub const fn body(&self) -> &FunctionBody {
&self.body &self.body
} }
/// Gets the scopes of the async generator declaration.
#[inline]
#[must_use]
pub const fn scopes(&self) -> &FunctionScopes {
&self.scopes
}
} }
impl ToIndentedString for AsyncGeneratorDeclaration { impl ToIndentedString for AsyncGeneratorDeclaration {
@ -72,7 +85,7 @@ impl ToIndentedString for AsyncGeneratorDeclaration {
"async function* {}({}) {}", "async function* {}({}) {}",
interner.resolve_expect(self.name.sym()), interner.resolve_expect(self.name.sym()),
join_nodes(interner, self.parameters.as_ref()), join_nodes(interner, self.parameters.as_ref()),
block_to_string(self.body.statements(), interner, indentation) block_to_string(&self.body.statements, interner, indentation)
) )
} }
} }
@ -84,7 +97,7 @@ impl VisitWith for AsyncGeneratorDeclaration {
{ {
try_break!(visitor.visit_identifier(&self.name)); try_break!(visitor.visit_identifier(&self.name));
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_script(&self.body) visitor.visit_function_body(&self.body)
} }
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -93,7 +106,7 @@ impl VisitWith for AsyncGeneratorDeclaration {
{ {
try_break!(visitor.visit_identifier_mut(&mut self.name)); try_break!(visitor.visit_identifier_mut(&mut self.name));
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_script_mut(&mut self.body) visitor.visit_function_body_mut(&mut self.body)
} }
} }
@ -117,26 +130,38 @@ impl From<AsyncGeneratorDeclaration> for Declaration {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct AsyncGeneratorExpression { pub struct AsyncGeneratorExpression {
pub(crate) name: Option<Identifier>, pub(crate) name: Option<Identifier>,
parameters: FormalParameterList, pub(crate) parameters: FormalParameterList,
body: FunctionBody, pub(crate) body: FunctionBody,
has_binding_identifier: bool, pub(crate) has_binding_identifier: bool,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) name_scope: Option<Scope>,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scopes: FunctionScopes,
} }
impl AsyncGeneratorExpression { impl AsyncGeneratorExpression {
/// Creates a new async generator expression. /// Creates a new async generator expression.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new( pub fn new(
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: FunctionBody, body: FunctionBody,
has_binding_identifier: bool, has_binding_identifier: bool,
) -> Self { ) -> Self {
let contains_direct_eval = contains(&parameters, ContainsSymbol::DirectEval)
|| contains(&body, ContainsSymbol::DirectEval);
Self { Self {
name, name,
parameters, parameters,
body, body,
has_binding_identifier, has_binding_identifier,
name_scope: None,
contains_direct_eval,
scopes: FunctionScopes::default(),
} }
} }
@ -167,6 +192,20 @@ impl AsyncGeneratorExpression {
pub const fn has_binding_identifier(&self) -> bool { pub const fn has_binding_identifier(&self) -> bool {
self.has_binding_identifier self.has_binding_identifier
} }
/// Gets the name scope of the async generator expression.
#[inline]
#[must_use]
pub const fn name_scope(&self) -> Option<&Scope> {
self.name_scope.as_ref()
}
/// Gets the scopes of the async generator expression.
#[inline]
#[must_use]
pub const fn scopes(&self) -> &FunctionScopes {
&self.scopes
}
} }
impl ToIndentedString for AsyncGeneratorExpression { impl ToIndentedString for AsyncGeneratorExpression {
@ -180,7 +219,7 @@ impl ToIndentedString for AsyncGeneratorExpression {
buf.push_str(&format!( buf.push_str(&format!(
"({}) {}", "({}) {}",
join_nodes(interner, self.parameters.as_ref()), join_nodes(interner, self.parameters.as_ref()),
block_to_string(self.body.statements(), interner, indentation) block_to_string(&self.body.statements, interner, indentation)
)); ));
buf buf
@ -203,7 +242,7 @@ impl VisitWith for AsyncGeneratorExpression {
try_break!(visitor.visit_identifier(ident)); try_break!(visitor.visit_identifier(ident));
} }
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_script(&self.body) visitor.visit_function_body(&self.body)
} }
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -214,6 +253,6 @@ impl VisitWith for AsyncGeneratorExpression {
try_break!(visitor.visit_identifier_mut(ident)); try_break!(visitor.visit_identifier_mut(ident));
} }
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_script_mut(&mut self.body) visitor.visit_function_body_mut(&mut self.body)
} }
} }

262
core/ast/src/function/class.rs

@ -3,7 +3,9 @@ use crate::{
block_to_string, block_to_string,
expression::{Expression, Identifier}, expression::{Expression, Identifier},
join_nodes, join_nodes,
operations::{contains, ContainsSymbol},
property::{MethodDefinitionKind, PropertyName}, property::{MethodDefinitionKind, PropertyName},
scope::{FunctionScopes, Scope},
try_break, try_break,
visitor::{VisitWith, Visitor, VisitorMut}, visitor::{VisitWith, Visitor, VisitorMut},
Declaration, Declaration,
@ -25,9 +27,12 @@ use std::hash::Hash;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ClassDeclaration { pub struct ClassDeclaration {
name: Identifier, name: Identifier,
super_ref: Option<Expression>, pub(crate) super_ref: Option<Expression>,
pub(crate) constructor: Option<FunctionExpression>, pub(crate) constructor: Option<FunctionExpression>,
pub(crate) elements: Box<[ClassElement]>, pub(crate) elements: Box<[ClassElement]>,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) name_scope: Scope,
} }
impl ClassDeclaration { impl ClassDeclaration {
@ -45,6 +50,7 @@ impl ClassDeclaration {
super_ref, super_ref,
constructor, constructor,
elements, elements,
name_scope: Scope::default(),
} }
} }
@ -75,6 +81,13 @@ impl ClassDeclaration {
pub const fn elements(&self) -> &[ClassElement] { pub const fn elements(&self) -> &[ClassElement] {
&self.elements &self.elements
} }
/// Gets the scope containing the class name binding.
#[inline]
#[must_use]
pub const fn name_scope(&self) -> &Scope {
&self.name_scope
}
} }
impl ToIndentedString for ClassDeclaration { impl ToIndentedString for ClassDeclaration {
@ -96,7 +109,7 @@ impl ToIndentedString for ClassDeclaration {
buf.push_str(&format!( buf.push_str(&format!(
"{indentation}constructor({}) {}\n", "{indentation}constructor({}) {}\n",
join_nodes(interner, expr.parameters().as_ref()), join_nodes(interner, expr.parameters().as_ref()),
block_to_string(expr.body().statements(), interner, indent_n + 1) block_to_string(&expr.body.statements, interner, indent_n + 1)
)); ));
} }
for element in &self.elements { for element in &self.elements {
@ -162,10 +175,12 @@ impl From<ClassDeclaration> for Declaration {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ClassExpression { pub struct ClassExpression {
pub(crate) name: Option<Identifier>, pub(crate) name: Option<Identifier>,
super_ref: Option<Expression>, pub(crate) super_ref: Option<Expression>,
pub(crate) constructor: Option<FunctionExpression>, pub(crate) constructor: Option<FunctionExpression>,
pub(crate) elements: Box<[ClassElement]>, pub(crate) elements: Box<[ClassElement]>,
has_binding_identifier: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) name_scope: Option<Scope>,
} }
impl ClassExpression { impl ClassExpression {
@ -179,12 +194,17 @@ impl ClassExpression {
elements: Box<[ClassElement]>, elements: Box<[ClassElement]>,
has_binding_identifier: bool, has_binding_identifier: bool,
) -> Self { ) -> Self {
let name_scope = if has_binding_identifier {
Some(Scope::default())
} else {
None
};
Self { Self {
name, name,
super_ref, super_ref,
constructor, constructor,
elements, elements,
has_binding_identifier, name_scope,
} }
} }
@ -215,12 +235,19 @@ impl ClassExpression {
pub const fn elements(&self) -> &[ClassElement] { pub const fn elements(&self) -> &[ClassElement] {
&self.elements &self.elements
} }
/// Gets the scope containing the class name binding if it exists.
#[inline]
#[must_use]
pub const fn name_scope(&self) -> Option<&Scope> {
self.name_scope.as_ref()
}
} }
impl ToIndentedString for ClassExpression { impl ToIndentedString for ClassExpression {
fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String { fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String {
let mut buf = "class".to_string(); let mut buf = "class".to_string();
if self.has_binding_identifier { if self.name_scope.is_some() {
if let Some(name) = self.name { if let Some(name) = self.name {
buf.push_str(&format!(" {}", interner.resolve_expect(name.sym()))); buf.push_str(&format!(" {}", interner.resolve_expect(name.sym())));
} }
@ -241,7 +268,7 @@ impl ToIndentedString for ClassExpression {
buf.push_str(&format!( buf.push_str(&format!(
"{indentation}constructor({}) {}\n", "{indentation}constructor({}) {}\n",
join_nodes(interner, expr.parameters().as_ref()), join_nodes(interner, expr.parameters().as_ref()),
block_to_string(expr.body().statements(), interner, indent_n + 1) block_to_string(&expr.body.statements, interner, indent_n + 1)
)); ));
} }
for element in &self.elements { for element in &self.elements {
@ -303,7 +330,41 @@ impl VisitWith for ClassExpression {
/// Just an alias for [`Script`](crate::Script), since it has the same exact semantics. /// Just an alias for [`Script`](crate::Script), since it has the same exact semantics.
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-ClassStaticBlockBody /// [spec]: https://tc39.es/ecma262/#prod-ClassStaticBlockBody
type StaticBlockBody = crate::Script; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct StaticBlockBody {
pub(crate) body: FunctionBody,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scopes: FunctionScopes,
}
impl StaticBlockBody {
/// Creates a new static block body.
#[inline]
#[must_use]
pub fn new(body: FunctionBody) -> Self {
Self {
body,
scopes: FunctionScopes::default(),
}
}
/// Gets the body static block.
#[inline]
#[must_use]
pub const fn statements(&self) -> &FunctionBody {
&self.body
}
/// Gets the scopes of the static block body.
#[inline]
#[must_use]
pub const fn scopes(&self) -> &FunctionScopes {
&self.scopes
}
}
/// An element that can be within a class. /// An element that can be within a class.
/// ///
@ -319,13 +380,13 @@ pub enum ClassElement {
MethodDefinition(ClassMethodDefinition), MethodDefinition(ClassMethodDefinition),
/// A field definition. /// A field definition.
FieldDefinition(PropertyName, Option<Expression>), FieldDefinition(ClassFieldDefinition),
/// A static field definition, accessible from the class constructor object /// A static field definition, accessible from the class constructor object
StaticFieldDefinition(PropertyName, Option<Expression>), StaticFieldDefinition(ClassFieldDefinition),
/// A private field definition, only accessible inside the class declaration. /// A private field definition, only accessible inside the class declaration.
PrivateFieldDefinition(PrivateName, Option<Expression>), PrivateFieldDefinition(PrivateFieldDefinition),
/// A private static field definition, only accessible from static methods and fields inside the /// A private static field definition, only accessible from static methods and fields inside the
/// class declaration. /// class declaration.
@ -335,39 +396,145 @@ pub enum ClassElement {
StaticBlock(StaticBlockBody), StaticBlock(StaticBlockBody),
} }
/// A non-private class element field definition.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-FieldDefinition
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ClassFieldDefinition {
pub(crate) name: PropertyName,
pub(crate) field: Option<Expression>,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scope: Scope,
}
impl ClassFieldDefinition {
/// Creates a new class field definition.
#[inline]
#[must_use]
pub fn new(name: PropertyName, field: Option<Expression>) -> Self {
Self {
name,
field,
scope: Scope::default(),
}
}
/// Returns the name of the class field definition.
#[inline]
#[must_use]
pub const fn name(&self) -> &PropertyName {
&self.name
}
/// Returns the field of the class field definition.
#[inline]
#[must_use]
pub const fn field(&self) -> Option<&Expression> {
self.field.as_ref()
}
/// Returns the scope of the class field definition.
#[inline]
#[must_use]
pub const fn scope(&self) -> &Scope {
&self.scope
}
}
/// A private class element field definition.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-FieldDefinition
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct PrivateFieldDefinition {
pub(crate) name: PrivateName,
pub(crate) field: Option<Expression>,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scope: Scope,
}
impl PrivateFieldDefinition {
/// Creates a new private field definition.
#[inline]
#[must_use]
pub fn new(name: PrivateName, field: Option<Expression>) -> Self {
Self {
name,
field,
scope: Scope::default(),
}
}
/// Returns the name of the private field definition.
#[inline]
#[must_use]
pub const fn name(&self) -> &PrivateName {
&self.name
}
/// Returns the field of the private field definition.
#[inline]
#[must_use]
pub const fn field(&self) -> Option<&Expression> {
self.field.as_ref()
}
/// Returns the scope of the private field definition.
#[inline]
#[must_use]
pub const fn scope(&self) -> &Scope {
&self.scope
}
}
impl ToIndentedString for ClassElement { impl ToIndentedString for ClassElement {
fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String { fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String {
let indentation = " ".repeat(indent_n + 1); let indentation = " ".repeat(indent_n + 1);
match self { match self {
Self::MethodDefinition(m) => m.to_indented_string(interner, indent_n), Self::MethodDefinition(m) => m.to_indented_string(interner, indent_n),
Self::FieldDefinition(name, field) => match field { Self::FieldDefinition(field) => match &field.field {
Some(expr) => { Some(expr) => {
format!( format!(
"{indentation}{} = {};\n", "{indentation}{} = {};\n",
name.to_interned_string(interner), field.name.to_interned_string(interner),
expr.to_no_indent_string(interner, indent_n + 1) expr.to_no_indent_string(interner, indent_n + 1)
) )
} }
None => { None => {
format!("{indentation}{};\n", name.to_interned_string(interner),) format!(
"{indentation}{};\n",
field.name.to_interned_string(interner),
)
} }
}, },
Self::StaticFieldDefinition(name, field) => match field { Self::StaticFieldDefinition(field) => match &field.field {
Some(expr) => { Some(expr) => {
format!( format!(
"{indentation}static {} = {};\n", "{indentation}static {} = {};\n",
name.to_interned_string(interner), field.name.to_interned_string(interner),
expr.to_no_indent_string(interner, indent_n + 1) expr.to_no_indent_string(interner, indent_n + 1)
) )
} }
None => { None => {
format!( format!(
"{indentation}static {};\n", "{indentation}static {};\n",
name.to_interned_string(interner), field.name.to_interned_string(interner),
) )
} }
}, },
Self::PrivateFieldDefinition(name, field) => match field { Self::PrivateFieldDefinition(PrivateFieldDefinition { name, field, .. }) => match field
{
Some(expr) => { Some(expr) => {
format!( format!(
"{indentation}#{} = {};\n", "{indentation}#{} = {};\n",
@ -397,10 +564,10 @@ impl ToIndentedString for ClassElement {
) )
} }
}, },
Self::StaticBlock(body) => { Self::StaticBlock(block) => {
format!( format!(
"{indentation}static {}\n", "{indentation}static {}\n",
block_to_string(body.statements(), interner, indent_n + 1) block_to_string(&block.body.statements, interner, indent_n + 1)
) )
} }
} }
@ -423,26 +590,26 @@ impl VisitWith for ClassElement {
} }
} }
try_break!(visitor.visit_formal_parameter_list(&m.parameters)); try_break!(visitor.visit_formal_parameter_list(&m.parameters));
visitor.visit_script(&m.body) visitor.visit_function_body(&m.body)
} }
Self::FieldDefinition(pn, maybe_expr) | Self::StaticFieldDefinition(pn, maybe_expr) => { Self::FieldDefinition(field) | Self::StaticFieldDefinition(field) => {
try_break!(visitor.visit_property_name(pn)); try_break!(visitor.visit_property_name(&field.name));
if let Some(expr) = maybe_expr { if let Some(expr) = &field.field {
visitor.visit_expression(expr) visitor.visit_expression(expr)
} else { } else {
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
} }
Self::PrivateFieldDefinition(name, maybe_expr) Self::PrivateFieldDefinition(PrivateFieldDefinition { name, field, .. })
| Self::PrivateStaticFieldDefinition(name, maybe_expr) => { | Self::PrivateStaticFieldDefinition(name, field) => {
try_break!(visitor.visit_private_name(name)); try_break!(visitor.visit_private_name(name));
if let Some(expr) = maybe_expr { if let Some(expr) = field {
visitor.visit_expression(expr) visitor.visit_expression(expr)
} else { } else {
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
} }
Self::StaticBlock(sl) => visitor.visit_script(sl), Self::StaticBlock(block) => visitor.visit_function_body(&block.body),
} }
} }
@ -461,26 +628,26 @@ impl VisitWith for ClassElement {
} }
} }
try_break!(visitor.visit_formal_parameter_list_mut(&mut m.parameters)); try_break!(visitor.visit_formal_parameter_list_mut(&mut m.parameters));
visitor.visit_script_mut(&mut m.body) visitor.visit_function_body_mut(&mut m.body)
} }
Self::FieldDefinition(pn, maybe_expr) | Self::StaticFieldDefinition(pn, maybe_expr) => { Self::FieldDefinition(field) | Self::StaticFieldDefinition(field) => {
try_break!(visitor.visit_property_name_mut(pn)); try_break!(visitor.visit_property_name_mut(&mut field.name));
if let Some(expr) = maybe_expr { if let Some(expr) = &mut field.field {
visitor.visit_expression_mut(expr) visitor.visit_expression_mut(expr)
} else { } else {
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
} }
Self::PrivateFieldDefinition(name, maybe_expr) Self::PrivateFieldDefinition(PrivateFieldDefinition { name, field, .. })
| Self::PrivateStaticFieldDefinition(name, maybe_expr) => { | Self::PrivateStaticFieldDefinition(name, field) => {
try_break!(visitor.visit_private_name_mut(name)); try_break!(visitor.visit_private_name_mut(name));
if let Some(expr) = maybe_expr { if let Some(expr) = field {
visitor.visit_expression_mut(expr) visitor.visit_expression_mut(expr)
} else { } else {
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
} }
Self::StaticBlock(sl) => visitor.visit_script_mut(sl), Self::StaticBlock(block) => visitor.visit_function_body_mut(&mut block.body),
} }
} }
} }
@ -499,29 +666,37 @@ impl VisitWith for ClassElement {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ClassMethodDefinition { pub struct ClassMethodDefinition {
name: ClassElementName, name: ClassElementName,
parameters: FormalParameterList, pub(crate) parameters: FormalParameterList,
body: FunctionBody, pub(crate) body: FunctionBody,
pub(crate) contains_direct_eval: bool,
kind: MethodDefinitionKind, kind: MethodDefinitionKind,
is_static: bool, is_static: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scopes: FunctionScopes,
} }
impl ClassMethodDefinition { impl ClassMethodDefinition {
/// Creates a new class method definition. /// Creates a new class method definition.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new( pub fn new(
name: ClassElementName, name: ClassElementName,
parameters: FormalParameterList, parameters: FormalParameterList,
body: FunctionBody, body: FunctionBody,
kind: MethodDefinitionKind, kind: MethodDefinitionKind,
is_static: bool, is_static: bool,
) -> Self { ) -> Self {
let contains_direct_eval = contains(&parameters, ContainsSymbol::DirectEval)
|| contains(&body, ContainsSymbol::DirectEval);
Self { Self {
name, name,
parameters, parameters,
body, body,
contains_direct_eval,
kind, kind,
is_static, is_static,
scopes: FunctionScopes::default(),
} }
} }
@ -566,6 +741,13 @@ impl ClassMethodDefinition {
pub const fn is_private(&self) -> bool { pub const fn is_private(&self) -> bool {
self.name.is_private() self.name.is_private()
} }
/// Gets the scopes of the class method definition.
#[inline]
#[must_use]
pub const fn scopes(&self) -> &FunctionScopes {
&self.scopes
}
} }
impl ToIndentedString for ClassMethodDefinition { impl ToIndentedString for ClassMethodDefinition {
@ -587,7 +769,7 @@ impl ToIndentedString for ClassMethodDefinition {
}; };
let name = self.name.to_interned_string(interner); let name = self.name.to_interned_string(interner);
let parameters = join_nodes(interner, self.parameters.as_ref()); let parameters = join_nodes(interner, self.parameters.as_ref());
let body = block_to_string(self.body.statements(), interner, indent_n + 1); let body = block_to_string(&self.body.statements, interner, indent_n + 1);
format!("{indentation}{prefix}{name}({parameters}) {body}\n") format!("{indentation}{prefix}{name}({parameters}) {body}\n")
} }
} }

76
core/ast/src/function/generator.rs

@ -2,7 +2,10 @@ use super::{FormalParameterList, FunctionBody};
use crate::{ use crate::{
block_to_string, block_to_string,
expression::{Expression, Identifier}, expression::{Expression, Identifier},
join_nodes, try_break, join_nodes,
operations::{contains, ContainsSymbol},
scope::{FunctionScopes, Scope},
try_break,
visitor::{VisitWith, Visitor, VisitorMut}, visitor::{VisitWith, Visitor, VisitorMut},
Declaration, Declaration,
}; };
@ -22,23 +25,27 @@ use core::ops::ControlFlow;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct GeneratorDeclaration { pub struct GeneratorDeclaration {
name: Identifier, name: Identifier,
parameters: FormalParameterList, pub(crate) parameters: FormalParameterList,
body: FunctionBody, pub(crate) body: FunctionBody,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scopes: FunctionScopes,
} }
impl GeneratorDeclaration { impl GeneratorDeclaration {
/// Creates a new generator declaration. /// Creates a new generator declaration.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new( pub fn new(name: Identifier, parameters: FormalParameterList, body: FunctionBody) -> Self {
name: Identifier, let contains_direct_eval = contains(&parameters, ContainsSymbol::DirectEval)
parameters: FormalParameterList, || contains(&body, ContainsSymbol::DirectEval);
body: FunctionBody,
) -> Self {
Self { Self {
name, name,
parameters, parameters,
body, body,
contains_direct_eval,
scopes: FunctionScopes::default(),
} }
} }
@ -62,6 +69,13 @@ impl GeneratorDeclaration {
pub const fn body(&self) -> &FunctionBody { pub const fn body(&self) -> &FunctionBody {
&self.body &self.body
} }
/// Returns the scopes of the generator declaration.
#[inline]
#[must_use]
pub const fn scopes(&self) -> &FunctionScopes {
&self.scopes
}
} }
impl ToIndentedString for GeneratorDeclaration { impl ToIndentedString for GeneratorDeclaration {
@ -70,7 +84,7 @@ impl ToIndentedString for GeneratorDeclaration {
"function* {}({}) {}", "function* {}({}) {}",
interner.resolve_expect(self.name.sym()), interner.resolve_expect(self.name.sym()),
join_nodes(interner, self.parameters.as_ref()), join_nodes(interner, self.parameters.as_ref()),
block_to_string(self.body.statements(), interner, indentation) block_to_string(&self.body.statements, interner, indentation)
) )
} }
} }
@ -82,7 +96,7 @@ impl VisitWith for GeneratorDeclaration {
{ {
try_break!(visitor.visit_identifier(&self.name)); try_break!(visitor.visit_identifier(&self.name));
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_script(&self.body) visitor.visit_function_body(&self.body)
} }
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -91,7 +105,7 @@ impl VisitWith for GeneratorDeclaration {
{ {
try_break!(visitor.visit_identifier_mut(&mut self.name)); try_break!(visitor.visit_identifier_mut(&mut self.name));
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_script_mut(&mut self.body) visitor.visit_function_body_mut(&mut self.body)
} }
} }
@ -115,26 +129,38 @@ impl From<GeneratorDeclaration> for Declaration {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct GeneratorExpression { pub struct GeneratorExpression {
pub(crate) name: Option<Identifier>, pub(crate) name: Option<Identifier>,
parameters: FormalParameterList, pub(crate) parameters: FormalParameterList,
body: FunctionBody, pub(crate) body: FunctionBody,
has_binding_identifier: bool, pub(crate) has_binding_identifier: bool,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) name_scope: Option<Scope>,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scopes: FunctionScopes,
} }
impl GeneratorExpression { impl GeneratorExpression {
/// Creates a new generator expression. /// Creates a new generator expression.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new( pub fn new(
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: FunctionBody, body: FunctionBody,
has_binding_identifier: bool, has_binding_identifier: bool,
) -> Self { ) -> Self {
let contains_direct_eval = contains(&parameters, ContainsSymbol::DirectEval)
|| contains(&body, ContainsSymbol::DirectEval);
Self { Self {
name, name,
parameters, parameters,
body, body,
has_binding_identifier, has_binding_identifier,
name_scope: None,
contains_direct_eval,
scopes: FunctionScopes::default(),
} }
} }
@ -165,6 +191,20 @@ impl GeneratorExpression {
pub const fn has_binding_identifier(&self) -> bool { pub const fn has_binding_identifier(&self) -> bool {
self.has_binding_identifier self.has_binding_identifier
} }
/// Gets the name scope of the generator expression.
#[inline]
#[must_use]
pub const fn name_scope(&self) -> Option<&Scope> {
self.name_scope.as_ref()
}
/// Gets the scopes of the generator expression.
#[inline]
#[must_use]
pub const fn scopes(&self) -> &FunctionScopes {
&self.scopes
}
} }
impl ToIndentedString for GeneratorExpression { impl ToIndentedString for GeneratorExpression {
@ -178,7 +218,7 @@ impl ToIndentedString for GeneratorExpression {
buf.push_str(&format!( buf.push_str(&format!(
"({}) {}", "({}) {}",
join_nodes(interner, self.parameters.as_ref()), join_nodes(interner, self.parameters.as_ref()),
block_to_string(self.body.statements(), interner, indentation) block_to_string(&self.body.statements, interner, indentation)
)); ));
buf buf
@ -201,7 +241,7 @@ impl VisitWith for GeneratorExpression {
try_break!(visitor.visit_identifier(ident)); try_break!(visitor.visit_identifier(ident));
} }
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_script(&self.body) visitor.visit_function_body(&self.body)
} }
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -212,6 +252,6 @@ impl VisitWith for GeneratorExpression {
try_break!(visitor.visit_identifier_mut(ident)); try_break!(visitor.visit_identifier_mut(ident));
} }
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_script_mut(&mut self.body) visitor.visit_function_body_mut(&mut self.body)
} }
} }

90
core/ast/src/function/mod.rs

@ -32,27 +32,107 @@ mod generator;
mod ordinary_function; mod ordinary_function;
mod parameters; mod parameters;
use std::ops::ControlFlow;
pub use arrow_function::ArrowFunction; pub use arrow_function::ArrowFunction;
pub use async_arrow_function::AsyncArrowFunction; pub use async_arrow_function::AsyncArrowFunction;
pub use async_function::{AsyncFunctionDeclaration, AsyncFunctionExpression}; pub use async_function::{AsyncFunctionDeclaration, AsyncFunctionExpression};
pub use async_generator::{AsyncGeneratorDeclaration, AsyncGeneratorExpression}; pub use async_generator::{AsyncGeneratorDeclaration, AsyncGeneratorExpression};
use boa_interner::{Interner, ToIndentedString};
pub use class::{ pub use class::{
ClassDeclaration, ClassElement, ClassElementName, ClassExpression, ClassMethodDefinition, ClassDeclaration, ClassElement, ClassElementName, ClassExpression, ClassFieldDefinition,
PrivateName, ClassMethodDefinition, PrivateFieldDefinition, PrivateName, StaticBlockBody,
}; };
pub use generator::{GeneratorDeclaration, GeneratorExpression}; pub use generator::{GeneratorDeclaration, GeneratorExpression};
pub use ordinary_function::{FunctionDeclaration, FunctionExpression}; pub use ordinary_function::{FunctionDeclaration, FunctionExpression};
pub use parameters::{FormalParameter, FormalParameterList, FormalParameterListFlags}; pub use parameters::{FormalParameter, FormalParameterList, FormalParameterListFlags};
use crate::Script; use crate::{
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
StatementList, StatementListItem,
};
/// A Function body. /// A Function body.
/// ///
/// Since [`Script`] and `FunctionBody` has the same semantics, this is currently /// Since `Script` and `FunctionBody` have the same semantics, this is currently
/// only an alias of the former. /// only an alias of the former.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-FunctionBody /// [spec]: https://tc39.es/ecma262/#prod-FunctionBody
pub type FunctionBody = Script; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FunctionBody {
pub(crate) statements: StatementList,
}
impl FunctionBody {
/// Creates a new `FunctionBody` AST node.
#[must_use]
pub fn new<S>(statements: S, strict: bool) -> Self
where
S: Into<Box<[StatementListItem]>>,
{
Self {
statements: StatementList::new(statements.into(), strict),
}
}
/// Gets the list of statements.
#[inline]
#[must_use]
pub const fn statements(&self) -> &[StatementListItem] {
self.statements.statements()
}
/// Gets the statement list.
#[inline]
#[must_use]
pub const fn statement_list(&self) -> &StatementList {
&self.statements
}
/// Get the strict mode.
#[inline]
#[must_use]
pub const fn strict(&self) -> bool {
self.statements.strict()
}
}
impl From<StatementList> for FunctionBody {
fn from(statements: StatementList) -> Self {
Self { statements }
}
}
impl ToIndentedString for FunctionBody {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
self.statements.to_indented_string(interner, indentation)
}
}
impl VisitWith for FunctionBody {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for statement in &*self.statements {
try_break!(visitor.visit_statement_list_item(statement));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for statement in &mut *self.statements.statements {
try_break!(visitor.visit_statement_list_item_mut(statement));
}
ControlFlow::Continue(())
}
}

85
core/ast/src/function/ordinary_function.rs

@ -2,7 +2,11 @@ use super::{FormalParameterList, FunctionBody};
use crate::{ use crate::{
block_to_string, block_to_string,
expression::{Expression, Identifier}, expression::{Expression, Identifier},
join_nodes, try_break, join_nodes,
operations::{contains, ContainsSymbol},
scope::{FunctionScopes, Scope},
scope_analyzer::{analyze_binding_escapes, collect_bindings},
try_break,
visitor::{VisitWith, Visitor, VisitorMut}, visitor::{VisitWith, Visitor, VisitorMut},
Declaration, Declaration,
}; };
@ -22,23 +26,27 @@ use core::ops::ControlFlow;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct FunctionDeclaration { pub struct FunctionDeclaration {
name: Identifier, name: Identifier,
parameters: FormalParameterList, pub(crate) parameters: FormalParameterList,
body: FunctionBody, pub(crate) body: FunctionBody,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scopes: FunctionScopes,
} }
impl FunctionDeclaration { impl FunctionDeclaration {
/// Creates a new function declaration. /// Creates a new function declaration.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new( pub fn new(name: Identifier, parameters: FormalParameterList, body: FunctionBody) -> Self {
name: Identifier, let contains_direct_eval = contains(&parameters, ContainsSymbol::DirectEval)
parameters: FormalParameterList, || contains(&body, ContainsSymbol::DirectEval);
body: FunctionBody,
) -> Self {
Self { Self {
name, name,
parameters, parameters,
body, body,
contains_direct_eval,
scopes: FunctionScopes::default(),
} }
} }
@ -62,6 +70,13 @@ impl FunctionDeclaration {
pub const fn body(&self) -> &FunctionBody { pub const fn body(&self) -> &FunctionBody {
&self.body &self.body
} }
/// Gets the scopes of the function declaration.
#[inline]
#[must_use]
pub const fn scopes(&self) -> &FunctionScopes {
&self.scopes
}
} }
impl ToIndentedString for FunctionDeclaration { impl ToIndentedString for FunctionDeclaration {
@ -70,7 +85,7 @@ impl ToIndentedString for FunctionDeclaration {
"function {}({}) {}", "function {}({}) {}",
interner.resolve_expect(self.name.sym()), interner.resolve_expect(self.name.sym()),
join_nodes(interner, self.parameters.as_ref()), join_nodes(interner, self.parameters.as_ref()),
block_to_string(self.body.statements(), interner, indentation) block_to_string(&self.body.statements, interner, indentation)
) )
} }
} }
@ -82,7 +97,7 @@ impl VisitWith for FunctionDeclaration {
{ {
try_break!(visitor.visit_identifier(&self.name)); try_break!(visitor.visit_identifier(&self.name));
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_script(&self.body) visitor.visit_function_body(&self.body)
} }
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -91,7 +106,7 @@ impl VisitWith for FunctionDeclaration {
{ {
try_break!(visitor.visit_identifier_mut(&mut self.name)); try_break!(visitor.visit_identifier_mut(&mut self.name));
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_script_mut(&mut self.body) visitor.visit_function_body_mut(&mut self.body)
} }
} }
@ -115,26 +130,38 @@ impl From<FunctionDeclaration> for Declaration {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct FunctionExpression { pub struct FunctionExpression {
pub(crate) name: Option<Identifier>, pub(crate) name: Option<Identifier>,
parameters: FormalParameterList, pub(crate) parameters: FormalParameterList,
body: FunctionBody, pub(crate) body: FunctionBody,
has_binding_identifier: bool, pub(crate) has_binding_identifier: bool,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) name_scope: Option<Scope>,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scopes: FunctionScopes,
} }
impl FunctionExpression { impl FunctionExpression {
/// Creates a new function expression. /// Creates a new function expression.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new( pub fn new(
name: Option<Identifier>, name: Option<Identifier>,
parameters: FormalParameterList, parameters: FormalParameterList,
body: FunctionBody, body: FunctionBody,
has_binding_identifier: bool, has_binding_identifier: bool,
) -> Self { ) -> Self {
let contains_direct_eval = contains(&parameters, ContainsSymbol::DirectEval)
|| contains(&body, ContainsSymbol::DirectEval);
Self { Self {
name, name,
parameters, parameters,
body, body,
has_binding_identifier, has_binding_identifier,
name_scope: None,
contains_direct_eval,
scopes: FunctionScopes::default(),
} }
} }
@ -165,6 +192,28 @@ impl FunctionExpression {
pub const fn has_binding_identifier(&self) -> bool { pub const fn has_binding_identifier(&self) -> bool {
self.has_binding_identifier self.has_binding_identifier
} }
/// Gets the name scope of the function expression.
#[inline]
#[must_use]
pub const fn name_scope(&self) -> Option<&Scope> {
self.name_scope.as_ref()
}
/// Gets the scopes of the function expression.
#[inline]
#[must_use]
pub const fn scopes(&self) -> &FunctionScopes {
&self.scopes
}
/// Analyze the scope of the function expression.
pub fn analyze_scope(&mut self, strict: bool, scope: &Scope, interner: &Interner) -> bool {
if !collect_bindings(self, strict, false, scope, interner) {
return false;
}
analyze_binding_escapes(self, false, scope.clone(), interner)
}
} }
impl ToIndentedString for FunctionExpression { impl ToIndentedString for FunctionExpression {
@ -178,7 +227,7 @@ impl ToIndentedString for FunctionExpression {
buf.push_str(&format!( buf.push_str(&format!(
"({}) {}", "({}) {}",
join_nodes(interner, self.parameters.as_ref()), join_nodes(interner, self.parameters.as_ref()),
block_to_string(self.body.statements(), interner, indentation) block_to_string(&self.body.statements, interner, indentation)
)); ));
buf buf
@ -201,7 +250,7 @@ impl VisitWith for FunctionExpression {
try_break!(visitor.visit_identifier(ident)); try_break!(visitor.visit_identifier(ident));
} }
try_break!(visitor.visit_formal_parameter_list(&self.parameters)); try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_script(&self.body) visitor.visit_function_body(&self.body)
} }
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
@ -212,6 +261,6 @@ impl VisitWith for FunctionExpression {
try_break!(visitor.visit_identifier_mut(ident)); try_break!(visitor.visit_identifier_mut(ident));
} }
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_script_mut(&mut self.body) visitor.visit_function_body_mut(&mut self.body)
} }
} }

31
core/ast/src/lib.rs

@ -37,10 +37,14 @@ pub mod keyword;
pub mod operations; pub mod operations;
pub mod pattern; pub mod pattern;
pub mod property; pub mod property;
pub mod scope;
pub mod scope_analyzer;
pub mod statement; pub mod statement;
pub mod visitor; pub mod visitor;
use boa_interner::{Interner, ToIndentedString, ToInternedString}; use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
use boa_string::{JsStr, JsString};
use expression::Identifier;
pub use self::{ pub use self::{
declaration::Declaration, declaration::Declaration,
@ -105,3 +109,28 @@ impl ToStringEscaped for [u16] {
.collect() .collect()
} }
} }
pub(crate) trait ToJsString {
fn to_js_string(&self, interner: &Interner) -> JsString;
}
impl ToJsString for Sym {
#[allow(clippy::cast_possible_truncation)]
fn to_js_string(&self, interner: &Interner) -> JsString {
// TODO: Identify latin1 encodeable strings during parsing to avoid this check.
let string = interner.resolve_expect(*self).utf16();
for c in string {
if u8::try_from(*c).is_err() {
return JsString::from(string);
}
}
let string = string.iter().map(|c| *c as u8).collect::<Vec<_>>();
JsString::from(JsStr::latin1(&string))
}
}
impl ToJsString for Identifier {
fn to_js_string(&self, interner: &Interner) -> JsString {
self.sym().to_js_string(interner)
}
}

159
core/ast/src/operations.rs

@ -16,17 +16,17 @@ use crate::{
access::{PrivatePropertyAccess, SuperPropertyAccess}, access::{PrivatePropertyAccess, SuperPropertyAccess},
literal::PropertyDefinition, literal::PropertyDefinition,
operator::BinaryInPrivate, operator::BinaryInPrivate,
Await, Identifier, OptionalOperationKind, SuperCall, Yield, Await, Call, Identifier, OptionalOperationKind, SuperCall, Yield,
}, },
function::{ function::{
ArrowFunction, AsyncArrowFunction, AsyncFunctionDeclaration, AsyncFunctionExpression, ArrowFunction, AsyncArrowFunction, AsyncFunctionDeclaration, AsyncFunctionExpression,
AsyncGeneratorDeclaration, AsyncGeneratorExpression, ClassDeclaration, ClassElement, AsyncGeneratorDeclaration, AsyncGeneratorExpression, ClassDeclaration, ClassElement,
ClassElementName, ClassExpression, FormalParameterList, FunctionDeclaration, ClassElementName, ClassExpression, FormalParameterList, FunctionBody, FunctionDeclaration,
FunctionExpression, GeneratorDeclaration, GeneratorExpression, FunctionExpression, GeneratorDeclaration, GeneratorExpression, PrivateFieldDefinition,
}, },
statement::{ statement::{
iteration::{ForLoopInitializer, IterableLoopInitializer}, iteration::{ForLoopInitializer, IterableLoopInitializer},
LabelledItem, LabelledItem, With,
}, },
try_break, try_break,
visitor::{NodeRef, VisitWith, Visitor}, visitor::{NodeRef, VisitWith, Visitor},
@ -61,6 +61,8 @@ pub enum ContainsSymbol {
MethodDefinition, MethodDefinition,
/// The `BindingIdentifier` "eval" or "arguments". /// The `BindingIdentifier` "eval" or "arguments".
EvalOrArguments, EvalOrArguments,
/// A direct call to `eval`.
DirectEval,
} }
/// Returns `true` if the node contains the given symbol. /// Returns `true` if the node contains the given symbol.
@ -80,6 +82,26 @@ where
impl<'ast> Visitor<'ast> for ContainsVisitor { impl<'ast> Visitor<'ast> for ContainsVisitor {
type BreakTy = (); type BreakTy = ();
fn visit_with(&mut self, node: &'ast With) -> ControlFlow<Self::BreakTy> {
try_break!(node.expression().visit_with(self));
node.statement().visit_with(self)
}
fn visit_call(&mut self, node: &'ast Call) -> ControlFlow<Self::BreakTy> {
if self.0 == ContainsSymbol::DirectEval {
if let Expression::Identifier(ident) = node.function().flatten() {
if ident.sym() == Sym::EVAL {
return ControlFlow::Break(());
}
}
}
try_break!(node.function().visit_with(self));
for arg in node.args() {
try_break!(arg.visit_with(self));
}
ControlFlow::Continue(())
}
fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow<Self::BreakTy> { fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow<Self::BreakTy> {
if self.0 == ContainsSymbol::EvalOrArguments if self.0 == ContainsSymbol::EvalOrArguments
&& (node.sym() == Sym::EVAL || node.sym() == Sym::ARGUMENTS) && (node.sym() == Sym::EVAL || node.sym() == Sym::ARGUMENTS)
@ -179,14 +201,18 @@ where
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> { fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
match node { match node {
ClassElement::MethodDefinition(m) => { ClassElement::MethodDefinition(m) => {
if self.0 == ContainsSymbol::DirectEval {
return ControlFlow::Continue(());
}
if let ClassElementName::PropertyName(name) = m.name() { if let ClassElementName::PropertyName(name) = m.name() {
name.visit_with(self) name.visit_with(self)
} else { } else {
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
} }
ClassElement::FieldDefinition(name, _) ClassElement::FieldDefinition(field)
| ClassElement::StaticFieldDefinition(name, _) => name.visit_with(self), | ClassElement::StaticFieldDefinition(field) => field.name.visit_with(self),
_ => ControlFlow::Continue(()), _ => ControlFlow::Continue(()),
} }
} }
@ -196,6 +222,10 @@ where
node: &'ast PropertyDefinition, node: &'ast PropertyDefinition,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
if let PropertyDefinition::MethodDefinition(m) = node { if let PropertyDefinition::MethodDefinition(m) = node {
if self.0 == ContainsSymbol::DirectEval {
return ControlFlow::Continue(());
}
if self.0 == ContainsSymbol::MethodDefinition { if self.0 == ContainsSymbol::MethodDefinition {
return ControlFlow::Break(()); return ControlFlow::Break(());
} }
@ -215,6 +245,7 @@ where
ContainsSymbol::SuperCall, ContainsSymbol::SuperCall,
ContainsSymbol::Super, ContainsSymbol::Super,
ContainsSymbol::This, ContainsSymbol::This,
ContainsSymbol::DirectEval,
] ]
.contains(&self.0) .contains(&self.0)
{ {
@ -234,6 +265,7 @@ where
ContainsSymbol::SuperCall, ContainsSymbol::SuperCall,
ContainsSymbol::Super, ContainsSymbol::Super,
ContainsSymbol::This, ContainsSymbol::This,
ContainsSymbol::DirectEval,
] ]
.contains(&self.0) .contains(&self.0)
{ {
@ -401,7 +433,7 @@ where
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-hasdirectsuper /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-hasdirectsuper
#[must_use] #[must_use]
#[inline] #[inline]
pub fn has_direct_super_new(params: &FormalParameterList, body: &Script) -> bool { pub fn has_direct_super_new(params: &FormalParameterList, body: &FunctionBody) -> bool {
contains(params, ContainsSymbol::SuperCall) || contains(body, ContainsSymbol::SuperCall) contains(params, ContainsSymbol::SuperCall) || contains(body, ContainsSymbol::SuperCall)
} }
@ -600,6 +632,11 @@ impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T>
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
fn visit_function_body(&mut self, node: &'ast FunctionBody) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.statement_list(), self.0);
ControlFlow::Continue(())
}
fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> { fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> {
match node { match node {
// ModuleItem : ImportDeclaration // ModuleItem : ImportDeclaration
@ -655,72 +692,72 @@ impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T>
&mut self, &mut self,
node: &'ast FunctionExpression, node: &'ast FunctionExpression,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_function_declaration( fn visit_function_declaration(
&mut self, &mut self,
node: &'ast FunctionDeclaration, node: &'ast FunctionDeclaration,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_async_function_expression( fn visit_async_function_expression(
&mut self, &mut self,
node: &'ast AsyncFunctionExpression, node: &'ast AsyncFunctionExpression,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_async_function_declaration( fn visit_async_function_declaration(
&mut self, &mut self,
node: &'ast AsyncFunctionDeclaration, node: &'ast AsyncFunctionDeclaration,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_generator_expression( fn visit_generator_expression(
&mut self, &mut self,
node: &'ast GeneratorExpression, node: &'ast GeneratorExpression,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_generator_declaration( fn visit_generator_declaration(
&mut self, &mut self,
node: &'ast GeneratorDeclaration, node: &'ast GeneratorDeclaration,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_async_generator_expression( fn visit_async_generator_expression(
&mut self, &mut self,
node: &'ast AsyncGeneratorExpression, node: &'ast AsyncGeneratorExpression,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_async_generator_declaration( fn visit_async_generator_declaration(
&mut self, &mut self,
node: &'ast AsyncGeneratorDeclaration, node: &'ast AsyncGeneratorDeclaration,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow<Self::BreakTy> { fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_async_arrow_function( fn visit_async_arrow_function(
&mut self, &mut self,
node: &'ast AsyncArrowFunction, node: &'ast AsyncArrowFunction,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> { fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
if let ClassElement::StaticBlock(body) = node { if let ClassElement::StaticBlock(block) = node {
self.visit_script(body); self.visit_function_body(&block.body);
} }
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
@ -787,6 +824,11 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
fn visit_function_body(&mut self, node: &'ast FunctionBody) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.statement_list(), self.0);
ControlFlow::Continue(())
}
fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> { fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> {
match node { match node {
// ModuleItem : ImportDeclaration // ModuleItem : ImportDeclaration
@ -898,7 +940,7 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
self.visit(node.body()) self.visit(node.body())
} }
fn visit_with(&mut self, node: &'ast crate::statement::With) -> ControlFlow<Self::BreakTy> { fn visit_with(&mut self, node: &'ast With) -> ControlFlow<Self::BreakTy> {
self.visit(node.statement()) self.visit(node.statement())
} }
@ -933,61 +975,61 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
&mut self, &mut self,
node: &'ast FunctionExpression, node: &'ast FunctionExpression,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_function_declaration( fn visit_function_declaration(
&mut self, &mut self,
node: &'ast FunctionDeclaration, node: &'ast FunctionDeclaration,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_async_function_expression( fn visit_async_function_expression(
&mut self, &mut self,
node: &'ast AsyncFunctionExpression, node: &'ast AsyncFunctionExpression,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_async_function_declaration( fn visit_async_function_declaration(
&mut self, &mut self,
node: &'ast AsyncFunctionDeclaration, node: &'ast AsyncFunctionDeclaration,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_generator_expression( fn visit_generator_expression(
&mut self, &mut self,
node: &'ast GeneratorExpression, node: &'ast GeneratorExpression,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_generator_declaration( fn visit_generator_declaration(
&mut self, &mut self,
node: &'ast GeneratorDeclaration, node: &'ast GeneratorDeclaration,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_async_generator_expression( fn visit_async_generator_expression(
&mut self, &mut self,
node: &'ast AsyncGeneratorExpression, node: &'ast AsyncGeneratorExpression,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_async_generator_declaration( fn visit_async_generator_declaration(
&mut self, &mut self,
node: &'ast AsyncGeneratorDeclaration, node: &'ast AsyncGeneratorDeclaration,
) -> ControlFlow<Self::BreakTy> { ) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body()) self.visit_function_body(node.body())
} }
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> { fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
if let ClassElement::StaticBlock(body) = node { if let ClassElement::StaticBlock(block) = node {
self.visit_script(body); self.visit_function_body(&block.body);
} }
node.visit_with(self) node.visit_with(self)
} }
@ -1138,7 +1180,7 @@ impl<'ast> Visitor<'ast> for AllPrivateIdentifiersValidVisitor {
names.push(name.description()); names.push(name.description());
} }
} }
ClassElement::PrivateFieldDefinition(name, _) ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { name, .. })
| ClassElement::PrivateStaticFieldDefinition(name, _) => { | ClassElement::PrivateStaticFieldDefinition(name, _) => {
names.push(name.description()); names.push(name.description());
} }
@ -1161,21 +1203,21 @@ impl<'ast> Visitor<'ast> for AllPrivateIdentifiersValidVisitor {
try_break!(visitor.visit(m.parameters())); try_break!(visitor.visit(m.parameters()));
try_break!(visitor.visit(m.body())); try_break!(visitor.visit(m.body()));
} }
ClassElement::FieldDefinition(name, expression) ClassElement::FieldDefinition(field)
| ClassElement::StaticFieldDefinition(name, expression) => { | ClassElement::StaticFieldDefinition(field) => {
try_break!(visitor.visit(name)); try_break!(visitor.visit(&field.name));
if let Some(expression) = expression { if let Some(expression) = &field.field {
try_break!(visitor.visit(expression)); try_break!(visitor.visit(expression));
} }
} }
ClassElement::PrivateFieldDefinition(_, expression) ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { field, .. })
| ClassElement::PrivateStaticFieldDefinition(_, expression) => { | ClassElement::PrivateStaticFieldDefinition(_, field) => {
if let Some(expression) = expression { if let Some(expression) = field {
try_break!(visitor.visit(expression)); try_break!(visitor.visit(expression));
} }
} }
ClassElement::StaticBlock(statement_list) => { ClassElement::StaticBlock(block) => {
try_break!(visitor.visit(statement_list)); try_break!(visitor.visit(&block.body));
} }
} }
} }
@ -1199,7 +1241,7 @@ impl<'ast> Visitor<'ast> for AllPrivateIdentifiersValidVisitor {
names.push(name.description()); names.push(name.description());
} }
} }
ClassElement::PrivateFieldDefinition(name, _) ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { name, .. })
| ClassElement::PrivateStaticFieldDefinition(name, _) => { | ClassElement::PrivateStaticFieldDefinition(name, _) => {
names.push(name.description()); names.push(name.description());
} }
@ -1222,21 +1264,21 @@ impl<'ast> Visitor<'ast> for AllPrivateIdentifiersValidVisitor {
try_break!(visitor.visit(m.parameters())); try_break!(visitor.visit(m.parameters()));
try_break!(visitor.visit(m.body())); try_break!(visitor.visit(m.body()));
} }
ClassElement::FieldDefinition(name, expression) ClassElement::FieldDefinition(field)
| ClassElement::StaticFieldDefinition(name, expression) => { | ClassElement::StaticFieldDefinition(field) => {
try_break!(visitor.visit(name)); try_break!(visitor.visit(&field.name));
if let Some(expression) = expression { if let Some(expression) = &field.field {
try_break!(visitor.visit(expression)); try_break!(visitor.visit(expression));
} }
} }
ClassElement::PrivateFieldDefinition(_, expression) ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { field, .. })
| ClassElement::PrivateStaticFieldDefinition(_, expression) => { | ClassElement::PrivateStaticFieldDefinition(_, field) => {
if let Some(expression) = expression { if let Some(expression) = field {
try_break!(visitor.visit(expression)); try_break!(visitor.visit(expression));
} }
} }
ClassElement::StaticBlock(statement_list) => { ClassElement::StaticBlock(block) => {
try_break!(visitor.visit(statement_list)); try_break!(visitor.visit(&block.body));
} }
} }
} }
@ -1755,6 +1797,12 @@ impl<'ast> Visitor<'ast> for LexicallyScopedDeclarationsVisitor<'_, 'ast> {
TopLevelLexicallyScopedDeclarationsVisitor(self.0).visit_statement_list(node.statements()) TopLevelLexicallyScopedDeclarationsVisitor(self.0).visit_statement_list(node.statements())
} }
fn visit_function_body(&mut self, node: &'ast FunctionBody) -> ControlFlow<Self::BreakTy> {
// 1. Return TopLevelVarScopedDeclarations of StatementList.
TopLevelLexicallyScopedDeclarationsVisitor(self.0)
.visit_statement_list(node.statement_list())
}
fn visit_export_declaration( fn visit_export_declaration(
&mut self, &mut self,
node: &'ast ExportDeclaration, node: &'ast ExportDeclaration,
@ -1974,6 +2022,11 @@ impl<'ast> Visitor<'ast> for VarScopedDeclarationsVisitor<'_> {
TopLevelVarScopedDeclarationsVisitor(self.0).visit_statement_list(node.statements()) TopLevelVarScopedDeclarationsVisitor(self.0).visit_statement_list(node.statements())
} }
fn visit_function_body(&mut self, node: &'ast FunctionBody) -> ControlFlow<Self::BreakTy> {
// 1. Return TopLevelVarScopedDeclarations of StatementList.
TopLevelVarScopedDeclarationsVisitor(self.0).visit_statement_list(node.statement_list())
}
fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> { fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> {
match node { match node {
Statement::Block(s) => self.visit(s), Statement::Block(s) => self.visit(s),
@ -2074,7 +2127,7 @@ impl<'ast> Visitor<'ast> for VarScopedDeclarationsVisitor<'_> {
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
fn visit_with(&mut self, node: &'ast crate::statement::With) -> ControlFlow<Self::BreakTy> { fn visit_with(&mut self, node: &'ast With) -> ControlFlow<Self::BreakTy> {
self.visit(node.statement()); self.visit(node.statement());
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
@ -2342,7 +2395,7 @@ impl<'ast> Visitor<'ast> for AnnexBFunctionDeclarationNamesVisitor<'_> {
self.visit(node.body()); self.visit(node.body());
if let Some(ForLoopInitializer::Lexical(node)) = node.init() { if let Some(ForLoopInitializer::Lexical(node)) = node.init() {
let bound_names = bound_names(node); let bound_names = bound_names(&node.declaration);
self.0.retain(|name| !bound_names.contains(name)); self.0.retain(|name| !bound_names.contains(name));
} }
@ -2395,7 +2448,7 @@ impl<'ast> Visitor<'ast> for AnnexBFunctionDeclarationNamesVisitor<'_> {
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
fn visit_with(&mut self, node: &'ast crate::statement::With) -> ControlFlow<Self::BreakTy> { fn visit_with(&mut self, node: &'ast With) -> ControlFlow<Self::BreakTy> {
self.visit(node.statement()) self.visit(node.statement())
} }
} }

529
core/ast/src/scope.rs

@ -0,0 +1,529 @@
//! This module implements the binding scope for various AST nodes.
//!
//! Scopes are used to track the bindings of identifiers in the AST.
use boa_string::JsString;
use rustc_hash::FxHashMap;
use std::{cell::RefCell, fmt::Debug, rc::Rc};
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::struct_excessive_bools)]
struct Binding {
index: u32,
mutable: bool,
lex: bool,
strict: bool,
escapes: bool,
}
/// A scope maps bound identifiers to their binding positions.
///
/// It can be either a global scope or a function scope or a declarative scope.
#[derive(Clone, PartialEq)]
pub struct Scope {
inner: Rc<Inner>,
}
impl Debug for Scope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Scope")
.field("outer", &self.inner.outer)
.field("index", &self.inner.index)
.field("bindings", &self.inner.bindings)
.field("function", &self.inner.function)
.finish()
}
}
impl Default for Scope {
fn default() -> Self {
Self::new_global()
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for Scope {
fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self::new_global())
}
}
#[derive(Debug, PartialEq)]
pub(crate) struct Inner {
outer: Option<Scope>,
index: u32,
bindings: RefCell<FxHashMap<JsString, Binding>>,
function: bool,
}
impl Scope {
/// Creates a new global scope.
#[must_use]
pub fn new_global() -> Self {
Self {
inner: Rc::new(Inner {
outer: None,
index: 0,
bindings: RefCell::default(),
function: true,
}),
}
}
/// Creates a new scope.
#[must_use]
pub fn new(parent: Self, function: bool) -> Self {
let index = parent.inner.index + 1;
Self {
inner: Rc::new(Inner {
outer: Some(parent),
index,
bindings: RefCell::default(),
function,
}),
}
}
/// Marks all bindings in this scope as escaping.
pub fn escape_all_bindings(&self) {
for binding in self.inner.bindings.borrow_mut().values_mut() {
binding.escapes = true;
}
}
/// Check if the scope has a lexical binding with the given name.
#[must_use]
pub fn has_lex_binding(&self, name: &JsString) -> bool {
self.inner
.bindings
.borrow()
.get(name)
.map_or(false, |binding| binding.lex)
}
/// Check if the scope has a binding with the given name.
#[must_use]
pub fn has_binding(&self, name: &JsString) -> bool {
self.inner.bindings.borrow().contains_key(name)
}
/// Get the binding locator for a binding with the given name.
/// Fall back to the global scope if the binding is not found.
#[must_use]
pub fn get_identifier_reference(&self, name: JsString) -> IdentifierReference {
if let Some(binding) = self.inner.bindings.borrow().get(&name) {
IdentifierReference::new(
BindingLocator::declarative(name, self.inner.index, binding.index),
binding.lex,
binding.escapes,
)
} else if let Some(outer) = &self.inner.outer {
outer.get_identifier_reference(name)
} else {
IdentifierReference::new(BindingLocator::global(name), false, true)
}
}
/// Returns the number of bindings in this scope.
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn num_bindings(&self) -> u32 {
self.inner.bindings.borrow().len() as u32
}
/// Returns the index of this scope.
#[must_use]
pub fn scope_index(&self) -> u32 {
self.inner.index
}
/// Check if the scope is a function scope.
#[must_use]
pub fn is_function(&self) -> bool {
self.inner.function
}
/// Check if the scope is a global scope.
#[must_use]
pub fn is_global(&self) -> bool {
self.inner.outer.is_none()
}
/// Get the locator for a binding name.
#[must_use]
pub fn get_binding(&self, name: &JsString) -> Option<BindingLocator> {
self.inner.bindings.borrow().get(name).map(|binding| {
BindingLocator::declarative(name.clone(), self.inner.index, binding.index)
})
}
/// Get the locator for a binding name.
#[must_use]
pub fn get_binding_reference(&self, name: &JsString) -> Option<IdentifierReference> {
self.inner.bindings.borrow().get(name).map(|binding| {
IdentifierReference::new(
BindingLocator::declarative(name.clone(), self.inner.index, binding.index),
binding.lex,
binding.escapes,
)
})
}
/// Simulate a binding access.
///
/// - If the binding access crosses a function border, the binding is marked as escaping.
/// - If the binding access is in an eval or with scope, the binding is marked as escaping.
pub fn access_binding(&self, name: &JsString, eval_or_with: bool) {
let mut crossed_function_border = false;
let mut current = self;
loop {
if let Some(binding) = current.inner.bindings.borrow_mut().get_mut(name) {
if crossed_function_border || eval_or_with {
binding.escapes = true;
}
return;
}
if let Some(outer) = &current.inner.outer {
if current.inner.function {
crossed_function_border = true;
}
current = outer;
} else {
return;
}
}
}
/// Creates a mutable binding.
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn create_mutable_binding(&self, name: JsString, function_scope: bool) -> BindingLocator {
let binding_index = self.inner.bindings.borrow().len() as u32;
self.inner.bindings.borrow_mut().insert(
name.clone(),
Binding {
index: binding_index,
mutable: true,
lex: !function_scope,
strict: false,
escapes: self.is_global(),
},
);
BindingLocator::declarative(name, self.inner.index, binding_index)
}
/// Crate an immutable binding.
#[allow(clippy::cast_possible_truncation)]
pub(crate) fn create_immutable_binding(&self, name: JsString, strict: bool) {
let binding_index = self.inner.bindings.borrow().len() as u32;
self.inner.bindings.borrow_mut().insert(
name,
Binding {
index: binding_index,
mutable: false,
lex: true,
strict,
escapes: self.is_global(),
},
);
}
/// Return the binding locator for a mutable binding.
///
/// # Errors
/// Returns an error if the binding is not mutable or does not exist.
pub fn set_mutable_binding(
&self,
name: JsString,
) -> Result<IdentifierReference, BindingLocatorError> {
Ok(match self.inner.bindings.borrow().get(&name) {
Some(binding) if binding.mutable => IdentifierReference::new(
BindingLocator::declarative(name, self.inner.index, binding.index),
binding.lex,
binding.escapes,
),
Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable),
Some(_) => return Err(BindingLocatorError::Silent),
None => self.inner.outer.as_ref().map_or_else(
|| {
Ok(IdentifierReference::new(
BindingLocator::global(name.clone()),
false,
true,
))
},
|outer| outer.set_mutable_binding(name.clone()),
)?,
})
}
#[cfg(feature = "annex-b")]
/// Return the binding locator for a set operation on an existing var binding.
///
/// # Errors
/// Returns an error if the binding is not mutable or does not exist.
pub fn set_mutable_binding_var(
&self,
name: JsString,
) -> Result<IdentifierReference, BindingLocatorError> {
if !self.is_function() {
return self.inner.outer.as_ref().map_or_else(
|| {
Ok(IdentifierReference::new(
BindingLocator::global(name.clone()),
false,
true,
))
},
|outer| outer.set_mutable_binding_var(name.clone()),
);
}
Ok(match self.inner.bindings.borrow().get(&name) {
Some(binding) if binding.mutable => IdentifierReference::new(
BindingLocator::declarative(name, self.inner.index, binding.index),
binding.lex,
binding.escapes,
),
Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable),
Some(_) => return Err(BindingLocatorError::Silent),
None => self.inner.outer.as_ref().map_or_else(
|| {
Ok(IdentifierReference::new(
BindingLocator::global(name.clone()),
false,
true,
))
},
|outer| outer.set_mutable_binding_var(name.clone()),
)?,
})
}
/// Gets the outer scope of this scope.
#[must_use]
pub fn outer(&self) -> Option<Self> {
self.inner.outer.clone()
}
}
/// A reference to an identifier in a scope.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct IdentifierReference {
locator: BindingLocator,
lexical: bool,
escapes: bool,
}
impl IdentifierReference {
/// Create a new identifier reference.
pub(crate) fn new(locator: BindingLocator, lexical: bool, escapes: bool) -> Self {
Self {
locator,
lexical,
escapes,
}
}
/// Get the binding locator for this identifier reference.
#[must_use]
pub fn locator(&self) -> BindingLocator {
self.locator.clone()
}
/// Returns if the binding can be function local.
#[must_use]
pub fn local(&self) -> bool {
self.locator.scope > 0 && !self.escapes
}
/// Check if this identifier reference is lexical.
#[must_use]
pub fn is_lexical(&self) -> bool {
self.lexical
}
}
/// A binding locator contains all information about a binding that is needed to resolve it at runtime.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct BindingLocator {
/// Name of the binding.
name: JsString,
/// Scope of the binding.
/// - 0: Global object
/// - 1: Global declarative scope
/// - n: Stack scope at index n - 2
scope: u32,
/// Index of the binding in the scope.
binding_index: u32,
}
impl BindingLocator {
/// Creates a new declarative binding locator that has knows indices.
pub(crate) const fn declarative(name: JsString, scope_index: u32, binding_index: u32) -> Self {
Self {
name,
scope: scope_index + 1,
binding_index,
}
}
/// Creates a binding locator that indicates that the binding is on the global object.
pub(super) const fn global(name: JsString) -> Self {
Self {
name,
scope: 0,
binding_index: 0,
}
}
/// Returns the name of the binding.
#[must_use]
pub const fn name(&self) -> &JsString {
&self.name
}
/// Returns if the binding is located on the global object.
#[must_use]
pub const fn is_global(&self) -> bool {
self.scope == 0
}
/// Returns the scope of the binding.
#[must_use]
pub fn scope(&self) -> BindingLocatorScope {
match self.scope {
0 => BindingLocatorScope::GlobalObject,
1 => BindingLocatorScope::GlobalDeclarative,
n => BindingLocatorScope::Stack(n - 2),
}
}
/// Sets the scope of the binding.
pub fn set_scope(&mut self, scope: BindingLocatorScope) {
self.scope = match scope {
BindingLocatorScope::GlobalObject => 0,
BindingLocatorScope::GlobalDeclarative => 1,
BindingLocatorScope::Stack(index) => index + 2,
};
}
/// Returns the binding index of the binding.
#[must_use]
pub const fn binding_index(&self) -> u32 {
self.binding_index
}
/// Sets the binding index of the binding.
pub fn set_binding_index(&mut self, index: u32) {
self.binding_index = index;
}
}
/// Action that is returned when a fallible binding operation.
#[derive(Copy, Clone, Debug)]
pub enum BindingLocatorError {
/// Trying to mutate immutable binding,
MutateImmutable,
/// Indicates that any action is silently ignored.
Silent,
}
/// The scope in which a binding is located.
#[derive(Clone, Copy, Debug)]
pub enum BindingLocatorScope {
/// The binding is located on the global object.
GlobalObject,
/// The binding is located in the global declarative scope.
GlobalDeclarative,
/// The binding is located in the scope stack at the given index.
Stack(u32),
}
/// A collection of function scopes.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FunctionScopes {
pub(crate) function_scope: Scope,
pub(crate) parameters_eval_scope: Option<Scope>,
pub(crate) parameters_scope: Option<Scope>,
pub(crate) lexical_scope: Option<Scope>,
}
impl FunctionScopes {
/// Returns the function scope for this function.
#[must_use]
pub fn function_scope(&self) -> &Scope {
&self.function_scope
}
/// Returns the parameters eval scope for this function.
#[must_use]
pub fn parameters_eval_scope(&self) -> Option<&Scope> {
self.parameters_eval_scope.as_ref()
}
/// Returns the parameters scope for this function.
#[must_use]
pub fn parameters_scope(&self) -> Option<&Scope> {
self.parameters_scope.as_ref()
}
/// Returns the lexical scope for this function.
#[must_use]
pub fn lexical_scope(&self) -> Option<&Scope> {
self.lexical_scope.as_ref()
}
/// Returns the effective paramter scope for this function.
pub(crate) fn parameter_scope(&self) -> Scope {
if let Some(parameters_eval_scope) = &self.parameters_eval_scope {
return parameters_eval_scope.clone();
}
self.function_scope.clone()
}
/// Returns the effective body scope for this function.
pub(crate) fn body_scope(&self) -> Scope {
if let Some(lexical_scope) = &self.lexical_scope {
return lexical_scope.clone();
}
if let Some(parameters_scope) = &self.parameters_scope {
return parameters_scope.clone();
}
if let Some(parameters_eval_scope) = &self.parameters_eval_scope {
return parameters_eval_scope.clone();
}
self.function_scope.clone()
}
/// Marks all bindings in all scopes as escaping.
pub(crate) fn escape_all_bindings(&self) {
self.function_scope.escape_all_bindings();
if let Some(parameters_eval_scope) = &self.parameters_eval_scope {
parameters_eval_scope.escape_all_bindings();
}
if let Some(parameters_scope) = &self.parameters_scope {
parameters_scope.escape_all_bindings();
}
if let Some(lexical_scope) = &self.lexical_scope {
lexical_scope.escape_all_bindings();
}
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for FunctionScopes {
fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self {
function_scope: Scope::new_global(),
parameters_eval_scope: None,
parameters_scope: None,
lexical_scope: None,
})
}
}

1992
core/ast/src/scope_analyzer.rs

File diff suppressed because it is too large Load Diff

78
core/ast/src/source.rs

@ -1,8 +1,14 @@
use std::ops::ControlFlow; use std::ops::ControlFlow;
use boa_interner::ToIndentedString; use boa_interner::{Interner, ToIndentedString};
use crate::{ use crate::{
expression::Identifier,
scope::Scope,
scope_analyzer::{
analyze_binding_escapes, collect_bindings, eval_declaration_instantiation_scope,
EvalDeclarationBindings,
},
visitor::{VisitWith, Visitor, VisitorMut}, visitor::{VisitWith, Visitor, VisitorMut},
ModuleItemList, StatementList, ModuleItemList, StatementList,
}; };
@ -44,6 +50,47 @@ impl Script {
pub const fn strict(&self) -> bool { pub const fn strict(&self) -> bool {
self.statements.strict() self.statements.strict()
} }
/// Analyze the scope of the script.
pub fn analyze_scope(&mut self, scope: &Scope, interner: &Interner) -> bool {
if !collect_bindings(self, self.strict(), false, scope, interner) {
return false;
}
analyze_binding_escapes(self, false, scope.clone(), interner)
}
/// Analyze the scope of the script in eval mode.
///
/// # Errors
///
/// Returns an error if the scope analysis fails with a syntax error.
pub fn analyze_scope_eval(
&mut self,
strict: bool,
variable_scope: &Scope,
lexical_scope: &Scope,
annex_b_function_names: &[Identifier],
interner: &Interner,
) -> Result<EvalDeclarationBindings, String> {
let bindings = eval_declaration_instantiation_scope(
self,
strict,
variable_scope,
lexical_scope,
annex_b_function_names,
interner,
)?;
if !collect_bindings(self, strict, true, lexical_scope, interner) {
return Err(String::from("Failed to analyze scope"));
}
if !analyze_binding_escapes(self, true, lexical_scope.clone(), interner) {
return Err(String::from("Failed to analyze scope"));
}
Ok(bindings)
}
} }
impl VisitWith for Script { impl VisitWith for Script {
@ -63,7 +110,7 @@ impl VisitWith for Script {
} }
impl ToIndentedString for Script { impl ToIndentedString for Script {
fn to_indented_string(&self, interner: &boa_interner::Interner, indentation: usize) -> String { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
self.statements.to_indented_string(interner, indentation) self.statements.to_indented_string(interner, indentation)
} }
} }
@ -77,14 +124,20 @@ impl ToIndentedString for Script {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, PartialEq)]
pub struct Module { pub struct Module {
items: ModuleItemList, pub(crate) items: ModuleItemList,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scope: Scope,
} }
impl Module { impl Module {
/// Creates a new `ModuleNode`. /// Creates a new `ModuleNode`.
#[must_use] #[must_use]
pub const fn new(items: ModuleItemList) -> Self { pub fn new(items: ModuleItemList) -> Self {
Self { items } Self {
items,
scope: Scope::default(),
}
} }
/// Gets the list of itemos of this `ModuleNode`. /// Gets the list of itemos of this `ModuleNode`.
@ -92,6 +145,21 @@ impl Module {
pub const fn items(&self) -> &ModuleItemList { pub const fn items(&self) -> &ModuleItemList {
&self.items &self.items
} }
/// Gets the scope of this `ModuleNode`.
#[inline]
#[must_use]
pub const fn scope(&self) -> &Scope {
&self.scope
}
/// Analyze the scope of the module.
pub fn analyze_scope(&mut self, scope: &Scope, interner: &Interner) -> bool {
if !collect_bindings(self, true, false, scope, interner) {
return false;
}
analyze_binding_escapes(self, false, scope.clone(), interner)
}
} }
impl VisitWith for Module { impl VisitWith for Module {

21
core/ast/src/statement/block.rs

@ -1,6 +1,8 @@
//! Block AST node. //! Block AST node.
use crate::{ use crate::{
operations::{contains, ContainsSymbol},
scope::Scope,
visitor::{VisitWith, Visitor, VisitorMut}, visitor::{VisitWith, Visitor, VisitorMut},
Statement, StatementList, Statement, StatementList,
}; };
@ -27,7 +29,11 @@ use core::ops::ControlFlow;
#[derive(Clone, Debug, PartialEq, Default)] #[derive(Clone, Debug, PartialEq, Default)]
pub struct Block { pub struct Block {
#[cfg_attr(feature = "serde", serde(flatten))] #[cfg_attr(feature = "serde", serde(flatten))]
statements: StatementList, pub(crate) statements: StatementList,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scope: Option<Scope>,
} }
impl Block { impl Block {
@ -37,6 +43,13 @@ impl Block {
pub const fn statement_list(&self) -> &StatementList { pub const fn statement_list(&self) -> &StatementList {
&self.statements &self.statements
} }
/// Gets the scope of the block.
#[inline]
#[must_use]
pub const fn scope(&self) -> Option<&Scope> {
self.scope.as_ref()
}
} }
impl<T> From<T> for Block impl<T> From<T> for Block
@ -44,8 +57,12 @@ where
T: Into<StatementList>, T: Into<StatementList>,
{ {
fn from(list: T) -> Self { fn from(list: T) -> Self {
let statements = list.into();
let contains_direct_eval = contains(&statements, ContainsSymbol::DirectEval);
Self { Self {
statements: list.into(), statements,
scope: None,
contains_direct_eval,
} }
} }
} }

37
core/ast/src/statement/iteration/for_in_loop.rs

@ -1,3 +1,5 @@
use crate::operations::{contains, ContainsSymbol};
use crate::scope::Scope;
use crate::try_break; use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{ use crate::{
@ -18,9 +20,17 @@ use core::ops::ControlFlow;
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ForInLoop { pub struct ForInLoop {
initializer: IterableLoopInitializer, pub(crate) initializer: IterableLoopInitializer,
target: Expression, pub(crate) target: Expression,
body: Box<Statement>, pub(crate) body: Box<Statement>,
pub(crate) target_contains_direct_eval: bool,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) target_scope: Option<Scope>,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scope: Option<Scope>,
} }
impl ForInLoop { impl ForInLoop {
@ -28,10 +38,17 @@ impl ForInLoop {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn new(initializer: IterableLoopInitializer, target: Expression, body: Statement) -> Self { pub fn new(initializer: IterableLoopInitializer, target: Expression, body: Statement) -> Self {
let target_contains_direct_eval = contains(&target, ContainsSymbol::DirectEval);
let contains_direct_eval = contains(&initializer, ContainsSymbol::DirectEval)
|| contains(&body, ContainsSymbol::DirectEval);
Self { Self {
initializer, initializer,
target, target,
body: body.into(), body: body.into(),
target_contains_direct_eval,
contains_direct_eval,
target_scope: None,
scope: None,
} }
} }
@ -55,6 +72,20 @@ impl ForInLoop {
pub const fn body(&self) -> &Statement { pub const fn body(&self) -> &Statement {
&self.body &self.body
} }
/// Returns the target scope of the for...in loop.
#[inline]
#[must_use]
pub const fn target_scope(&self) -> Option<&Scope> {
self.target_scope.as_ref()
}
/// Returns the scope of the for...in loop.
#[inline]
#[must_use]
pub const fn scope(&self) -> Option<&Scope> {
self.scope.as_ref()
}
} }
impl ToIndentedString for ForInLoop { impl ToIndentedString for ForInLoop {

75
core/ast/src/statement/iteration/for_loop.rs

@ -1,3 +1,5 @@
use crate::operations::{contains, ContainsSymbol};
use crate::scope::Scope;
use crate::try_break; use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{ use crate::{
@ -23,7 +25,7 @@ use core::ops::ControlFlow;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ForLoop { pub struct ForLoop {
#[cfg_attr(feature = "serde", serde(flatten))] #[cfg_attr(feature = "serde", serde(flatten))]
inner: Box<InnerForLoop>, pub(crate) inner: Box<InnerForLoop>,
} }
impl ForLoop { impl ForLoop {
@ -138,27 +140,39 @@ impl VisitWith for ForLoop {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
struct InnerForLoop { pub(crate) struct InnerForLoop {
init: Option<ForLoopInitializer>, pub(crate) init: Option<ForLoopInitializer>,
condition: Option<Expression>, pub(crate) condition: Option<Expression>,
final_expr: Option<Expression>, pub(crate) final_expr: Option<Expression>,
body: Statement, pub(crate) body: Statement,
pub(crate) contains_direct_eval: bool,
} }
impl InnerForLoop { impl InnerForLoop {
/// Creates a new inner for loop. /// Creates a new inner for loop.
#[inline] #[inline]
const fn new( fn new(
init: Option<ForLoopInitializer>, init: Option<ForLoopInitializer>,
condition: Option<Expression>, condition: Option<Expression>,
final_expr: Option<Expression>, final_expr: Option<Expression>,
body: Statement, body: Statement,
) -> Self { ) -> Self {
let mut contains_direct_eval = contains(&body, ContainsSymbol::DirectEval);
if let Some(init) = &init {
contains_direct_eval |= contains(init, ContainsSymbol::DirectEval);
}
if let Some(condition) = &condition {
contains_direct_eval |= contains(condition, ContainsSymbol::DirectEval);
}
if let Some(final_expr) = &final_expr {
contains_direct_eval |= contains(final_expr, ContainsSymbol::DirectEval);
}
Self { Self {
init, init,
condition, condition,
final_expr, final_expr,
body, body,
contains_direct_eval,
} }
} }
@ -204,14 +218,48 @@ pub enum ForLoopInitializer {
/// A var declaration initializer. /// A var declaration initializer.
Var(VarDeclaration), Var(VarDeclaration),
/// A lexical declaration initializer. /// A lexical declaration initializer.
Lexical(LexicalDeclaration), Lexical(ForLoopInitializerLexical),
}
/// A lexical declaration initializer for a `ForLoop`.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ForLoopInitializerLexical {
pub(crate) declaration: LexicalDeclaration,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scope: Scope,
}
impl ForLoopInitializerLexical {
/// Creates a new lexical declaration initializer.
#[inline]
#[must_use]
pub fn new(declaration: LexicalDeclaration, scope: Scope) -> Self {
Self { declaration, scope }
}
/// Returns the declaration of the lexical initializer.
#[inline]
#[must_use]
pub const fn declaration(&self) -> &LexicalDeclaration {
&self.declaration
}
/// Returns the scope of the lexical initializer.
#[inline]
#[must_use]
pub const fn scope(&self) -> &Scope {
&self.scope
}
} }
impl ToInternedString for ForLoopInitializer { impl ToInternedString for ForLoopInitializer {
fn to_interned_string(&self, interner: &Interner) -> String { fn to_interned_string(&self, interner: &Interner) -> String {
match self { match self {
Self::Var(var) => var.to_interned_string(interner), Self::Var(var) => var.to_interned_string(interner),
Self::Lexical(lex) => lex.to_interned_string(interner), Self::Lexical(lex) => lex.declaration.to_interned_string(interner),
Self::Expression(expr) => expr.to_interned_string(interner), Self::Expression(expr) => expr.to_interned_string(interner),
} }
} }
@ -227,7 +275,10 @@ impl From<Expression> for ForLoopInitializer {
impl From<LexicalDeclaration> for ForLoopInitializer { impl From<LexicalDeclaration> for ForLoopInitializer {
#[inline] #[inline]
fn from(list: LexicalDeclaration) -> Self { fn from(list: LexicalDeclaration) -> Self {
Self::Lexical(list) Self::Lexical(ForLoopInitializerLexical {
declaration: list,
scope: Scope::default(),
})
} }
} }
@ -246,7 +297,7 @@ impl VisitWith for ForLoopInitializer {
match self { match self {
Self::Expression(expr) => visitor.visit_expression(expr), Self::Expression(expr) => visitor.visit_expression(expr),
Self::Var(vd) => visitor.visit_var_declaration(vd), Self::Var(vd) => visitor.visit_var_declaration(vd),
Self::Lexical(ld) => visitor.visit_lexical_declaration(ld), Self::Lexical(ld) => visitor.visit_lexical_declaration(&ld.declaration),
} }
} }
@ -257,7 +308,7 @@ impl VisitWith for ForLoopInitializer {
match self { match self {
Self::Expression(expr) => visitor.visit_expression_mut(expr), Self::Expression(expr) => visitor.visit_expression_mut(expr),
Self::Var(vd) => visitor.visit_var_declaration_mut(vd), Self::Var(vd) => visitor.visit_var_declaration_mut(vd),
Self::Lexical(ld) => visitor.visit_lexical_declaration_mut(ld), Self::Lexical(ld) => visitor.visit_lexical_declaration_mut(&mut ld.declaration),
} }
} }
} }

37
core/ast/src/statement/iteration/for_of_loop.rs

@ -1,3 +1,5 @@
use crate::operations::{contains, ContainsSymbol};
use crate::scope::Scope;
use crate::try_break; use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{ use crate::{
@ -23,10 +25,18 @@ use core::ops::ControlFlow;
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ForOfLoop { pub struct ForOfLoop {
init: IterableLoopInitializer, pub(crate) init: IterableLoopInitializer,
iterable: Expression, pub(crate) iterable: Expression,
body: Box<Statement>, pub(crate) body: Box<Statement>,
r#await: bool, r#await: bool,
pub(crate) iterable_contains_direct_eval: bool,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) iterable_scope: Option<Scope>,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scope: Option<Scope>,
} }
impl ForOfLoop { impl ForOfLoop {
@ -39,11 +49,18 @@ impl ForOfLoop {
body: Statement, body: Statement,
r#await: bool, r#await: bool,
) -> Self { ) -> Self {
let iterable_contains_direct_eval = contains(&iterable, ContainsSymbol::DirectEval);
let contains_direct_eval = contains(&init, ContainsSymbol::DirectEval)
|| contains(&body, ContainsSymbol::DirectEval);
Self { Self {
init, init,
iterable, iterable,
body: body.into(), body: body.into(),
iterable_contains_direct_eval,
contains_direct_eval,
r#await, r#await,
iterable_scope: None,
scope: None,
} }
} }
@ -74,6 +91,20 @@ impl ForOfLoop {
pub const fn r#await(&self) -> bool { pub const fn r#await(&self) -> bool {
self.r#await self.r#await
} }
/// Return the iterable scope of the for...of loop.
#[inline]
#[must_use]
pub const fn iterable_scope(&self) -> Option<&Scope> {
self.iterable_scope.as_ref()
}
/// Return the scope of the for...of loop.
#[inline]
#[must_use]
pub const fn scope(&self) -> Option<&Scope> {
self.scope.as_ref()
}
} }
impl ToIndentedString for ForOfLoop { impl ToIndentedString for ForOfLoop {

2
core/ast/src/statement/iteration/mod.rs

@ -18,7 +18,7 @@ use core::ops::ControlFlow;
pub use self::{ pub use self::{
do_while_loop::DoWhileLoop, do_while_loop::DoWhileLoop,
for_in_loop::ForInLoop, for_in_loop::ForInLoop,
for_loop::{ForLoop, ForLoopInitializer}, for_loop::{ForLoop, ForLoopInitializer, ForLoopInitializerLexical},
for_of_loop::ForOfLoop, for_of_loop::ForOfLoop,
r#break::Break, r#break::Break,
r#continue::Continue, r#continue::Continue,

28
core/ast/src/statement/switch.rs

@ -1,6 +1,8 @@
//! Switch node. //! Switch node.
use crate::{ use crate::{
expression::Expression, expression::Expression,
operations::{contains, ContainsSymbol},
scope::Scope,
statement::Statement, statement::Statement,
try_break, try_break,
visitor::{VisitWith, Visitor, VisitorMut}, visitor::{VisitWith, Visitor, VisitorMut},
@ -114,8 +116,12 @@ impl VisitWith for Case {
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Switch { pub struct Switch {
val: Expression, pub(crate) val: Expression,
cases: Box<[Case]>, pub(crate) cases: Box<[Case]>,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scope: Option<Scope>,
} }
impl Switch { impl Switch {
@ -123,7 +129,16 @@ impl Switch {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn new(val: Expression, cases: Box<[Case]>) -> Self { pub fn new(val: Expression, cases: Box<[Case]>) -> Self {
Self { val, cases } let mut contains_direct_eval = false;
for case in &cases {
contains_direct_eval |= contains(case, ContainsSymbol::DirectEval);
}
Self {
val,
cases,
contains_direct_eval,
scope: None,
}
} }
/// Gets the value to switch. /// Gets the value to switch.
@ -151,6 +166,13 @@ impl Switch {
} }
None None
} }
/// Gets the scope of the switch statement.
#[inline]
#[must_use]
pub const fn scope(&self) -> Option<&Scope> {
self.scope.as_ref()
}
} }
impl ToIndentedString for Switch { impl ToIndentedString for Switch {

30
core/ast/src/statement/try.rs

@ -1,5 +1,7 @@
//! Error handling statements //! Error handling statements
use crate::operations::{contains, ContainsSymbol};
use crate::scope::Scope;
use crate::try_break; use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{ use crate::{
@ -142,16 +144,29 @@ impl VisitWith for Try {
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Catch { pub struct Catch {
parameter: Option<Binding>, pub(crate) parameter: Option<Binding>,
block: Block, pub(crate) block: Block,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scope: Scope,
} }
impl Catch { impl Catch {
/// Creates a new catch block. /// Creates a new catch block.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new(parameter: Option<Binding>, block: Block) -> Self { pub fn new(parameter: Option<Binding>, block: Block) -> Self {
Self { parameter, block } let mut contains_direct_eval = contains(&block, ContainsSymbol::DirectEval);
if let Some(param) = &parameter {
contains_direct_eval |= contains(param, ContainsSymbol::DirectEval);
}
Self {
parameter,
block,
contains_direct_eval,
scope: Scope::default(),
}
} }
/// Gets the parameter of the catch block. /// Gets the parameter of the catch block.
@ -167,6 +182,13 @@ impl Catch {
pub const fn block(&self) -> &Block { pub const fn block(&self) -> &Block {
&self.block &self.block
} }
/// Returns the scope of the catch block.
#[inline]
#[must_use]
pub const fn scope(&self) -> &Scope {
&self.scope
}
} }
impl ToIndentedString for Catch { impl ToIndentedString for Catch {

15
core/ast/src/statement/with.rs

@ -1,5 +1,6 @@
use crate::{ use crate::{
expression::Expression, expression::Expression,
scope::Scope,
statement::Statement, statement::Statement,
try_break, try_break,
visitor::{VisitWith, Visitor, VisitorMut}, visitor::{VisitWith, Visitor, VisitorMut},
@ -19,8 +20,11 @@ use core::ops::ControlFlow;
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct With { pub struct With {
expression: Expression, pub(crate) expression: Expression,
statement: Box<Statement>, pub(crate) statement: Box<Statement>,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scope: Scope,
} }
impl With { impl With {
@ -30,6 +34,7 @@ impl With {
Self { Self {
expression, expression,
statement: Box::new(statement), statement: Box::new(statement),
scope: Scope::default(),
} }
} }
@ -44,6 +49,12 @@ impl With {
pub const fn statement(&self) -> &Statement { pub const fn statement(&self) -> &Statement {
&self.statement &self.statement
} }
/// Returns the scope of the `With` statement.
#[must_use]
pub const fn scope(&self) -> &Scope {
&self.scope
}
} }
impl From<With> for Statement { impl From<With> for Statement {

2
core/ast/src/statement_list.rs

@ -98,7 +98,7 @@ impl VisitWith for StatementListItem {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, PartialEq)]
pub struct StatementList { pub struct StatementList {
statements: Box<[StatementListItem]>, pub(crate) statements: Box<[StatementListItem]>,
strict: bool, strict: bool,
} }

7
core/ast/src/visitor.rs

@ -31,7 +31,7 @@ use crate::{
function::{ function::{
ArrowFunction, AsyncArrowFunction, AsyncFunctionDeclaration, AsyncFunctionExpression, ArrowFunction, AsyncArrowFunction, AsyncFunctionDeclaration, AsyncFunctionExpression,
AsyncGeneratorDeclaration, AsyncGeneratorExpression, ClassDeclaration, ClassElement, AsyncGeneratorDeclaration, AsyncGeneratorExpression, ClassDeclaration, ClassElement,
ClassExpression, FormalParameter, FormalParameterList, FunctionDeclaration, ClassExpression, FormalParameter, FormalParameterList, FunctionBody, FunctionDeclaration,
FunctionExpression, GeneratorDeclaration, GeneratorExpression, PrivateName, FunctionExpression, GeneratorDeclaration, GeneratorExpression, PrivateName,
}, },
pattern::{ArrayPattern, ArrayPatternElement, ObjectPattern, ObjectPatternElement, Pattern}, pattern::{ArrayPattern, ArrayPatternElement, ObjectPattern, ObjectPatternElement, Pattern},
@ -126,6 +126,7 @@ macro_rules! node_ref {
node_ref! { node_ref! {
Script, Script,
Module, Module,
FunctionBody,
StatementList, StatementList,
StatementListItem, StatementListItem,
Statement, Statement,
@ -234,6 +235,7 @@ pub trait Visitor<'ast>: Sized {
define_visit!(visit_script, Script); define_visit!(visit_script, Script);
define_visit!(visit_module, Module); define_visit!(visit_module, Module);
define_visit!(visit_function_body, FunctionBody);
define_visit!(visit_statement_list, StatementList); define_visit!(visit_statement_list, StatementList);
define_visit!(visit_statement_list_item, StatementListItem); define_visit!(visit_statement_list_item, StatementListItem);
define_visit!(visit_statement, Statement); define_visit!(visit_statement, Statement);
@ -339,6 +341,7 @@ pub trait Visitor<'ast>: Sized {
match node { match node {
NodeRef::Script(n) => self.visit_script(n), NodeRef::Script(n) => self.visit_script(n),
NodeRef::Module(n) => self.visit_module(n), NodeRef::Module(n) => self.visit_module(n),
NodeRef::FunctionBody(n) => self.visit_function_body(n),
NodeRef::StatementList(n) => self.visit_statement_list(n), NodeRef::StatementList(n) => self.visit_statement_list(n),
NodeRef::StatementListItem(n) => self.visit_statement_list_item(n), NodeRef::StatementListItem(n) => self.visit_statement_list_item(n),
NodeRef::Statement(n) => self.visit_statement(n), NodeRef::Statement(n) => self.visit_statement(n),
@ -449,6 +452,7 @@ pub trait VisitorMut<'ast>: Sized {
define_visit_mut!(visit_script_mut, Script); define_visit_mut!(visit_script_mut, Script);
define_visit_mut!(visit_module_mut, Module); define_visit_mut!(visit_module_mut, Module);
define_visit_mut!(visit_function_body_mut, FunctionBody);
define_visit_mut!(visit_statement_list_mut, StatementList); define_visit_mut!(visit_statement_list_mut, StatementList);
define_visit_mut!(visit_statement_list_item_mut, StatementListItem); define_visit_mut!(visit_statement_list_item_mut, StatementListItem);
define_visit_mut!(visit_statement_mut, Statement); define_visit_mut!(visit_statement_mut, Statement);
@ -563,6 +567,7 @@ pub trait VisitorMut<'ast>: Sized {
match node { match node {
NodeRefMut::Script(n) => self.visit_script_mut(n), NodeRefMut::Script(n) => self.visit_script_mut(n),
NodeRefMut::Module(n) => self.visit_module_mut(n), NodeRefMut::Module(n) => self.visit_module_mut(n),
NodeRefMut::FunctionBody(n) => self.visit_function_body_mut(n),
NodeRefMut::StatementList(n) => self.visit_statement_list_mut(n), NodeRefMut::StatementList(n) => self.visit_statement_list_mut(n),
NodeRefMut::StatementListItem(n) => self.visit_statement_list_item_mut(n), NodeRefMut::StatementListItem(n) => self.visit_statement_list_item_mut(n),
NodeRefMut::Statement(n) => self.visit_statement_mut(n), NodeRefMut::Statement(n) => self.visit_statement_mut(n),

2
core/engine/Cargo.toml

@ -56,7 +56,7 @@ flowgraph = []
trace = ["js"] trace = ["js"]
# Enable Boa's additional ECMAScript features for web browsers. # Enable Boa's additional ECMAScript features for web browsers.
annex-b = ["boa_parser/annex-b"] annex-b = ["boa_ast/annex-b", "boa_parser/annex-b"]
# Enable Boa's Temporal proposal implementation # Enable Boa's Temporal proposal implementation
temporal = ["dep:icu_calendar", "dep:temporal_rs"] temporal = ["dep:icu_calendar", "dep:temporal_rs"]

70
core/engine/src/builtins/eval/mod.rs

@ -9,22 +9,23 @@
//! [spec]: https://tc39.es/ecma262/#sec-eval-x //! [spec]: https://tc39.es/ecma262/#sec-eval-x
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
use std::rc::Rc;
use crate::{ use crate::{
builtins::{function::OrdinaryFunction, BuiltInObject}, builtins::{function::OrdinaryFunction, BuiltInObject},
bytecompiler::{eval_declaration_instantiation_context, ByteCompiler}, bytecompiler::{eval_declaration_instantiation_context, ByteCompiler},
context::intrinsics::Intrinsics, context::intrinsics::Intrinsics,
environments::{CompileTimeEnvironment, Environment}, environments::Environment,
error::JsNativeError, error::JsNativeError,
js_string, js_string,
object::JsObject, object::JsObject,
realm::Realm, realm::Realm,
string::StaticJsStrings, string::StaticJsStrings,
vm::{CallFrame, CallFrameFlags, Opcode}, vm::{CallFrame, CallFrameFlags, Constant, Opcode},
Context, JsArgs, JsResult, JsString, JsValue, Context, JsArgs, JsResult, JsString, JsValue,
}; };
use boa_ast::operations::{contains, contains_arguments, ContainsSymbol}; use boa_ast::{
operations::{contains, contains_arguments, ContainsSymbol},
scope::Scope,
};
use boa_gc::Gc; use boa_gc::Gc;
use boa_parser::{Parser, Source}; use boa_parser::{Parser, Source};
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -62,7 +63,7 @@ impl Eval {
/// [spec]: https://tc39.es/ecma262/#sec-eval-x /// [spec]: https://tc39.es/ecma262/#sec-eval-x
fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Return ? PerformEval(x, false, false). // 1. Return ? PerformEval(x, false, false).
Self::perform_eval(args.get_or_undefined(0), false, false, context) Self::perform_eval(args.get_or_undefined(0), false, None, false, context)
} }
/// `19.2.1.1 PerformEval ( x, strictCaller, direct )` /// `19.2.1.1 PerformEval ( x, strictCaller, direct )`
@ -74,6 +75,7 @@ impl Eval {
pub(crate) fn perform_eval( pub(crate) fn perform_eval(
x: &JsValue, x: &JsValue,
direct: bool, direct: bool,
lexical_scope: Option<Scope>,
mut strict: bool, mut strict: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
@ -128,7 +130,7 @@ impl Eval {
if strict { if strict {
parser.set_strict(); parser.set_strict();
} }
let body = parser.parse_eval(direct, context.interner_mut())?; let mut body = parser.parse_eval(direct, context.interner_mut())?;
// 6. Let inFunction be false. // 6. Let inFunction be false.
// 7. Let inMethod be false. // 7. Let inMethod be false.
@ -229,11 +231,18 @@ impl Eval {
} }
}); });
let var_environment = context.vm.environments.outer_function_environment().clone(); let (var_environment, mut variable_scope) =
let mut var_env = var_environment.compile_env(); if let Some(e) = context.vm.environments.outer_function_environment() {
(e.0, e.1)
} else {
(
context.realm().environment().clone(),
context.realm().scope().clone(),
)
};
let lex_env = context.vm.environments.current_compile_environment(); let lexical_scope = lexical_scope.unwrap_or(context.realm().scope().clone());
let lex_env = Rc::new(CompileTimeEnvironment::new(lex_env, strict)); let lexical_scope = Scope::new(lexical_scope, strict);
let mut annex_b_function_names = Vec::new(); let mut annex_b_function_names = Vec::new();
@ -241,8 +250,12 @@ impl Eval {
&mut annex_b_function_names, &mut annex_b_function_names,
&body, &body,
strict, strict,
if strict { &lex_env } else { &var_env }, if strict {
&lex_env, &lexical_scope
} else {
&variable_scope
},
&lexical_scope,
context, context,
)?; )?;
@ -252,8 +265,8 @@ impl Eval {
js_string!("<main>"), js_string!("<main>"),
body.strict(), body.strict(),
false, false,
var_env.clone(), variable_scope.clone(),
lex_env.clone(), lexical_scope.clone(),
false, false,
false, false,
context.interner_mut(), context.interner_mut(),
@ -262,23 +275,36 @@ impl Eval {
compiler.current_open_environments_count += 1; compiler.current_open_environments_count += 1;
let env_index = compiler.constants.len() as u32; let scope_index = compiler.constants.len() as u32;
compiler compiler
.constants .constants
.push(crate::vm::Constant::CompileTimeEnvironment(lex_env.clone())); .push(Constant::Scope(lexical_scope.clone()));
compiler.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); compiler.emit_with_varying_operand(Opcode::PushScope, scope_index);
if strict { if strict {
var_env = lex_env.clone(); variable_scope = lexical_scope.clone();
compiler.variable_environment = lex_env.clone(); compiler.variable_scope = lexical_scope.clone();
} }
#[cfg(feature = "annex-b")] #[cfg(feature = "annex-b")]
{ {
compiler.annex_b_function_names = annex_b_function_names; compiler
.annex_b_function_names
.clone_from(&annex_b_function_names);
} }
compiler.eval_declaration_instantiation(&body, strict, &var_env, &lex_env); let bindings = body
.analyze_scope_eval(
strict,
&variable_scope,
&lexical_scope,
&annex_b_function_names,
compiler.interner(),
)
.map_err(|e| JsNativeError::syntax().with_message(e))?;
compiler.eval_declaration_instantiation(&body, strict, &variable_scope, bindings);
compiler.compile_statement_list(body.statements(), true, false); compiler.compile_statement_list(body.statements(), true, false);
let code_block = Gc::new(compiler.finish()); let code_block = Gc::new(compiler.finish());

1
core/engine/src/builtins/function/arguments.rs

@ -79,6 +79,7 @@ impl UnmappedArguments {
/// This struct stores all the data to access mapped function parameters in their environment. /// This struct stores all the data to access mapped function parameters in their environment.
#[derive(Debug, Clone, Trace, Finalize)] #[derive(Debug, Clone, Trace, Finalize)]
pub(crate) struct MappedArguments { pub(crate) struct MappedArguments {
#[unsafe_ignore_trace]
binding_indices: Vec<Option<u32>>, binding_indices: Vec<Option<u32>>,
environment: Gc<DeclarativeEnvironment>, environment: Gc<DeclarativeEnvironment>,
} }

43
core/engine/src/builtins/function/mod.rs

@ -17,10 +17,7 @@ use crate::{
}, },
bytecompiler::FunctionCompiler, bytecompiler::FunctionCompiler,
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
environments::{ environments::{EnvironmentStack, FunctionSlots, PrivateEnvironment, ThisBindingStatus},
BindingLocatorEnvironment, EnvironmentStack, FunctionSlots, PrivateEnvironment,
ThisBindingStatus,
},
error::JsNativeError, error::JsNativeError,
js_string, js_string,
native_function::NativeFunctionObject, native_function::NativeFunctionObject,
@ -45,6 +42,7 @@ use boa_ast::{
all_private_identifiers_valid, bound_names, contains, lexically_declared_names, all_private_identifiers_valid, bound_names, contains, lexically_declared_names,
ContainsSymbol, ContainsSymbol,
}, },
scope::BindingLocatorScope,
}; };
use boa_gc::{self, custom_trace, Finalize, Gc, Trace}; use boa_gc::{self, custom_trace, Finalize, Gc, Trace};
use boa_interner::Sym; use boa_interner::Sym;
@ -408,6 +406,8 @@ impl BuiltInFunctionObject {
new_target.clone() new_target.clone()
}; };
let strict = context.is_strict();
let default = if r#async && generator { let default = if r#async && generator {
// 5. Else, // 5. Else,
// a. Assert: kind is async-generator. // a. Assert: kind is async-generator.
@ -635,6 +635,14 @@ impl BuiltInFunctionObject {
body body
}; };
let mut function =
boa_ast::function::FunctionExpression::new(None, parameters, body, false);
if !function.analyze_scope(strict, context.realm().scope(), context.interner()) {
return Err(JsNativeError::syntax()
.with_message("failed to analyze function scope")
.into());
}
let in_with = context.vm.environments.has_object_environment(); let in_with = context.vm.environments.has_object_environment();
let code = FunctionCompiler::new() let code = FunctionCompiler::new()
.name(js_string!("anonymous")) .name(js_string!("anonymous"))
@ -642,10 +650,11 @@ impl BuiltInFunctionObject {
.r#async(r#async) .r#async(r#async)
.in_with(in_with) .in_with(in_with)
.compile( .compile(
&parameters, function.parameters(),
&body, function.body(),
context.realm().environment().compile_env(), context.realm().scope().clone(),
context.realm().environment().compile_env(), context.realm().scope().clone(),
function.scopes(),
context.interner_mut(), context.interner_mut(),
); );
@ -1008,12 +1017,9 @@ pub(crate) fn function_call(
let mut last_env = 0; let mut last_env = 0;
if code.has_binding_identifier() { if code.has_binding_identifier() {
let index = context let index = context.vm.environments.push_lexical(1);
.vm
.environments
.push_lexical(code.constant_compile_time_environment(last_env));
context.vm.environments.put_lexical_value( context.vm.environments.put_lexical_value(
BindingLocatorEnvironment::Stack(index), BindingLocatorScope::Stack(index),
0, 0,
function_object.clone().into(), function_object.clone().into(),
); );
@ -1021,7 +1027,7 @@ pub(crate) fn function_call(
} }
context.vm.environments.push_function( context.vm.environments.push_function(
code.constant_compile_time_environment(last_env), code.constant_scope(last_env),
FunctionSlots::new(this, function_object.clone(), None), FunctionSlots::new(this, function_object.clone(), None),
); );
@ -1101,12 +1107,9 @@ fn function_construct(
let mut last_env = 0; let mut last_env = 0;
if code.has_binding_identifier() { if code.has_binding_identifier() {
let index = context let index = context.vm.environments.push_lexical(1);
.vm
.environments
.push_lexical(code.constant_compile_time_environment(last_env));
context.vm.environments.put_lexical_value( context.vm.environments.put_lexical_value(
BindingLocatorEnvironment::Stack(index), BindingLocatorScope::Stack(index),
0, 0,
this_function_object.clone().into(), this_function_object.clone().into(),
); );
@ -1114,7 +1117,7 @@ fn function_construct(
} }
context.vm.environments.push_function( context.vm.environments.push_function(
code.constant_compile_time_environment(last_env), code.constant_scope(last_env),
FunctionSlots::new( FunctionSlots::new(
this.clone().map_or(ThisBindingStatus::Uninitialized, |o| { this.clone().map_or(ThisBindingStatus::Uninitialized, |o| {
ThisBindingStatus::Initialized(o.into()) ThisBindingStatus::Initialized(o.into())

7
core/engine/src/builtins/json/mod.rs

@ -15,6 +15,7 @@
use std::{borrow::Cow, iter::once}; use std::{borrow::Cow, iter::once};
use boa_ast::scope::Scope;
use boa_macros::{js_str, utf16}; use boa_macros::{js_str, utf16};
use itertools::Itertools; use itertools::Itertools;
@ -111,15 +112,15 @@ impl Json {
// 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral. // 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral.
let mut parser = Parser::new(Source::from_bytes(&script_string)); let mut parser = Parser::new(Source::from_bytes(&script_string));
parser.set_json_parse(); parser.set_json_parse();
let script = parser.parse_script(context.interner_mut())?; let script = parser.parse_script(&Scope::new_global(), context.interner_mut())?;
let code_block = { let code_block = {
let in_with = context.vm.environments.has_object_environment(); let in_with = context.vm.environments.has_object_environment();
let mut compiler = ByteCompiler::new( let mut compiler = ByteCompiler::new(
js_string!("<main>"), js_string!("<main>"),
script.strict(), script.strict(),
true, true,
context.realm().environment().compile_env(), context.realm().scope().clone(),
context.realm().environment().compile_env(), context.realm().scope().clone(),
false, false,
false, false,
context.interner_mut(), context.interner_mut(),

101
core/engine/src/bytecompiler/class.rs

@ -10,6 +10,7 @@ use boa_ast::{
FunctionExpression, FunctionExpression,
}, },
property::{MethodDefinitionKind, PropertyName}, property::{MethodDefinitionKind, PropertyName},
scope::Scope,
Expression, Expression,
}; };
use boa_gc::Gc; use boa_gc::Gc;
@ -32,6 +33,7 @@ pub(crate) struct ClassSpec<'a> {
constructor: Option<&'a FunctionExpression>, constructor: Option<&'a FunctionExpression>,
elements: &'a [ClassElement], elements: &'a [ClassElement],
has_binding_identifier: bool, has_binding_identifier: bool,
name_scope: Option<&'a Scope>,
} }
impl<'a> From<&'a ClassDeclaration> for ClassSpec<'a> { impl<'a> From<&'a ClassDeclaration> for ClassSpec<'a> {
@ -42,6 +44,7 @@ impl<'a> From<&'a ClassDeclaration> for ClassSpec<'a> {
constructor: class.constructor(), constructor: class.constructor(),
elements: class.elements(), elements: class.elements(),
has_binding_identifier: true, has_binding_identifier: true,
name_scope: Some(class.name_scope()),
} }
} }
} }
@ -54,6 +57,7 @@ impl<'a> From<&'a ClassExpression> for ClassSpec<'a> {
constructor: class.constructor(), constructor: class.constructor(),
elements: class.elements(), elements: class.elements(),
has_binding_identifier: class.name().is_some(), has_binding_identifier: class.name().is_some(),
name_scope: class.name_scope(),
} }
} }
} }
@ -75,13 +79,11 @@ impl ByteCompiler<'_> {
.map_or(Sym::EMPTY_STRING, Identifier::sym) .map_or(Sym::EMPTY_STRING, Identifier::sym)
.to_js_string(self.interner()); .to_js_string(self.interner());
let old_lex_env = if class.has_binding_identifier { let outer_scope = if let Some(name_scope) = class.name_scope {
let old_lex_env = self.lexical_environment.clone(); let outer_scope = self.lexical_scope.clone();
let env_index = self.push_compile_environment(false); let scope_index = self.push_scope(name_scope);
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); self.emit_with_varying_operand(Opcode::PushScope, scope_index);
self.lexical_environment Some(outer_scope)
.create_immutable_binding(class_name.clone(), true);
Some(old_lex_env)
} else { } else {
None None
}; };
@ -90,8 +92,8 @@ impl ByteCompiler<'_> {
class_name.clone(), class_name.clone(),
true, true,
self.json_parse, self.json_parse,
self.variable_environment.clone(), self.variable_scope.clone(),
self.lexical_environment.clone(), self.lexical_scope.clone(),
false, false,
false, false,
self.interner, self.interner,
@ -100,10 +102,9 @@ impl ByteCompiler<'_> {
compiler.code_block_flags |= CodeBlockFlags::IS_CLASS_CONSTRUCTOR; compiler.code_block_flags |= CodeBlockFlags::IS_CLASS_CONSTRUCTOR;
// Function environment
let _ = compiler.push_compile_environment(true);
if let Some(expr) = &class.constructor { if let Some(expr) = &class.constructor {
let _ = compiler.push_scope(expr.scopes().function_scope());
compiler.length = expr.parameters().length(); compiler.length = expr.parameters().length();
compiler.params = expr.parameters().clone(); compiler.params = expr.parameters().clone();
@ -113,15 +114,20 @@ impl ByteCompiler<'_> {
false, false,
true, true,
false, false,
expr.scopes(),
); );
compiler.compile_statement_list(expr.body().statements(), false, false); compiler.compile_statement_list(expr.body().statement_list(), false, false);
compiler.emit_opcode(Opcode::PushUndefined); compiler.emit_opcode(Opcode::PushUndefined);
} else if class.super_ref.is_some() { } else if class.super_ref.is_some() {
// We push an empty, unused function scope since the compiler expects a function scope.
let _ = compiler.push_scope(&Scope::new(compiler.lexical_scope.clone(), true));
compiler.emit_opcode(Opcode::SuperCallDerived); compiler.emit_opcode(Opcode::SuperCallDerived);
compiler.emit_opcode(Opcode::BindThisValue); compiler.emit_opcode(Opcode::BindThisValue);
} else { } else {
// We push an empty, unused function scope since the compiler expects a function scope.
let _ = compiler.push_scope(&Scope::new(compiler.lexical_scope.clone(), true));
compiler.emit_opcode(Opcode::PushUndefined); compiler.emit_opcode(Opcode::PushUndefined);
} }
compiler.emit_opcode(Opcode::SetReturnValue); compiler.emit_opcode(Opcode::SetReturnValue);
@ -157,8 +163,12 @@ impl ByteCompiler<'_> {
self.emit_u32(index); self.emit_u32(index);
} }
} }
ClassElement::PrivateFieldDefinition(name, _) ClassElement::PrivateFieldDefinition(field) => {
| ClassElement::PrivateStaticFieldDefinition(name, _) => { count += 1;
let index = self.get_or_insert_private_name(*field.name());
self.emit_u32(index);
}
ClassElement::PrivateStaticFieldDefinition(name, _) => {
count += 1; count += 1;
let index = self.get_or_insert_private_name(*name); let index = self.get_or_insert_private_name(*name);
self.emit_u32(index); self.emit_u32(index);
@ -171,7 +181,7 @@ impl ByteCompiler<'_> {
let mut static_elements = Vec::new(); let mut static_elements = Vec::new();
let mut static_field_name_count = 0; let mut static_field_name_count = 0;
if old_lex_env.is_some() { if outer_scope.is_some() {
self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup);
self.emit_binding(BindingOpcode::InitLexical, class_name.clone()); self.emit_binding(BindingOpcode::InitLexical, class_name.clone());
} }
@ -258,9 +268,9 @@ impl ByteCompiler<'_> {
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::Swap);
} }
} }
ClassElement::FieldDefinition(name, field) => { ClassElement::FieldDefinition(field) => {
self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup);
match name { match field.name() {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.emit_push_literal(Literal::String( self.emit_push_literal(Literal::String(
self.interner().resolve_expect(*name).into_common(false), self.interner().resolve_expect(*name).into_common(false),
@ -274,8 +284,8 @@ impl ByteCompiler<'_> {
js_string!(), js_string!(),
true, true,
self.json_parse, self.json_parse,
self.variable_environment.clone(), self.variable_scope.clone(),
self.lexical_environment.clone(), self.lexical_scope.clone(),
false, false,
false, false,
self.interner, self.interner,
@ -283,8 +293,8 @@ impl ByteCompiler<'_> {
); );
// Function environment // Function environment
let _ = field_compiler.push_compile_environment(true); let _ = field_compiler.push_scope(field.scope());
let is_anonymous_function = if let Some(node) = field { let is_anonymous_function = if let Some(node) = &field.field() {
field_compiler.compile_expr(node, true); field_compiler.compile_expr(node, true);
node.is_anonymous_function_definition() node.is_anonymous_function_definition()
} else { } else {
@ -303,22 +313,22 @@ impl ByteCompiler<'_> {
&[Operand::Bool(is_anonymous_function)], &[Operand::Bool(is_anonymous_function)],
); );
} }
ClassElement::PrivateFieldDefinition(name, field) => { ClassElement::PrivateFieldDefinition(field) => {
self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup);
let name_index = self.get_or_insert_private_name(*name); let name_index = self.get_or_insert_private_name(*field.name());
let mut field_compiler = ByteCompiler::new( let mut field_compiler = ByteCompiler::new(
class_name.clone(), class_name.clone(),
true, true,
self.json_parse, self.json_parse,
self.variable_environment.clone(), self.variable_scope.clone(),
self.lexical_environment.clone(), self.lexical_scope.clone(),
false, false,
false, false,
self.interner, self.interner,
self.in_with, self.in_with,
); );
let _ = field_compiler.push_compile_environment(true); let _ = field_compiler.push_scope(field.scope());
if let Some(node) = field { if let Some(node) = field.field() {
field_compiler.compile_expr(node, true); field_compiler.compile_expr(node, true);
} else { } else {
field_compiler.emit_opcode(Opcode::PushUndefined); field_compiler.emit_opcode(Opcode::PushUndefined);
@ -332,8 +342,8 @@ impl ByteCompiler<'_> {
self.emit_with_varying_operand(Opcode::GetFunction, index); self.emit_with_varying_operand(Opcode::GetFunction, index);
self.emit_with_varying_operand(Opcode::PushClassFieldPrivate, name_index); self.emit_with_varying_operand(Opcode::PushClassFieldPrivate, name_index);
} }
ClassElement::StaticFieldDefinition(name, field) => { ClassElement::StaticFieldDefinition(field) => {
let name_index = match name { let name_index = match field.name() {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
Some(self.get_or_insert_name((*name).into())) Some(self.get_or_insert_name((*name).into()))
} }
@ -351,15 +361,15 @@ impl ByteCompiler<'_> {
class_name.clone(), class_name.clone(),
true, true,
self.json_parse, self.json_parse,
self.variable_environment.clone(), self.variable_scope.clone(),
self.lexical_environment.clone(), self.lexical_scope.clone(),
false, false,
false, false,
self.interner, self.interner,
self.in_with, self.in_with,
); );
let _ = field_compiler.push_compile_environment(true); let _ = field_compiler.push_scope(field.scope());
let is_anonymous_function = if let Some(node) = field { let is_anonymous_function = if let Some(node) = &field.field() {
field_compiler.compile_expr(node, true); field_compiler.compile_expr(node, true);
node.is_anonymous_function_definition() node.is_anonymous_function_definition()
} else { } else {
@ -389,29 +399,34 @@ impl ByteCompiler<'_> {
let index = self.get_or_insert_private_name(*name); let index = self.get_or_insert_private_name(*name);
self.emit_with_varying_operand(Opcode::DefinePrivateField, index); self.emit_with_varying_operand(Opcode::DefinePrivateField, index);
} }
ClassElement::StaticBlock(body) => { ClassElement::StaticBlock(block) => {
let mut compiler = ByteCompiler::new( let mut compiler = ByteCompiler::new(
Sym::EMPTY_STRING.to_js_string(self.interner()), Sym::EMPTY_STRING.to_js_string(self.interner()),
true, true,
false, false,
self.variable_environment.clone(), self.variable_scope.clone(),
self.lexical_environment.clone(), self.lexical_scope.clone(),
false, false,
false, false,
self.interner, self.interner,
self.in_with, self.in_with,
); );
let _ = compiler.push_compile_environment(true); let _ = compiler.push_scope(block.scopes().function_scope());
compiler.function_declaration_instantiation( compiler.function_declaration_instantiation(
body, block.statements(),
&FormalParameterList::default(), &FormalParameterList::default(),
false, false,
true, true,
false, false,
block.scopes(),
); );
compiler.compile_statement_list(body.statements(), false, false); compiler.compile_statement_list(
block.statements().statement_list(),
false,
false,
);
let code = Gc::new(compiler.finish()); let code = Gc::new(compiler.finish());
static_elements.push(StaticElement::StaticBlock(code)); static_elements.push(StaticElement::StaticBlock(code));
@ -459,9 +474,9 @@ impl ByteCompiler<'_> {
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::Pop); self.emit_opcode(Opcode::Pop);
if let Some(old_lex_env) = old_lex_env { if let Some(outer_scope) = outer_scope {
self.pop_compile_environment(); self.pop_scope();
self.lexical_environment = old_lex_env; self.lexical_scope = outer_scope;
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
} }

656
core/engine/src/bytecompiler/declarations.rs

@ -1,13 +1,10 @@
use std::rc::Rc;
use crate::{ use crate::{
bytecompiler::{ByteCompiler, FunctionCompiler, FunctionSpec, NodeKind}, bytecompiler::{ByteCompiler, FunctionCompiler, FunctionSpec, NodeKind},
environments::CompileTimeEnvironment,
vm::{BindingOpcode, Opcode}, vm::{BindingOpcode, Opcode},
Context, JsNativeError, JsResult, Context, JsNativeError, JsResult,
}; };
use boa_ast::{ use boa_ast::{
declaration::{Binding, LexicalDeclaration, VariableList}, declaration::Binding,
expression::Identifier, expression::Identifier,
function::{FormalParameterList, FunctionBody}, function::{FormalParameterList, FunctionBody},
operations::{ operations::{
@ -15,8 +12,10 @@ use boa_ast::{
lexically_scoped_declarations, var_declared_names, var_scoped_declarations, lexically_scoped_declarations, var_declared_names, var_scoped_declarations,
LexicallyScopedDeclaration, VarScopedDeclaration, LexicallyScopedDeclaration, VarScopedDeclaration,
}, },
scope::{FunctionScopes, Scope},
scope_analyzer::EvalDeclarationBindings,
visitor::NodeRef, visitor::NodeRef,
Declaration, Script, StatementListItem, Script,
}; };
use boa_interner::{JStrRef, Sym}; use boa_interner::{JStrRef, Sym};
@ -40,7 +39,7 @@ use super::{Operand, ToJsString};
pub(crate) fn global_declaration_instantiation_context( pub(crate) fn global_declaration_instantiation_context(
_annex_b_function_names: &mut Vec<Identifier>, _annex_b_function_names: &mut Vec<Identifier>,
_script: &Script, _script: &Script,
_env: &Rc<CompileTimeEnvironment>, _env: &Scope,
_context: &mut Context, _context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
Ok(()) Ok(())
@ -59,7 +58,7 @@ pub(crate) fn global_declaration_instantiation_context(
pub(crate) fn global_declaration_instantiation_context( pub(crate) fn global_declaration_instantiation_context(
annex_b_function_names: &mut Vec<Identifier>, annex_b_function_names: &mut Vec<Identifier>,
script: &Script, script: &Script,
env: &Rc<CompileTimeEnvironment>, env: &Scope,
context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
// SKIP: 1. Let lexNames be the LexicallyDeclaredNames of script. // SKIP: 1. Let lexNames be the LexicallyDeclaredNames of script.
@ -202,8 +201,8 @@ pub(crate) fn eval_declaration_instantiation_context(
#[allow(unused, clippy::ptr_arg)] annex_b_function_names: &mut Vec<Identifier>, #[allow(unused, clippy::ptr_arg)] annex_b_function_names: &mut Vec<Identifier>,
body: &Script, body: &Script,
#[allow(unused)] strict: bool, #[allow(unused)] strict: bool,
#[allow(unused)] var_env: &Rc<CompileTimeEnvironment>, #[allow(unused)] var_env: &Scope,
#[allow(unused)] lex_env: &Rc<CompileTimeEnvironment>, #[allow(unused)] lex_env: &Scope,
context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
// SKIP: 3. If strict is false, then // SKIP: 3. If strict is false, then
@ -293,7 +292,7 @@ pub(crate) fn eval_declaration_instantiation_context(
// 3. Assert: The following loop will terminate. // 3. Assert: The following loop will terminate.
// 4. Repeat, while thisEnv is not varEnv, // 4. Repeat, while thisEnv is not varEnv,
while this_env.environment_index() != lex_env.environment_index() { while this_env.scope_index() != lex_env.scope_index() {
let f = f.to_js_string(context.interner()); let f = f.to_js_string(context.interner());
// a. If thisEnv is not an Object Environment Record, then // a. If thisEnv is not an Object Environment Record, then
@ -381,29 +380,15 @@ impl ByteCompiler<'_> {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation /// [spec]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
pub(crate) fn global_declaration_instantiation( pub(crate) fn global_declaration_instantiation(&mut self, script: &Script) {
&mut self,
script: &Script,
env: &Rc<CompileTimeEnvironment>,
) {
// 1. Let lexNames be the LexicallyDeclaredNames of script. // 1. Let lexNames be the LexicallyDeclaredNames of script.
let lex_names = lexically_declared_names(script); let lex_names = lexically_declared_names(script);
// 2. Let varNames be the VarDeclaredNames of script. // 2. Let varNames be the VarDeclaredNames of script.
let var_names = var_declared_names(script);
// 3. For each element name of lexNames, do // 3. For each element name of lexNames, do
for name in lex_names { for name in lex_names {
let name = name.to_js_string(self.interner()); let name = name.to_js_string(self.interner());
// Note: Our implementation differs from the spec here.
// a. If env.HasVarDeclaration(name) is true, throw a SyntaxError exception.
// b. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
if env.has_binding(&name) {
self.emit_syntax_error("duplicate lexical declaration");
return;
}
// c. Let hasRestrictedGlobal be ? env.HasRestrictedGlobalProperty(name). // c. Let hasRestrictedGlobal be ? env.HasRestrictedGlobalProperty(name).
let index = self.get_or_insert_string(name); let index = self.get_or_insert_string(name);
self.emit_with_varying_operand(Opcode::HasRestrictedGlobalProperty, index); self.emit_with_varying_operand(Opcode::HasRestrictedGlobalProperty, index);
@ -414,17 +399,6 @@ impl ByteCompiler<'_> {
self.patch_jump(exit); self.patch_jump(exit);
} }
// 4. For each element name of varNames, do
for name in var_names {
let name = name.to_js_string(self.interner());
// a. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
if env.has_lex_binding(&name) {
self.emit_syntax_error("duplicate lexical declaration");
return;
}
}
// 5. Let varDeclarations be the VarScopedDeclarations of script. // 5. Let varDeclarations be the VarScopedDeclarations of script.
// Note: VarScopedDeclarations for a Script node is TopLevelVarScopedDeclarations. // Note: VarScopedDeclarations for a Script node is TopLevelVarScopedDeclarations.
let var_declarations = var_scoped_declarations(script); let var_declarations = var_scoped_declarations(script);
@ -502,67 +476,42 @@ impl ByteCompiler<'_> {
} }
} }
// NOTE: These steps depend on the global object are done before bytecode compilation.
//
// SKIP: 11. NOTE: No abnormal terminations occur after this algorithm step if the global object is an ordinary object.
// However, if the global object is a Proxy exotic object it may exhibit behaviours
// that cause abnormal terminations in some of the following steps.
// SKIP: 12. NOTE: Annex B.3.2.2 adds additional steps at this point.
// SKIP: 12. Perform the following steps:
// SKIP: a. Let strict be IsStrict of script.
// SKIP: b. If strict is false, then
// 13. Let lexDeclarations be the LexicallyScopedDeclarations of script.
// 14. Let privateEnv be null.
// 15. For each element d of lexDeclarations, do
for statement in &**script.statements() {
// a. NOTE: Lexically declared names are only instantiated here but not initialized.
// b. For each element dn of the BoundNames of d, do
// i. If IsConstantDeclaration of d is true, then
// 1. Perform ? env.CreateImmutableBinding(dn, true).
// ii. Else,
// 1. Perform ? env.CreateMutableBinding(dn, false).
if let StatementListItem::Declaration(declaration) = statement {
match declaration {
Declaration::ClassDeclaration(class) => {
for name in bound_names(class) {
let name = name.to_js_string(self.interner());
env.create_mutable_binding(name, false);
}
}
Declaration::Lexical(LexicalDeclaration::Let(declaration)) => {
for name in bound_names(declaration) {
let name = name.to_js_string(self.interner());
env.create_mutable_binding(name, false);
}
}
Declaration::Lexical(LexicalDeclaration::Const(declaration)) => {
for name in bound_names(declaration) {
let name = name.to_js_string(self.interner());
env.create_immutable_binding(name, true);
}
}
_ => {}
}
}
}
// 16. For each Parse Node f of functionsToInitialize, do // 16. For each Parse Node f of functionsToInitialize, do
for function in functions_to_initialize { for function in functions_to_initialize {
// a. Let fn be the sole element of the BoundNames of f. // a. Let fn be the sole element of the BoundNames of f.
let (name, generator, r#async, parameters, body) = match &function { let (name, generator, r#async, parameters, body, scopes) = match &function {
VarScopedDeclaration::FunctionDeclaration(f) => { VarScopedDeclaration::FunctionDeclaration(f) => (
(f.name(), false, false, f.parameters(), f.body()) f.name(),
} false,
VarScopedDeclaration::GeneratorDeclaration(f) => { false,
(f.name(), true, false, f.parameters(), f.body()) f.parameters(),
} f.body(),
VarScopedDeclaration::AsyncFunctionDeclaration(f) => { f.scopes().clone(),
(f.name(), false, true, f.parameters(), f.body()) ),
} VarScopedDeclaration::GeneratorDeclaration(f) => (
VarScopedDeclaration::AsyncGeneratorDeclaration(f) => { f.name(),
(f.name(), true, true, f.parameters(), f.body()) true,
} false,
f.parameters(),
f.body(),
f.scopes().clone(),
),
VarScopedDeclaration::AsyncFunctionDeclaration(f) => (
f.name(),
false,
true,
f.parameters(),
f.body(),
f.scopes().clone(),
),
VarScopedDeclaration::AsyncGeneratorDeclaration(f) => (
f.name(),
true,
true,
f.parameters(),
f.body(),
f.scopes().clone(),
),
VarScopedDeclaration::VariableDeclaration(_) => continue, VarScopedDeclaration::VariableDeclaration(_) => continue,
}; };
@ -572,12 +521,12 @@ impl ByteCompiler<'_> {
.r#async(r#async) .r#async(r#async)
.strict(self.strict()) .strict(self.strict())
.in_with(self.in_with) .in_with(self.in_with)
.binding_identifier(None)
.compile( .compile(
parameters, parameters,
body, body,
self.variable_environment.clone(), self.variable_scope.clone(),
self.lexical_environment.clone(), self.lexical_scope.clone(),
&scopes,
self.interner, self.interner,
); );
@ -614,51 +563,13 @@ impl ByteCompiler<'_> {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-blockdeclarationinstantiation /// [spec]: https://tc39.es/ecma262/#sec-blockdeclarationinstantiation
pub(crate) fn block_declaration_instantiation<'a, N>( pub(crate) fn block_declaration_instantiation<'a, N>(&mut self, block: &'a N)
&mut self, where
block: &'a N,
env: &Rc<CompileTimeEnvironment>,
) where
&'a N: Into<NodeRef<'a>>, &'a N: Into<NodeRef<'a>>,
{ {
// 1. Let declarations be the LexicallyScopedDeclarations of code. // 1. Let declarations be the LexicallyScopedDeclarations of code.
let declarations = lexically_scoped_declarations(block); let declarations = lexically_scoped_declarations(block);
// 2. Let privateEnv be the running execution context's PrivateEnvironment.
// Note: Private environments are currently handled differently.
// 3. For each element d of declarations, do
for d in &declarations {
// i. If IsConstantDeclaration of d is true, then
if let LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const(d)) = d
{
// a. For each element dn of the BoundNames of d, do
for dn in bound_names::<'_, VariableList>(d) {
// 1. Perform ! env.CreateImmutableBinding(dn, true).
let dn = dn.to_js_string(self.interner());
env.create_immutable_binding(dn, true);
}
}
// ii. Else,
else {
// a. For each element dn of the BoundNames of d, do
for dn in d.bound_names() {
let dn = dn.to_js_string(self.interner());
#[cfg(not(feature = "annex-b"))]
// 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6.
env.create_mutable_binding(dn, false);
#[cfg(feature = "annex-b")]
// 1. If ! env.HasBinding(dn) is false, then
if !env.has_binding(&dn) {
// a. Perform ! env.CreateMutableBinding(dn, false).
env.create_mutable_binding(dn, false);
}
}
}
}
// Note: Not sure if the spec is wrong here or if our implementation just differs too much, // Note: Not sure if the spec is wrong here or if our implementation just differs too much,
// but we need 3.a to be finished for all declarations before 3.b can be done. // but we need 3.a to be finished for all declarations before 3.b can be done.
@ -697,65 +608,14 @@ impl ByteCompiler<'_> {
pub(crate) fn eval_declaration_instantiation( pub(crate) fn eval_declaration_instantiation(
&mut self, &mut self,
body: &Script, body: &Script,
strict: bool, #[allow(unused_variables)] strict: bool,
var_env: &Rc<CompileTimeEnvironment>, var_env: &Scope,
lex_env: &Rc<CompileTimeEnvironment>, bindings: EvalDeclarationBindings,
) { ) {
// 2. Let varDeclarations be the VarScopedDeclarations of body. // 2. Let varDeclarations be the VarScopedDeclarations of body.
let var_declarations = var_scoped_declarations(body); let var_declarations = var_scoped_declarations(body);
// 3. If strict is false, then // SKIP: 3. If strict is false, then
if !strict {
// 1. Let varNames be the VarDeclaredNames of body.
let var_names = var_declared_names(body);
// a. If varEnv is a Global Environment Record, then
if var_env.is_global() {
// i. For each element name of varNames, do
for name in &var_names {
let name = name.to_js_string(self.interner());
// 1. If varEnv.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
// 2. NOTE: eval will not create a global var declaration that would be shadowed by a global lexical declaration.
if var_env.has_lex_binding(&name) {
self.emit_syntax_error("duplicate lexical declaration");
return;
}
}
}
// b. Let thisEnv be lexEnv.
let mut this_env = lex_env.clone();
// c. Assert: The following loop will terminate.
// d. Repeat, while thisEnv is not varEnv,
while this_env.environment_index() != var_env.environment_index() {
// i. If thisEnv is not an Object Environment Record, then
// 1. NOTE: The environment of with statements cannot contain any lexical
// declaration so it doesn't need to be checked for var/let hoisting conflicts.
// 2. For each element name of varNames, do
for name in &var_names {
let name = self.interner().resolve_expect(name.sym()).utf16().into();
// a. If ! thisEnv.HasBinding(name) is true, then
if this_env.has_binding(&name) {
// i. Throw a SyntaxError exception.
// ii. NOTE: Annex B.3.4 defines alternate semantics for the above step.
let msg = format!("variable declaration {} in eval function already exists as a lexical variable", name.to_std_string_escaped());
self.emit_syntax_error(&msg);
return;
}
// b. NOTE: A direct eval will not hoist var declaration over a like-named lexical declaration.
}
// ii. Set thisEnv to thisEnv.[[OuterEnv]].
if let Some(outer) = this_env.outer() {
this_env = outer;
} else {
break;
}
}
}
// NOTE: These steps depend on the current environment state are done before bytecode compilation, // NOTE: These steps depend on the current environment state are done before bytecode compilation,
// in `eval_declaration_instantiation_context`. // in `eval_declaration_instantiation_context`.
@ -820,18 +680,14 @@ impl ByteCompiler<'_> {
// NOTE: This diviates from the specification, we split the first part of defining the annex-b names // NOTE: This diviates from the specification, we split the first part of defining the annex-b names
// in `eval_declaration_instantiation_context`, because it depends on the context. // in `eval_declaration_instantiation_context`, because it depends on the context.
if !var_env.is_global() { if !var_env.is_global() {
for name in self.annex_b_function_names.clone() { for binding in bindings.new_annex_b_function_names {
let f = name.to_js_string(self.interner());
// i. Let bindingExists be ! varEnv.HasBinding(F). // i. Let bindingExists be ! varEnv.HasBinding(F).
// ii. If bindingExists is false, then // ii. If bindingExists is false, then
if !var_env.has_binding(&f) { // i. Perform ! varEnv.CreateMutableBinding(F, true).
// i. Perform ! varEnv.CreateMutableBinding(F, true). // ii. Perform ! varEnv.InitializeBinding(F, undefined).
// ii. Perform ! varEnv.InitializeBinding(F, undefined). let index = self.get_or_insert_binding(binding);
let binding = var_env.create_mutable_binding(f, true); self.emit_opcode(Opcode::PushUndefined);
let index = self.get_or_insert_binding(binding); self.emit_binding_access(Opcode::DefInitVar, &index);
self.emit_opcode(Opcode::PushUndefined);
self.emit_with_varying_operand(Opcode::DefInitVar, index);
}
} }
} }
} }
@ -877,54 +733,43 @@ impl ByteCompiler<'_> {
// 15. Let lexDeclarations be the LexicallyScopedDeclarations of body. // 15. Let lexDeclarations be the LexicallyScopedDeclarations of body.
// 16. For each element d of lexDeclarations, do // 16. For each element d of lexDeclarations, do
for statement in &**body.statements() {
// a. NOTE: Lexically declared names are only instantiated here but not initialized.
// b. For each element dn of the BoundNames of d, do
// i. If IsConstantDeclaration of d is true, then
// 1. Perform ? lexEnv.CreateImmutableBinding(dn, true).
// ii. Else,
// 1. Perform ? lexEnv.CreateMutableBinding(dn, false).
if let StatementListItem::Declaration(declaration) = statement {
match declaration {
Declaration::ClassDeclaration(class) => {
for name in bound_names(class) {
let name = name.to_js_string(self.interner());
lex_env.create_mutable_binding(name, false);
}
}
Declaration::Lexical(LexicalDeclaration::Let(declaration)) => {
for name in bound_names(declaration) {
let name = name.to_js_string(self.interner());
lex_env.create_mutable_binding(name, false);
}
}
Declaration::Lexical(LexicalDeclaration::Const(declaration)) => {
for name in bound_names(declaration) {
let name = name.to_js_string(self.interner());
lex_env.create_immutable_binding(name, true);
}
}
_ => {}
}
}
}
// 17. For each Parse Node f of functionsToInitialize, do // 17. For each Parse Node f of functionsToInitialize, do
for function in functions_to_initialize { for function in functions_to_initialize {
// a. Let fn be the sole element of the BoundNames of f. // a. Let fn be the sole element of the BoundNames of f.
let (name, generator, r#async, parameters, body) = match &function { let (name, generator, r#async, parameters, body, scopes) = match &function {
VarScopedDeclaration::FunctionDeclaration(f) => { VarScopedDeclaration::FunctionDeclaration(f) => (
(f.name(), false, false, f.parameters(), f.body()) f.name(),
} false,
VarScopedDeclaration::GeneratorDeclaration(f) => { false,
(f.name(), true, false, f.parameters(), f.body()) f.parameters(),
} f.body(),
VarScopedDeclaration::AsyncFunctionDeclaration(f) => { f.scopes().clone(),
(f.name(), false, true, f.parameters(), f.body()) ),
} VarScopedDeclaration::GeneratorDeclaration(f) => (
VarScopedDeclaration::AsyncGeneratorDeclaration(f) => { f.name(),
(f.name(), true, true, f.parameters(), f.body()) true,
} false,
f.parameters(),
f.body(),
f.scopes().clone(),
),
VarScopedDeclaration::AsyncFunctionDeclaration(f) => (
f.name(),
false,
true,
f.parameters(),
f.body(),
f.scopes().clone(),
),
VarScopedDeclaration::AsyncGeneratorDeclaration(f) => (
f.name(),
true,
true,
f.parameters(),
f.body(),
f.scopes().clone(),
),
VarScopedDeclaration::VariableDeclaration(_) => { VarScopedDeclaration::VariableDeclaration(_) => {
continue; continue;
} }
@ -936,12 +781,13 @@ impl ByteCompiler<'_> {
.r#async(r#async) .r#async(r#async)
.strict(self.strict()) .strict(self.strict())
.in_with(self.in_with) .in_with(self.in_with)
.binding_identifier(Some(name.sym().to_js_string(self.interner()))) .name_scope(None)
.compile( .compile(
parameters, parameters,
body, body,
self.variable_environment.clone(), self.variable_scope.clone(),
self.lexical_environment.clone(), self.lexical_scope.clone(),
&scopes,
self.interner, self.interner,
); );
@ -966,25 +812,24 @@ impl ByteCompiler<'_> {
let index = self.push_function_to_constants(code); let index = self.push_function_to_constants(code);
self.emit_with_varying_operand(Opcode::GetFunction, index); self.emit_with_varying_operand(Opcode::GetFunction, index);
let name = name.to_js_string(self.interner());
// i. Let bindingExists be ! varEnv.HasBinding(fn). // i. Let bindingExists be ! varEnv.HasBinding(fn).
let binding_exists = var_env.has_binding(&name); let (binding, binding_exists) = bindings
.new_function_names
.get(&name)
.expect("binding must exist");
// ii. If bindingExists is false, then // ii. If bindingExists is false, then
// iii. Else, // iii. Else,
if binding_exists { if *binding_exists {
// 1. Perform ! varEnv.SetMutableBinding(fn, fo, false). // 1. Perform ! varEnv.SetMutableBinding(fn, fo, false).
let binding = var_env.set_mutable_binding(name).expect("must not fail"); let index = self.get_or_insert_binding(binding.clone());
let index = self.get_or_insert_binding(binding); self.emit_binding_access(Opcode::SetName, &index);
self.emit_with_varying_operand(Opcode::SetName, index);
} else { } else {
// 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14. // 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14.
// 2. Perform ! varEnv.CreateMutableBinding(fn, true). // 2. Perform ! varEnv.CreateMutableBinding(fn, true).
// 3. Perform ! varEnv.InitializeBinding(fn, fo). // 3. Perform ! varEnv.InitializeBinding(fn, fo).
let binding = var_env.create_mutable_binding(name, !strict); let index = self.get_or_insert_binding(binding.clone());
let index = self.get_or_insert_binding(binding); self.emit_binding_access(Opcode::DefInitVar, &index);
self.emit_with_varying_operand(Opcode::DefInitVar, index);
} }
} }
} }
@ -1001,24 +846,17 @@ impl ByteCompiler<'_> {
&[Operand::Bool(true), Operand::Varying(index)], &[Operand::Bool(true), Operand::Varying(index)],
); );
} }
// b. Else, }
else { // 18.b
let name = name.to_js_string(self.interner()); for binding in bindings.new_var_names {
// i. Let bindingExists be ! varEnv.HasBinding(vn).
// i. Let bindingExists be ! varEnv.HasBinding(vn). // ii. If bindingExists is false, then
let binding_exists = var_env.has_binding(&name); // 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14.
// 2. Perform ! varEnv.CreateMutableBinding(vn, true).
// ii. If bindingExists is false, then // 3. Perform ! varEnv.InitializeBinding(vn, undefined).
if !binding_exists { let index = self.get_or_insert_binding(binding);
// 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14. self.emit_opcode(Opcode::PushUndefined);
// 2. Perform ! varEnv.CreateMutableBinding(vn, true). self.emit_binding_access(Opcode::DefInitVar, &index);
// 3. Perform ! varEnv.InitializeBinding(vn, undefined).
let binding = var_env.create_mutable_binding(name, true);
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined);
self.emit_with_varying_operand(Opcode::DefInitVar, index);
}
}
} }
// 19. Return unused. // 19. Return unused.
@ -1037,6 +875,7 @@ impl ByteCompiler<'_> {
arrow: bool, arrow: bool,
strict: bool, strict: bool,
generator: bool, generator: bool,
scopes: &FunctionScopes,
) { ) {
// 1. Let calleeContext be the running execution context. // 1. Let calleeContext be the running execution context.
// 2. Let code be func.[[ECMAScriptCode]]. // 2. Let code be func.[[ECMAScriptCode]].
@ -1123,25 +962,13 @@ impl ByteCompiler<'_> {
} }
} }
// 19. If strict is true or hasParameterExpressions is false, then // 19-20
if strict || !has_parameter_expressions { if let Some(scope) = scopes.parameters_eval_scope() {
// a. NOTE: Only a single Environment Record is needed for the parameters, let scope_index = self.push_scope(scope);
// since calls to eval in strict mode code cannot create new bindings which are visible outside of the eval. self.emit_with_varying_operand(Opcode::PushScope, scope_index);
// b. Let env be the LexicalEnvironment of calleeContext.
} }
// 20. Else,
else {
// a. NOTE: A separate Environment Record is needed to ensure that bindings created by
// direct eval calls in the formal parameter list are outside the environment where parameters are declared.
// b. Let calleeEnv be the LexicalEnvironment of calleeContext.
// c. Let env be NewDeclarativeEnvironment(calleeEnv).
// d. Assert: The VariableEnvironment of calleeContext is calleeEnv.
// e. Set the LexicalEnvironment of calleeContext to env.
let env_index = self.push_compile_environment(false);
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index);
};
let env = self.lexical_environment.clone(); let scope = self.lexical_scope.clone();
// 22. If argumentsObjectNeeded is true, then // 22. If argumentsObjectNeeded is true, then
// //
@ -1165,44 +992,10 @@ impl ByteCompiler<'_> {
self.emitted_mapped_arguments_object_opcode = true; self.emitted_mapped_arguments_object_opcode = true;
} }
// c. If strict is true, then
if strict {
// i. Perform ! env.CreateImmutableBinding("arguments", false).
// ii. NOTE: In strict mode code early errors prevent attempting to assign
// to this binding, so its mutability is not observable.
env.create_immutable_binding(arguments.clone(), false);
}
// d. Else,
else {
// i. Perform ! env.CreateMutableBinding("arguments", false).
env.create_mutable_binding(arguments.clone(), false);
}
// e. Perform ! env.InitializeBinding("arguments", ao). // e. Perform ! env.InitializeBinding("arguments", ao).
self.emit_binding(BindingOpcode::InitLexical, arguments); self.emit_binding(BindingOpcode::InitLexical, arguments);
} }
// 21. For each String paramName of parameterNames, do
for param_name in &parameter_names {
let param_name = param_name.to_js_string(self.interner());
// a. Let alreadyDeclared be ! env.HasBinding(paramName).
let already_declared = env.has_binding(&param_name);
// b. NOTE: Early errors ensure that duplicate parameter names can only occur in non-strict
// functions that do not have parameter default values or rest parameters.
// c. If alreadyDeclared is false, then
if !already_declared {
// i. Perform ! env.CreateMutableBinding(paramName, false).
env.create_mutable_binding(param_name, false);
// Note: These steps are not necessary in our implementation.
// ii. If hasDuplicates is true, then
// 1. Perform ! env.InitializeBinding(paramName, undefined).
}
}
// 22. If argumentsObjectNeeded is true, then // 22. If argumentsObjectNeeded is true, then
if arguments_object_needed { if arguments_object_needed {
// MOVED: a-e. // MOVED: a-e.
@ -1257,82 +1050,87 @@ impl ByteCompiler<'_> {
// 27. If hasParameterExpressions is false, then // 27. If hasParameterExpressions is false, then
// 28. Else, // 28. Else,
#[allow(unused_variables, unused_mut)] #[allow(unused_variables, unused_mut)]
let (mut instantiated_var_names, mut var_env) = if has_parameter_expressions { let (mut instantiated_var_names, mut variable_scope) =
// a. NOTE: A separate Environment Record is needed to ensure that closures created by if let Some(scope) = scopes.parameters_scope() {
// expressions in the formal parameter list do not have // a. NOTE: A separate Environment Record is needed to ensure that closures created by
// visibility of declarations in the function body. // expressions in the formal parameter list do not have
// b. Let varEnv be NewDeclarativeEnvironment(env). // visibility of declarations in the function body.
// c. Set the VariableEnvironment of calleeContext to varEnv. // b. Let varEnv be NewDeclarativeEnvironment(env).
let env_index = self.push_compile_environment(false); // c. Set the VariableEnvironment of calleeContext to varEnv.
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); let scope_index = self.push_scope(scope);
self.emit_with_varying_operand(Opcode::PushScope, scope_index);
let mut var_env = self.lexical_environment.clone();
let mut variable_scope = self.lexical_scope.clone();
// d. Let instantiatedVarNames be a new empty List.
let mut instantiated_var_names = Vec::new(); // d. Let instantiatedVarNames be a new empty List.
let mut instantiated_var_names = Vec::new();
// e. For each element n of varNames, do
for n in var_names { // e. For each element n of varNames, do
// i. If instantiatedVarNames does not contain n, then for n in var_names {
if !instantiated_var_names.contains(&n) { // i. If instantiatedVarNames does not contain n, then
// 1. Append n to instantiatedVarNames. if !instantiated_var_names.contains(&n) {
instantiated_var_names.push(n); // 1. Append n to instantiatedVarNames.
instantiated_var_names.push(n);
let n_string = n.to_js_string(self.interner());
let n_string = n.to_js_string(self.interner());
// 2. Perform ! varEnv.CreateMutableBinding(n, false).
let binding = var_env.create_mutable_binding(n_string.clone(), false); // 2. Perform ! varEnv.CreateMutableBinding(n, false).
let binding = variable_scope
// 3. If parameterBindings does not contain n, or if functionNames contains n, then .get_binding_reference(&n_string)
if !parameter_bindings.contains(&n) || function_names.contains(&n) { .expect("must have binding");
// a. Let initialValue be undefined.
self.emit_opcode(Opcode::PushUndefined); // 3. If parameterBindings does not contain n, or if functionNames contains n, then
} if !parameter_bindings.contains(&n) || function_names.contains(&n) {
// 4. Else, // a. Let initialValue be undefined.
else { self.emit_opcode(Opcode::PushUndefined);
// a. Let initialValue be ! env.GetBindingValue(n, false). }
let binding = env.get_binding(&n_string).expect("must have binding"); // 4. Else,
let index = self.get_or_insert_binding(binding); else {
self.emit_with_varying_operand(Opcode::GetName, index); // a. Let initialValue be ! env.GetBindingValue(n, false).
} let binding = scope
.get_binding_reference(&n_string)
.expect("must have binding");
let index = self.get_or_insert_binding(binding);
self.emit_binding_access(Opcode::GetName, &index);
}
// 5. Perform ! varEnv.InitializeBinding(n, initialValue). // 5. Perform ! varEnv.InitializeBinding(n, initialValue).
let index = self.get_or_insert_binding(binding); let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined); self.emit_opcode(Opcode::PushUndefined);
self.emit_with_varying_operand(Opcode::DefInitVar, index); self.emit_binding_access(Opcode::DefInitVar, &index);
// 6. NOTE: A var with the same name as a formal parameter initially has // 6. NOTE: A var with the same name as a formal parameter initially has
// the same value as the corresponding initialized parameter. // the same value as the corresponding initialized parameter.
}
} }
}
(instantiated_var_names, var_env)
} else {
// a. NOTE: Only a single Environment Record is needed for the parameters and top-level vars.
// b. Let instantiatedVarNames be a copy of the List parameterBindings.
let mut instantiated_var_names = parameter_bindings;
// c. For each element n of varNames, do
for n in var_names {
// i. If instantiatedVarNames does not contain n, then
if !instantiated_var_names.contains(&n) {
// 1. Append n to instantiatedVarNames.
instantiated_var_names.push(n);
let n = n.to_js_string(self.interner()); (instantiated_var_names, variable_scope)
} else {
// 2. Perform ! env.CreateMutableBinding(n, false). // a. NOTE: Only a single Environment Record is needed for the parameters and top-level vars.
// 3. Perform ! env.InitializeBinding(n, undefined). // b. Let instantiatedVarNames be a copy of the List parameterBindings.
let binding = env.create_mutable_binding(n, true); let mut instantiated_var_names = parameter_bindings;
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined); // c. For each element n of varNames, do
self.emit_with_varying_operand(Opcode::DefInitVar, index); for n in var_names {
// i. If instantiatedVarNames does not contain n, then
if !instantiated_var_names.contains(&n) {
// 1. Append n to instantiatedVarNames.
instantiated_var_names.push(n);
let n = n.to_js_string(self.interner());
// 2. Perform ! env.CreateMutableBinding(n, false).
// 3. Perform ! env.InitializeBinding(n, undefined).
let binding = scope.get_binding_reference(&n).expect("binding must exist");
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined);
self.emit_binding_access(Opcode::DefInitVar, &index);
}
} }
}
// d. Let varEnv be env. // d. Let varEnv be env.
(instantiated_var_names, env) (instantiated_var_names, scope)
}; };
// 29. NOTE: Annex B.3.2.1 adds additional steps at this point. // 29. NOTE: Annex B.3.2.1 adds additional steps at this point.
// 29. If strict is false, then // 29. If strict is false, then
@ -1355,10 +1153,12 @@ impl ByteCompiler<'_> {
// a. Perform ! varEnv.CreateMutableBinding(F, false). // a. Perform ! varEnv.CreateMutableBinding(F, false).
// b. Perform ! varEnv.InitializeBinding(F, undefined). // b. Perform ! varEnv.InitializeBinding(F, undefined).
let binding = var_env.create_mutable_binding(f_string, false); let binding = variable_scope
.get_binding_reference(&f_string)
.expect("binding must exist");
let index = self.get_or_insert_binding(binding); let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined); self.emit_opcode(Opcode::PushUndefined);
self.emit_with_varying_operand(Opcode::DefInitVar, index); self.emit_binding_access(Opcode::DefInitVar, &index);
// c. Append F to instantiatedVarNames. // c. Append F to instantiatedVarNames.
instantiated_var_names.push(f); instantiated_var_names.push(f);
@ -1376,58 +1176,10 @@ impl ByteCompiler<'_> {
} }
} }
// 30. If strict is false, then // 30-31
// 31. Else, if let Some(scope) = scopes.lexical_scope() {
let lex_env = if strict { let scope_index = self.push_scope(scope);
// a. Let lexEnv be varEnv. self.emit_with_varying_operand(Opcode::PushScope, scope_index);
var_env
} else {
// a. Let lexEnv be NewDeclarativeEnvironment(varEnv).
// b. NOTE: Non-strict functions use a separate Environment Record for top-level lexical
// declarations so that a direct eval can determine whether any var scoped declarations
// introduced by the eval code conflict with pre-existing top-level lexically scoped declarations.
// This is not needed for strict functions because a strict direct eval always
// places all declarations into a new Environment Record.
let env_index = self.push_compile_environment(false);
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index);
self.lexical_environment.clone()
};
// 32. Set the LexicalEnvironment of calleeContext to lexEnv.
// 33. Let lexDeclarations be the LexicallyScopedDeclarations of code.
// 34. For each element d of lexDeclarations, do
// a. NOTE: A lexically declared name cannot be the same as a function/generator declaration,
// formal parameter, or a var name. Lexically declared names are only instantiated here but not initialized.
// b. For each element dn of the BoundNames of d, do
// i. If IsConstantDeclaration of d is true, then
// 1. Perform ! lexEnv.CreateImmutableBinding(dn, true).
// ii. Else,
// 1. Perform ! lexEnv.CreateMutableBinding(dn, false).
for statement in &**body.statements() {
if let StatementListItem::Declaration(declaration) = statement {
match declaration {
Declaration::ClassDeclaration(class) => {
for name in bound_names(class) {
let name = name.to_js_string(self.interner());
lex_env.create_mutable_binding(name, false);
}
}
Declaration::Lexical(LexicalDeclaration::Let(declaration)) => {
for name in bound_names(declaration) {
let name = name.to_js_string(self.interner());
lex_env.create_mutable_binding(name, false);
}
}
Declaration::Lexical(LexicalDeclaration::Const(declaration)) => {
for name in bound_names(declaration) {
let name = name.to_js_string(self.interner());
lex_env.create_immutable_binding(name, true);
}
}
_ => {}
}
}
} }
// 35. Let privateEnv be the PrivateEnvironment of calleeContext. // 35. Let privateEnv be the PrivateEnvironment of calleeContext.

25
core/engine/src/bytecompiler/env.rs

@ -1,33 +1,28 @@
use boa_ast::scope::Scope;
use super::ByteCompiler; use super::ByteCompiler;
use crate::environments::CompileTimeEnvironment;
use std::rc::Rc;
impl ByteCompiler<'_> { impl ByteCompiler<'_> {
/// Push either a new declarative or function environment on the compile time environment stack. /// Push either a new declarative or function scope on the environment stack.
#[must_use] #[must_use]
pub(crate) fn push_compile_environment(&mut self, function_scope: bool) -> u32 { pub(crate) fn push_scope(&mut self, scope: &Scope) -> u32 {
self.current_open_environments_count += 1; self.current_open_environments_count += 1;
let env = Rc::new(CompileTimeEnvironment::new(
self.lexical_environment.clone(),
function_scope,
));
let index = self.constants.len() as u32; let index = self.constants.len() as u32;
self.constants self.constants
.push(crate::vm::Constant::CompileTimeEnvironment(env.clone())); .push(crate::vm::Constant::Scope(scope.clone()));
if function_scope { if scope.is_function() {
self.variable_environment = env.clone(); self.variable_scope = scope.clone();
} }
self.lexical_environment = env; self.lexical_scope = scope.clone();
index index
} }
/// Pops the top compile time environment and returns its index in the compile time environments array. /// Pops the top scope.
pub(crate) fn pop_compile_environment(&mut self) { pub(crate) fn pop_scope(&mut self) {
self.current_open_environments_count -= 1; self.current_open_environments_count -= 1;
} }
} }

31
core/engine/src/bytecompiler/expression/assign.rs

@ -1,11 +1,13 @@
use crate::{ use crate::{
bytecompiler::{Access, ByteCompiler, Operand, ToJsString}, bytecompiler::{Access, ByteCompiler, Operand, ToJsString},
environments::BindingLocatorError,
vm::{BindingOpcode, Opcode}, vm::{BindingOpcode, Opcode},
}; };
use boa_ast::expression::{ use boa_ast::{
access::{PropertyAccess, PropertyAccessField}, expression::{
operator::{assign::AssignOp, Assign}, access::{PropertyAccess, PropertyAccessField},
operator::{assign::AssignOp, Assign},
},
scope::BindingLocatorError,
}; };
impl ByteCompiler<'_> { impl ByteCompiler<'_> {
@ -57,15 +59,14 @@ impl ByteCompiler<'_> {
Access::Variable { name } => { Access::Variable { name } => {
let name = name.to_js_string(self.interner()); let name = name.to_js_string(self.interner());
let binding = self let binding = self.lexical_scope.get_identifier_reference(name.clone());
.lexical_environment let is_lexical = binding.is_lexical();
.get_identifier_reference(name.clone()); let index = self.get_or_insert_binding(binding);
let index = self.get_or_insert_binding(binding.locator());
if binding.is_lexical() { if is_lexical {
self.emit_with_varying_operand(Opcode::GetName, index); self.emit_binding_access(Opcode::GetName, &index);
} else { } else {
self.emit_with_varying_operand(Opcode::GetNameAndLocator, index); self.emit_binding_access(Opcode::GetNameAndLocator, &index);
} }
if short_circuit { if short_circuit {
@ -78,11 +79,11 @@ impl ByteCompiler<'_> {
if use_expr { if use_expr {
self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup);
} }
if binding.is_lexical() { if is_lexical {
match self.lexical_environment.set_mutable_binding(name.clone()) { match self.lexical_scope.set_mutable_binding(name.clone()) {
Ok(binding) => { Ok(binding) => {
let index = self.get_or_insert_binding(binding); let index = self.get_or_insert_binding(binding);
self.emit_with_varying_operand(Opcode::SetName, index); self.emit_binding_access(Opcode::SetName, &index);
} }
Err(BindingLocatorError::MutateImmutable) => { Err(BindingLocatorError::MutateImmutable) => {
let index = self.get_or_insert_string(name); let index = self.get_or_insert_string(name);
@ -93,7 +94,7 @@ impl ByteCompiler<'_> {
} }
} }
} else { } else {
self.emit_opcode(Opcode::SetNameByLocator); self.emit_binding_access(Opcode::SetNameByLocator, &index);
} }
} }
Access::Property { access } => match access { Access::Property { access } => match access {

2
core/engine/src/bytecompiler/expression/object_literal.rs

@ -56,7 +56,7 @@ impl ByteCompiler<'_> {
MethodKind::Set => Opcode::SetPropertySetterByName, MethodKind::Set => Opcode::SetPropertySetterByName,
MethodKind::Ordinary => Opcode::DefineOwnPropertyByName, MethodKind::Ordinary => Opcode::DefineOwnPropertyByName,
}; };
self.object_method((m, *name).into(), kind); self.object_method(m.into(), kind);
self.emit_opcode(Opcode::SetHomeObject); self.emit_opcode(Opcode::SetHomeObject);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit_with_varying_operand(opcode, index); self.emit_with_varying_operand(opcode, index);

8
core/engine/src/bytecompiler/expression/unary.rs

@ -28,11 +28,9 @@ impl ByteCompiler<'_> {
match unary.target().flatten() { match unary.target().flatten() {
Expression::Identifier(identifier) => { Expression::Identifier(identifier) => {
let identifier = identifier.to_js_string(self.interner()); let identifier = identifier.to_js_string(self.interner());
let binding = self let binding = self.lexical_scope.get_identifier_reference(identifier);
.lexical_environment let index = self.get_or_insert_binding(binding);
.get_identifier_reference(identifier); self.emit_binding_access(Opcode::GetNameOrUndefined, &index);
let index = self.get_or_insert_binding(binding.locator());
self.emit_with_varying_operand(Opcode::GetNameOrUndefined, index);
} }
expr => self.compile_expr(expr, true), expr => self.compile_expr(expr, true),
} }

31
core/engine/src/bytecompiler/expression/update.rs

@ -1,11 +1,13 @@
use crate::{ use crate::{
bytecompiler::{Access, ByteCompiler, Operand, ToJsString}, bytecompiler::{Access, ByteCompiler, Operand, ToJsString},
environments::BindingLocatorError,
vm::Opcode, vm::Opcode,
}; };
use boa_ast::expression::{ use boa_ast::{
access::{PropertyAccess, PropertyAccessField}, expression::{
operator::{update::UpdateOp, Update}, access::{PropertyAccess, PropertyAccessField},
operator::{update::UpdateOp, Update},
},
scope::BindingLocatorError,
}; };
impl ByteCompiler<'_> { impl ByteCompiler<'_> {
@ -24,15 +26,14 @@ impl ByteCompiler<'_> {
match Access::from_update_target(update.target()) { match Access::from_update_target(update.target()) {
Access::Variable { name } => { Access::Variable { name } => {
let name = name.to_js_string(self.interner()); let name = name.to_js_string(self.interner());
let binding = self let binding = self.lexical_scope.get_identifier_reference(name.clone());
.lexical_environment let is_lexical = binding.is_lexical();
.get_identifier_reference(name.clone()); let index = self.get_or_insert_binding(binding);
let index = self.get_or_insert_binding(binding.locator());
if binding.is_lexical() { if is_lexical {
self.emit_with_varying_operand(Opcode::GetName, index); self.emit_binding_access(Opcode::GetName, &index);
} else { } else {
self.emit_with_varying_operand(Opcode::GetNameAndLocator, index); self.emit_binding_access(Opcode::GetNameAndLocator, &index);
} }
self.emit_opcode(opcode); self.emit_opcode(opcode);
@ -42,11 +43,11 @@ impl ByteCompiler<'_> {
self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup);
} }
if binding.is_lexical() { if is_lexical {
match self.lexical_environment.set_mutable_binding(name.clone()) { match self.lexical_scope.set_mutable_binding(name.clone()) {
Ok(binding) => { Ok(binding) => {
let index = self.get_or_insert_binding(binding); let index = self.get_or_insert_binding(binding);
self.emit_with_varying_operand(Opcode::SetName, index); self.emit_binding_access(Opcode::SetName, &index);
} }
Err(BindingLocatorError::MutateImmutable) => { Err(BindingLocatorError::MutateImmutable) => {
let index = self.get_or_insert_string(name); let index = self.get_or_insert_string(name);
@ -57,7 +58,7 @@ impl ByteCompiler<'_> {
} }
} }
} else { } else {
self.emit_opcode(Opcode::SetNameByLocator); self.emit_binding_access(Opcode::SetNameByLocator, &index);
} }
} }
Access::Property { access } => match access { Access::Property { access } => match access {

40
core/engine/src/bytecompiler/function.rs

@ -1,14 +1,14 @@
use std::rc::Rc;
use crate::{ use crate::{
builtins::function::ThisMode, builtins::function::ThisMode,
bytecompiler::ByteCompiler, bytecompiler::ByteCompiler,
environments::CompileTimeEnvironment,
js_string, js_string,
vm::{CodeBlock, CodeBlockFlags, Opcode}, vm::{CodeBlock, CodeBlockFlags, Opcode},
JsString, JsString,
}; };
use boa_ast::function::{FormalParameterList, FunctionBody}; use boa_ast::{
function::{FormalParameterList, FunctionBody},
scope::{FunctionScopes, Scope},
};
use boa_gc::Gc; use boa_gc::Gc;
use boa_interner::Interner; use boa_interner::Interner;
@ -23,7 +23,7 @@ pub(crate) struct FunctionCompiler {
arrow: bool, arrow: bool,
method: bool, method: bool,
in_with: bool, in_with: bool,
binding_identifier: Option<JsString>, name_scope: Option<Scope>,
} }
impl FunctionCompiler { impl FunctionCompiler {
@ -37,7 +37,7 @@ impl FunctionCompiler {
arrow: false, arrow: false,
method: false, method: false,
in_with: false, in_with: false,
binding_identifier: None, name_scope: None,
} }
} }
@ -81,9 +81,9 @@ impl FunctionCompiler {
self self
} }
/// Indicate if the function has a binding identifier. /// Provide the name scope of the function.
pub(crate) fn binding_identifier(mut self, binding_identifier: Option<JsString>) -> Self { pub(crate) fn name_scope(mut self, name_scope: Option<Scope>) -> Self {
self.binding_identifier = binding_identifier; self.name_scope = name_scope;
self self
} }
@ -98,8 +98,9 @@ impl FunctionCompiler {
mut self, mut self,
parameters: &FormalParameterList, parameters: &FormalParameterList,
body: &FunctionBody, body: &FunctionBody,
variable_environment: Rc<CompileTimeEnvironment>, variable_environment: Scope,
lexical_environment: Rc<CompileTimeEnvironment>, lexical_environment: Scope,
scopes: &FunctionScopes,
interner: &mut Interner, interner: &mut Interner,
) -> Gc<CodeBlock> { ) -> Gc<CodeBlock> {
self.strict = self.strict || body.strict(); self.strict = self.strict || body.strict();
@ -127,16 +128,12 @@ impl FunctionCompiler {
compiler.this_mode = ThisMode::Lexical; compiler.this_mode = ThisMode::Lexical;
} }
if let Some(binding_identifier) = self.binding_identifier { if let Some(scope) = self.name_scope {
compiler.code_block_flags |= CodeBlockFlags::HAS_BINDING_IDENTIFIER; compiler.code_block_flags |= CodeBlockFlags::HAS_BINDING_IDENTIFIER;
let _ = compiler.push_compile_environment(false); let _ = compiler.push_scope(&scope);
compiler
.lexical_environment
.create_immutable_binding(binding_identifier, self.strict);
} }
// Function environment // Function environment
let _ = compiler.push_compile_environment(true); let _ = compiler.push_scope(scopes.function_scope());
// Taken from: // Taken from:
// - 15.9.3 Runtime Semantics: EvaluateAsyncConciseBody: <https://tc39.es/ecma262/#sec-runtime-semantics-evaluateasyncconcisebody> // - 15.9.3 Runtime Semantics: EvaluateAsyncConciseBody: <https://tc39.es/ecma262/#sec-runtime-semantics-evaluateasyncconcisebody>
@ -169,6 +166,7 @@ impl FunctionCompiler {
self.arrow, self.arrow,
self.strict, self.strict,
self.generator, self.generator,
scopes,
); );
// Taken from: // Taken from:
@ -184,10 +182,12 @@ impl FunctionCompiler {
} }
} }
compiler.compile_statement_list(body.statements(), false, false); compiler.compile_statement_list(body.statement_list(), false, false);
compiler.params = parameters.clone(); compiler.params = parameters.clone();
Gc::new(compiler.finish()) let code = compiler.finish();
Gc::new(code)
} }
} }

346
core/engine/src/bytecompiler/mod.rs

@ -12,11 +12,10 @@ mod register;
mod statement; mod statement;
mod utils; mod utils;
use std::{cell::Cell, rc::Rc}; use std::cell::Cell;
use crate::{ use crate::{
builtins::function::{arguments::MappedArguments, ThisMode}, builtins::function::{arguments::MappedArguments, ThisMode},
environments::{BindingLocator, BindingLocatorError, CompileTimeEnvironment},
js_string, js_string,
vm::{ vm::{
BindingOpcode, CallFrame, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind, BindingOpcode, CallFrame, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind,
@ -41,6 +40,7 @@ use boa_ast::{
operations::returns_value, operations::returns_value,
pattern::Pattern, pattern::Pattern,
property::MethodDefinitionKind, property::MethodDefinitionKind,
scope::{BindingLocator, BindingLocatorError, FunctionScopes, IdentifierReference, Scope},
Declaration, Expression, Statement, StatementList, StatementListItem, Declaration, Expression, Statement, StatementList, StatementListItem,
}; };
use boa_gc::Gc; use boa_gc::Gc;
@ -120,7 +120,8 @@ pub(crate) struct FunctionSpec<'a> {
pub(crate) name: Option<Identifier>, pub(crate) name: Option<Identifier>,
parameters: &'a FormalParameterList, parameters: &'a FormalParameterList,
body: &'a FunctionBody, body: &'a FunctionBody,
pub(crate) has_binding_identifier: bool, pub(crate) scopes: &'a FunctionScopes,
pub(crate) name_scope: Option<&'a Scope>,
} }
impl<'a> From<&'a FunctionDeclaration> for FunctionSpec<'a> { impl<'a> From<&'a FunctionDeclaration> for FunctionSpec<'a> {
@ -130,7 +131,8 @@ impl<'a> From<&'a FunctionDeclaration> for FunctionSpec<'a> {
name: Some(function.name()), name: Some(function.name()),
parameters: function.parameters(), parameters: function.parameters(),
body: function.body(), body: function.body(),
has_binding_identifier: true, scopes: function.scopes(),
name_scope: None,
} }
} }
} }
@ -142,7 +144,8 @@ impl<'a> From<&'a GeneratorDeclaration> for FunctionSpec<'a> {
name: Some(function.name()), name: Some(function.name()),
parameters: function.parameters(), parameters: function.parameters(),
body: function.body(), body: function.body(),
has_binding_identifier: true, scopes: function.scopes(),
name_scope: None,
} }
} }
} }
@ -154,7 +157,8 @@ impl<'a> From<&'a AsyncFunctionDeclaration> for FunctionSpec<'a> {
name: Some(function.name()), name: Some(function.name()),
parameters: function.parameters(), parameters: function.parameters(),
body: function.body(), body: function.body(),
has_binding_identifier: true, scopes: function.scopes(),
name_scope: None,
} }
} }
} }
@ -166,7 +170,8 @@ impl<'a> From<&'a AsyncGeneratorDeclaration> for FunctionSpec<'a> {
name: Some(function.name()), name: Some(function.name()),
parameters: function.parameters(), parameters: function.parameters(),
body: function.body(), body: function.body(),
has_binding_identifier: true, scopes: function.scopes(),
name_scope: None,
} }
} }
} }
@ -178,7 +183,8 @@ impl<'a> From<&'a FunctionExpression> for FunctionSpec<'a> {
name: function.name(), name: function.name(),
parameters: function.parameters(), parameters: function.parameters(),
body: function.body(), body: function.body(),
has_binding_identifier: function.has_binding_identifier(), scopes: function.scopes(),
name_scope: function.name_scope(),
} }
} }
} }
@ -190,7 +196,8 @@ impl<'a> From<&'a ArrowFunction> for FunctionSpec<'a> {
name: function.name(), name: function.name(),
parameters: function.parameters(), parameters: function.parameters(),
body: function.body(), body: function.body(),
has_binding_identifier: false, scopes: function.scopes(),
name_scope: None,
} }
} }
} }
@ -202,7 +209,8 @@ impl<'a> From<&'a AsyncArrowFunction> for FunctionSpec<'a> {
name: function.name(), name: function.name(),
parameters: function.parameters(), parameters: function.parameters(),
body: function.body(), body: function.body(),
has_binding_identifier: false, scopes: function.scopes(),
name_scope: None,
} }
} }
} }
@ -214,7 +222,8 @@ impl<'a> From<&'a AsyncFunctionExpression> for FunctionSpec<'a> {
name: function.name(), name: function.name(),
parameters: function.parameters(), parameters: function.parameters(),
body: function.body(), body: function.body(),
has_binding_identifier: function.has_binding_identifier(), scopes: function.scopes(),
name_scope: function.name_scope(),
} }
} }
} }
@ -226,7 +235,8 @@ impl<'a> From<&'a GeneratorExpression> for FunctionSpec<'a> {
name: function.name(), name: function.name(),
parameters: function.parameters(), parameters: function.parameters(),
body: function.body(), body: function.body(),
has_binding_identifier: function.has_binding_identifier(), scopes: function.scopes(),
name_scope: function.name_scope(),
} }
} }
} }
@ -238,7 +248,8 @@ impl<'a> From<&'a AsyncGeneratorExpression> for FunctionSpec<'a> {
name: function.name(), name: function.name(),
parameters: function.parameters(), parameters: function.parameters(),
body: function.body(), body: function.body(),
has_binding_identifier: function.has_binding_identifier(), scopes: function.scopes(),
name_scope: function.name_scope(),
} }
} }
} }
@ -257,7 +268,8 @@ impl<'a> From<&'a ClassMethodDefinition> for FunctionSpec<'a> {
name: None, name: None,
parameters: method.parameters(), parameters: method.parameters(),
body: method.body(), body: method.body(),
has_binding_identifier: false, scopes: method.scopes(),
name_scope: None,
} }
} }
} }
@ -273,29 +285,11 @@ impl<'a> From<&'a ObjectMethodDefinition> for FunctionSpec<'a> {
FunctionSpec { FunctionSpec {
kind, kind,
name: None, name: method.name().literal().map(Into::into),
parameters: method.parameters(), parameters: method.parameters(),
body: method.body(), body: method.body(),
has_binding_identifier: false, scopes: method.scopes(),
} name_scope: None,
}
}
impl<'a> From<(&'a ObjectMethodDefinition, Sym)> for FunctionSpec<'a> {
fn from(method: (&'a ObjectMethodDefinition, Sym)) -> Self {
let kind = match method.0.kind() {
MethodDefinitionKind::Generator => FunctionKind::Generator,
MethodDefinitionKind::AsyncGenerator => FunctionKind::AsyncGenerator,
MethodDefinitionKind::Async => FunctionKind::Async,
_ => FunctionKind::Ordinary,
};
FunctionSpec {
kind,
name: Some(Identifier::new(method.1)),
parameters: method.0.parameters(),
body: method.0.body(),
has_binding_identifier: true,
} }
} }
} }
@ -402,11 +396,13 @@ pub struct ByteCompiler<'ctx> {
/// Locators for all bindings in the codeblock. /// Locators for all bindings in the codeblock.
pub(crate) bindings: Vec<BindingLocator>, pub(crate) bindings: Vec<BindingLocator>,
/// The current variable environment. pub(crate) local_binding_registers: FxHashMap<IdentifierReference, u32>,
pub(crate) variable_environment: Rc<CompileTimeEnvironment>,
/// The current variable scope.
pub(crate) variable_scope: Scope,
/// The current lexical environment. /// The current lexical scope.
pub(crate) lexical_environment: Rc<CompileTimeEnvironment>, pub(crate) lexical_scope: Scope,
pub(crate) current_open_environments_count: u32, pub(crate) current_open_environments_count: u32,
current_stack_value_count: u32, current_stack_value_count: u32,
@ -436,6 +432,11 @@ pub struct ByteCompiler<'ctx> {
pub(crate) annex_b_function_names: Vec<Identifier>, pub(crate) annex_b_function_names: Vec<Identifier>,
} }
pub(crate) enum BindingKind {
Stack(u32),
Local(u32),
}
impl<'ctx> ByteCompiler<'ctx> { impl<'ctx> ByteCompiler<'ctx> {
/// Represents a placeholder address that will be patched later. /// Represents a placeholder address that will be patched later.
const DUMMY_ADDRESS: u32 = u32::MAX; const DUMMY_ADDRESS: u32 = u32::MAX;
@ -449,8 +450,8 @@ impl<'ctx> ByteCompiler<'ctx> {
name: JsString, name: JsString,
strict: bool, strict: bool,
json_parse: bool, json_parse: bool,
variable_environment: Rc<CompileTimeEnvironment>, variable_scope: Scope,
lexical_environment: Rc<CompileTimeEnvironment>, lexical_scope: Scope,
is_async: bool, is_async: bool,
is_generator: bool, is_generator: bool,
interner: &'ctx mut Interner, interner: &'ctx mut Interner,
@ -496,6 +497,7 @@ impl<'ctx> ByteCompiler<'ctx> {
bytecode: Vec::default(), bytecode: Vec::default(),
constants: ThinVec::default(), constants: ThinVec::default(),
bindings: Vec::default(), bindings: Vec::default(),
local_binding_registers: FxHashMap::default(),
this_mode: ThisMode::Global, this_mode: ThisMode::Global,
params: FormalParameterList::default(), params: FormalParameterList::default(),
current_open_environments_count: 0, current_open_environments_count: 0,
@ -512,8 +514,8 @@ impl<'ctx> ByteCompiler<'ctx> {
jump_info: Vec::new(), jump_info: Vec::new(),
async_handler: None, async_handler: None,
json_parse, json_parse,
variable_environment, variable_scope,
lexical_environment, lexical_scope,
interner, interner,
#[cfg(feature = "annex-b")] #[cfg(feature = "annex-b")]
@ -581,15 +583,24 @@ impl<'ctx> ByteCompiler<'ctx> {
} }
#[inline] #[inline]
pub(crate) fn get_or_insert_binding(&mut self, binding: BindingLocator) -> u32 { pub(crate) fn get_or_insert_binding(&mut self, binding: IdentifierReference) -> BindingKind {
if let Some(index) = self.bindings_map.get(&binding) { if binding.local() {
return *index; return BindingKind::Local(
*self
.local_binding_registers
.entry(binding)
.or_insert_with(|| self.register_allocator.alloc_persistent().index()),
);
}
if let Some(index) = self.bindings_map.get(&binding.locator()) {
return BindingKind::Stack(*index);
} }
let index = self.bindings.len() as u32; let index = self.bindings.len() as u32;
self.bindings.push(binding.clone()); self.bindings.push(binding.locator().clone());
self.bindings_map.insert(binding, index); self.bindings_map.insert(binding.locator(), index);
index BindingKind::Stack(index)
} }
#[inline] #[inline]
@ -603,47 +614,43 @@ impl<'ctx> ByteCompiler<'ctx> {
fn emit_binding(&mut self, opcode: BindingOpcode, name: JsString) { fn emit_binding(&mut self, opcode: BindingOpcode, name: JsString) {
match opcode { match opcode {
BindingOpcode::Var => { BindingOpcode::Var => {
let binding = self.variable_environment.get_identifier_reference(name); let binding = self.variable_scope.get_identifier_reference(name);
if !binding.locator().is_global() { if !binding.locator().is_global() {
let index = self.get_or_insert_binding(binding.locator()); let index = self.get_or_insert_binding(binding);
self.emit_with_varying_operand(Opcode::DefVar, index); self.emit_binding_access(Opcode::DefVar, &index);
} }
} }
BindingOpcode::InitVar => { BindingOpcode::InitVar => match self.lexical_scope.set_mutable_binding(name.clone()) {
match self.lexical_environment.set_mutable_binding(name.clone()) { Ok(binding) => {
Ok(binding) => { let index = self.get_or_insert_binding(binding);
let index = self.get_or_insert_binding(binding); self.emit_binding_access(Opcode::DefInitVar, &index);
self.emit_with_varying_operand(Opcode::DefInitVar, index);
}
Err(BindingLocatorError::MutateImmutable) => {
let index = self.get_or_insert_string(name);
self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index);
}
Err(BindingLocatorError::Silent) => {
self.emit_opcode(Opcode::Pop);
}
} }
} Err(BindingLocatorError::MutateImmutable) => {
let index = self.get_or_insert_string(name);
self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index);
}
Err(BindingLocatorError::Silent) => {
self.emit_opcode(Opcode::Pop);
}
},
BindingOpcode::InitLexical => { BindingOpcode::InitLexical => {
let binding = self.lexical_environment.get_identifier_reference(name); let binding = self.lexical_scope.get_identifier_reference(name);
let index = self.get_or_insert_binding(binding.locator()); let index = self.get_or_insert_binding(binding);
self.emit_with_varying_operand(Opcode::PutLexicalValue, index); self.emit_binding_access(Opcode::PutLexicalValue, &index);
} }
BindingOpcode::SetName => { BindingOpcode::SetName => match self.lexical_scope.set_mutable_binding(name.clone()) {
match self.lexical_environment.set_mutable_binding(name.clone()) { Ok(binding) => {
Ok(binding) => { let index = self.get_or_insert_binding(binding);
let index = self.get_or_insert_binding(binding); self.emit_binding_access(Opcode::SetName, &index);
self.emit_with_varying_operand(Opcode::SetName, index);
}
Err(BindingLocatorError::MutateImmutable) => {
let index = self.get_or_insert_string(name);
self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index);
}
Err(BindingLocatorError::Silent) => {
self.emit_opcode(Opcode::Pop);
}
} }
} Err(BindingLocatorError::MutateImmutable) => {
let index = self.get_or_insert_string(name);
self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index);
}
Err(BindingLocatorError::Silent) => {
self.emit_opcode(Opcode::Pop);
}
},
} }
} }
@ -706,6 +713,29 @@ impl<'ctx> ByteCompiler<'ctx> {
} }
} }
pub(crate) fn emit_binding_access(&mut self, opcode: Opcode, binding: &BindingKind) {
match binding {
BindingKind::Stack(index) => match opcode {
Opcode::SetNameByLocator => self.emit_opcode(opcode),
_ => self.emit_with_varying_operand(opcode, *index),
},
BindingKind::Local(index) => match opcode {
Opcode::GetName | Opcode::GetNameOrUndefined | Opcode::GetNameAndLocator => {
self.emit_with_varying_operand(Opcode::PushFromLocal, *index);
}
Opcode::GetLocator | Opcode::DefVar => {}
Opcode::SetName
| Opcode::DefInitVar
| Opcode::PutLexicalValue
| Opcode::SetNameByLocator => {
self.emit_with_varying_operand(Opcode::PopIntoLocal, *index);
}
Opcode::DeleteName => self.emit_opcode(Opcode::PushFalse),
_ => unreachable!("invalid opcode for binding access"),
},
}
}
pub(crate) fn emit_operand(&mut self, operand: Operand, varying_kind: VaryingOperandKind) { pub(crate) fn emit_operand(&mut self, operand: Operand, varying_kind: VaryingOperandKind) {
match operand { match operand {
Operand::Bool(v) => self.emit_u8(v.into()), Operand::Bool(v) => self.emit_u8(v.into()),
@ -934,9 +964,9 @@ impl<'ctx> ByteCompiler<'ctx> {
match access { match access {
Access::Variable { name } => { Access::Variable { name } => {
let name = self.resolve_identifier_expect(name); let name = self.resolve_identifier_expect(name);
let binding = self.lexical_environment.get_identifier_reference(name); let binding = self.lexical_scope.get_identifier_reference(name);
let index = self.get_or_insert_binding(binding.locator()); let index = self.get_or_insert_binding(binding);
self.emit_with_varying_operand(Opcode::GetName, index); self.emit_binding_access(Opcode::GetName, &index);
} }
Access::Property { access } => match access { Access::Property { access } => match access {
PropertyAccess::Simple(access) => match access.field() { PropertyAccess::Simple(access) => match access.field() {
@ -999,13 +1029,12 @@ impl<'ctx> ByteCompiler<'ctx> {
match access { match access {
Access::Variable { name } => { Access::Variable { name } => {
let name = self.resolve_identifier_expect(name); let name = self.resolve_identifier_expect(name);
let binding = self let binding = self.lexical_scope.get_identifier_reference(name.clone());
.lexical_environment let is_lexical = binding.is_lexical();
.get_identifier_reference(name.clone()); let index = self.get_or_insert_binding(binding);
let index = self.get_or_insert_binding(binding.locator());
if !binding.is_lexical() { if !is_lexical {
self.emit_with_varying_operand(Opcode::GetLocator, index); self.emit_binding_access(Opcode::GetLocator, &index);
} }
expr_fn(self, 0); expr_fn(self, 0);
@ -1013,11 +1042,11 @@ impl<'ctx> ByteCompiler<'ctx> {
self.emit(Opcode::Dup, &[]); self.emit(Opcode::Dup, &[]);
} }
if binding.is_lexical() { if is_lexical {
match self.lexical_environment.set_mutable_binding(name.clone()) { match self.lexical_scope.set_mutable_binding(name.clone()) {
Ok(binding) => { Ok(binding) => {
let index = self.get_or_insert_binding(binding); let index = self.get_or_insert_binding(binding);
self.emit_with_varying_operand(Opcode::SetName, index); self.emit_binding_access(Opcode::SetName, &index);
} }
Err(BindingLocatorError::MutateImmutable) => { Err(BindingLocatorError::MutateImmutable) => {
let index = self.get_or_insert_string(name); let index = self.get_or_insert_string(name);
@ -1028,7 +1057,7 @@ impl<'ctx> ByteCompiler<'ctx> {
} }
} }
} else { } else {
self.emit_opcode(Opcode::SetNameByLocator); self.emit_binding_access(Opcode::SetNameByLocator, &index);
} }
} }
Access::Property { access } => match access { Access::Property { access } => match access {
@ -1112,9 +1141,9 @@ impl<'ctx> ByteCompiler<'ctx> {
}, },
Access::Variable { name } => { Access::Variable { name } => {
let name = name.to_js_string(self.interner()); let name = name.to_js_string(self.interner());
let binding = self.lexical_environment.get_identifier_reference(name); let binding = self.lexical_scope.get_identifier_reference(name);
let index = self.get_or_insert_binding(binding.locator()); let index = self.get_or_insert_binding(binding);
self.emit_with_varying_operand(Opcode::DeleteName, index); self.emit_binding_access(Opcode::DeleteName, &index);
} }
Access::This => { Access::This => {
self.emit_opcode(Opcode::PushTrue); self.emit_opcode(Opcode::PushTrue);
@ -1333,13 +1362,11 @@ impl<'ctx> ByteCompiler<'ctx> {
Binding::Identifier(ident) => { Binding::Identifier(ident) => {
let ident = ident.to_js_string(self.interner()); let ident = ident.to_js_string(self.interner());
if let Some(expr) = variable.init() { if let Some(expr) = variable.init() {
let binding = self let binding = self.lexical_scope.get_identifier_reference(ident.clone());
.lexical_environment let index = self.get_or_insert_binding(binding);
.get_identifier_reference(ident.clone()); self.emit_binding_access(Opcode::GetLocator, &index);
let index = self.get_or_insert_binding(binding.locator());
self.emit_with_varying_operand(Opcode::GetLocator, index);
self.compile_expr(expr, true); self.compile_expr(expr, true);
self.emit_opcode(Opcode::SetNameByLocator); self.emit_binding_access(Opcode::SetNameByLocator, &index);
} else { } else {
self.emit_binding(BindingOpcode::Var, ident); self.emit_binding(BindingOpcode::Var, ident);
} }
@ -1429,19 +1456,14 @@ impl<'ctx> ByteCompiler<'ctx> {
let name = function.name(); let name = function.name();
if self.annex_b_function_names.contains(&name) { if self.annex_b_function_names.contains(&name) {
let name = name.to_js_string(self.interner()); let name = name.to_js_string(self.interner());
let binding = self let binding = self.lexical_scope.get_identifier_reference(name.clone());
.lexical_environment let index = self.get_or_insert_binding(binding);
.get_identifier_reference(name.clone()); self.emit_binding_access(Opcode::GetName, &index);
let index = self.get_or_insert_binding(binding.locator());
self.emit_with_varying_operand(Opcode::GetName, index); match self.variable_scope.set_mutable_binding_var(name.clone()) {
match self
.variable_environment
.set_mutable_binding_var(name.clone())
{
Ok(binding) => { Ok(binding) => {
let index = self.get_or_insert_binding(binding); let index = self.get_or_insert_binding(binding);
self.emit_with_varying_operand(Opcode::SetName, index); self.emit_binding_access(Opcode::SetName, &index);
} }
Err(BindingLocatorError::MutateImmutable) => { Err(BindingLocatorError::MutateImmutable) => {
let index = self.get_or_insert_string(name); let index = self.get_or_insert_string(name);
@ -1471,7 +1493,8 @@ impl<'ctx> ByteCompiler<'ctx> {
name, name,
parameters, parameters,
body, body,
has_binding_identifier, scopes,
name_scope,
.. ..
} = function; } = function;
@ -1481,12 +1504,6 @@ impl<'ctx> ByteCompiler<'ctx> {
Some(js_string!()) Some(js_string!())
}; };
let binding_identifier = if has_binding_identifier {
name.clone()
} else {
None
};
let code = FunctionCompiler::new() let code = FunctionCompiler::new()
.name(name) .name(name)
.generator(generator) .generator(generator)
@ -1494,12 +1511,13 @@ impl<'ctx> ByteCompiler<'ctx> {
.strict(self.strict()) .strict(self.strict())
.arrow(arrow) .arrow(arrow)
.in_with(self.in_with) .in_with(self.in_with)
.binding_identifier(binding_identifier) .name_scope(name_scope.cloned())
.compile( .compile(
parameters, parameters,
body, body,
self.variable_environment.clone(), self.variable_scope.clone(),
self.lexical_environment.clone(), self.lexical_scope.clone(),
scopes,
self.interner, self.interner,
); );
@ -1510,16 +1528,12 @@ impl<'ctx> ByteCompiler<'ctx> {
/// pushing it to the stack if necessary. /// pushing it to the stack if necessary.
pub(crate) fn function_with_binding( pub(crate) fn function_with_binding(
&mut self, &mut self,
mut function: FunctionSpec<'_>, function: FunctionSpec<'_>,
node_kind: NodeKind, node_kind: NodeKind,
use_expr: bool, use_expr: bool,
) { ) {
let name = function.name; let name = function.name;
if node_kind == NodeKind::Declaration {
function.has_binding_identifier = false;
}
let index = self.function(function); let index = self.function(function);
self.emit_with_varying_operand(Opcode::GetFunction, index); self.emit_with_varying_operand(Opcode::GetFunction, index);
@ -1550,7 +1564,8 @@ impl<'ctx> ByteCompiler<'ctx> {
name, name,
parameters, parameters,
body, body,
has_binding_identifier, scopes,
name_scope,
.. ..
} = function; } = function;
@ -1565,12 +1580,6 @@ impl<'ctx> ByteCompiler<'ctx> {
Some(js_string!()) Some(js_string!())
}; };
let binding_identifier = if has_binding_identifier {
name.clone()
} else {
None
};
let code = FunctionCompiler::new() let code = FunctionCompiler::new()
.name(name) .name(name)
.generator(generator) .generator(generator)
@ -1579,12 +1588,13 @@ impl<'ctx> ByteCompiler<'ctx> {
.arrow(arrow) .arrow(arrow)
.method(true) .method(true)
.in_with(self.in_with) .in_with(self.in_with)
.binding_identifier(binding_identifier) .name_scope(name_scope.cloned())
.compile( .compile(
parameters, parameters,
body, body,
self.variable_environment.clone(), self.variable_scope.clone(),
self.lexical_environment.clone(), self.lexical_scope.clone(),
scopes,
self.interner, self.interner,
); );
@ -1603,7 +1613,7 @@ impl<'ctx> ByteCompiler<'ctx> {
name, name,
parameters, parameters,
body, body,
has_binding_identifier, scopes,
.. ..
} = function; } = function;
@ -1613,12 +1623,6 @@ impl<'ctx> ByteCompiler<'ctx> {
Some(js_string!()) Some(js_string!())
}; };
let binding_identifier = if has_binding_identifier {
name.clone()
} else {
None
};
let code = FunctionCompiler::new() let code = FunctionCompiler::new()
.name(name) .name(name)
.generator(generator) .generator(generator)
@ -1627,12 +1631,13 @@ impl<'ctx> ByteCompiler<'ctx> {
.arrow(arrow) .arrow(arrow)
.method(true) .method(true)
.in_with(self.in_with) .in_with(self.in_with)
.binding_identifier(binding_identifier) .name_scope(function.name_scope.cloned())
.compile( .compile(
parameters, parameters,
body, body,
self.variable_environment.clone(), self.variable_scope.clone(),
self.lexical_environment.clone(), self.lexical_scope.clone(),
scopes,
self.interner, self.interner,
); );
@ -1669,9 +1674,9 @@ impl<'ctx> ByteCompiler<'ctx> {
if self.in_with { if self.in_with {
let name = self.resolve_identifier_expect(*ident); let name = self.resolve_identifier_expect(*ident);
let binding = self.lexical_environment.get_identifier_reference(name); let binding = self.lexical_scope.get_identifier_reference(name);
let index = self.get_or_insert_binding(binding.locator()); let index = self.get_or_insert_binding(binding);
self.emit_with_varying_operand(Opcode::ThisForObjectEnvironmentName, index); self.emit_binding_access(Opcode::ThisForObjectEnvironmentName, &index);
} else { } else {
self.emit_opcode(Opcode::PushUndefined); self.emit_opcode(Opcode::PushUndefined);
} }
@ -1709,9 +1714,21 @@ impl<'ctx> ByteCompiler<'ctx> {
} }
match kind { match kind {
CallKind::CallEval if contains_spread => self.emit_opcode(Opcode::CallEvalSpread),
CallKind::CallEval => { CallKind::CallEval => {
self.emit_with_varying_operand(Opcode::CallEval, call.args().len() as u32); let scope_index = self.constants.len() as u32;
self.constants
.push(Constant::Scope(self.lexical_scope.clone()));
if contains_spread {
self.emit_with_varying_operand(Opcode::CallEvalSpread, scope_index);
} else {
self.emit(
Opcode::CallEval,
&[
Operand::Varying(call.args().len() as u32),
Operand::Varying(scope_index),
],
);
}
} }
CallKind::Call if contains_spread => self.emit_opcode(Opcode::CallSpread), CallKind::Call if contains_spread => self.emit_opcode(Opcode::CallSpread),
CallKind::Call => { CallKind::Call => {
@ -1737,6 +1754,16 @@ impl<'ctx> ByteCompiler<'ctx> {
} }
self.r#return(false); self.r#return(false);
let mapped_arguments_binding_indices = self
.emitted_mapped_arguments_object_opcode
.then(|| MappedArguments::binding_indices(&self.params))
.unwrap_or_default();
let max_local_binding_register_index =
self.local_binding_registers.values().max().unwrap_or(&0);
let local_bindings_initialized =
vec![false; (max_local_binding_register_index + 1) as usize].into_boxed_slice();
let register_count = self.register_allocator.finish(); let register_count = self.register_allocator.finish();
// NOTE: Offset the handlers stack count so we don't pop the registers // NOTE: Offset the handlers stack count so we don't pop the registers
@ -1745,12 +1772,6 @@ impl<'ctx> ByteCompiler<'ctx> {
handler.stack_count += register_count; handler.stack_count += register_count;
} }
let mapped_arguments_binding_indices = if self.emitted_mapped_arguments_object_opcode {
MappedArguments::binding_indices(&self.params)
} else {
ThinVec::new()
};
CodeBlock { CodeBlock {
name: self.function_name, name: self.function_name,
length: self.length, length: self.length,
@ -1761,6 +1782,7 @@ impl<'ctx> ByteCompiler<'ctx> {
bytecode: self.bytecode.into_boxed_slice(), bytecode: self.bytecode.into_boxed_slice(),
constants: self.constants, constants: self.constants,
bindings: self.bindings.into_boxed_slice(), bindings: self.bindings.into_boxed_slice(),
local_bindings_initialized,
handlers: self.handlers, handlers: self.handlers,
flags: Cell::new(self.code_block_flags), flags: Cell::new(self.code_block_flags),
ic: self.ic.into_boxed_slice(), ic: self.ic.into_boxed_slice(),

2
core/engine/src/bytecompiler/module.rs

@ -45,8 +45,6 @@ impl ByteCompiler<'_> {
ExportDeclaration::DefaultClassDeclaration(cl) => self.class(cl.into(), false), ExportDeclaration::DefaultClassDeclaration(cl) => self.class(cl.into(), false),
ExportDeclaration::DefaultAssignmentExpression(expr) => { ExportDeclaration::DefaultAssignmentExpression(expr) => {
let name = Sym::DEFAULT_EXPORT.to_js_string(self.interner()); let name = Sym::DEFAULT_EXPORT.to_js_string(self.interner());
self.lexical_environment
.create_mutable_binding(name.clone(), false);
self.compile_expr(expr, true); self.compile_expr(expr, true);
if expr.is_anonymous_function_definition() { if expr.is_anonymous_function_definition() {

22
core/engine/src/bytecompiler/statement/block.rs

@ -4,16 +4,22 @@ use boa_ast::statement::Block;
impl ByteCompiler<'_> { impl ByteCompiler<'_> {
/// Compile a [`Block`] `boa_ast` node /// Compile a [`Block`] `boa_ast` node
pub(crate) fn compile_block(&mut self, block: &Block, use_expr: bool) { pub(crate) fn compile_block(&mut self, block: &Block, use_expr: bool) {
let old_lex_env = self.lexical_environment.clone(); let outer_scope = if let Some(scope) = block.scope() {
let env_index = self.push_compile_environment(false); let outer_scope = self.lexical_scope.clone();
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); let scope_index = self.push_scope(scope);
let env = self.lexical_environment.clone(); self.emit_with_varying_operand(Opcode::PushScope, scope_index);
Some(outer_scope)
} else {
None
};
self.block_declaration_instantiation(block, &env); self.block_declaration_instantiation(block);
self.compile_statement_list(block.statement_list(), use_expr, true); self.compile_statement_list(block.statement_list(), use_expr, true);
self.pop_compile_environment(); if let Some(outer_scope) = outer_scope {
self.lexical_environment = old_lex_env; self.pop_scope();
self.emit_opcode(Opcode::PopEnvironment); self.lexical_scope = outer_scope;
self.emit_opcode(Opcode::PopEnvironment);
}
} }
} }

191
core/engine/src/bytecompiler/statement/loop.rs

@ -1,6 +1,7 @@
use boa_ast::{ use boa_ast::{
declaration::Binding, declaration::Binding,
operations::bound_names, operations::bound_names,
scope::BindingLocatorError,
statement::{ statement::{
iteration::{ForLoopInitializer, IterableLoopInitializer}, iteration::{ForLoopInitializer, IterableLoopInitializer},
DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop,
@ -10,7 +11,6 @@ use boa_interner::Sym;
use crate::{ use crate::{
bytecompiler::{Access, ByteCompiler, Operand, ToJsString}, bytecompiler::{Access, ByteCompiler, Operand, ToJsString},
environments::BindingLocatorError,
vm::{BindingOpcode, Opcode}, vm::{BindingOpcode, Opcode},
}; };
@ -22,7 +22,7 @@ impl ByteCompiler<'_> {
use_expr: bool, use_expr: bool,
) { ) {
let mut let_binding_indices = None; let mut let_binding_indices = None;
let mut old_lex_env = None; let mut outer_scope = None;
if let Some(init) = for_loop.init() { if let Some(init) = for_loop.init() {
match init { match init {
@ -31,45 +31,42 @@ impl ByteCompiler<'_> {
self.compile_var_decl(decl); self.compile_var_decl(decl);
} }
ForLoopInitializer::Lexical(decl) => { ForLoopInitializer::Lexical(decl) => {
old_lex_env = Some(self.lexical_environment.clone()); outer_scope = Some(self.lexical_scope.clone());
let env_index = self.push_compile_environment(false); let scope_index = self.push_scope(decl.scope());
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); self.emit_with_varying_operand(Opcode::PushScope, scope_index);
let names = bound_names(decl); let names = bound_names(decl.declaration());
if decl.is_const() { if decl.declaration().is_const() {
for name in &names {
let name = name.to_js_string(self.interner());
self.lexical_environment
.create_immutable_binding(name, true);
}
} else { } else {
let mut indices = Vec::new(); let mut indices = Vec::new();
for name in &names { for name in &names {
let name = name.to_js_string(self.interner()); let name = name.to_js_string(self.interner());
let binding = let binding = self
self.lexical_environment.create_mutable_binding(name, false); .lexical_scope
.get_binding_reference(&name)
.expect("binding must exist");
let index = self.get_or_insert_binding(binding); let index = self.get_or_insert_binding(binding);
indices.push(index); indices.push(index);
} }
let_binding_indices = Some((indices, env_index)); let_binding_indices = Some((indices, scope_index));
} }
self.compile_lexical_decl(decl); self.compile_lexical_decl(decl.declaration());
} }
} }
} }
self.push_empty_loop_jump_control(use_expr); self.push_empty_loop_jump_control(use_expr);
if let Some((let_binding_indices, env_index)) = &let_binding_indices { if let Some((let_binding_indices, scope_index)) = &let_binding_indices {
for index in let_binding_indices { for index in let_binding_indices {
self.emit_with_varying_operand(Opcode::GetName, *index); self.emit_binding_access(Opcode::GetName, index);
} }
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, *env_index); self.emit_with_varying_operand(Opcode::PushScope, *scope_index);
for index in let_binding_indices.iter().rev() { for index in let_binding_indices.iter().rev() {
self.emit_with_varying_operand(Opcode::PutLexicalValue, *index); self.emit_binding_access(Opcode::PutLexicalValue, index);
} }
} }
@ -83,16 +80,16 @@ impl ByteCompiler<'_> {
.expect("jump_control must exist as it was just pushed") .expect("jump_control must exist as it was just pushed")
.set_start_address(start_address); .set_start_address(start_address);
if let Some((let_binding_indices, env_index)) = &let_binding_indices { if let Some((let_binding_indices, scope_index)) = &let_binding_indices {
for index in let_binding_indices { for index in let_binding_indices {
self.emit_with_varying_operand(Opcode::GetName, *index); self.emit_binding_access(Opcode::GetName, index);
} }
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, *env_index); self.emit_with_varying_operand(Opcode::PushScope, *scope_index);
for index in let_binding_indices.iter().rev() { for index in let_binding_indices.iter().rev() {
self.emit_with_varying_operand(Opcode::PutLexicalValue, *index); self.emit_binding_access(Opcode::PutLexicalValue, index);
} }
} }
@ -118,9 +115,9 @@ impl ByteCompiler<'_> {
self.patch_jump(exit); self.patch_jump(exit);
self.pop_loop_control_info(); self.pop_loop_control_info();
if let Some(old_lex_env) = old_lex_env { if let Some(outer_scope) = outer_scope {
self.pop_compile_environment(); self.pop_scope();
self.lexical_environment = old_lex_env; self.lexical_scope = outer_scope;
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
} }
} }
@ -141,27 +138,16 @@ impl ByteCompiler<'_> {
} }
} }
} }
let initializer_bound_names = match for_in_loop.initializer() { if let Some(scope) = for_in_loop.target_scope() {
IterableLoopInitializer::Let(declaration) let outer_scope = self.lexical_scope.clone();
| IterableLoopInitializer::Const(declaration) => bound_names(declaration), let scope_index = self.push_scope(scope);
_ => Vec::new(), self.emit_with_varying_operand(Opcode::PushScope, scope_index);
};
if initializer_bound_names.is_empty() {
self.compile_expr(for_in_loop.target(), true); self.compile_expr(for_in_loop.target(), true);
self.pop_scope();
self.lexical_scope = outer_scope;
self.emit_opcode(Opcode::PopEnvironment);
} else { } else {
let old_lex_env = self.lexical_environment.clone();
let env_index = self.push_compile_environment(false);
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index);
for name in &initializer_bound_names {
let name = name.to_js_string(self.interner());
self.lexical_environment.create_mutable_binding(name, false);
}
self.compile_expr(for_in_loop.target(), true); self.compile_expr(for_in_loop.target(), true);
self.pop_compile_environment();
self.lexical_environment = old_lex_env;
self.emit_opcode(Opcode::PopEnvironment);
} }
let early_exit = self.jump_if_null_or_undefined(); let early_exit = self.jump_if_null_or_undefined();
@ -177,12 +163,12 @@ impl ByteCompiler<'_> {
self.emit_opcode(Opcode::IteratorValue); self.emit_opcode(Opcode::IteratorValue);
let mut old_lex_env = None; let mut outer_scope = None;
if !initializer_bound_names.is_empty() { if let Some(scope) = for_in_loop.scope() {
old_lex_env = Some(self.lexical_environment.clone()); outer_scope = Some(self.lexical_scope.clone());
let env_index = self.push_compile_environment(false); let scope_index = self.push_scope(scope);
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); self.emit_with_varying_operand(Opcode::PushScope, scope_index);
} }
match for_in_loop.initializer() { match for_in_loop.initializer() {
@ -206,35 +192,13 @@ impl ByteCompiler<'_> {
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar); self.compile_declaration_pattern(pattern, BindingOpcode::InitVar);
} }
}, },
IterableLoopInitializer::Let(declaration) => match declaration { IterableLoopInitializer::Let(declaration)
Binding::Identifier(ident) => { | IterableLoopInitializer::Const(declaration) => match declaration {
let ident = ident.to_js_string(self.interner());
self.lexical_environment
.create_mutable_binding(ident.clone(), false);
self.emit_binding(BindingOpcode::InitLexical, ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
let ident = ident.to_js_string(self.interner());
self.lexical_environment
.create_mutable_binding(ident, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical);
}
},
IterableLoopInitializer::Const(declaration) => match declaration {
Binding::Identifier(ident) => { Binding::Identifier(ident) => {
let ident = ident.to_js_string(self.interner()); let ident = ident.to_js_string(self.interner());
self.lexical_environment
.create_immutable_binding(ident.clone(), true);
self.emit_binding(BindingOpcode::InitLexical, ident); self.emit_binding(BindingOpcode::InitLexical, ident);
} }
Binding::Pattern(pattern) => { Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
let ident = ident.to_js_string(self.interner());
self.lexical_environment
.create_immutable_binding(ident, true);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical); self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical);
} }
}, },
@ -245,9 +209,9 @@ impl ByteCompiler<'_> {
self.compile_stmt(for_in_loop.body(), use_expr, true); self.compile_stmt(for_in_loop.body(), use_expr, true);
if let Some(old_lex_env) = old_lex_env { if let Some(outer_scope) = outer_scope {
self.pop_compile_environment(); self.pop_scope();
self.lexical_environment = old_lex_env; self.lexical_scope = outer_scope;
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
} }
@ -270,27 +234,16 @@ impl ByteCompiler<'_> {
label: Option<Sym>, label: Option<Sym>,
use_expr: bool, use_expr: bool,
) { ) {
let initializer_bound_names = match for_of_loop.initializer() { if let Some(scope) = for_of_loop.iterable_scope() {
IterableLoopInitializer::Let(declaration) let outer_scope = self.lexical_scope.clone();
| IterableLoopInitializer::Const(declaration) => bound_names(declaration), let scope_index = self.push_scope(scope);
_ => Vec::new(), self.emit_with_varying_operand(Opcode::PushScope, scope_index);
};
if initializer_bound_names.is_empty() {
self.compile_expr(for_of_loop.iterable(), true); self.compile_expr(for_of_loop.iterable(), true);
self.pop_scope();
self.lexical_scope = outer_scope;
self.emit_opcode(Opcode::PopEnvironment);
} else { } else {
let old_lex_env = self.lexical_environment.clone();
let env_index = self.push_compile_environment(false);
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index);
for name in &initializer_bound_names {
let name = name.to_js_string(self.interner());
self.lexical_environment.create_mutable_binding(name, false);
}
self.compile_expr(for_of_loop.iterable(), true); self.compile_expr(for_of_loop.iterable(), true);
self.pop_compile_environment();
self.lexical_environment = old_lex_env;
self.emit_opcode(Opcode::PopEnvironment);
} }
if for_of_loop.r#await() { if for_of_loop.r#await() {
@ -318,23 +271,23 @@ impl ByteCompiler<'_> {
let exit = self.jump_if_true(); let exit = self.jump_if_true();
self.emit_opcode(Opcode::IteratorValue); self.emit_opcode(Opcode::IteratorValue);
let mut old_lex_env = None; let mut outer_scope = None;
if !initializer_bound_names.is_empty() { if let Some(scope) = for_of_loop.scope() {
old_lex_env = Some(self.lexical_environment.clone()); outer_scope = Some(self.lexical_scope.clone());
let env_index = self.push_compile_environment(false); let scope_index = self.push_scope(scope);
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); self.emit_with_varying_operand(Opcode::PushScope, scope_index);
}; }
let handler_index = self.push_handler(); let handler_index = self.push_handler();
match for_of_loop.initializer() { match for_of_loop.initializer() {
IterableLoopInitializer::Identifier(ref ident) => { IterableLoopInitializer::Identifier(ref ident) => {
let ident = ident.to_js_string(self.interner()); let ident = ident.to_js_string(self.interner());
match self.lexical_environment.set_mutable_binding(ident.clone()) { match self.lexical_scope.set_mutable_binding(ident.clone()) {
Ok(binding) => { Ok(binding) => {
let index = self.get_or_insert_binding(binding); let index = self.get_or_insert_binding(binding);
self.emit_with_varying_operand(Opcode::DefInitVar, index); self.emit_binding_access(Opcode::DefInitVar, &index);
} }
Err(BindingLocatorError::MutateImmutable) => { Err(BindingLocatorError::MutateImmutable) => {
let index = self.get_or_insert_string(ident); let index = self.get_or_insert_string(ident);
@ -365,35 +318,13 @@ impl ByteCompiler<'_> {
} }
} }
} }
IterableLoopInitializer::Let(declaration) => match declaration { IterableLoopInitializer::Let(declaration)
Binding::Identifier(ident) => { | IterableLoopInitializer::Const(declaration) => match declaration {
let ident = ident.to_js_string(self.interner());
self.lexical_environment
.create_mutable_binding(ident.clone(), false);
self.emit_binding(BindingOpcode::InitLexical, ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
let ident = ident.to_js_string(self.interner());
self.lexical_environment
.create_mutable_binding(ident, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical);
}
},
IterableLoopInitializer::Const(declaration) => match declaration {
Binding::Identifier(ident) => { Binding::Identifier(ident) => {
let ident = ident.to_js_string(self.interner()); let ident = ident.to_js_string(self.interner());
self.lexical_environment
.create_immutable_binding(ident.clone(), true);
self.emit_binding(BindingOpcode::InitLexical, ident); self.emit_binding(BindingOpcode::InitLexical, ident);
} }
Binding::Pattern(pattern) => { Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
let ident = ident.to_js_string(self.interner());
self.lexical_environment
.create_immutable_binding(ident, true);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical); self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical);
} }
}, },
@ -423,9 +354,9 @@ impl ByteCompiler<'_> {
self.patch_jump(exit); self.patch_jump(exit);
} }
if let Some(old_lex_env) = old_lex_env { if let Some(outer_scope) = outer_scope {
self.pop_compile_environment(); self.pop_scope();
self.lexical_environment = old_lex_env; self.lexical_scope = outer_scope;
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
} }

22
core/engine/src/bytecompiler/statement/switch.rs

@ -6,12 +6,16 @@ impl ByteCompiler<'_> {
pub(crate) fn compile_switch(&mut self, switch: &Switch, use_expr: bool) { pub(crate) fn compile_switch(&mut self, switch: &Switch, use_expr: bool) {
self.compile_expr(switch.val(), true); self.compile_expr(switch.val(), true);
let old_lex_env = self.lexical_environment.clone(); let outer_scope = if let Some(scope) = switch.scope() {
let env_index = self.push_compile_environment(false); let outer_scope = self.lexical_scope.clone();
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); let scope_index = self.push_scope(scope);
let env = self.lexical_environment.clone(); self.emit_with_varying_operand(Opcode::PushScope, scope_index);
Some(outer_scope)
} else {
None
};
self.block_declaration_instantiation(switch, &env); self.block_declaration_instantiation(switch);
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
self.push_switch_control_info(None, start_address, use_expr); self.push_switch_control_info(None, start_address, use_expr);
@ -52,8 +56,10 @@ impl ByteCompiler<'_> {
self.pop_switch_control_info(); self.pop_switch_control_info();
self.pop_compile_environment(); if let Some(outer_scope) = outer_scope {
self.lexical_environment = old_lex_env; self.pop_scope();
self.emit_opcode(Opcode::PopEnvironment); self.lexical_scope = outer_scope;
self.emit_opcode(Opcode::PopEnvironment);
}
} }
} }

17
core/engine/src/bytecompiler/statement/try.rs

@ -4,7 +4,6 @@ use crate::{
}; };
use boa_ast::{ use boa_ast::{
declaration::Binding, declaration::Binding,
operations::bound_names,
statement::{Block, Catch, Finally, Try}, statement::{Block, Catch, Finally, Try},
Statement, StatementListItem, Statement, StatementListItem,
}; };
@ -111,23 +110,17 @@ impl ByteCompiler<'_> {
pub(crate) fn compile_catch_stmt(&mut self, catch: &Catch, _has_finally: bool, use_expr: bool) { pub(crate) fn compile_catch_stmt(&mut self, catch: &Catch, _has_finally: bool, use_expr: bool) {
// stack: exception // stack: exception
let old_lex_env = self.lexical_environment.clone(); let outer_scope = self.lexical_scope.clone();
let env_index = self.push_compile_environment(false); let scope_index = self.push_scope(catch.scope());
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); self.emit_with_varying_operand(Opcode::PushScope, scope_index);
let env = self.lexical_environment.clone();
if let Some(binding) = catch.parameter() { if let Some(binding) = catch.parameter() {
match binding { match binding {
Binding::Identifier(ident) => { Binding::Identifier(ident) => {
let ident = ident.to_js_string(self.interner()); let ident = ident.to_js_string(self.interner());
env.create_mutable_binding(ident.clone(), false);
self.emit_binding(BindingOpcode::InitLexical, ident); self.emit_binding(BindingOpcode::InitLexical, ident);
} }
Binding::Pattern(pattern) => { Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
let ident = ident.to_js_string(self.interner());
env.create_mutable_binding(ident, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical); self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical);
} }
} }
@ -137,8 +130,8 @@ impl ByteCompiler<'_> {
self.compile_catch_finally_block(catch.block(), use_expr); self.compile_catch_finally_block(catch.block(), use_expr);
self.pop_compile_environment(); self.pop_scope();
self.lexical_environment = old_lex_env; self.lexical_scope = outer_scope;
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
} }

8
core/engine/src/bytecompiler/statement/with.rs

@ -6,8 +6,8 @@ impl ByteCompiler<'_> {
pub(crate) fn compile_with(&mut self, with: &With, use_expr: bool) { pub(crate) fn compile_with(&mut self, with: &With, use_expr: bool) {
self.compile_expr(with.expression(), true); self.compile_expr(with.expression(), true);
let old_lex_env = self.lexical_environment.clone(); let outer_scope = self.lexical_scope.clone();
let _ = self.push_compile_environment(false); let _ = self.push_scope(with.scope());
self.emit_opcode(Opcode::PushObjectEnvironment); self.emit_opcode(Opcode::PushObjectEnvironment);
let in_with = self.in_with; let in_with = self.in_with;
@ -15,8 +15,8 @@ impl ByteCompiler<'_> {
self.compile_stmt(with.statement(), use_expr, true); self.compile_stmt(with.statement(), use_expr, true);
self.in_with = in_with; self.in_with = in_with;
self.pop_compile_environment(); self.pop_scope();
self.lexical_environment = old_lex_env; self.lexical_scope = outer_scope;
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
} }
} }

219
core/engine/src/environments/compile.rs

@ -1,219 +0,0 @@
use std::{cell::RefCell, rc::Rc};
use crate::{environments::runtime::BindingLocator, JsString};
use boa_gc::{empty_trace, Finalize, Trace};
use rustc_hash::FxHashMap;
use super::runtime::BindingLocatorError;
/// A compile time binding represents a binding at bytecode compile time in a [`CompileTimeEnvironment`].
///
/// It contains the binding index and a flag to indicate if this is a mutable binding or not.
#[derive(Debug)]
struct CompileTimeBinding {
index: u32,
mutable: bool,
lex: bool,
strict: bool,
}
/// A compile time environment maps bound identifiers to their binding positions.
///
/// A compile time environment also indicates, if it is a function environment.
#[derive(Debug, Finalize)]
pub(crate) struct CompileTimeEnvironment {
outer: Option<Rc<Self>>,
environment_index: u32,
bindings: RefCell<FxHashMap<JsString, CompileTimeBinding>>,
function_scope: bool,
}
// Safety: Nothing in this struct needs tracing, so this is safe.
unsafe impl Trace for CompileTimeEnvironment {
empty_trace!();
}
impl CompileTimeEnvironment {
/// Creates a new global compile time environment.
pub(crate) fn new_global() -> Self {
Self {
outer: None,
environment_index: 0,
bindings: RefCell::default(),
function_scope: true,
}
}
/// Creates a new compile time environment.
pub(crate) fn new(parent: Rc<Self>, function_scope: bool) -> Self {
let index = parent.environment_index + 1;
Self {
outer: Some(parent),
environment_index: index,
bindings: RefCell::default(),
function_scope,
}
}
/// Check if environment has a lexical binding with the given name.
pub(crate) fn has_lex_binding(&self, name: &JsString) -> bool {
self.bindings
.borrow()
.get(name)
.map_or(false, |binding| binding.lex)
}
/// Check if the environment has a binding with the given name.
pub(crate) fn has_binding(&self, name: &JsString) -> bool {
self.bindings.borrow().contains_key(name)
}
/// Get the binding locator for a binding with the given name.
/// Fall back to the global environment if the binding is not found.
pub(crate) fn get_identifier_reference(&self, name: JsString) -> IdentifierReference {
if let Some(binding) = self.bindings.borrow().get(&name) {
IdentifierReference::new(
BindingLocator::declarative(name, self.environment_index, binding.index),
binding.lex,
)
} else if let Some(outer) = &self.outer {
outer.get_identifier_reference(name)
} else {
IdentifierReference::new(BindingLocator::global(name), false)
}
}
/// Returns the number of bindings in this environment.
pub(crate) fn num_bindings(&self) -> u32 {
self.bindings.borrow().len() as u32
}
/// Returns the index of this environment.
pub(crate) fn environment_index(&self) -> u32 {
self.environment_index
}
/// Check if the environment is a function environment.
pub(crate) const fn is_function(&self) -> bool {
self.function_scope
}
/// Check if the environment is a global environment.
pub(crate) const fn is_global(&self) -> bool {
self.outer.is_none()
}
/// Get the locator for a binding name.
pub(crate) fn get_binding(&self, name: &JsString) -> Option<BindingLocator> {
self.bindings.borrow().get(name).map(|binding| {
BindingLocator::declarative(name.clone(), self.environment_index, binding.index)
})
}
/// Create a mutable binding.
pub(crate) fn create_mutable_binding(
&self,
name: JsString,
function_scope: bool,
) -> BindingLocator {
let binding_index = self.bindings.borrow().len() as u32;
self.bindings.borrow_mut().insert(
name.clone(),
CompileTimeBinding {
index: binding_index,
mutable: true,
lex: !function_scope,
strict: false,
},
);
BindingLocator::declarative(name, self.environment_index, binding_index)
}
/// Crate an immutable binding.
pub(crate) fn create_immutable_binding(&self, name: JsString, strict: bool) -> BindingLocator {
let binding_index = self.bindings.borrow().len() as u32;
self.bindings.borrow_mut().insert(
name.clone(),
CompileTimeBinding {
index: binding_index,
mutable: false,
lex: true,
strict,
},
);
BindingLocator::declarative(name, self.environment_index, binding_index)
}
/// Return the binding locator for a mutable binding.
pub(crate) fn set_mutable_binding(
&self,
name: JsString,
) -> Result<BindingLocator, BindingLocatorError> {
Ok(match self.bindings.borrow().get(&name) {
Some(binding) if binding.mutable => {
BindingLocator::declarative(name, self.environment_index, binding.index)
}
Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable),
Some(_) => return Err(BindingLocatorError::Silent),
None => self.outer.as_ref().map_or_else(
|| Ok(BindingLocator::global(name.clone())),
|outer| outer.set_mutable_binding(name.clone()),
)?,
})
}
#[cfg(feature = "annex-b")]
/// Return the binding locator for a set operation on an existing var binding.
pub(crate) fn set_mutable_binding_var(
&self,
name: JsString,
) -> Result<BindingLocator, BindingLocatorError> {
if !self.is_function() {
return self.outer.as_ref().map_or_else(
|| Ok(BindingLocator::global(name.clone())),
|outer| outer.set_mutable_binding_var(name.clone()),
);
}
Ok(match self.bindings.borrow().get(&name) {
Some(binding) if binding.mutable => {
BindingLocator::declarative(name, self.environment_index, binding.index)
}
Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable),
Some(_) => return Err(BindingLocatorError::Silent),
None => self.outer.as_ref().map_or_else(
|| Ok(BindingLocator::global(name.clone())),
|outer| outer.set_mutable_binding_var(name.clone()),
)?,
})
}
/// Gets the outer environment of this environment.
pub(crate) fn outer(&self) -> Option<Rc<Self>> {
self.outer.clone()
}
}
/// A reference to an identifier in a compile time environment.
pub(crate) struct IdentifierReference {
locator: BindingLocator,
lexical: bool,
}
impl IdentifierReference {
/// Create a new identifier reference.
pub(crate) fn new(locator: BindingLocator, lexical: bool) -> Self {
Self { locator, lexical }
}
/// Get the binding locator for this identifier reference.
pub(crate) fn locator(&self) -> BindingLocator {
self.locator.clone()
}
/// Check if this identifier reference is lexical.
pub(crate) fn is_lexical(&self) -> bool {
self.lexical
}
}

10
core/engine/src/environments/mod.rs

@ -24,15 +24,11 @@
//! //!
//! [spec]: https://tc39.es/ecma262/#sec-environment-records //! [spec]: https://tc39.es/ecma262/#sec-environment-records
mod compile;
mod runtime; mod runtime;
pub(crate) use { pub(crate) use runtime::{
compile::CompileTimeEnvironment, DeclarativeEnvironment, Environment, EnvironmentStack, FunctionSlots, PrivateEnvironment,
runtime::{ ThisBindingStatus,
BindingLocator, BindingLocatorEnvironment, BindingLocatorError, DeclarativeEnvironment,
Environment, EnvironmentStack, FunctionSlots, PrivateEnvironment, ThisBindingStatus,
},
}; };
#[cfg(test)] #[cfg(test)]

19
core/engine/src/environments/runtime/declarative/function.rs

@ -1,3 +1,4 @@
use boa_ast::scope::Scope;
use boa_gc::{custom_trace, Finalize, GcRefCell, Trace}; use boa_gc::{custom_trace, Finalize, GcRefCell, Trace};
use crate::{builtins::function::OrdinaryFunction, JsNativeError, JsObject, JsResult, JsValue}; use crate::{builtins::function::OrdinaryFunction, JsNativeError, JsObject, JsResult, JsValue};
@ -8,14 +9,25 @@ use super::PoisonableEnvironment;
pub(crate) struct FunctionEnvironment { pub(crate) struct FunctionEnvironment {
inner: PoisonableEnvironment, inner: PoisonableEnvironment,
slots: Box<FunctionSlots>, slots: Box<FunctionSlots>,
// Safety: Nothing in `Scope` needs tracing.
#[unsafe_ignore_trace]
scope: Scope,
} }
impl FunctionEnvironment { impl FunctionEnvironment {
/// Creates a new `FunctionEnvironment`. /// Creates a new `FunctionEnvironment`.
pub(crate) fn new(bindings: u32, poisoned: bool, with: bool, slots: FunctionSlots) -> Self { pub(crate) fn new(
bindings: u32,
poisoned: bool,
with: bool,
slots: FunctionSlots,
scope: Scope,
) -> Self {
Self { Self {
inner: PoisonableEnvironment::new(bindings, poisoned, with), inner: PoisonableEnvironment::new(bindings, poisoned, with),
slots: Box::new(slots), slots: Box::new(slots),
scope,
} }
} }
@ -24,6 +36,11 @@ impl FunctionEnvironment {
&self.slots &self.slots
} }
/// Gets the compile time environment of this function environment.
pub(crate) const fn compile(&self) -> &Scope {
&self.scope
}
/// Gets the `poisonable_environment` of this function environment. /// Gets the `poisonable_environment` of this function environment.
pub(crate) const fn poisonable_environment(&self) -> &PoisonableEnvironment { pub(crate) const fn poisonable_environment(&self) -> &PoisonableEnvironment {
&self.inner &self.inner

35
core/engine/src/environments/runtime/declarative/mod.rs

@ -8,9 +8,9 @@ pub(crate) use global::GlobalEnvironment;
pub(crate) use lexical::LexicalEnvironment; pub(crate) use lexical::LexicalEnvironment;
pub(crate) use module::ModuleEnvironment; pub(crate) use module::ModuleEnvironment;
use crate::{environments::CompileTimeEnvironment, JsResult, JsValue}; use crate::{JsResult, JsValue};
use boa_gc::{Finalize, GcRefCell, Trace}; use boa_gc::{Finalize, GcRefCell, Trace};
use std::{cell::Cell, rc::Rc}; use std::cell::Cell;
/// A declarative environment holds binding values at runtime. /// A declarative environment holds binding values at runtime.
/// ///
@ -35,10 +35,6 @@ use std::{cell::Cell, rc::Rc};
#[derive(Debug, Trace, Finalize)] #[derive(Debug, Trace, Finalize)]
pub(crate) struct DeclarativeEnvironment { pub(crate) struct DeclarativeEnvironment {
kind: DeclarativeEnvironmentKind, kind: DeclarativeEnvironmentKind,
// Safety: Nothing in CompileTimeEnvironment needs tracing.
#[unsafe_ignore_trace]
compile: Rc<CompileTimeEnvironment>,
} }
impl DeclarativeEnvironment { impl DeclarativeEnvironment {
@ -46,21 +42,12 @@ impl DeclarativeEnvironment {
pub(crate) fn global() -> Self { pub(crate) fn global() -> Self {
Self { Self {
kind: DeclarativeEnvironmentKind::Global(GlobalEnvironment::new()), kind: DeclarativeEnvironmentKind::Global(GlobalEnvironment::new()),
compile: Rc::new(CompileTimeEnvironment::new_global()),
} }
} }
/// Creates a new `DeclarativeEnvironment` from its kind and compile environment. /// Creates a new `DeclarativeEnvironment` from its kind and compile environment.
pub(crate) fn new( pub(crate) fn new(kind: DeclarativeEnvironmentKind) -> Self {
kind: DeclarativeEnvironmentKind, Self { kind }
compile: Rc<CompileTimeEnvironment>,
) -> Self {
Self { kind, compile }
}
/// Gets the compile time environment of this environment.
pub(crate) fn compile_env(&self) -> Rc<CompileTimeEnvironment> {
self.compile.clone()
} }
/// Returns a reference to the the kind of the environment. /// Returns a reference to the the kind of the environment.
@ -68,6 +55,11 @@ impl DeclarativeEnvironment {
&self.kind &self.kind
} }
/// Returns whether this environment is a function environment.
pub(crate) fn is_function(&self) -> bool {
matches!(self.kind(), DeclarativeEnvironmentKind::Function(_))
}
/// Gets the binding value from the environment by index. /// Gets the binding value from the environment by index.
/// ///
/// # Panics /// # Panics
@ -130,7 +122,7 @@ impl DeclarativeEnvironment {
/// Extends the environment with the bindings from the compile time environment. /// Extends the environment with the bindings from the compile time environment.
pub(crate) fn extend_from_compile(&self) { pub(crate) fn extend_from_compile(&self) {
if let Some(env) = self.kind().as_function() { if let Some(env) = self.kind().as_function() {
let compile_bindings_number = self.compile_env().num_bindings() as usize; let compile_bindings_number = env.compile().num_bindings() as usize;
let mut bindings = env.poisonable_environment().bindings().borrow_mut(); let mut bindings = env.poisonable_environment().bindings().borrow_mut();
if compile_bindings_number > bindings.len() { if compile_bindings_number > bindings.len() {
bindings.resize(compile_bindings_number, None); bindings.resize(compile_bindings_number, None);
@ -280,8 +272,7 @@ pub(crate) struct PoisonableEnvironment {
bindings: GcRefCell<Vec<Option<JsValue>>>, bindings: GcRefCell<Vec<Option<JsValue>>>,
#[unsafe_ignore_trace] #[unsafe_ignore_trace]
poisoned: Cell<bool>, poisoned: Cell<bool>,
#[unsafe_ignore_trace] with: bool,
with: Cell<bool>,
} }
impl PoisonableEnvironment { impl PoisonableEnvironment {
@ -290,7 +281,7 @@ impl PoisonableEnvironment {
Self { Self {
bindings: GcRefCell::new(vec![None; bindings_count as usize]), bindings: GcRefCell::new(vec![None; bindings_count as usize]),
poisoned: Cell::new(poisoned), poisoned: Cell::new(poisoned),
with: Cell::new(with), with,
} }
} }
@ -326,7 +317,7 @@ impl PoisonableEnvironment {
/// Returns `true` if this environment is inside a `with` environment. /// Returns `true` if this environment is inside a `with` environment.
fn with(&self) -> bool { fn with(&self) -> bool {
self.with.get() self.with
} }
/// Poisons this environment for future binding searches. /// Poisons this environment for future binding searches.

18
core/engine/src/environments/runtime/declarative/module.rs

@ -1,5 +1,6 @@
use std::cell::RefCell; use std::cell::RefCell;
use boa_ast::scope::Scope;
use boa_gc::{Finalize, GcRefCell, Trace}; use boa_gc::{Finalize, GcRefCell, Trace};
use crate::{module::Module, JsString, JsValue}; use crate::{module::Module, JsString, JsValue};
@ -36,16 +37,26 @@ enum BindingType {
#[derive(Debug, Trace, Finalize)] #[derive(Debug, Trace, Finalize)]
pub(crate) struct ModuleEnvironment { pub(crate) struct ModuleEnvironment {
bindings: GcRefCell<Vec<BindingType>>, bindings: GcRefCell<Vec<BindingType>>,
// Safety: Nothing in CompileTimeEnvironment needs tracing.
#[unsafe_ignore_trace]
compile: Scope,
} }
impl ModuleEnvironment { impl ModuleEnvironment {
/// Creates a new `LexicalEnvironment`. /// Creates a new `LexicalEnvironment`.
pub(crate) fn new(bindings: u32) -> Self { pub(crate) fn new(bindings: u32, compile: Scope) -> Self {
Self { Self {
bindings: GcRefCell::new(vec![BindingType::Direct(None); bindings as usize]), bindings: GcRefCell::new(vec![BindingType::Direct(None); bindings as usize]),
compile,
} }
} }
/// Gets the compile time environment of this module environment.
pub(crate) const fn compile(&self) -> &Scope {
&self.compile
}
/// Get the binding value from the environment by it's index. /// Get the binding value from the environment by it's index.
/// ///
/// # Panics /// # Panics
@ -63,7 +74,10 @@ impl ModuleEnvironment {
match &*accessor.clone().borrow() { match &*accessor.clone().borrow() {
BindingAccessor::Identifier(name) => { BindingAccessor::Identifier(name) => {
let index = env let index = env
.compile_env() .kind()
.as_module()
.expect("must be module environment")
.compile()
.get_binding(name) .get_binding(name)
.expect("linking must ensure the binding exists"); .expect("linking must ensure the binding exists");

263
core/engine/src/environments/runtime/mod.rs

@ -1,11 +1,9 @@
use std::rc::Rc;
use crate::{ use crate::{
environments::CompileTimeEnvironment,
object::{JsObject, PrivateName}, object::{JsObject, PrivateName},
Context, JsResult, JsString, JsSymbol, JsValue, Context, JsResult, JsString, JsSymbol, JsValue,
}; };
use boa_gc::{empty_trace, Finalize, Gc, Trace}; use boa_ast::scope::{BindingLocator, BindingLocatorScope, Scope};
use boa_gc::{Finalize, Gc, Trace};
mod declarative; mod declarative;
mod private; mod private;
@ -76,18 +74,18 @@ impl EnvironmentStack {
} }
/// Gets the next outer function environment. /// Gets the next outer function environment.
pub(crate) fn outer_function_environment(&self) -> &Gc<DeclarativeEnvironment> { pub(crate) fn outer_function_environment(&self) -> Option<(Gc<DeclarativeEnvironment>, Scope)> {
for env in self for env in self
.stack .stack
.iter() .iter()
.filter_map(Environment::as_declarative) .filter_map(Environment::as_declarative)
.rev() .rev()
{ {
if let DeclarativeEnvironmentKind::Function(_) = &env.kind() { if let Some(function_env) = env.kind().as_function() {
return env; return Some((env.clone(), function_env.compile().clone()));
} }
} }
self.global() None
} }
/// Pop all current environments except the global environment. /// Pop all current environments except the global environment.
@ -157,9 +155,7 @@ impl EnvironmentStack {
} }
/// Push a lexical environment on the environments stack and return it's index. /// Push a lexical environment on the environments stack and return it's index.
pub(crate) fn push_lexical(&mut self, compile_environment: Rc<CompileTimeEnvironment>) -> u32 { pub(crate) fn push_lexical(&mut self, bindings_count: u32) -> u32 {
let num_bindings = compile_environment.num_bindings();
let (poisoned, with) = { let (poisoned, with) = {
// Check if the outer environment is a declarative environment. // Check if the outer environment is a declarative environment.
let with = if let Some(env) = self.stack.last() { let with = if let Some(env) = self.stack.last() {
@ -180,26 +176,17 @@ impl EnvironmentStack {
let index = self.stack.len() as u32; let index = self.stack.len() as u32;
self.stack.push(Environment::Declarative(Gc::new( self.stack.push(Environment::Declarative(Gc::new(
DeclarativeEnvironment::new( DeclarativeEnvironment::new(DeclarativeEnvironmentKind::Lexical(
DeclarativeEnvironmentKind::Lexical(LexicalEnvironment::new( LexicalEnvironment::new(bindings_count, poisoned, with),
num_bindings, )),
poisoned,
with,
)),
compile_environment,
),
))); )));
index index
} }
/// Push a function environment on the environments stack. /// Push a function environment on the environments stack.
pub(crate) fn push_function( pub(crate) fn push_function(&mut self, scope: Scope, function_slots: FunctionSlots) {
&mut self, let num_bindings = scope.num_bindings();
compile_environment: Rc<CompileTimeEnvironment>,
function_slots: FunctionSlots,
) {
let num_bindings = compile_environment.num_bindings();
let (poisoned, with) = { let (poisoned, with) = {
// Check if the outer environment is a declarative environment. // Check if the outer environment is a declarative environment.
@ -219,26 +206,19 @@ impl EnvironmentStack {
}; };
self.stack.push(Environment::Declarative(Gc::new( self.stack.push(Environment::Declarative(Gc::new(
DeclarativeEnvironment::new( DeclarativeEnvironment::new(DeclarativeEnvironmentKind::Function(
DeclarativeEnvironmentKind::Function(FunctionEnvironment::new( FunctionEnvironment::new(num_bindings, poisoned, with, function_slots, scope),
num_bindings, )),
poisoned,
with,
function_slots,
)),
compile_environment,
),
))); )));
} }
/// Push a module environment on the environments stack. /// Push a module environment on the environments stack.
pub(crate) fn push_module(&mut self, compile_environment: Rc<CompileTimeEnvironment>) { pub(crate) fn push_module(&mut self, scope: Scope) {
let num_bindings = compile_environment.num_bindings(); let num_bindings = scope.num_bindings();
self.stack.push(Environment::Declarative(Gc::new( self.stack.push(Environment::Declarative(Gc::new(
DeclarativeEnvironment::new( DeclarativeEnvironment::new(DeclarativeEnvironmentKind::Module(
DeclarativeEnvironmentKind::Module(ModuleEnvironment::new(num_bindings)), ModuleEnvironment::new(num_bindings, scope),
compile_environment, )),
),
))); )));
} }
@ -258,16 +238,6 @@ impl EnvironmentStack {
} }
} }
/// Get the compile environment for the current runtime environment.
pub(crate) fn current_compile_environment(&self) -> Rc<CompileTimeEnvironment> {
self.stack
.iter()
.filter_map(Environment::as_declarative)
.last()
.map(|env| env.compile_env())
.unwrap_or(self.global().compile_env())
}
/// Mark that there may be added bindings from the current environment to the next function /// Mark that there may be added bindings from the current environment to the next function
/// environment. /// environment.
pub(crate) fn poison_until_last_function(&mut self) { pub(crate) fn poison_until_last_function(&mut self) {
@ -278,7 +248,7 @@ impl EnvironmentStack {
.filter_map(Environment::as_declarative) .filter_map(Environment::as_declarative)
{ {
env.poison(); env.poison();
if env.compile_env().is_function() { if env.is_function() {
return; return;
} }
} }
@ -293,14 +263,15 @@ impl EnvironmentStack {
#[track_caller] #[track_caller]
pub(crate) fn put_lexical_value( pub(crate) fn put_lexical_value(
&mut self, &mut self,
environment: BindingLocatorEnvironment, environment: BindingLocatorScope,
binding_index: u32, binding_index: u32,
value: JsValue, value: JsValue,
) { ) {
let env = match environment { let env = match environment {
BindingLocatorEnvironment::GlobalObject BindingLocatorScope::GlobalObject | BindingLocatorScope::GlobalDeclarative => {
| BindingLocatorEnvironment::GlobalDeclarative => self.global(), self.global()
BindingLocatorEnvironment::Stack(index) => self }
BindingLocatorScope::Stack(index) => self
.stack .stack
.get(index as usize) .get(index as usize)
.and_then(Environment::as_declarative) .and_then(Environment::as_declarative)
@ -317,14 +288,15 @@ impl EnvironmentStack {
#[track_caller] #[track_caller]
pub(crate) fn put_value_if_uninitialized( pub(crate) fn put_value_if_uninitialized(
&mut self, &mut self,
environment: BindingLocatorEnvironment, environment: BindingLocatorScope,
binding_index: u32, binding_index: u32,
value: JsValue, value: JsValue,
) { ) {
let env = match environment { let env = match environment {
BindingLocatorEnvironment::GlobalObject BindingLocatorScope::GlobalObject | BindingLocatorScope::GlobalDeclarative => {
| BindingLocatorEnvironment::GlobalDeclarative => self.global(), self.global()
BindingLocatorEnvironment::Stack(index) => self }
BindingLocatorScope::Stack(index) => self
.stack .stack
.get(index as usize) .get(index as usize)
.and_then(Environment::as_declarative) .and_then(Environment::as_declarative)
@ -388,103 +360,6 @@ impl EnvironmentStack {
} }
} }
/// A binding locator contains all information about a binding that is needed to resolve it at runtime.
///
/// Binding locators get created at bytecode compile time and are accessible at runtime via the [`crate::vm::CodeBlock`].
#[derive(Clone, Debug, Eq, Hash, PartialEq, Finalize)]
pub(crate) struct BindingLocator {
/// Name of the binding.
name: JsString,
/// Environment of the binding.
/// - 0: Global object
/// - 1: Global declarative environment
/// - n: Stack environment at index n - 2
environment: u32,
/// Index of the binding in the environment.
binding_index: u32,
}
unsafe impl Trace for BindingLocator {
empty_trace!();
}
impl BindingLocator {
/// Creates a new declarative binding locator that has knows indices.
pub(crate) const fn declarative(
name: JsString,
environment_index: u32,
binding_index: u32,
) -> Self {
Self {
name,
environment: environment_index + 1,
binding_index,
}
}
/// Creates a binding locator that indicates that the binding is on the global object.
pub(super) const fn global(name: JsString) -> Self {
Self {
name,
environment: 0,
binding_index: 0,
}
}
/// Returns the name of the binding.
pub(crate) const fn name(&self) -> &JsString {
&self.name
}
/// Returns if the binding is located on the global object.
pub(crate) const fn is_global(&self) -> bool {
self.environment == 0
}
/// Returns the environment of the binding.
pub(crate) fn environment(&self) -> BindingLocatorEnvironment {
match self.environment {
0 => BindingLocatorEnvironment::GlobalObject,
1 => BindingLocatorEnvironment::GlobalDeclarative,
n => BindingLocatorEnvironment::Stack(n - 2),
}
}
/// Sets the environment of the binding.
fn set_environment(&mut self, environment: BindingLocatorEnvironment) {
self.environment = match environment {
BindingLocatorEnvironment::GlobalObject => 0,
BindingLocatorEnvironment::GlobalDeclarative => 1,
BindingLocatorEnvironment::Stack(index) => index + 2,
};
}
/// Returns the binding index of the binding.
pub(crate) const fn binding_index(&self) -> u32 {
self.binding_index
}
}
/// Action that is returned when a fallible binding operation.
#[derive(Debug)]
pub(crate) enum BindingLocatorError {
/// Trying to mutate immutable binding,
MutateImmutable,
/// Indicates that any action is silently ignored.
Silent,
}
/// The environment in which a binding is located.
#[derive(Clone, Copy, Debug)]
pub(crate) enum BindingLocatorEnvironment {
GlobalObject,
GlobalDeclarative,
Stack(u32),
}
impl Context { impl Context {
/// Gets the corresponding runtime binding of the provided `BindingLocator`, modifying /// Gets the corresponding runtime binding of the provided `BindingLocator`, modifying
/// its indexes in place. /// its indexes in place.
@ -502,10 +377,9 @@ impl Context {
} }
} }
let (global, min_index) = match locator.environment() { let (global, min_index) = match locator.scope() {
BindingLocatorEnvironment::GlobalObject BindingLocatorScope::GlobalObject | BindingLocatorScope::GlobalDeclarative => (true, 0),
| BindingLocatorEnvironment::GlobalDeclarative => (true, 0), BindingLocatorScope::Stack(index) => (false, index),
BindingLocatorEnvironment::Stack(index) => (false, index),
}; };
let max_index = self.vm.environments.stack.len() as u32; let max_index = self.vm.environments.stack.len() as u32;
@ -513,11 +387,10 @@ impl Context {
match self.environment_expect(index) { match self.environment_expect(index) {
Environment::Declarative(env) => { Environment::Declarative(env) => {
if env.poisoned() { if env.poisoned() {
let compile = env.compile_env(); if let Some(env) = env.kind().as_function() {
if compile.is_function() { if let Some(b) = env.compile().get_binding(locator.name()) {
if let Some(b) = compile.get_binding(locator.name()) { locator.set_scope(b.scope());
locator.set_environment(b.environment()); locator.set_binding_index(b.binding_index());
locator.binding_index = b.binding_index();
return Ok(()); return Ok(());
} }
} }
@ -535,21 +408,17 @@ impl Context {
continue; continue;
} }
} }
locator.set_environment(BindingLocatorEnvironment::Stack(index)); locator.set_scope(BindingLocatorScope::Stack(index));
return Ok(()); return Ok(());
} }
} }
} }
} }
if global { if global && self.realm().environment().poisoned() {
let env = self.vm.environments.global(); if let Some(b) = self.realm().scope().get_binding(locator.name()) {
if env.poisoned() { locator.set_scope(b.scope());
let compile = env.compile_env(); locator.set_binding_index(b.binding_index());
if let Some(b) = compile.get_binding(locator.name()) {
locator.set_environment(b.environment());
locator.binding_index = b.binding_index();
}
} }
} }
@ -567,10 +436,9 @@ impl Context {
} }
} }
let min_index = match locator.environment() { let min_index = match locator.scope() {
BindingLocatorEnvironment::GlobalObject BindingLocatorScope::GlobalObject | BindingLocatorScope::GlobalDeclarative => 0,
| BindingLocatorEnvironment::GlobalDeclarative => 0, BindingLocatorScope::Stack(index) => index,
BindingLocatorEnvironment::Stack(index) => index,
}; };
let max_index = self.vm.environments.stack.len() as u32; let max_index = self.vm.environments.stack.len() as u32;
@ -578,9 +446,10 @@ impl Context {
match self.environment_expect(index) { match self.environment_expect(index) {
Environment::Declarative(env) => { Environment::Declarative(env) => {
if env.poisoned() { if env.poisoned() {
let compile = env.compile_env(); if let Some(env) = env.kind().as_function() {
if compile.is_function() && compile.get_binding(locator.name()).is_some() { if env.compile().get_binding(locator.name()).is_some() {
break; break;
}
} }
} else if !env.with() { } else if !env.with() {
break; break;
@ -611,17 +480,17 @@ impl Context {
/// ///
/// Panics if the environment or binding index are out of range. /// Panics if the environment or binding index are out of range.
pub(crate) fn is_initialized_binding(&mut self, locator: &BindingLocator) -> JsResult<bool> { pub(crate) fn is_initialized_binding(&mut self, locator: &BindingLocator) -> JsResult<bool> {
match locator.environment() { match locator.scope() {
BindingLocatorEnvironment::GlobalObject => { BindingLocatorScope::GlobalObject => {
let key = locator.name().clone(); let key = locator.name().clone();
let obj = self.global_object(); let obj = self.global_object();
obj.has_property(key, self) obj.has_property(key, self)
} }
BindingLocatorEnvironment::GlobalDeclarative => { BindingLocatorScope::GlobalDeclarative => {
let env = self.vm.environments.global(); let env = self.vm.environments.global();
Ok(env.get(locator.binding_index()).is_some()) Ok(env.get(locator.binding_index()).is_some())
} }
BindingLocatorEnvironment::Stack(index) => match self.environment_expect(index) { BindingLocatorScope::Stack(index) => match self.environment_expect(index) {
Environment::Declarative(env) => Ok(env.get(locator.binding_index()).is_some()), Environment::Declarative(env) => Ok(env.get(locator.binding_index()).is_some()),
Environment::Object(obj) => { Environment::Object(obj) => {
let key = locator.name().clone(); let key = locator.name().clone();
@ -638,17 +507,17 @@ impl Context {
/// ///
/// Panics if the environment or binding index are out of range. /// Panics if the environment or binding index are out of range.
pub(crate) fn get_binding(&mut self, locator: &BindingLocator) -> JsResult<Option<JsValue>> { pub(crate) fn get_binding(&mut self, locator: &BindingLocator) -> JsResult<Option<JsValue>> {
match locator.environment() { match locator.scope() {
BindingLocatorEnvironment::GlobalObject => { BindingLocatorScope::GlobalObject => {
let key = locator.name().clone(); let key = locator.name().clone();
let obj = self.global_object(); let obj = self.global_object();
obj.try_get(key, self) obj.try_get(key, self)
} }
BindingLocatorEnvironment::GlobalDeclarative => { BindingLocatorScope::GlobalDeclarative => {
let env = self.vm.environments.global(); let env = self.vm.environments.global();
Ok(env.get(locator.binding_index())) Ok(env.get(locator.binding_index()))
} }
BindingLocatorEnvironment::Stack(index) => match self.environment_expect(index) { BindingLocatorScope::Stack(index) => match self.environment_expect(index) {
Environment::Declarative(env) => Ok(env.get(locator.binding_index())), Environment::Declarative(env) => Ok(env.get(locator.binding_index())),
Environment::Object(obj) => { Environment::Object(obj) => {
let key = locator.name().clone(); let key = locator.name().clone();
@ -671,17 +540,17 @@ impl Context {
value: JsValue, value: JsValue,
strict: bool, strict: bool,
) -> JsResult<()> { ) -> JsResult<()> {
match locator.environment() { match locator.scope() {
BindingLocatorEnvironment::GlobalObject => { BindingLocatorScope::GlobalObject => {
let key = locator.name().clone(); let key = locator.name().clone();
let obj = self.global_object(); let obj = self.global_object();
obj.set(key, value, strict, self)?; obj.set(key, value, strict, self)?;
} }
BindingLocatorEnvironment::GlobalDeclarative => { BindingLocatorScope::GlobalDeclarative => {
let env = self.vm.environments.global(); let env = self.vm.environments.global();
env.set(locator.binding_index(), value); env.set(locator.binding_index(), value);
} }
BindingLocatorEnvironment::Stack(index) => match self.environment_expect(index) { BindingLocatorScope::Stack(index) => match self.environment_expect(index) {
Environment::Declarative(decl) => { Environment::Declarative(decl) => {
decl.set(locator.binding_index(), value); decl.set(locator.binding_index(), value);
} }
@ -703,14 +572,14 @@ impl Context {
/// ///
/// Panics if the environment or binding index are out of range. /// Panics if the environment or binding index are out of range.
pub(crate) fn delete_binding(&mut self, locator: &BindingLocator) -> JsResult<bool> { pub(crate) fn delete_binding(&mut self, locator: &BindingLocator) -> JsResult<bool> {
match locator.environment() { match locator.scope() {
BindingLocatorEnvironment::GlobalObject => { BindingLocatorScope::GlobalObject => {
let key = locator.name().clone(); let key = locator.name().clone();
let obj = self.global_object(); let obj = self.global_object();
obj.__delete__(&key.into(), &mut self.into()) obj.__delete__(&key.into(), &mut self.into())
} }
BindingLocatorEnvironment::GlobalDeclarative => Ok(false), BindingLocatorScope::GlobalDeclarative => Ok(false),
BindingLocatorEnvironment::Stack(index) => match self.environment_expect(index) { BindingLocatorScope::Stack(index) => match self.environment_expect(index) {
Environment::Declarative(_) => Ok(false), Environment::Declarative(_) => Ok(false),
Environment::Object(obj) => { Environment::Object(obj) => {
let key = locator.name().clone(); let key = locator.name().clone();

6
core/engine/src/module/mod.rs

@ -163,15 +163,17 @@ impl Module {
) -> JsResult<Self> { ) -> JsResult<Self> {
let _timer = Profiler::global().start_event("Module parsing", "Main"); let _timer = Profiler::global().start_event("Module parsing", "Main");
let path = src.path().map(Path::to_path_buf); let path = src.path().map(Path::to_path_buf);
let realm = realm.unwrap_or_else(|| context.realm().clone());
let mut parser = Parser::new(src); let mut parser = Parser::new(src);
parser.set_identifier(context.next_parser_identifier()); parser.set_identifier(context.next_parser_identifier());
let module = parser.parse_module(context.interner_mut())?; let module = parser.parse_module(realm.scope(), context.interner_mut())?;
let src = SourceTextModule::new(module, context.interner()); let src = SourceTextModule::new(module, context.interner());
Ok(Self { Ok(Self {
inner: Gc::new(ModuleRepr { inner: Gc::new(ModuleRepr {
realm: realm.unwrap_or_else(|| context.realm().clone()), realm,
namespace: GcRefCell::default(), namespace: GcRefCell::default(),
kind: ModuleKind::SourceText(src), kind: ModuleKind::SourceText(src),
host_defined: HostDefined::default(), host_defined: HostDefined::default(),

10
core/engine/src/module/namespace.rs

@ -309,7 +309,10 @@ fn module_namespace_exotic_try_get(
}; };
let locator = env let locator = env
.compile_env() .kind()
.as_module()
.expect("must be module environment")
.compile()
.get_binding(&name) .get_binding(&name)
.expect("checked before that the name was reachable"); .expect("checked before that the name was reachable");
@ -386,7 +389,10 @@ fn module_namespace_exotic_get(
}; };
let locator = env let locator = env
.compile_env() .kind()
.as_module()
.expect("must be module environment")
.compile()
.get_binding(&name) .get_binding(&name)
.expect("checked before that the name was reachable"); .expect("checked before that the name was reachable");

84
core/engine/src/module/source.rs

@ -2,13 +2,14 @@ use std::{cell::Cell, collections::HashSet, hash::BuildHasherDefault, rc::Rc};
use boa_ast::{ use boa_ast::{
declaration::{ declaration::{
ExportEntry, ImportEntry, ImportName, IndirectExportEntry, LexicalDeclaration, ExportEntry, ImportEntry, ImportName, IndirectExportEntry, LocalExportEntry,
LocalExportEntry, ReExportImportName, ReExportImportName,
}, },
operations::{ operations::{
bound_names, contains, lexically_scoped_declarations, var_scoped_declarations, bound_names, contains, lexically_scoped_declarations, var_scoped_declarations,
ContainsSymbol, LexicallyScopedDeclaration, ContainsSymbol, LexicallyScopedDeclaration,
}, },
scope::BindingLocator,
}; };
use boa_gc::{Finalize, Gc, GcRefCell, Trace}; use boa_gc::{Finalize, Gc, GcRefCell, Trace};
use boa_interner::Interner; use boa_interner::Interner;
@ -19,9 +20,7 @@ use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
use crate::{ use crate::{
builtins::{promise::PromiseCapability, Promise}, builtins::{promise::PromiseCapability, Promise},
bytecompiler::{ByteCompiler, FunctionSpec, ToJsString}, bytecompiler::{ByteCompiler, FunctionSpec, ToJsString},
environments::{ environments::{DeclarativeEnvironment, EnvironmentStack},
BindingLocator, CompileTimeEnvironment, DeclarativeEnvironment, EnvironmentStack,
},
js_string, js_string,
module::ModuleKind, module::ModuleKind,
object::{FunctionObjectBuilder, JsPromise}, object::{FunctionObjectBuilder, JsPromise},
@ -1423,15 +1422,14 @@ impl SourceTextModule {
// 5. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). // 5. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]).
// 6. Set module.[[Environment]] to env. // 6. Set module.[[Environment]] to env.
let global_env = realm.environment().clone(); let global_env = realm.environment().clone();
let global_compile_env = global_env.compile_env(); let env = self.code.source.scope().clone();
let env = Rc::new(CompileTimeEnvironment::new(global_compile_env, true));
let mut compiler = ByteCompiler::new( let mut compiler = ByteCompiler::new(
js_string!("<main>"), js_string!("<main>"),
true, true,
false, false,
env.clone(), self.code.source.scope().clone(),
env.clone(), self.code.source.scope().clone(),
true, true,
false, false,
context.interner_mut(), context.interner_mut(),
@ -1471,7 +1469,7 @@ impl SourceTextModule {
// 2. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true). // 2. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true).
// 3. Perform ! env.InitializeBinding(in.[[LocalName]], namespace). // 3. Perform ! env.InitializeBinding(in.[[LocalName]], namespace).
let local_name = entry.local_name().to_js_string(compiler.interner()); let local_name = entry.local_name().to_js_string(compiler.interner());
let locator = env.create_immutable_binding(local_name, true); let locator = env.get_binding(&local_name).expect("binding must exist");
if let BindingName::Name(_) = resolution.binding_name { if let BindingName::Name(_) = resolution.binding_name {
// 1. Perform env.CreateImportBinding(in.[[LocalName]], resolution.[[Module]], // 1. Perform env.CreateImportBinding(in.[[LocalName]], resolution.[[Module]],
@ -1493,10 +1491,8 @@ impl SourceTextModule {
// b. If in.[[ImportName]] is namespace-object, then // b. If in.[[ImportName]] is namespace-object, then
// ii. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true). // ii. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true).
// iii. Perform ! env.InitializeBinding(in.[[LocalName]], namespace). // iii. Perform ! env.InitializeBinding(in.[[LocalName]], namespace).
let locator = env.create_immutable_binding( let name = entry.local_name().to_js_string(compiler.interner());
entry.local_name().to_js_string(compiler.interner()), let locator = env.get_binding(&name).expect("binding must exist");
true,
);
// i. Let namespace be GetModuleNamespace(importedModule). // i. Let namespace be GetModuleNamespace(importedModule).
// deferred to initialization below // deferred to initialization below
@ -1522,10 +1518,12 @@ impl SourceTextModule {
if !declared_var_names.contains(&name) { if !declared_var_names.contains(&name) {
// 1. Perform ! env.CreateMutableBinding(dn, false). // 1. Perform ! env.CreateMutableBinding(dn, false).
// 2. Perform ! env.InitializeBinding(dn, undefined). // 2. Perform ! env.InitializeBinding(dn, undefined).
let binding = env.create_mutable_binding(name.clone(), false); let binding = env
.get_binding_reference(&name)
.expect("binding must exist");
let index = compiler.get_or_insert_binding(binding); let index = compiler.get_or_insert_binding(binding);
compiler.emit_opcode(Opcode::PushUndefined); compiler.emit_opcode(Opcode::PushUndefined);
compiler.emit_with_varying_operand(Opcode::DefInitVar, index); compiler.emit_binding_access(Opcode::DefInitVar, &index);
// 3. Append dn to declaredVarNames. // 3. Append dn to declaredVarNames.
declared_var_names.push(name); declared_var_names.push(name);
@ -1549,68 +1547,38 @@ impl SourceTextModule {
// 2. Perform ! env.InitializeBinding(dn, fo). // 2. Perform ! env.InitializeBinding(dn, fo).
// //
// deferred to below. // deferred to below.
let (mut spec, locator): (FunctionSpec<'_>, _) = match declaration { let (spec, locator): (FunctionSpec<'_>, _) = match declaration {
LexicallyScopedDeclaration::FunctionDeclaration(f) => { LexicallyScopedDeclaration::FunctionDeclaration(f) => {
let name = bound_names(f)[0].to_js_string(compiler.interner()); let name = bound_names(f)[0].to_js_string(compiler.interner());
let locator = env.create_mutable_binding(name, false); let locator = env.get_binding(&name).expect("binding must exist");
(f.into(), locator) (f.into(), locator)
} }
LexicallyScopedDeclaration::GeneratorDeclaration(g) => { LexicallyScopedDeclaration::GeneratorDeclaration(g) => {
let name = bound_names(g)[0].to_js_string(compiler.interner()); let name = bound_names(g)[0].to_js_string(compiler.interner());
let locator = env.create_mutable_binding(name, false); let locator = env.get_binding(&name).expect("binding must exist");
(g.into(), locator) (g.into(), locator)
} }
LexicallyScopedDeclaration::AsyncFunctionDeclaration(af) => { LexicallyScopedDeclaration::AsyncFunctionDeclaration(af) => {
let name = bound_names(af)[0].to_js_string(compiler.interner()); let name = bound_names(af)[0].to_js_string(compiler.interner());
let locator = env.create_mutable_binding(name, false); let locator = env.get_binding(&name).expect("binding must exist");
(af.into(), locator) (af.into(), locator)
} }
LexicallyScopedDeclaration::AsyncGeneratorDeclaration(ag) => { LexicallyScopedDeclaration::AsyncGeneratorDeclaration(ag) => {
let name = bound_names(ag)[0].to_js_string(compiler.interner()); let name = bound_names(ag)[0].to_js_string(compiler.interner());
let locator = env.create_mutable_binding(name, false); let locator = env.get_binding(&name).expect("binding must exist");
(ag.into(), locator) (ag.into(), locator)
} }
LexicallyScopedDeclaration::ClassDeclaration(class) => { LexicallyScopedDeclaration::ClassDeclaration(_)
for name in bound_names(class) { | LexicallyScopedDeclaration::LexicalDeclaration(_)
let name = name.to_js_string(compiler.interner()); | LexicallyScopedDeclaration::AssignmentExpression(_) => {
env.create_mutable_binding(name, false);
}
continue;
}
// i. If IsConstantDeclaration of d is true, then
LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const(
c,
)) => {
// a. For each element dn of the BoundNames of d, do
for name in bound_names(c) {
let name = name.to_js_string(compiler.interner());
// 1. Perform ! env.CreateImmutableBinding(dn, true).
env.create_immutable_binding(name, true);
}
continue;
}
LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Let(l)) => {
for name in bound_names(l) {
let name = name.to_js_string(compiler.interner());
env.create_mutable_binding(name, false);
}
continue;
}
LexicallyScopedDeclaration::AssignmentExpression(expr) => {
for name in bound_names(expr) {
let name = name.to_js_string(compiler.interner());
env.create_mutable_binding(name, false);
}
continue; continue;
} }
}; };
spec.has_binding_identifier = false;
functions.push((spec, locator)); functions.push((spec, locator));
} }
@ -1628,7 +1596,7 @@ impl SourceTextModule {
// 8. Let moduleContext be a new ECMAScript code execution context. // 8. Let moduleContext be a new ECMAScript code execution context.
let mut envs = EnvironmentStack::new(global_env); let mut envs = EnvironmentStack::new(global_env);
envs.push_module(env); envs.push_module(self.code.source.scope().clone());
// 9. Set the Function of moduleContext to null. // 9. Set the Function of moduleContext to null.
// 10. Assert: module.[[Realm]] is not undefined. // 10. Assert: module.[[Realm]] is not undefined.
@ -1656,7 +1624,7 @@ impl SourceTextModule {
// i. Let namespace be GetModuleNamespace(importedModule). // i. Let namespace be GetModuleNamespace(importedModule).
let namespace = module.namespace(context); let namespace = module.namespace(context);
context.vm.environments.put_lexical_value( context.vm.environments.put_lexical_value(
locator.environment(), locator.scope(),
locator.binding_index(), locator.binding_index(),
namespace.into(), namespace.into(),
); );
@ -1677,7 +1645,7 @@ impl SourceTextModule {
BindingName::Namespace => { BindingName::Namespace => {
let namespace = export_locator.module.namespace(context); let namespace = export_locator.module.namespace(context);
context.vm.environments.put_lexical_value( context.vm.environments.put_lexical_value(
locator.environment(), locator.scope(),
locator.binding_index(), locator.binding_index(),
namespace.into(), namespace.into(),
); );
@ -1693,7 +1661,7 @@ impl SourceTextModule {
let function = create_function_object_fast(code, context); let function = create_function_object_fast(code, context);
context.vm.environments.put_lexical_value( context.vm.environments.put_lexical_value(
locator.environment(), locator.scope(),
locator.binding_index(), locator.binding_index(),
function.into(), function.into(),
); );

37
core/engine/src/module/synthetic.rs

@ -1,12 +1,11 @@
use std::rc::Rc; use boa_ast::scope::Scope;
use boa_gc::{Finalize, Gc, GcRefCell, Trace}; use boa_gc::{Finalize, Gc, GcRefCell, Trace};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use crate::{ use crate::{
builtins::promise::ResolvingFunctions, builtins::promise::ResolvingFunctions,
bytecompiler::ByteCompiler, bytecompiler::ByteCompiler,
environments::{CompileTimeEnvironment, DeclarativeEnvironment, EnvironmentStack}, environments::{DeclarativeEnvironment, EnvironmentStack},
js_string, js_string,
object::JsPromise, object::JsPromise,
vm::{ActiveRunnable, CallFrame, CodeBlock}, vm::{ActiveRunnable, CallFrame, CodeBlock},
@ -205,12 +204,18 @@ impl SyntheticModule {
export_name.to_std_string_escaped() export_name.to_std_string_escaped()
)) ))
})?; })?;
let locator = env.compile_env().get_binding(export_name).ok_or_else(|| { let locator = env
JsNativeError::reference().with_message(format!( .kind()
"cannot set name `{}` which was not included in the list of exports", .as_module()
export_name.to_std_string_escaped() .expect("must be module environment")
)) .compile()
})?; .get_binding(export_name)
.ok_or_else(|| {
JsNativeError::reference().with_message(format!(
"cannot set name `{}` which was not included in the list of exports",
export_name.to_std_string_escaped()
))
})?;
env.set(locator.binding_index(), export_value); env.set(locator.binding_index(), export_value);
Ok(()) Ok(())
@ -275,8 +280,8 @@ impl SyntheticModule {
// 2. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). // 2. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]).
// 3. Set module.[[Environment]] to env. // 3. Set module.[[Environment]] to env.
let global_env = module_self.realm().environment().clone(); let global_env = module_self.realm().environment().clone();
let global_compile_env = global_env.compile_env(); let global_scope = module_self.realm().scope().clone();
let module_compile_env = Rc::new(CompileTimeEnvironment::new(global_compile_env, true)); let module_scope = Scope::new(global_scope, true);
// TODO: A bit of a hack to be able to pass the currently active runnable without an // TODO: A bit of a hack to be able to pass the currently active runnable without an
// available codeblock to execute. // available codeblock to execute.
@ -284,8 +289,8 @@ impl SyntheticModule {
js_string!("<main>"), js_string!("<main>"),
true, true,
false, false,
module_compile_env.clone(), module_scope.clone(),
module_compile_env.clone(), module_scope.clone(),
false, false,
false, false,
context.interner_mut(), context.interner_mut(),
@ -298,19 +303,19 @@ impl SyntheticModule {
.iter() .iter()
.map(|name| { .map(|name| {
// a. Perform ! env.CreateMutableBinding(exportName, false). // a. Perform ! env.CreateMutableBinding(exportName, false).
module_compile_env.create_mutable_binding(name.clone(), false) module_scope.create_mutable_binding(name.clone(), false)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let cb = Gc::new(compiler.finish()); let cb = Gc::new(compiler.finish());
let mut envs = EnvironmentStack::new(global_env); let mut envs = EnvironmentStack::new(global_env);
envs.push_module(module_compile_env); envs.push_module(module_scope);
for locator in exports { for locator in exports {
// b. Perform ! env.InitializeBinding(exportName, undefined). // b. Perform ! env.InitializeBinding(exportName, undefined).
envs.put_lexical_value( envs.put_lexical_value(
locator.environment(), locator.scope(),
locator.binding_index(), locator.binding_index(),
JsValue::undefined(), JsValue::undefined(),
); );

20
core/engine/src/realm.rs

@ -8,6 +8,7 @@
use std::any::TypeId; use std::any::TypeId;
use boa_ast::scope::Scope;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use crate::{ use crate::{
@ -54,7 +55,16 @@ impl std::fmt::Debug for Realm {
#[derive(Trace, Finalize)] #[derive(Trace, Finalize)]
struct Inner { struct Inner {
intrinsics: Intrinsics, intrinsics: Intrinsics,
/// The global declarative environment of this realm.
environment: Gc<DeclarativeEnvironment>, environment: Gc<DeclarativeEnvironment>,
/// The global scope of this realm.
/// This is directly related to the global declarative environment.
// Safety: Nothing in `Scope` needs tracing.
#[unsafe_ignore_trace]
scope: Scope,
global_object: JsObject, global_object: JsObject,
global_this: JsObject, global_this: JsObject,
template_map: GcRefCell<FxHashMap<u64, JsObject>>, template_map: GcRefCell<FxHashMap<u64, JsObject>>,
@ -79,11 +89,13 @@ impl Realm {
.create_global_this(&intrinsics) .create_global_this(&intrinsics)
.unwrap_or_else(|| global_object.clone()); .unwrap_or_else(|| global_object.clone());
let environment = Gc::new(DeclarativeEnvironment::global()); let environment = Gc::new(DeclarativeEnvironment::global());
let scope = Scope::new_global();
let realm = Self { let realm = Self {
inner: Gc::new(Inner { inner: Gc::new(Inner {
intrinsics, intrinsics,
environment, environment,
scope,
global_object, global_object,
global_this, global_this,
template_map: GcRefCell::default(), template_map: GcRefCell::default(),
@ -156,6 +168,12 @@ impl Realm {
&self.inner.environment &self.inner.environment
} }
/// Returns the scope of this realm.
#[must_use]
pub fn scope(&self) -> &Scope {
&self.inner.scope
}
pub(crate) fn global_object(&self) -> &JsObject { pub(crate) fn global_object(&self) -> &JsObject {
&self.inner.global_object &self.inner.global_object
} }
@ -170,7 +188,7 @@ impl Realm {
/// Resizes the number of bindings on the global environment. /// Resizes the number of bindings on the global environment.
pub(crate) fn resize_global_env(&self) { pub(crate) fn resize_global_env(&self) {
let binding_number = self.environment().compile_env().num_bindings(); let binding_number = self.scope().num_bindings();
let env = self let env = self
.environment() .environment()
.kind() .kind()

14
core/engine/src/script.rs

@ -90,7 +90,8 @@ impl Script {
if context.is_strict() { if context.is_strict() {
parser.set_strict(); parser.set_strict();
} }
let mut code = parser.parse_script(context.interner_mut())?; let scope = context.realm().scope().clone();
let mut code = parser.parse_script(&scope, context.interner_mut())?;
if !context.optimizer_options().is_empty() { if !context.optimizer_options().is_empty() {
context.optimize_statement_list(code.statements_mut()); context.optimize_statement_list(code.statements_mut());
} }
@ -124,7 +125,7 @@ impl Script {
global_declaration_instantiation_context( global_declaration_instantiation_context(
&mut annex_b_function_names, &mut annex_b_function_names,
&self.inner.source, &self.inner.source,
&self.inner.realm.environment().compile_env(), self.inner.realm.scope(),
context, context,
)?; )?;
@ -132,8 +133,8 @@ impl Script {
js_string!("<main>"), js_string!("<main>"),
self.inner.source.strict(), self.inner.source.strict(),
false, false,
self.inner.realm.environment().compile_env(), self.inner.realm.scope().clone(),
self.inner.realm.environment().compile_env(), self.inner.realm.scope().clone(),
false, false,
false, false,
context.interner_mut(), context.interner_mut(),
@ -146,10 +147,7 @@ impl Script {
} }
// TODO: move to `Script::evaluate` to make this operation infallible. // TODO: move to `Script::evaluate` to make this operation infallible.
compiler.global_declaration_instantiation( compiler.global_declaration_instantiation(&self.inner.source);
&self.inner.source,
&self.inner.realm.environment().compile_env(),
);
compiler.compile_statement_list(self.inner.source.statements(), true, false); compiler.compile_statement_list(self.inner.source.statements(), true, false);
let cb = Gc::new(compiler.finish()); let cb = Gc::new(compiler.finish());

11
core/engine/src/vm/call_frame/mod.rs

@ -7,12 +7,13 @@ use crate::{
iterable::IteratorRecord, iterable::IteratorRecord,
promise::{PromiseCapability, ResolvingFunctions}, promise::{PromiseCapability, ResolvingFunctions},
}, },
environments::{BindingLocator, EnvironmentStack}, environments::EnvironmentStack,
object::{JsFunction, JsObject}, object::{JsFunction, JsObject},
realm::Realm, realm::Realm,
vm::CodeBlock, vm::CodeBlock,
JsValue, JsValue,
}; };
use boa_ast::scope::BindingLocator;
use boa_gc::{Finalize, Gc, Trace}; use boa_gc::{Finalize, Gc, Trace};
use thin_vec::ThinVec; use thin_vec::ThinVec;
@ -55,8 +56,14 @@ pub struct CallFrame {
pub(crate) iterators: ThinVec<IteratorRecord>, pub(crate) iterators: ThinVec<IteratorRecord>,
// The stack of bindings being updated. // The stack of bindings being updated.
// SAFETY: Nothing in `BindingLocator` requires tracing, so this is safe.
#[unsafe_ignore_trace]
pub(crate) binding_stack: Vec<BindingLocator>, pub(crate) binding_stack: Vec<BindingLocator>,
// SAFETY: Nothing requires tracing, so this is safe.
#[unsafe_ignore_trace]
pub(crate) local_binings_initialized: Box<[bool]>,
/// How many iterations a loop has done. /// How many iterations a loop has done.
pub(crate) loop_iteration_count: u64, pub(crate) loop_iteration_count: u64,
@ -147,6 +154,7 @@ impl CallFrame {
environments: EnvironmentStack, environments: EnvironmentStack,
realm: Realm, realm: Realm,
) -> Self { ) -> Self {
let local_binings_initialized = code_block.local_bindings_initialized.clone();
Self { Self {
code_block, code_block,
pc: 0, pc: 0,
@ -155,6 +163,7 @@ impl CallFrame {
argument_count: 0, argument_count: 0,
iterators: ThinVec::new(), iterators: ThinVec::new(),
binding_stack: Vec::new(), binding_stack: Vec::new(),
local_binings_initialized,
loop_iteration_count: 0, loop_iteration_count: 0,
active_runnable, active_runnable,
environments, environments,

58
core/engine/src/vm/code_block.rs

@ -7,14 +7,14 @@ use crate::{
function::{OrdinaryFunction, ThisMode}, function::{OrdinaryFunction, ThisMode},
OrdinaryObject, OrdinaryObject,
}, },
environments::{BindingLocator, CompileTimeEnvironment},
object::JsObject, object::JsObject,
Context, JsBigInt, JsString, JsValue, Context, JsBigInt, JsString, JsValue,
}; };
use bitflags::bitflags; use bitflags::bitflags;
use boa_ast::scope::{BindingLocator, Scope};
use boa_gc::{empty_trace, Finalize, Gc, Trace}; use boa_gc::{empty_trace, Finalize, Gc, Trace};
use boa_profiler::Profiler; use boa_profiler::Profiler;
use std::{cell::Cell, fmt::Display, mem::size_of, rc::Rc}; use std::{cell::Cell, fmt::Display, mem::size_of};
use thin_vec::ThinVec; use thin_vec::ThinVec;
use super::{InlineCache, Instruction, InstructionIterator}; use super::{InlineCache, Instruction, InstructionIterator};
@ -112,11 +112,9 @@ pub(crate) enum Constant {
Function(Gc<CodeBlock>), Function(Gc<CodeBlock>),
BigInt(#[unsafe_ignore_trace] JsBigInt), BigInt(#[unsafe_ignore_trace] JsBigInt),
/// Compile time environments in this function. /// Declarative or function scope.
// Safety: Nothing in CompileTimeEnvironment needs tracing, so this is safe. // Safety: Nothing in `Scope` needs tracing, so this is safe.
// Scope(#[unsafe_ignore_trace] Scope),
// TODO(#3034): Maybe changing this to Gc after garbage collection would be better than Rc.
CompileTimeEnvironment(#[unsafe_ignore_trace] Rc<CompileTimeEnvironment>),
} }
/// The internal representation of a JavaScript function. /// The internal representation of a JavaScript function.
@ -157,6 +155,8 @@ pub struct CodeBlock {
#[unsafe_ignore_trace] #[unsafe_ignore_trace]
pub(crate) bindings: Box<[BindingLocator]>, pub(crate) bindings: Box<[BindingLocator]>,
pub(crate) local_bindings_initialized: Box<[bool]>,
/// Exception [`Handler`]s. /// Exception [`Handler`]s.
#[unsafe_ignore_trace] #[unsafe_ignore_trace]
pub(crate) handlers: ThinVec<Handler>, pub(crate) handlers: ThinVec<Handler>,
@ -176,6 +176,7 @@ impl CodeBlock {
bytecode: Box::default(), bytecode: Box::default(),
constants: ThinVec::default(), constants: ThinVec::default(),
bindings: Box::default(), bindings: Box::default(),
local_bindings_initialized: Box::default(),
name, name,
flags: Cell::new(flags), flags: Cell::new(flags),
length, length,
@ -308,21 +309,18 @@ impl CodeBlock {
panic!("expected function constant at index {index}") panic!("expected function constant at index {index}")
} }
/// Get the [`CompileTimeEnvironment`] constant from the [`CodeBlock`]. /// Get the [`Scope`] constant from the [`CodeBlock`].
/// ///
/// # Panics /// # Panics
/// ///
/// If the type of the [`Constant`] is not [`Constant::CompileTimeEnvironment`]. /// If the type of the [`Constant`] is not [`Constant::Scope`].
/// Or `index` is greater or equal to length of `constants`. /// Or `index` is greater or equal to length of `constants`.
pub(crate) fn constant_compile_time_environment( pub(crate) fn constant_scope(&self, index: usize) -> Scope {
&self, if let Some(Constant::Scope(value)) = self.constants.get(index) {
index: usize,
) -> Rc<CompileTimeEnvironment> {
if let Some(Constant::CompileTimeEnvironment(value)) = self.constants.get(index) {
return value.clone(); return value.clone();
} }
panic!("expected compile time environment constant at index {index}") panic!("expected scope constant at index {index}")
} }
} }
@ -416,8 +414,11 @@ impl CodeBlock {
| Instruction::Coalesce { exit: value } => value.to_string(), | Instruction::Coalesce { exit: value } => value.to_string(),
Instruction::CallEval { Instruction::CallEval {
argument_count: value, argument_count: value,
scope_index,
} => {
format!("{}, {}", value.value(), scope_index.value())
} }
| Instruction::Call { Instruction::Call {
argument_count: value, argument_count: value,
} }
| Instruction::New { | Instruction::New {
@ -428,9 +429,9 @@ impl CodeBlock {
} }
| Instruction::ConcatToString { value_count: value } | Instruction::ConcatToString { value_count: value }
| Instruction::GetArgument { index: value } => value.value().to_string(), | Instruction::GetArgument { index: value } => value.value().to_string(),
Instruction::PushDeclarativeEnvironment { Instruction::PushScope { index } | Instruction::CallEvalSpread { index } => {
compile_environments_index, index.value().to_string()
} => compile_environments_index.value().to_string(), }
Instruction::CopyDataProperties { Instruction::CopyDataProperties {
excluded_key_count: value1, excluded_key_count: value1,
excluded_key_count_computed: value2, excluded_key_count_computed: value2,
@ -549,8 +550,12 @@ impl CodeBlock {
} => { } => {
format!("is_anonymous_function: {is_anonymous_function}") format!("is_anonymous_function: {is_anonymous_function}")
} }
Instruction::PopIntoRegister { dst } => format!("dst:reg{}", dst.value()), Instruction::PopIntoRegister { dst } | Instruction::PopIntoLocal { dst } => {
Instruction::PushFromRegister { src } => format!("src:reg{}", src.value()), format!("dst:reg{}", dst.value())
}
Instruction::PushFromRegister { src } | Instruction::PushFromLocal { src } => {
format!("src:reg{}", src.value())
}
Instruction::Pop Instruction::Pop
| Instruction::Dup | Instruction::Dup
| Instruction::Swap | Instruction::Swap
@ -657,7 +662,6 @@ impl CodeBlock {
| Instruction::NewTarget | Instruction::NewTarget
| Instruction::ImportMeta | Instruction::ImportMeta
| Instruction::SuperCallPrepare | Instruction::SuperCallPrepare
| Instruction::CallEvalSpread
| Instruction::CallSpread | Instruction::CallSpread
| Instruction::NewSpread | Instruction::NewSpread
| Instruction::SuperCallSpread | Instruction::SuperCallSpread
@ -724,9 +728,7 @@ impl CodeBlock {
| Instruction::Reserved46 | Instruction::Reserved46
| Instruction::Reserved47 | Instruction::Reserved47
| Instruction::Reserved48 | Instruction::Reserved48
| Instruction::Reserved49 | Instruction::Reserved49 => unreachable!("Reserved opcodes are unrechable"),
| Instruction::Reserved50
| Instruction::Reserved51 => unreachable!("Reserved opcodes are unrechable"),
} }
} }
} }
@ -800,11 +802,11 @@ impl Display for CodeBlock {
code.name().to_std_string_escaped(), code.name().to_std_string_escaped(),
code.length code.length
)?, )?,
Constant::CompileTimeEnvironment(v) => { Constant::Scope(v) => {
writeln!( writeln!(
f, f,
"[ENVIRONMENT] index: {}, bindings: {}", "[SCOPE] index: {}, bindings: {}",
v.environment_index(), v.scope_index(),
v.num_bindings() v.num_bindings()
)?; )?;
} }

10
core/engine/src/vm/flowgraph/mod.rs

@ -227,7 +227,7 @@ impl CodeBlock {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
} }
Instruction::PushDeclarativeEnvironment { .. } => { Instruction::PushScope { .. } => {
let random = rand::random(); let random = rand::random();
graph.add_node( graph.add_node(
@ -434,7 +434,7 @@ impl CodeBlock {
| Instruction::Await | Instruction::Await
| Instruction::NewTarget | Instruction::NewTarget
| Instruction::ImportMeta | Instruction::ImportMeta
| Instruction::CallEvalSpread | Instruction::CallEvalSpread { .. }
| Instruction::CallSpread | Instruction::CallSpread
| Instruction::NewSpread | Instruction::NewSpread
| Instruction::SuperCallSpread | Instruction::SuperCallSpread
@ -457,6 +457,8 @@ impl CodeBlock {
| Instruction::CreateGlobalVarBinding { .. } | Instruction::CreateGlobalVarBinding { .. }
| Instruction::PopIntoRegister { .. } | Instruction::PopIntoRegister { .. }
| Instruction::PushFromRegister { .. } | Instruction::PushFromRegister { .. }
| Instruction::PopIntoLocal { .. }
| Instruction::PushFromLocal { .. }
| Instruction::Nop => { | Instruction::Nop => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
@ -514,9 +516,7 @@ impl CodeBlock {
| Instruction::Reserved46 | Instruction::Reserved46
| Instruction::Reserved47 | Instruction::Reserved47
| Instruction::Reserved48 | Instruction::Reserved48
| Instruction::Reserved49 | Instruction::Reserved49 => unreachable!("Reserved opcodes are unrechable"),
| Instruction::Reserved50
| Instruction::Reserved51 => unreachable!("Reserved opcodes are unrechable"),
} }
} }

66
core/engine/src/vm/opcode/call/mod.rs

@ -15,7 +15,11 @@ use crate::{
pub(crate) struct CallEval; pub(crate) struct CallEval;
impl CallEval { impl CallEval {
fn operation(context: &mut Context, argument_count: usize) -> JsResult<CompletionType> { fn operation(
context: &mut Context,
argument_count: usize,
scope_index: usize,
) -> JsResult<CompletionType> {
let at = context.vm.stack.len() - argument_count; let at = context.vm.stack.len() - argument_count;
let func = &context.vm.stack[at - 1]; let func = &context.vm.stack[at - 1];
@ -45,7 +49,14 @@ impl CallEval {
// let strictCaller be true. Otherwise let strictCaller be false. // let strictCaller be true. Otherwise let strictCaller be false.
// v. Return ? PerformEval(evalArg, strictCaller, true). // v. Return ? PerformEval(evalArg, strictCaller, true).
let strict = context.vm.frame().code_block.strict(); let strict = context.vm.frame().code_block.strict();
let result = crate::builtins::eval::Eval::perform_eval(x, true, strict, context)?; let scope = context.vm.frame().code_block().constant_scope(scope_index);
let result = crate::builtins::eval::Eval::perform_eval(
x,
true,
Some(scope),
strict,
context,
)?;
context.vm.push(result); context.vm.push(result);
} else { } else {
// NOTE: This is a deviation from the spec, to optimize the case when we dont pass anything to `eval`. // NOTE: This is a deviation from the spec, to optimize the case when we dont pass anything to `eval`.
@ -63,23 +74,24 @@ impl CallEval {
impl Operation for CallEval { impl Operation for CallEval {
const NAME: &'static str = "CallEval"; const NAME: &'static str = "CallEval";
const INSTRUCTION: &'static str = "INST - CallEval"; const INSTRUCTION: &'static str = "INST - CallEval";
// TODO: Calls will require a big refactor in order to track
// the cost of the call.
const COST: u8 = 5; const COST: u8 = 5;
fn execute(context: &mut Context) -> JsResult<CompletionType> { fn execute(context: &mut Context) -> JsResult<CompletionType> {
let argument_count = context.vm.read::<u8>(); let argument_count = context.vm.read::<u8>();
Self::operation(context, argument_count as usize) let scope_index = context.vm.read::<u8>();
Self::operation(context, argument_count as usize, scope_index as usize)
} }
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> { fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let argument_count = context.vm.read::<u16>() as usize; let argument_count = context.vm.read::<u16>() as usize;
Self::operation(context, argument_count) let scope_index = context.vm.read::<u16>();
Self::operation(context, argument_count, scope_index as usize)
} }
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> { fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let argument_count = context.vm.read::<u32>(); let argument_count = context.vm.read::<u32>();
Self::operation(context, argument_count as usize) let scope_index = context.vm.read::<u32>();
Self::operation(context, argument_count as usize, scope_index as usize)
} }
} }
@ -90,14 +102,8 @@ impl Operation for CallEval {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct CallEvalSpread; pub(crate) struct CallEvalSpread;
impl Operation for CallEvalSpread { impl CallEvalSpread {
const NAME: &'static str = "CallEvalSpread"; fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> {
const INSTRUCTION: &'static str = "INST - CallEvalSpread";
// TODO: Calls will require a big refactor in order to track
// the cost of the call.
const COST: u8 = 5;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
// Get the arguments that are stored as an array object on the stack. // Get the arguments that are stored as an array object on the stack.
let arguments_array = context.vm.pop(); let arguments_array = context.vm.pop();
let arguments_array_object = arguments_array let arguments_array_object = arguments_array
@ -137,7 +143,14 @@ impl Operation for CallEvalSpread {
// let strictCaller be true. Otherwise let strictCaller be false. // let strictCaller be true. Otherwise let strictCaller be false.
// v. Return ? PerformEval(evalArg, strictCaller, true). // v. Return ? PerformEval(evalArg, strictCaller, true).
let strict = context.vm.frame().code_block.strict(); let strict = context.vm.frame().code_block.strict();
let result = crate::builtins::eval::Eval::perform_eval(x, true, strict, context)?; let scope = context.vm.frame().code_block().constant_scope(index);
let result = crate::builtins::eval::Eval::perform_eval(
x,
true,
Some(scope),
strict,
context,
)?;
context.vm.push(result); context.vm.push(result);
} else { } else {
// NOTE: This is a deviation from the spec, to optimize the case when we dont pass anything to `eval`. // NOTE: This is a deviation from the spec, to optimize the case when we dont pass anything to `eval`.
@ -155,6 +168,27 @@ impl Operation for CallEvalSpread {
} }
} }
impl Operation for CallEvalSpread {
const NAME: &'static str = "CallEvalSpread";
const INSTRUCTION: &'static str = "INST - CallEvalSpread";
const COST: u8 = 5;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u8>();
Self::operation(context, index as usize)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u16>();
Self::operation(context, index as usize)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>();
Self::operation(context, index as usize)
}
}
/// `Call` implements the Opcode Operation for `Opcode::Call` /// `Call` implements the Opcode Operation for `Opcode::Call`
/// ///
/// Operation: /// Operation:

4
core/engine/src/vm/opcode/define/mod.rs

@ -23,7 +23,7 @@ impl DefVar {
let binding_locator = context.vm.frame().code_block.bindings[index].clone(); let binding_locator = context.vm.frame().code_block.bindings[index].clone();
context.vm.environments.put_value_if_uninitialized( context.vm.environments.put_value_if_uninitialized(
binding_locator.environment(), binding_locator.scope(),
binding_locator.binding_index(), binding_locator.binding_index(),
JsValue::undefined(), JsValue::undefined(),
); );
@ -106,7 +106,7 @@ impl PutLexicalValue {
let value = context.vm.pop(); let value = context.vm.pop();
let binding_locator = context.vm.frame().code_block.bindings[index].clone(); let binding_locator = context.vm.frame().code_block.bindings[index].clone();
context.vm.environments.put_lexical_value( context.vm.environments.put_lexical_value(
binding_locator.environment(), binding_locator.scope(),
binding_locator.binding_index(), binding_locator.binding_index(),
value, value,
); );

89
core/engine/src/vm/opcode/locals/mod.rs

@ -0,0 +1,89 @@
use crate::{
vm::{opcode::Operation, CompletionType},
Context, JsNativeError, JsResult,
};
/// `PopIntoLocal` implements the Opcode Operation for `Opcode::PopIntoLocal`
///
/// Operation:
/// - Pop value from the stack and push to a local binding register `dst`.
#[derive(Debug, Clone, Copy)]
pub(crate) struct PopIntoLocal;
impl PopIntoLocal {
#[allow(clippy::unnecessary_wraps)]
#[allow(clippy::needless_pass_by_value)]
fn operation(dst: u32, context: &mut Context) -> JsResult<CompletionType> {
context.vm.frame_mut().local_binings_initialized[dst as usize] = true;
let value = context.vm.pop();
let rp = context.vm.frame().rp;
context.vm.stack[(rp + dst) as usize] = value;
Ok(CompletionType::Normal)
}
}
impl Operation for PopIntoLocal {
const NAME: &'static str = "PopIntoLocal";
const INSTRUCTION: &'static str = "INST - PopIntoLocal";
const COST: u8 = 2;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let dst = u32::from(context.vm.read::<u8>());
Self::operation(dst, context)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let dst = u32::from(context.vm.read::<u16>());
Self::operation(dst, context)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let dst = context.vm.read::<u32>();
Self::operation(dst, context)
}
}
/// `PushFromLocal` implements the Opcode Operation for `Opcode::PushFromLocal`
///
/// Operation:
/// - Copy value at local binding register `src` and push it into the stack.
#[derive(Debug, Clone, Copy)]
pub(crate) struct PushFromLocal;
impl PushFromLocal {
#[allow(clippy::unnecessary_wraps)]
#[allow(clippy::needless_pass_by_value)]
fn operation(dst: u32, context: &mut Context) -> JsResult<CompletionType> {
if !context.vm.frame().local_binings_initialized[dst as usize] {
return Err(JsNativeError::reference()
.with_message("access to uninitialized binding")
.into());
}
let rp = context.vm.frame().rp;
let value = context.vm.stack[(rp + dst) as usize].clone();
context.vm.push(value);
Ok(CompletionType::Normal)
}
}
impl Operation for PushFromLocal {
const NAME: &'static str = "PushFromLocal";
const INSTRUCTION: &'static str = "INST - PushFromLocal";
const COST: u8 = 2;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let dst = u32::from(context.vm.read::<u8>());
Self::operation(dst, context)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let dst = u32::from(context.vm.read::<u16>());
Self::operation(dst, context)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let dst = context.vm.read::<u32>();
Self::operation(dst, context)
}
}

31
core/engine/src/vm/opcode/mod.rs

@ -19,6 +19,7 @@ mod generator;
mod get; mod get;
mod global; mod global;
mod iteration; mod iteration;
mod locals;
mod meta; mod meta;
mod modifier; mod modifier;
mod new; mod new;
@ -65,6 +66,8 @@ pub(crate) use global::*;
#[doc(inline)] #[doc(inline)]
pub(crate) use iteration::*; pub(crate) use iteration::*;
#[doc(inline)] #[doc(inline)]
pub(crate) use locals::*;
#[doc(inline)]
pub(crate) use meta::*; pub(crate) use meta::*;
#[doc(inline)] #[doc(inline)]
pub(crate) use modifier::*; pub(crate) use modifier::*;
@ -1698,17 +1701,17 @@ generate_opcodes! {
/// Call a function named "eval". /// Call a function named "eval".
/// ///
/// Operands: argument_count: `VaryingOperand` /// Operands: argument_count: `VaryingOperand`, scope_index: `VaryingOperand`
/// ///
/// Stack: this, func, argument_1, ... argument_n **=>** result /// Stack: this, func, argument_1, ... argument_n **=>** result
CallEval { argument_count: VaryingOperand }, CallEval { argument_count: VaryingOperand, scope_index: VaryingOperand },
/// Call a function named "eval" where the arguments contain spreads. /// Call a function named "eval" where the arguments contain spreads.
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: this, func, arguments_array **=>** result /// Stack: this, func, arguments_array **=>** result
CallEvalSpread, CallEvalSpread { index: VaryingOperand },
/// Call a function. /// Call a function.
/// ///
@ -1794,12 +1797,26 @@ generate_opcodes! {
/// Stack: **=>** value /// Stack: **=>** value
PushFromRegister { src: VaryingOperand }, PushFromRegister { src: VaryingOperand },
/// Pop value from the stack and push to a local binding register `dst`.
///
/// Operands:
///
/// Stack: value **=>**
PopIntoLocal { dst: VaryingOperand },
/// Copy value at local binding register `src` and push it into the stack.
///
/// Operands:
///
/// Stack: **=>** value
PushFromLocal { src: VaryingOperand },
/// Push a declarative environment. /// Push a declarative environment.
/// ///
/// Operands: compile_environments_index: `VaryingOperand` /// Operands: index: `VaryingOperand`
/// ///
/// Stack: **=>** /// Stack: **=>**
PushDeclarativeEnvironment { compile_environments_index: VaryingOperand }, PushScope { index: VaryingOperand },
/// Push an object environment. /// Push an object environment.
/// ///
@ -2265,10 +2282,6 @@ generate_opcodes! {
Reserved48 => Reserved, Reserved48 => Reserved,
/// Reserved [`Opcode`]. /// Reserved [`Opcode`].
Reserved49 => Reserved, Reserved49 => Reserved,
/// Reserved [`Opcode`].
Reserved50 => Reserved,
/// Reserved [`Opcode`].
Reserved51 => Reserved,
} }
/// Specific opcodes for bindings. /// Specific opcodes for bindings.

37
core/engine/src/vm/opcode/push/environment.rs

@ -6,47 +6,40 @@ use crate::{
}; };
use boa_gc::Gc; use boa_gc::Gc;
/// `PushDeclarativeEnvironment` implements the Opcode Operation for `Opcode::PushDeclarativeEnvironment` /// `PushScope` implements the Opcode Operation for `Opcode::PushScope`
/// ///
/// Operation: /// Operation:
/// - Push a declarative environment /// - Push a declarative environment
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct PushDeclarativeEnvironment; pub(crate) struct PushScope;
impl PushDeclarativeEnvironment { impl PushScope {
#[allow(clippy::unnecessary_wraps)] #[allow(clippy::unnecessary_wraps)]
fn operation( fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> {
context: &mut Context, let scope = context.vm.frame().code_block().constant_scope(index);
compile_environments_index: usize, context.vm.environments.push_lexical(scope.num_bindings());
) -> JsResult<CompletionType> {
let compile_environment = context
.vm
.frame()
.code_block()
.constant_compile_time_environment(compile_environments_index);
context.vm.environments.push_lexical(compile_environment);
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }
impl Operation for PushDeclarativeEnvironment { impl Operation for PushScope {
const NAME: &'static str = "PushDeclarativeEnvironment"; const NAME: &'static str = "PushScope";
const INSTRUCTION: &'static str = "INST - PushDeclarativeEnvironment"; const INSTRUCTION: &'static str = "INST - PushScope";
const COST: u8 = 3; const COST: u8 = 3;
fn execute(context: &mut Context) -> JsResult<CompletionType> { fn execute(context: &mut Context) -> JsResult<CompletionType> {
let compile_environments_index = context.vm.read::<u8>() as usize; let index = context.vm.read::<u8>() as usize;
Self::operation(context, compile_environments_index) Self::operation(context, index)
} }
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> { fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let compile_environments_index = context.vm.read::<u16>() as usize; let index = context.vm.read::<u16>() as usize;
Self::operation(context, compile_environments_index) Self::operation(context, index)
} }
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> { fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let compile_environments_index = context.vm.read::<u32>() as usize; let index = context.vm.read::<u32>() as usize;
Self::operation(context, compile_environments_index) Self::operation(context, index)
} }
} }

14
core/engine/src/vm/opcode/set/name.rs

@ -1,5 +1,7 @@
use boa_ast::scope::{BindingLocator, BindingLocatorScope};
use crate::{ use crate::{
environments::{BindingLocator, BindingLocatorEnvironment, Environment}, environments::Environment,
vm::{opcode::Operation, CompletionType}, vm::{opcode::Operation, CompletionType},
Context, JsNativeError, JsResult, Context, JsNativeError, JsResult,
}; };
@ -125,17 +127,17 @@ fn verify_initialized(locator: &BindingLocator, context: &mut Context) -> JsResu
let key = locator.name(); let key = locator.name();
let strict = context.vm.frame().code_block.strict(); let strict = context.vm.frame().code_block.strict();
let message = match locator.environment() { let message = match locator.scope() {
BindingLocatorEnvironment::GlobalObject if strict => Some(format!( BindingLocatorScope::GlobalObject if strict => Some(format!(
"cannot assign to uninitialized global property `{}`", "cannot assign to uninitialized global property `{}`",
key.to_std_string_escaped() key.to_std_string_escaped()
)), )),
BindingLocatorEnvironment::GlobalObject => None, BindingLocatorScope::GlobalObject => None,
BindingLocatorEnvironment::GlobalDeclarative => Some(format!( BindingLocatorScope::GlobalDeclarative => Some(format!(
"cannot assign to uninitialized binding `{}`", "cannot assign to uninitialized binding `{}`",
key.to_std_string_escaped() key.to_std_string_escaped()
)), )),
BindingLocatorEnvironment::Stack(index) => match context.environment_expect(index) { BindingLocatorScope::Stack(index) => match context.environment_expect(index) {
Environment::Declarative(_) => Some(format!( Environment::Declarative(_) => Some(format!(
"cannot assign to uninitialized binding `{}`", "cannot assign to uninitialized binding `{}`",
key.to_std_string_escaped() key.to_std_string_escaped()

2
core/engine/src/vm/runtime_limits.rs

@ -17,7 +17,7 @@ impl Default for RuntimeLimits {
Self { Self {
loop_iteration_limit: u64::MAX, loop_iteration_limit: u64::MAX,
resursion_limit: 512, resursion_limit: 512,
stack_size_limit: 1024, stack_size_limit: 1024 * 10,
} }
} }
} }

60
core/parser/src/parser/expression/assignment/arrow_function.rs

@ -26,7 +26,7 @@ use boa_ast::{
function::{FormalParameter, FormalParameterList}, function::{FormalParameter, FormalParameterList},
operations::{contains, ContainsSymbol}, operations::{contains, ContainsSymbol},
statement::Return, statement::Return,
Expression, Punctuator, StatementList, Expression, Punctuator,
}; };
use boa_interner::Interner; use boa_interner::Interner;
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -98,18 +98,6 @@ where
) )
}; };
cursor.peek_expect_no_lineterminator(0, "arrow function", interner)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::Arrow),
"arrow function",
interner,
)?;
let arrow = cursor.arrow();
cursor.set_arrow(true);
let body = ConciseBody::new(self.allow_in).parse(cursor, interner)?;
cursor.set_arrow(arrow);
// Early Error: ArrowFormalParameters are UniqueFormalParameters. // Early Error: ArrowFormalParameters are UniqueFormalParameters.
if params.has_duplicates() { if params.has_duplicates() {
return Err(Error::lex(LexError::Syntax( return Err(Error::lex(LexError::Syntax(
@ -134,6 +122,18 @@ where
))); )));
} }
cursor.peek_expect_no_lineterminator(0, "arrow function", interner)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::Arrow),
"arrow function",
interner,
)?;
let arrow = cursor.arrow();
cursor.set_arrow(true);
let body = ConciseBody::new(self.allow_in).parse(cursor, interner)?;
cursor.set_arrow(arrow);
// Early Error: It is a Syntax Error if ConciseBodyContainsUseStrict of ConciseBody is true // Early Error: It is a Syntax Error if ConciseBodyContainsUseStrict of ConciseBody is true
// and IsSimpleParameterList of ArrowParameters is false. // and IsSimpleParameterList of ArrowParameters is false.
if body.strict() && !params.is_simple() { if body.strict() && !params.is_simple() {
@ -182,23 +182,23 @@ where
type Output = ast::function::FunctionBody; type Output = ast::function::FunctionBody;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let stmts = let stmts = match cursor.peek(0, interner).or_abrupt()?.kind() {
match cursor.peek(0, interner).or_abrupt()?.kind() { TokenKind::Punctuator(Punctuator::OpenBlock) => {
TokenKind::Punctuator(Punctuator::OpenBlock) => { cursor.advance(interner);
cursor.advance(interner); let body = FunctionBody::new(false, false).parse(cursor, interner)?;
let body = FunctionBody::new(false, false).parse(cursor, interner)?; cursor.expect(Punctuator::CloseBlock, "arrow function", interner)?;
cursor.expect(Punctuator::CloseBlock, "arrow function", interner)?; body
body }
} _ => ast::function::FunctionBody::new(
_ => ast::function::FunctionBody::new(StatementList::from(vec![ [ast::Statement::Return(Return::new(
ast::Statement::Return(Return::new( ExpressionBody::new(self.allow_in, false)
ExpressionBody::new(self.allow_in, false) .parse(cursor, interner)?
.parse(cursor, interner)? .into(),
.into(), ))
)) .into()],
.into(), false,
])), ),
}; };
Ok(stmts) Ok(stmts)
} }

36
core/parser/src/parser/expression/assignment/async_arrow_function.rs

@ -27,7 +27,7 @@ use boa_ast::{
declaration::Variable, declaration::Variable,
function::{FormalParameter, FormalParameterList}, function::{FormalParameter, FormalParameterList},
statement::Return, statement::Return,
Punctuator, StatementList, Punctuator,
}; };
use boa_interner::Interner; use boa_interner::Interner;
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -173,23 +173,23 @@ where
type Output = ast::function::FunctionBody; type Output = ast::function::FunctionBody;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let body = let body = match cursor.peek(0, interner).or_abrupt()?.kind() {
match cursor.peek(0, interner).or_abrupt()?.kind() { TokenKind::Punctuator(Punctuator::OpenBlock) => {
TokenKind::Punctuator(Punctuator::OpenBlock) => { cursor.advance(interner);
cursor.advance(interner); let body = FunctionBody::new(false, true).parse(cursor, interner)?;
let body = FunctionBody::new(false, true).parse(cursor, interner)?; cursor.expect(Punctuator::CloseBlock, "async arrow function", interner)?;
cursor.expect(Punctuator::CloseBlock, "async arrow function", interner)?; body
body }
} _ => ast::function::FunctionBody::new(
_ => ast::function::FunctionBody::new(StatementList::from(vec![ [ast::Statement::Return(Return::new(
ast::Statement::Return(Return::new( ExpressionBody::new(self.allow_in, true)
ExpressionBody::new(self.allow_in, true) .parse(cursor, interner)?
.parse(cursor, interner)? .into(),
.into(), ))
)) .into()],
.into(), false,
])), ),
}; };
Ok(body) Ok(body)
} }

18
core/parser/src/parser/expression/primary/async_function_expression/tests.rs

@ -27,10 +27,10 @@ fn check_async_expression() {
Some(add.into()), Some(add.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(1).into())), Return::new(Some(Literal::from(1).into())),
))] ))],
.into(), false,
), ),
false, false,
) )
@ -65,7 +65,7 @@ fn check_nested_async_expression() {
Some(a.into()), Some(a.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![Declaration::Lexical(LexicalDeclaration::Const( [Declaration::Lexical(LexicalDeclaration::Const(
vec![Variable::from_identifier( vec![Variable::from_identifier(
b.into(), b.into(),
Some( Some(
@ -73,11 +73,11 @@ fn check_nested_async_expression() {
Some(b.into()), Some(b.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![Statement::Return(Return::new(Some( [Statement::Return(Return::new(Some(
Literal::from(1).into(), Literal::from(1).into(),
))) )))
.into()] .into()],
.into(), false,
), ),
false, false,
) )
@ -87,8 +87,8 @@ fn check_nested_async_expression() {
.try_into() .try_into()
.unwrap(), .unwrap(),
)) ))
.into()] .into()],
.into(), false,
), ),
false, false,
) )

22
core/parser/src/parser/expression/primary/async_generator_expression/tests.rs

@ -28,10 +28,10 @@ fn check_async_generator_expr() {
Some(add.into()), Some(add.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(1).into())), Return::new(Some(Literal::from(1).into())),
))] ))],
.into(), false,
), ),
false, false,
) )
@ -66,7 +66,7 @@ fn check_nested_async_generator_expr() {
Some(a.into()), Some(a.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![Declaration::Lexical(LexicalDeclaration::Const( [Declaration::Lexical(LexicalDeclaration::Const(
vec![Variable::from_identifier( vec![Variable::from_identifier(
b.into(), b.into(),
Some( Some(
@ -74,12 +74,10 @@ fn check_nested_async_generator_expr() {
Some(b.into()), Some(b.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement( [StatementListItem::Statement(Statement::Return(
Statement::Return(Return::new(Some( Return::new(Some(Literal::from(1).into())),
Literal::from(1).into(), ))],
))), false,
)]
.into(),
), ),
false, false,
) )
@ -89,8 +87,8 @@ fn check_nested_async_generator_expr() {
.try_into() .try_into()
.unwrap(), .unwrap(),
)) ))
.into()] .into()],
.into(), false,
), ),
false, false,
) )

27
core/parser/src/parser/expression/primary/function_expression/tests.rs

@ -27,10 +27,10 @@ fn check_function_expression() {
Some(add.into()), Some(add.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(1).into())), Return::new(Some(Literal::from(1).into())),
))] ))],
.into(), false,
), ),
false, false,
) )
@ -65,7 +65,7 @@ fn check_nested_function_expression() {
Some(a.into()), Some(a.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![Declaration::Lexical(LexicalDeclaration::Const( [Declaration::Lexical(LexicalDeclaration::Const(
vec![Variable::from_identifier( vec![Variable::from_identifier(
b.into(), b.into(),
Some( Some(
@ -73,12 +73,10 @@ fn check_nested_function_expression() {
Some(b.into()), Some(b.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement( [StatementListItem::Statement(Statement::Return(
Statement::Return(Return::new(Some( Return::new(Some(Literal::from(1).into())),
Literal::from(1).into(), ))],
))), false,
)]
.into(),
), ),
false, false,
) )
@ -88,8 +86,8 @@ fn check_nested_function_expression() {
.try_into() .try_into()
.unwrap(), .unwrap(),
)) ))
.into()] .into()],
.into(), false,
), ),
false, false,
) )
@ -116,13 +114,14 @@ fn check_function_non_reserved_keyword() {
Some($interner.get_or_intern_static($keyword, utf16!($keyword)).into()), Some($interner.get_or_intern_static($keyword, utf16!($keyword)).into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement( [StatementListItem::Statement(
Statement::Return( Statement::Return(
Return::new( Return::new(
Some(Literal::from(1).into()) Some(Literal::from(1).into())
) )
) )
)].into() )],
false,
), ),
true, true,
) )

12
core/parser/src/parser/expression/primary/generator_expression/tests.rs

@ -25,10 +25,10 @@ fn check_generator_function_expression() {
Some(gen.into()), Some(gen.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Expression( [StatementListItem::Statement(Statement::Expression(
Expression::from(Yield::new(Some(Literal::from(1).into()), false)), Expression::from(Yield::new(Some(Literal::from(1).into()), false)),
))] ))],
.into(), false,
), ),
false, false,
) )
@ -60,10 +60,10 @@ fn check_generator_function_delegate_yield_expression() {
Some(gen.into()), Some(gen.into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Expression( [StatementListItem::Statement(Statement::Expression(
Expression::from(Yield::new(Some(Literal::from(1).into()), true)), Expression::from(Yield::new(Some(Literal::from(1).into()), true)),
))] ))],
.into(), false,
), ),
false, false,
) )

13
core/parser/src/parser/expression/primary/object_initializer/mod.rs

@ -31,12 +31,15 @@ use boa_ast::{
}, },
Identifier, Identifier,
}, },
function::{ClassElementName as ClassElementNameNode, FormalParameterList, PrivateName}, function::{
ClassElementName as ClassElementNameNode, FormalParameterList,
FunctionBody as FunctionBodyAst, PrivateName,
},
operations::{ operations::{
bound_names, contains, has_direct_super_new, lexically_declared_names, ContainsSymbol, bound_names, contains, has_direct_super_new, lexically_declared_names, ContainsSymbol,
}, },
property::{MethodDefinitionKind, PropertyName as PropertyNameNode}, property::{MethodDefinitionKind, PropertyName as PropertyNameNode},
Expression, Keyword, Punctuator, Script, Expression, Keyword, Punctuator,
}; };
use boa_interner::{Interner, Sym}; use boa_interner::{Interner, Sym};
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -749,7 +752,7 @@ impl<R> TokenParser<R> for GeneratorMethod
where where
R: ReadChar, R: ReadChar,
{ {
type Output = (ClassElementNameNode, FormalParameterList, Script); type Output = (ClassElementNameNode, FormalParameterList, FunctionBodyAst);
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let _timer = Profiler::global().start_event("GeneratorMethod", "Parsing"); let _timer = Profiler::global().start_event("GeneratorMethod", "Parsing");
@ -845,7 +848,7 @@ impl<R> TokenParser<R> for AsyncGeneratorMethod
where where
R: ReadChar, R: ReadChar,
{ {
type Output = (ClassElementNameNode, FormalParameterList, Script); type Output = (ClassElementNameNode, FormalParameterList, FunctionBodyAst);
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let _timer = Profiler::global().start_event("AsyncGeneratorMethod", "Parsing"); let _timer = Profiler::global().start_event("AsyncGeneratorMethod", "Parsing");
@ -955,7 +958,7 @@ impl<R> TokenParser<R> for AsyncMethod
where where
R: ReadChar, R: ReadChar,
{ {
type Output = (ClassElementNameNode, FormalParameterList, Script); type Output = (ClassElementNameNode, FormalParameterList, FunctionBodyAst);
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let _timer = Profiler::global().start_event("AsyncMethod", "Parsing"); let _timer = Profiler::global().start_event("AsyncMethod", "Parsing");

6
core/parser/src/parser/expression/tests.rs

@ -10,8 +10,8 @@ use boa_ast::{
}, },
Call, Identifier, Parenthesized, RegExpLiteral, Call, Identifier, Parenthesized, RegExpLiteral,
}, },
function::{AsyncArrowFunction, FormalParameter, FormalParameterList}, function::{AsyncArrowFunction, FormalParameter, FormalParameterList, FunctionBody},
Declaration, Expression, Script, Statement, Declaration, Expression, Statement,
}; };
use boa_interner::{Interner, Sym}; use boa_interner::{Interner, Sym};
use boa_macros::utf16; use boa_macros::utf16;
@ -701,7 +701,7 @@ fn parse_async_arrow_function_named_of() {
), ),
false, false,
)]), )]),
Script::default(), FunctionBody::default(),
))) )))
.into(), .into(),
], ],

8
core/parser/src/parser/function/mod.rs

@ -455,7 +455,7 @@ where
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let _timer = Profiler::global().start_event("FunctionStatementList", "Parsing"); let _timer = Profiler::global().start_event("FunctionStatementList", "Parsing");
let statement_list = StatementList::new( let body = StatementList::new(
self.allow_yield, self.allow_yield,
self.allow_await, self.allow_await,
true, true,
@ -465,20 +465,20 @@ where
) )
.parse(cursor, interner)?; .parse(cursor, interner)?;
if let Err(error) = check_labels(&statement_list) { if let Err(error) = check_labels(&body) {
return Err(Error::lex(LexError::Syntax( return Err(Error::lex(LexError::Syntax(
error.message(interner).into(), error.message(interner).into(),
Position::new(1, 1), Position::new(1, 1),
))); )));
} }
if contains_invalid_object_literal(&statement_list) { if contains_invalid_object_literal(&body) {
return Err(Error::lex(LexError::Syntax( return Err(Error::lex(LexError::Syntax(
"invalid object literal in function statement list".into(), "invalid object literal in function statement list".into(),
Position::new(1, 1), Position::new(1, 1),
))); )));
} }
Ok(ast::function::FunctionBody::new(statement_list)) Ok(body.into())
} }
} }

102
core/parser/src/parser/function/tests.rs

@ -32,12 +32,12 @@ fn check_basic() {
interner.get_or_intern_static("foo", utf16!("foo")).into(), interner.get_or_intern_static("foo", utf16!("foo")).into(),
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(),
)), )),
))] ))],
.into(), false,
), ),
)) ))
.into()], .into()],
@ -70,12 +70,12 @@ fn check_duplicates_strict_off() {
interner.get_or_intern_static("foo", utf16!("foo")).into(), interner.get_or_intern_static("foo", utf16!("foo")).into(),
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(),
)), )),
))] ))],
.into(), false,
), ),
)) ))
.into()], .into()],
@ -106,12 +106,12 @@ fn check_basic_semicolon_insertion() {
interner.get_or_intern_static("foo", utf16!("foo")).into(), interner.get_or_intern_static("foo", utf16!("foo")).into(),
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(),
)), )),
))] ))],
.into(), false,
), ),
)) ))
.into()], .into()],
@ -135,10 +135,10 @@ fn check_empty_return() {
interner.get_or_intern_static("foo", utf16!("foo")).into(), interner.get_or_intern_static("foo", utf16!("foo")).into(),
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(None), Return::new(None),
))] ))],
.into(), false,
), ),
)) ))
.into()], .into()],
@ -162,10 +162,10 @@ fn check_empty_return_semicolon_insertion() {
interner.get_or_intern_static("foo", utf16!("foo")).into(), interner.get_or_intern_static("foo", utf16!("foo")).into(),
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(None), Return::new(None),
))] ))],
.into(), false,
), ),
)) ))
.into()], .into()],
@ -286,7 +286,7 @@ fn check_arrow() {
None, None,
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Binary::new( Binary::new(
ArithmeticOp::Add.into(), ArithmeticOp::Add.into(),
@ -295,8 +295,8 @@ fn check_arrow() {
) )
.into(), .into(),
)), )),
))] ))],
.into(), false,
), ),
))) )))
.into()], .into()],
@ -324,7 +324,7 @@ fn check_arrow_semicolon_insertion() {
None, None,
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Binary::new( Binary::new(
ArithmeticOp::Add.into(), ArithmeticOp::Add.into(),
@ -333,8 +333,8 @@ fn check_arrow_semicolon_insertion() {
) )
.into(), .into(),
)), )),
))] ))],
.into(), false,
), ),
))) )))
.into()], .into()],
@ -362,10 +362,10 @@ fn check_arrow_epty_return() {
None, None,
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(None), Return::new(None),
))] ))],
.into(), false,
), ),
))) )))
.into()], .into()],
@ -393,10 +393,10 @@ fn check_arrow_empty_return_semicolon_insertion() {
None, None,
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(None), Return::new(None),
))] ))],
.into(), false,
), ),
))) )))
.into()], .into()],
@ -423,15 +423,15 @@ fn check_arrow_assignment() {
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new( Identifier::new(
interner.get_or_intern_static("a", utf16!("a")), interner.get_or_intern_static("a", utf16!("a")),
) )
.into(), .into(),
)), )),
))] ))],
.into(), false,
), ),
) )
.into(), .into(),
@ -464,15 +464,15 @@ fn check_arrow_assignment_nobrackets() {
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new( Identifier::new(
interner.get_or_intern_static("a", utf16!("a")), interner.get_or_intern_static("a", utf16!("a")),
) )
.into(), .into(),
)), )),
))] ))],
.into(), false,
), ),
) )
.into(), .into(),
@ -505,15 +505,15 @@ fn check_arrow_assignment_noparenthesis() {
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new( Identifier::new(
interner.get_or_intern_static("a", utf16!("a")), interner.get_or_intern_static("a", utf16!("a")),
) )
.into(), .into(),
)), )),
))] ))],
.into(), false,
), ),
) )
.into(), .into(),
@ -546,15 +546,15 @@ fn check_arrow_assignment_noparenthesis_nobrackets() {
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new( Identifier::new(
interner.get_or_intern_static("a", utf16!("a")), interner.get_or_intern_static("a", utf16!("a")),
) )
.into(), .into(),
)), )),
))] ))],
.into(), false,
), ),
) )
.into(), .into(),
@ -593,15 +593,15 @@ fn check_arrow_assignment_2arg() {
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new( Identifier::new(
interner.get_or_intern_static("a", utf16!("a")), interner.get_or_intern_static("a", utf16!("a")),
) )
.into(), .into(),
)), )),
))] ))],
.into(), false,
), ),
) )
.into(), .into(),
@ -640,15 +640,15 @@ fn check_arrow_assignment_2arg_nobrackets() {
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new( Identifier::new(
interner.get_or_intern_static("a", utf16!("a")), interner.get_or_intern_static("a", utf16!("a")),
) )
.into(), .into(),
)), )),
))] ))],
.into(), false,
), ),
) )
.into(), .into(),
@ -691,15 +691,15 @@ fn check_arrow_assignment_3arg() {
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new( Identifier::new(
interner.get_or_intern_static("a", utf16!("a")), interner.get_or_intern_static("a", utf16!("a")),
) )
.into(), .into(),
)), )),
))] ))],
.into(), false,
), ),
) )
.into(), .into(),
@ -742,15 +742,15 @@ fn check_arrow_assignment_3arg_nobrackets() {
Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), Some(interner.get_or_intern_static("foo", utf16!("foo")).into()),
params, params,
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some( Return::new(Some(
Identifier::new( Identifier::new(
interner.get_or_intern_static("a", utf16!("a")), interner.get_or_intern_static("a", utf16!("a")),
) )
.into(), .into(),
)), )),
))] ))],
.into(), false,
), ),
) )
.into(), .into(),

31
core/parser/src/parser/mod.rs

@ -26,6 +26,7 @@ use boa_ast::{
all_private_identifiers_valid, check_labels, contains, contains_invalid_object_literal, all_private_identifiers_valid, check_labels, contains, contains_invalid_object_literal,
lexically_declared_names, var_declared_names, ContainsSymbol, lexically_declared_names, var_declared_names, ContainsSymbol,
}, },
scope::Scope,
Position, StatementList, Position, StatementList,
}; };
use boa_interner::Interner; use boa_interner::Interner;
@ -139,9 +140,20 @@ impl<'a, R: ReadChar> Parser<'a, R> {
/// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed. /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-Script /// [spec]: https://tc39.es/ecma262/#prod-Script
pub fn parse_script(&mut self, interner: &mut Interner) -> ParseResult<boa_ast::Script> { pub fn parse_script(
&mut self,
scope: &Scope,
interner: &mut Interner,
) -> ParseResult<boa_ast::Script> {
self.cursor.set_goal(InputElement::HashbangOrRegExp); self.cursor.set_goal(InputElement::HashbangOrRegExp);
ScriptParser::new(false).parse(&mut self.cursor, interner) let mut ast = ScriptParser::new(false).parse(&mut self.cursor, interner)?;
if !ast.analyze_scope(scope, interner) {
return Err(Error::general(
"invalid scope analysis",
Position::new(1, 1),
));
}
Ok(ast)
} }
/// Parse the full input as an [ECMAScript Module][spec] into the boa AST representation. /// Parse the full input as an [ECMAScript Module][spec] into the boa AST representation.
@ -152,12 +164,23 @@ impl<'a, R: ReadChar> Parser<'a, R> {
/// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed. /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-Module /// [spec]: https://tc39.es/ecma262/#prod-Module
pub fn parse_module(&mut self, interner: &mut Interner) -> ParseResult<boa_ast::Module> pub fn parse_module(
&mut self,
scope: &Scope,
interner: &mut Interner,
) -> ParseResult<boa_ast::Module>
where where
R: ReadChar, R: ReadChar,
{ {
self.cursor.set_goal(InputElement::HashbangOrRegExp); self.cursor.set_goal(InputElement::HashbangOrRegExp);
ModuleParser.parse(&mut self.cursor, interner) let mut module = ModuleParser.parse(&mut self.cursor, interner)?;
if !module.analyze_scope(scope, interner) {
return Err(Error::general(
"invalid scope analysis",
Position::new(1, 1),
));
}
Ok(module)
} }
/// [`19.2.1.1 PerformEval ( x, strictCaller, direct )`][spec] /// [`19.2.1.1 PerformEval ( x, strictCaller, direct )`][spec]

12
core/parser/src/parser/statement/block/tests.rs

@ -82,10 +82,10 @@ fn non_empty() {
hello.into(), hello.into(),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(10).into())), Return::new(Some(Literal::from(10).into())),
))] ))],
.into(), false,
), ),
)) ))
.into(), .into(),
@ -139,10 +139,10 @@ fn hoisting() {
hello.into(), hello.into(),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![StatementListItem::Statement(Statement::Return( [StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(10).into())), Return::new(Some(Literal::from(10).into())),
))] ))],
.into(), false,
), ),
)) ))
.into(), .into(),

101
core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs

@ -26,8 +26,9 @@ use boa_ast::{
self as ast, self as ast,
expression::Identifier, expression::Identifier,
function::{ function::{
self, ClassDeclaration as ClassDeclarationNode, ClassElementName, ClassMethodDefinition, self, ClassDeclaration as ClassDeclarationNode, ClassElementName, ClassFieldDefinition,
FormalParameterList, FunctionExpression, ClassMethodDefinition, FormalParameterList, FunctionExpression, PrivateFieldDefinition,
StaticBlockBody,
}, },
operations::{contains, contains_arguments, ContainsSymbol}, operations::{contains, contains_arguments, ContainsSymbol},
Expression, Keyword, Punctuator, Expression, Keyword, Punctuator,
@ -414,8 +415,8 @@ where
} }
} }
} }
function::ClassElement::PrivateFieldDefinition(name, init) => { function::ClassElement::PrivateFieldDefinition(field) => {
if let Some(node) = init { if let Some(node) = field.field() {
if contains(node, ContainsSymbol::SuperCall) { if contains(node, ContainsSymbol::SuperCall) {
return Err(Error::lex(LexError::Syntax( return Err(Error::lex(LexError::Syntax(
"invalid super usage".into(), "invalid super usage".into(),
@ -424,7 +425,7 @@ where
} }
} }
if private_elements_names if private_elements_names
.insert(name.description(), PrivateElement::Value) .insert(field.name().description(), PrivateElement::Value)
.is_some() .is_some()
{ {
return Err(Error::general( return Err(Error::general(
@ -452,16 +453,18 @@ where
)); ));
} }
} }
function::ClassElement::FieldDefinition(_, Some(node)) function::ClassElement::FieldDefinition(field)
| function::ClassElement::StaticFieldDefinition(_, Some(node)) => { | function::ClassElement::StaticFieldDefinition(field) => {
if contains(node, ContainsSymbol::SuperCall) { if let Some(field) = field.field() {
return Err(Error::lex(LexError::Syntax( if contains(field, ContainsSymbol::SuperCall) {
"invalid super usage".into(), return Err(Error::lex(LexError::Syntax(
position, "invalid super usage".into(),
))); position,
)));
}
} }
} }
_ => {} function::ClassElement::StaticBlock(_) => {}
} }
elements.push(element); elements.push(element);
} }
@ -675,7 +678,7 @@ where
cursor.set_strict(strict); cursor.set_strict(strict);
statement_list statement_list
}; };
function::ClassElement::StaticBlock(function::FunctionBody::new(statement_list)) function::ClassElement::StaticBlock(StaticBlockBody::new(statement_list.into()))
} }
TokenKind::Punctuator(Punctuator::Mul) => { TokenKind::Punctuator(Punctuator::Mul) => {
let token = cursor.peek(1, interner).or_abrupt()?; let token = cursor.peek(1, interner).or_abrupt()?;
@ -937,16 +940,14 @@ where
} }
_ => { _ => {
cursor.expect_semicolon("expected semicolon", interner)?; cursor.expect_semicolon("expected semicolon", interner)?;
let field = ClassFieldDefinition::new(
ast::property::PropertyName::Literal(Sym::GET),
None,
);
if r#static { if r#static {
function::ClassElement::StaticFieldDefinition( function::ClassElement::StaticFieldDefinition(field)
ast::property::PropertyName::Literal(Sym::GET),
None,
)
} else { } else {
function::ClassElement::FieldDefinition( function::ClassElement::FieldDefinition(field)
ast::property::PropertyName::Literal(Sym::GET),
None,
)
} }
} }
} }
@ -1055,16 +1056,14 @@ where
} }
_ => { _ => {
cursor.expect_semicolon("expected semicolon", interner)?; cursor.expect_semicolon("expected semicolon", interner)?;
let field = ClassFieldDefinition::new(
ast::property::PropertyName::Literal(Sym::SET),
None,
);
if r#static { if r#static {
function::ClassElement::StaticFieldDefinition( function::ClassElement::StaticFieldDefinition(field)
ast::property::PropertyName::Literal(Sym::SET),
None,
)
} else { } else {
function::ClassElement::FieldDefinition( function::ClassElement::FieldDefinition(field)
ast::property::PropertyName::Literal(Sym::SET),
None,
)
} }
} }
} }
@ -1102,8 +1101,7 @@ where
) )
} else { } else {
function::ClassElement::PrivateFieldDefinition( function::ClassElement::PrivateFieldDefinition(
PrivateName::new(name), PrivateFieldDefinition::new(PrivateName::new(name), Some(rhs)),
Some(rhs),
) )
} }
} }
@ -1150,8 +1148,7 @@ where
) )
} else { } else {
function::ClassElement::PrivateFieldDefinition( function::ClassElement::PrivateFieldDefinition(
PrivateName::new(name), PrivateFieldDefinition::new(PrivateName::new(name), None),
None,
) )
} }
} }
@ -1195,10 +1192,11 @@ where
if let Some(name) = name.literal() { if let Some(name) = name.literal() {
rhs.set_anonymous_function_definition_name(&Identifier::new(name)); rhs.set_anonymous_function_definition_name(&Identifier::new(name));
} }
let field = ClassFieldDefinition::new(name, Some(rhs));
if r#static { if r#static {
function::ClassElement::StaticFieldDefinition(name, Some(rhs)) function::ClassElement::StaticFieldDefinition(field)
} else { } else {
function::ClassElement::FieldDefinition(name, Some(rhs)) function::ClassElement::FieldDefinition(field)
} }
} }
TokenKind::Punctuator(Punctuator::OpenParen) => { TokenKind::Punctuator(Punctuator::OpenParen) => {
@ -1259,10 +1257,11 @@ where
} }
} }
cursor.expect_semicolon("expected semicolon", interner)?; cursor.expect_semicolon("expected semicolon", interner)?;
let field = ClassFieldDefinition::new(name, None);
if r#static { if r#static {
function::ClassElement::StaticFieldDefinition(name, None) function::ClassElement::StaticFieldDefinition(field)
} else { } else {
function::ClassElement::FieldDefinition(name, None) function::ClassElement::FieldDefinition(field)
} }
} }
} }
@ -1273,10 +1272,28 @@ where
match &element { match &element {
// FieldDefinition : ClassElementName Initializer [opt] // FieldDefinition : ClassElementName Initializer [opt]
// It is a Syntax Error if Initializer is present and ContainsArguments of Initializer is true. // It is a Syntax Error if Initializer is present and ContainsArguments of Initializer is true.
function::ClassElement::FieldDefinition(_, Some(node)) function::ClassElement::FieldDefinition(field)
| function::ClassElement::StaticFieldDefinition(_, Some(node)) | function::ClassElement::StaticFieldDefinition(field) => {
| function::ClassElement::PrivateFieldDefinition(_, Some(node)) if let Some(field) = field.field() {
| function::ClassElement::PrivateStaticFieldDefinition(_, Some(node)) => { if contains_arguments(field) {
return Err(Error::general(
"'arguments' not allowed in class field definition",
position,
));
}
}
}
function::ClassElement::PrivateFieldDefinition(field) => {
if let Some(node) = field.field() {
if contains_arguments(node) {
return Err(Error::general(
"'arguments' not allowed in class field definition",
position,
));
}
}
}
function::ClassElement::PrivateStaticFieldDefinition(_, Some(node)) => {
if contains_arguments(node) { if contains_arguments(node) {
return Err(Error::general( return Err(Error::general(
"'arguments' not allowed in class field definition", "'arguments' not allowed in class field definition",

19
core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs

@ -7,11 +7,11 @@ use boa_ast::{
Call, Identifier, Call, Identifier,
}, },
function::{ function::{
ClassDeclaration, ClassElement, ClassMethodDefinition, FormalParameterList, FunctionBody, ClassDeclaration, ClassElement, ClassFieldDefinition, ClassMethodDefinition,
FunctionExpression, FormalParameterList, FunctionBody, FunctionExpression,
}, },
property::{MethodDefinitionKind, PropertyName}, property::{MethodDefinitionKind, PropertyName},
Declaration, Expression, Statement, StatementList, StatementListItem, Declaration, Expression, Statement, StatementListItem,
}; };
use boa_interner::Interner; use boa_interner::Interner;
use boa_macros::utf16; use boa_macros::utf16;
@ -50,10 +50,10 @@ fn check_async_ordinary_method() {
fn check_async_field_initialization() { fn check_async_field_initialization() {
let interner = &mut Interner::default(); let interner = &mut Interner::default();
let elements = vec![ClassElement::FieldDefinition( let elements = vec![ClassElement::FieldDefinition(ClassFieldDefinition::new(
PropertyName::Literal(interner.get_or_intern_static("async", utf16!("async"))), PropertyName::Literal(interner.get_or_intern_static("async", utf16!("async"))),
Some(Literal::from(1).into()), Some(Literal::from(1).into()),
)]; ))];
check_script_parser( check_script_parser(
"class A { "class A {
@ -76,10 +76,10 @@ fn check_async_field_initialization() {
fn check_async_field() { fn check_async_field() {
let interner = &mut Interner::default(); let interner = &mut Interner::default();
let elements = vec![ClassElement::FieldDefinition( let elements = vec![ClassElement::FieldDefinition(ClassFieldDefinition::new(
PropertyName::Literal(interner.get_or_intern_static("async", utf16!("async"))), PropertyName::Literal(interner.get_or_intern_static("async", utf16!("async"))),
None, None,
)]; ))];
check_script_parser( check_script_parser(
"class A { "class A {
@ -121,10 +121,7 @@ fn check_new_target_with_property_access() {
let constructor = FunctionExpression::new( let constructor = FunctionExpression::new(
Some(interner.get_or_intern_static("A", utf16!("A")).into()), Some(interner.get_or_intern_static("A", utf16!("A")).into()),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new(StatementList::new( FunctionBody::new([Statement::Expression(console).into()], false),
[Statement::Expression(console).into()],
false,
)),
false, false,
); );

61
core/parser/src/parser/statement/iteration/for_statement.rs

@ -262,16 +262,15 @@ where
(init, _) => init, (init, _) => init,
}; };
if let Some(ForLoopInitializer::Lexical(ast::declaration::LexicalDeclaration::Const( if let Some(ForLoopInitializer::Lexical(initializer)) = &init {
ref list, if let ast::declaration::LexicalDeclaration::Const(list) = initializer.declaration() {
))) = init for decl in list.as_ref() {
{ if decl.init().is_none() {
for decl in list.as_ref() { return Err(Error::general(
if decl.init().is_none() { "Expected initializer for const declaration",
return Err(Error::general( position,
"Expected initializer for const declaration", ));
position, }
));
} }
} }
} }
@ -313,9 +312,9 @@ where
// Early Error: It is a Syntax Error if any element of the BoundNames of // Early Error: It is a Syntax Error if any element of the BoundNames of
// LexicalDeclaration also occurs in the VarDeclaredNames of Statement. // LexicalDeclaration also occurs in the VarDeclaredNames of Statement.
// Note: only applies to lexical bindings. // Note: only applies to lexical bindings.
if let Some(ForLoopInitializer::Lexical(ref decl)) = init { if let Some(ForLoopInitializer::Lexical(initializer)) = &init {
let vars = var_declared_names(&body); let vars = var_declared_names(&body);
for name in bound_names(decl) { for name in bound_names(initializer.declaration()) {
if vars.contains(&name) { if vars.contains(&name) {
return Err(Error::general( return Err(Error::general(
"For loop initializer declared in loop body", "For loop initializer declared in loop body",
@ -381,30 +380,32 @@ fn initializer_to_iterable_loop_initializer(
))), ))),
} }
} }
ForLoopInitializer::Lexical(decl) => match decl.variable_list().as_ref() { ForLoopInitializer::Lexical(initializer) => {
[declaration] => { match initializer.declaration().variable_list().as_ref() {
if declaration.init().is_some() { [decl] => {
return Err(Error::lex(LexError::Syntax( if decl.init().is_some() {
return Err(Error::lex(LexError::Syntax(
format!("a lexical declaration in the head of a {loop_type} loop can't have an initializer") format!("a lexical declaration in the head of a {loop_type} loop can't have an initializer")
.into(), .into(),
position, position,
))); )));
}
Ok(match decl {
ast::declaration::LexicalDeclaration::Const(_) => {
IterableLoopInitializer::Const(declaration.binding().clone())
}
ast::declaration::LexicalDeclaration::Let(_) => {
IterableLoopInitializer::Let(declaration.binding().clone())
} }
}) Ok(match initializer.declaration() {
ast::declaration::LexicalDeclaration::Const(_) => {
IterableLoopInitializer::Const(decl.binding().clone())
}
ast::declaration::LexicalDeclaration::Let(_) => {
IterableLoopInitializer::Let(decl.binding().clone())
}
})
}
_ => Err(Error::lex(LexError::Syntax(
format!("only one variable can be declared in the head of a {loop_type} loop")
.into(),
position,
))),
} }
_ => Err(Error::lex(LexError::Syntax( }
format!("only one variable can be declared in the head of a {loop_type} loop")
.into(),
position,
))),
},
ForLoopInitializer::Var(decl) => match decl.0.as_ref() { ForLoopInitializer::Var(decl) => match decl.0.as_ref() {
[declaration] => { [declaration] => {
// https://tc39.es/ecma262/#sec-initializers-in-forin-statement-heads // https://tc39.es/ecma262/#sec-initializers-in-forin-statement-heads

3
core/parser/src/parser/tests/format/mod.rs

@ -17,6 +17,7 @@ fn test_formatting(source: &'static str) {
// Remove preceding newline. // Remove preceding newline.
use crate::{Parser, Source}; use crate::{Parser, Source};
use boa_ast::scope::Scope;
use boa_interner::{Interner, ToInternedString}; use boa_interner::{Interner, ToInternedString};
let source = &source[1..]; let source = &source[1..];
@ -33,7 +34,7 @@ fn test_formatting(source: &'static str) {
let source = Source::from_bytes(source); let source = Source::from_bytes(source);
let interner = &mut Interner::default(); let interner = &mut Interner::default();
let result = Parser::new(source) let result = Parser::new(source)
.parse_script(interner) .parse_script(&Scope::new_global(), interner)
.expect("parsing failed") .expect("parsing failed")
.to_interned_string(interner); .to_interned_string(interner);
if scenario != result { if scenario != result {

17
core/parser/src/parser/tests/mod.rs

@ -22,6 +22,7 @@ use boa_ast::{
ArrowFunction, FormalParameter, FormalParameterList, FormalParameterListFlags, ArrowFunction, FormalParameter, FormalParameterList, FormalParameterListFlags,
FunctionBody, FunctionDeclaration, FunctionBody, FunctionDeclaration,
}, },
scope::Scope,
statement::{If, Return}, statement::{If, Return},
Expression, Script, Statement, StatementList, StatementListItem, Expression, Script, Statement, StatementList, StatementListItem,
}; };
@ -34,11 +35,14 @@ pub(super) fn check_script_parser<L>(js: &str, expr: L, interner: &mut Interner)
where where
L: Into<Box<[StatementListItem]>>, L: Into<Box<[StatementListItem]>>,
{ {
let mut script = Script::new(StatementList::from(expr.into()));
let scope = Scope::new_global();
script.analyze_scope(&scope, interner);
assert_eq!( assert_eq!(
Parser::new(Source::from_bytes(js)) Parser::new(Source::from_bytes(js))
.parse_script(interner) .parse_script(&Scope::new_global(), interner)
.expect("failed to parse"), .expect("failed to parse"),
Script::new(StatementList::from(expr.into())) script,
); );
} }
@ -46,7 +50,7 @@ where
#[track_caller] #[track_caller]
pub(super) fn check_invalid_script(js: &str) { pub(super) fn check_invalid_script(js: &str) {
assert!(Parser::new(Source::from_bytes(js)) assert!(Parser::new(Source::from_bytes(js))
.parse_script(&mut Interner::default()) .parse_script(&Scope::new_global(), &mut Interner::default())
.is_err()); .is_err());
} }
@ -126,8 +130,8 @@ fn hoisting() {
hello.into(), hello.into(),
FormalParameterList::default(), FormalParameterList::default(),
FunctionBody::new( FunctionBody::new(
vec![Statement::Return(Return::new(Some(Literal::from(10).into()))).into()] [Statement::Return(Return::new(Some(Literal::from(10).into()))).into()],
.into(), false,
), ),
)) ))
.into(), .into(),
@ -508,7 +512,8 @@ fn spread_in_arrow_function() {
None, None,
params, params,
FunctionBody::new( FunctionBody::new(
vec![Statement::Expression(Expression::from(Identifier::from(b))).into()].into(), [Statement::Expression(Expression::from(Identifier::from(b))).into()],
false,
), ),
))) )))
.into()], .into()],

3
examples/src/bin/commuter_visitor.rs

@ -63,7 +63,8 @@ fn main() {
let mut parser = Parser::new(Source::from_filepath(Path::new("./scripts/calc.js")).unwrap()); let mut parser = Parser::new(Source::from_filepath(Path::new("./scripts/calc.js")).unwrap());
let mut ctx = Context::default(); let mut ctx = Context::default();
let mut script = parser.parse_script(ctx.interner_mut()).unwrap(); let scope = ctx.realm().scope().clone();
let mut script = parser.parse_script(&scope, ctx.interner_mut()).unwrap();
let mut visitor = CommutorVisitor::default(); let mut visitor = CommutorVisitor::default();

3
examples/src/bin/symbol_visitor.rs

@ -27,7 +27,8 @@ fn main() {
let mut parser = Parser::new(Source::from_filepath(Path::new("./scripts/calc.js")).unwrap()); let mut parser = Parser::new(Source::from_filepath(Path::new("./scripts/calc.js")).unwrap());
let mut ctx = Context::default(); let mut ctx = Context::default();
let script = parser.parse_script(ctx.interner_mut()).unwrap(); let scope = ctx.realm().scope().clone();
let script = parser.parse_script(&scope, ctx.interner_mut()).unwrap();
let mut visitor = SymbolVisitor::default(); let mut visitor = SymbolVisitor::default();

8
tests/fuzz/fuzz_targets/parser-idempotency.rs

@ -3,6 +3,7 @@
mod common; mod common;
use crate::common::FuzzData; use crate::common::FuzzData;
use boa_ast::scope::Scope;
use boa_interner::ToInternedString; use boa_interner::ToInternedString;
use boa_parser::{Parser, Source}; use boa_parser::{Parser, Source};
use libfuzzer_sys::{fuzz_target, Corpus}; use libfuzzer_sys::{fuzz_target, Corpus};
@ -15,11 +16,11 @@ fn do_fuzz(mut data: FuzzData) -> Result<(), Box<dyn Error>> {
let original = data.ast.to_interned_string(&data.interner); let original = data.ast.to_interned_string(&data.interner);
let mut parser = Parser::new(Source::from_reader(Cursor::new(&original), None)); let mut parser = Parser::new(Source::from_reader(Cursor::new(&original), None));
let scope = Scope::new_global();
let before = data.interner.len(); let before = data.interner.len();
// For a variety of reasons, we may not actually produce valid code here (e.g., nameless function). // For a variety of reasons, we may not actually produce valid code here (e.g., nameless function).
// Fail fast and only make the next checks if we were valid. // Fail fast and only make the next checks if we were valid.
if let Ok(first) = parser.parse_script(&mut data.interner) { if let Ok(first) = parser.parse_script(&scope, &mut data.interner) {
let after_first = data.interner.len(); let after_first = data.interner.len();
let first_interned = first.to_interned_string(&data.interner); let first_interned = first.to_interned_string(&data.interner);
@ -33,10 +34,11 @@ fn do_fuzz(mut data: FuzzData) -> Result<(), Box<dyn Error>> {
first first
); );
let mut parser = Parser::new(Source::from_reader(Cursor::new(&first_interned), None)); let mut parser = Parser::new(Source::from_reader(Cursor::new(&first_interned), None));
let second_scope = Scope::new_global();
// Now, we most assuredly should produce valid code. It has already gone through a first pass. // Now, we most assuredly should produce valid code. It has already gone through a first pass.
let second = parser let second = parser
.parse_script(&mut data.interner) .parse_script(&second_scope, &mut data.interner)
.expect("Could not parse the first-pass interned copy."); .expect("Could not parse the first-pass interned copy.");
let second_interned = second.to_interned_string(&data.interner); let second_interned = second.to_interned_string(&data.interner);
let after_second = data.interner.len(); let after_second = data.interner.len();

Loading…
Cancel
Save