use std::collections::{HashMap, HashSet};
mod calls;
mod classes;
mod declarations;
mod expressions;
mod functions;
mod identifiers;
mod modules;
mod promoter;
mod statements;
mod unresolver;
use hermes_ast::context::{GCLock, NodeRc};
use hermes_ast::node::Node;
use hermes_ast::node_child::{NodeLabel, Strictness};
use hermes_ast::visitor::{Path, TransformResult, Visitor, VisitorMut};
use hermes_ast::SemaId;
use hermes_support::diag::{Subsystem, Warning};
use hermes_support::manager::SourceErrorManager;
use hermes_support::persistent_scoped_map::Scope;
use crate::decl_collector::DeclCollector;
use classes::ClassContext;
use promoter::get_promoted_scoped_func_decls;
use crate::ids::{DeclId, FunctionInfoId, ScopeId};
use crate::keywords::Keywords;
use crate::sem_context::{
Atom, Binding, BindingTable, BindingTableScopePtr, ConstructorKind,
CustomDirectives, DeclKind, SemContext, SourceVisibility,
};
const AST_MAX_RECURSION_DEPTH: u32 =
if cfg!(debug_assertions) { 512 } else { 1024 };
const DEBUG_INFO_SETTING_ALL: bool = false;
#[derive(Debug, Clone)]
pub(crate) struct Label {
pub declaration_node: NodeRc,
pub target_statement: NodeRc,
}
pub(crate) struct FunctionContext {
pub sem_info: FunctionInfoId,
pub node: Option<NodeRc>,
pub label_map: HashMap<NodeLabel, Label>,
pub current_loop: Option<NodeRc>,
pub current_loop_or_switch: Option<NodeRc>,
pub is_formal_params: bool,
pub decls: Option<DeclCollector>,
pub promoted_func_decls: HashMap<Atom, DeclId>,
pub binding_table_scope_depth: u32,
}
#[derive(Debug, Clone, Copy, Default)]
struct FoundDirectives<'ast> {
use_strict_node: Option<&'ast Node<'ast>>,
source_visibility: SourceVisibility,
always_inline: bool,
no_inline: bool,
builtin: bool,
}
#[must_use = "every enter_scope must be paired with exit_scope"]
pub(crate) struct ScopeState {
old_scope: Option<ScopeId>,
}
#[must_use = "every enter_function must be paired with exit_function"]
pub(crate) struct FunctionState {
was_global_function_context: bool,
}
pub struct SemanticResolver<'bt, 'sc, 'sm, 'ad> {
sem_ctx: &'sc mut SemContext,
sm: &'sm mut SourceErrorManager,
binding_table: &'bt BindingTable,
ambient_decls: &'ad [NodeRc],
restricted_global_properties: HashSet<Atom>,
compile: bool,
cur_scope: Option<ScopeId>,
global_scope: BindingTableScopePtr,
function_stack: Vec<FunctionContext>,
global_function_context: Option<usize>,
class_stack: Vec<ClassContext>,
binding_scopes: Vec<Scope<'bt, Atom, Binding>>,
recursion_depth: u32,
can_reference_super: bool,
forbid_await_as_identifier: bool,
forbid_await_expression: bool,
forbid_special_arguments_reference: bool,
forbid_arguments_as_identifier: bool,
}
impl<'bt, 'sc, 'sm, 'ad> SemanticResolver<'bt, 'sc, 'sm, 'ad> {
pub fn new(
binding_table: &'bt BindingTable,
sem_ctx: &'sc mut SemContext,
sm: &'sm mut SourceErrorManager,
ambient_decls: &'ad [NodeRc],
compile: bool,
) -> SemanticResolver<'bt, 'sc, 'sm, 'ad> {
let mut restricted_global_properties = HashSet::new();
restricted_global_properties.insert(sem_ctx.kw.ident_na_n);
restricted_global_properties.insert(sem_ctx.kw.ident_undefined);
restricted_global_properties.insert(sem_ctx.kw.ident_infinity);
sm.enable_buffering();
SemanticResolver {
sem_ctx,
sm,
binding_table,
ambient_decls,
restricted_global_properties,
compile,
cur_scope: None,
global_scope: BindingTableScopePtr::default(),
function_stack: Vec::new(),
global_function_context: None,
class_stack: Vec::new(),
binding_scopes: Vec::new(),
recursion_depth: AST_MAX_RECURSION_DEPTH,
can_reference_super: false,
forbid_await_as_identifier: false,
forbid_await_expression: false,
forbid_special_arguments_reference: false,
forbid_arguments_as_identifier: false,
}
}
pub fn run<'gc>(
&mut self,
gc: &'gc GCLock,
root: &'gc Node<'gc>,
) -> Option<&'gc Node<'gc>> {
if self.sm.error_count() != 0 {
return None;
}
let new_root = root.visit_mut(gc, self, None);
if self.sm.error_count() != 0 {
return None;
}
Some(new_root.expect("the resolver never removes the root"))
}
pub fn run_always<'gc>(
&mut self,
gc: &'gc GCLock,
root: &'gc Node<'gc>,
) -> &'gc Node<'gc> {
if self.sm.error_count() != 0 {
return root;
}
root.visit_mut(gc, self, None)
.expect("the resolver never removes the root")
}
pub fn compile(&self) -> bool {
self.compile
}
pub fn is_restricted_global_property(&self, name: Atom) -> bool {
self.restricted_global_properties.contains(&name)
}
pub fn in_global_scope_context(&self) -> bool {
match self.global_function_context {
Some(idx) => idx + 1 == self.function_stack.len(),
None => false,
}
}
fn kw(&self) -> &Keywords {
&self.sem_ctx.kw
}
fn function_context(&self) -> &FunctionContext {
self.function_stack
.last()
.expect("no active function context")
}
fn function_context_mut(&mut self) -> &mut FunctionContext {
self.function_stack
.last_mut()
.expect("no active function context")
}
fn cur_function_info(&self) -> FunctionInfoId {
self.function_context().sem_info
}
#[allow(clippy::too_many_arguments)]
fn enter_function<'ast>(
&mut self,
gc: &'ast GCLock,
node: &'ast Node<'ast>,
parent_sem_info: Option<FunctionInfoId>,
strict: bool,
cons_kind: ConstructorKind,
custom_directives: CustomDirectives,
install_as_global_context: bool,
) -> FunctionState {
let sem_info = self.sem_ctx.new_function(
SemContext::node_is_arrow(Some(node)),
cons_kind,
parent_sem_info,
self.cur_scope,
strict,
custom_directives,
);
let mut depth_exceeded_at: Option<&'ast Node<'ast>> = None;
let decls = DeclCollector::run(
node,
gc,
&self.sem_ctx.kw,
self.recursion_depth,
&mut |n| depth_exceeded_at = Some(n),
);
if let Some(n) = depth_exceeded_at {
self.recursion_depth = 0;
self.recursion_depth_exceeded(n);
}
self.function_stack.push(FunctionContext {
sem_info,
node: Some(NodeRc::from_node(gc, node)),
label_map: HashMap::new(),
current_loop: None,
current_loop_or_switch: None,
is_formal_params: false,
decls: Some(decls),
promoted_func_decls: HashMap::new(),
binding_table_scope_depth: 0,
});
if install_as_global_context {
self.global_function_context = Some(self.function_stack.len() - 1);
}
set_node_sem_info(node, sem_info);
FunctionState {
was_global_function_context: install_as_global_context,
}
}
fn enter_function_with_info(
&mut self,
sem_info: FunctionInfoId,
) -> FunctionState {
self.function_stack.push(FunctionContext {
sem_info,
node: None,
label_map: HashMap::new(),
current_loop: None,
current_loop_or_switch: None,
is_formal_params: false,
decls: None,
promoted_func_decls: HashMap::new(),
binding_table_scope_depth: 0,
});
FunctionState {
was_global_function_context: false,
}
}
fn enter_function_static_block<'ast>(
&mut self,
gc: &'ast GCLock,
node: &'ast Node<'ast>,
sem_info: FunctionInfoId,
) -> FunctionState {
let mut depth_exceeded_at: Option<&'ast Node<'ast>> = None;
let decls = DeclCollector::run(
node,
gc,
&self.sem_ctx.kw,
self.recursion_depth,
&mut |n| depth_exceeded_at = Some(n),
);
if let Some(n) = depth_exceeded_at {
self.recursion_depth = 0;
self.recursion_depth_exceeded(n);
}
self.function_stack.push(FunctionContext {
sem_info,
node: None,
label_map: HashMap::new(),
current_loop: None,
current_loop_or_switch: None,
is_formal_params: false,
decls: Some(decls),
promoted_func_decls: HashMap::new(),
binding_table_scope_depth: 0,
});
FunctionState {
was_global_function_context: false,
}
}
fn exit_function(&mut self, state: FunctionState) {
self.function_stack
.pop()
.expect("no active function context");
if state.was_global_function_context {
self.global_function_context = None;
}
}
fn recursion_depth_exceeded(&mut self, node: &Node) {
self.sm.error(
node.range().end,
"Too many nested expressions/statements/declarations",
);
}
fn inc_recursion_depth(&mut self, node: &Node) -> bool {
if self.recursion_depth == 0 {
return false;
}
self.recursion_depth -= 1;
if self.recursion_depth == 0 {
self.recursion_depth_exceeded(node);
return false;
}
true
}
fn dec_recursion_depth(&mut self) {
if self.recursion_depth != 0 {
self.recursion_depth += 1;
}
}
fn enter_scope(
&mut self,
scope_node: Option<&Node>,
is_function_body_scope: bool,
) -> ScopeState {
let old_scope = self.cur_scope;
let binding_table = self.binding_table;
self.binding_scopes.push(Scope::new(binding_table));
let scope = self
.sem_ctx
.new_scope(self.cur_function_info(), self.cur_scope);
self.cur_scope = Some(scope);
if let Some(scope_node) = scope_node {
set_node_scope(scope_node, scope);
}
if DEBUG_INFO_SETTING_ALL {
let ptr = self.binding_table.current_scope();
self.sem_ctx.scope_mut(scope).binding_table_scope = ptr;
}
if is_function_body_scope {
let func = self.cur_function_info();
let idx = self.sem_ctx.function(func).get_scopes().len() as u32 - 1;
self.sem_ctx.function_mut(func).function_body_scope_idx = idx;
let depth = self.cur_binding_scope().depth();
self.function_context_mut().binding_table_scope_depth = depth;
}
ScopeState { old_scope }
}
fn exit_scope(&mut self, state: ScopeState) {
self.binding_scopes.pop().expect("no open binding scope");
self.cur_scope = state.old_scope;
}
fn cur_binding_scope(&self) -> &Scope<'bt, Atom, Binding> {
self.binding_scopes.last().expect("no open binding scope")
}
fn visit_node<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
path: Option<Path<'gc>>,
) -> TransformResult<&'gc Node<'gc>> {
match node {
Node::Program(_) => self.visit_program(gc, node),
Node::Identifier(_) => self.visit_identifier(gc, node, path),
Node::VariableDeclaration(_) => {
self.visit_variable_declaration(gc, node)
}
Node::BlockStatement(_) => {
self.visit_block_statement(gc, node, path)
}
Node::BinaryExpression(_) => self.visit_binary_expression(gc, node),
Node::AssignmentExpression(_) => {
self.visit_assignment_expression(gc, node)
}
Node::UpdateExpression(_) => self.visit_update_expression(gc, node),
Node::UnaryExpression(_) => self.visit_unary_expression(gc, node),
Node::FunctionDeclaration(_) => {
self.visit_function_declaration(gc, node, path)
}
Node::FunctionExpression(_) => {
self.visit_function_expression(gc, node, path)
}
Node::ArrowFunctionExpression(_) => {
self.visit_arrow_function_expression(gc, node, path)
}
Node::ReturnStatement(_) => self.visit_return_statement(gc, node),
Node::SwitchStatement(_) => self.visit_switch_statement(gc, node),
Node::ForInStatement(_) | Node::ForOfStatement(_) => {
self.visit_for_in_of(gc, node)
}
Node::ForStatement(_) => self.visit_for_statement(gc, node),
Node::WhileStatement(_) | Node::DoWhileStatement(_) => {
self.visit_while_like(gc, node)
}
Node::LabeledStatement(_) => {
self.visit_labeled_statement(gc, node)
}
Node::BreakStatement(_) => self.visit_break_statement(gc, node),
Node::ContinueStatement(_) => {
self.visit_continue_statement(gc, node)
}
Node::YieldExpression(_) => self.visit_yield_expression(gc, node),
Node::AwaitExpression(_) => self.visit_await_expression(gc, node),
Node::SpreadElement(_) => {
self.visit_spread_element(gc, node, path)
}
Node::MetaProperty(_) => self.visit_meta_property(gc, node),
Node::CoverEmptyArgs(_)
| Node::CoverTrailingComma(_)
| Node::CoverInitializer(_)
| Node::CoverRestElement(_)
| Node::CoverTypedIdentifier(_) => self.visit_cover_node(node),
Node::TypeCastExpression(_) => {
self.visit_type_cast_expression(gc, node)
}
Node::AsExpression(_) => self.visit_as_expression(gc, node),
Node::MatchStatement(_) => self.visit_match_statement(gc, node),
Node::MatchExpression(_) => self.visit_match_expression(gc, node),
Node::WithStatement(_) => self.visit_with_statement(gc, node),
Node::TryStatement(_) => self.visit_try_statement(gc, node),
Node::CatchClause(_) => self.visit_catch_clause(gc, node),
Node::RegExpLiteral(_) => self.visit_regexp_literal(gc, node),
Node::ClassDeclaration(_) => {
self.visit_class_declaration(gc, node)
}
Node::ClassExpression(_) => self.visit_class_expression(gc, node),
Node::ClassProperty(_) => self.visit_class_property(gc, node),
Node::MethodDefinition(_) => {
self.visit_method_definition(gc, node)
}
Node::Super(_) => self.visit_super(path),
Node::PrivateName(_) => self.visit_private_name(gc, node),
Node::ClassPrivateProperty(_) => {
self.visit_class_private_property(gc, node)
}
Node::StaticBlock(_) => self.visit_static_block(gc, node),
Node::MemberExpression(_) | Node::OptionalMemberExpression(_) => {
self.visit_member_like_expression(gc, node, path)
}
Node::CallExpression(_) => self.visit_call_expression(gc, node),
Node::ExpressionStatement(_)
| Node::ImportSpecifier(_)
| Node::ImportDefaultSpecifier(_)
| Node::ImportNamespaceSpecifier(_)
| Node::ImportAttribute(_)
| Node::ExportSpecifier(_)
| Node::ExportNamespaceSpecifier(_)
| Node::DebuggerStatement(_)
| Node::BigIntLiteral(_)
| Node::TaggedTemplateExpression(_)
| Node::ImportExpression(_)
| Node::ClassBody(_)
| Node::ThisExpression(_)
| Node::ThrowStatement(_)
| Node::EmptyStatement(_)
| Node::NumericLiteral(_)
| Node::StringLiteral(_)
| Node::BooleanLiteral(_)
| Node::NullLiteral(_)
| Node::Property(_)
| Node::ObjectExpression(_)
| Node::VariableDeclarator(_)
| Node::RestElement(_)
| Node::AssignmentPattern(_)
| Node::ArrayExpression(_)
| Node::ConditionalExpression(_)
| Node::LogicalExpression(_)
| Node::SequenceExpression(_)
| Node::TemplateLiteral(_)
| Node::TemplateElement(_)
| Node::SwitchCase(_)
| Node::NewExpression(_)
| Node::OptionalCallExpression(_)
| Node::SHBuiltin(_)
| Node::IfStatement(_)
| Node::Empty(_) => node.visit_children_mut(gc, self),
Node::ObjectPattern(_) => self.visit_object_pattern(gc, node),
Node::ArrayPattern(_) => self.visit_array_pattern(gc, node),
Node::TypeAlias(_)
| Node::TypeParameterDeclaration(_)
| Node::TypeParameterInstantiation(_) => TransformResult::Unchanged,
Node::ImportDeclaration(_) => {
self.visit_import_declaration(gc, node)
}
Node::ExportNamedDeclaration(_) => {
self.visit_export_named_declaration(gc, node)
}
Node::ExportDefaultDeclaration(_) => {
self.visit_export_default_declaration(gc, node)
}
Node::ExportAllDeclaration(_) => {
self.visit_export_all_declaration(gc, node)
}
n if n.is_flow() => node.visit_children_mut(gc, self),
n if n.is_match_pattern() => node.visit_children_mut(gc, self),
Node::MatchStatementCase(_)
| Node::MatchExpressionCase(_)
| Node::MatchObjectPatternProperty(_)
| Node::MatchInstanceObjectPattern(_)
| Node::MatchRestPattern(_) => node.visit_children_mut(gc, self),
_ => panic!(
"sema: unhandled node kind {} (S3+/dialect phases)",
node.node_type_str()
),
}
}
fn visit_program<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let program = match node {
Node::Program(p) => p,
_ => unreachable!("visit_program called on a non-Program node"),
};
let ctx_strict_mode = gc.ctx().strict_mode();
let func_state = self.enter_function(
gc,
node,
None,
ctx_strict_mode,
ConstructorKind::None,
CustomDirectives {
source_visibility: SourceVisibility::Default,
always_inline: false,
..Default::default()
},
true,
);
let directives = self.scan_directives(program.body.iter());
if directives.use_strict_node.is_some() {
let f = self.cur_function_info();
self.sem_ctx.function_mut(f).strict = true;
}
let f = self.cur_function_info();
program
.strictness
.set(make_strictness(self.sem_ctx.function(f).strict));
if directives.source_visibility
> self.sem_ctx.function(f).custom_directives.source_visibility
{
self.sem_ctx
.function_mut(f)
.custom_directives
.source_visibility = directives.source_visibility;
}
self.sem_ctx.function_mut(f).is_program_node = true;
let result = {
let scope_state =
self.enter_scope(Some(node), true);
self.global_scope = self.cur_binding_scope().ptr();
self.sem_ctx
.set_binding_table_global_scope(self.global_scope.clone());
if DEBUG_INFO_SETTING_ALL {
let f = self.cur_function_info();
self.sem_ctx.function_mut(f).binding_table_scope =
self.global_scope.clone();
}
self.process_collected_declarations(gc, node);
if !self.sem_ctx.function(self.cur_function_info()).strict {
let promoted =
get_promoted_scoped_func_decls(self, gc, node);
self.process_promoted_func_decls(gc, &promoted);
}
self.process_ambient_decls(gc);
let result = node.visit_children_mut(gc, self);
self.exit_scope(scope_state);
result
};
self.exit_function(func_state);
result
}
fn process_collected_declarations(
&mut self,
gc: &GCLock,
scope_node: &Node,
) {
let decls: Option<Vec<NodeRc>> = self
.function_context()
.decls
.as_ref()
.expect("FunctionContext without a DeclCollector")
.scope_decls_for_node(scope_node.node_id())
.cloned();
if let Some(decls) = decls {
self.process_declarations(gc, &decls);
}
}
fn process_promoted_func_decls(
&mut self,
gc: &GCLock,
promoted_func_decls: &[NodeRc],
) {
let kind = if self.in_global_scope_context() {
DeclKind::GlobalProperty
} else {
DeclKind::Var
};
for func_decl_rc in promoted_func_decls {
let func_decl_node = func_decl_rc.node(gc);
let func_decl = match func_decl_node {
Node::FunctionDeclaration(fd) => fd,
_ => panic!(
"cast<FunctionDeclarationNode> failed: promoted decl is \
a {}",
func_decl_node.node_type_str()
),
};
let ident_node = func_decl.id.expect(
"cast<IdentifierNode>(funcDecl->_id) on a nameless promoted \
function declaration",
);
self.validate_and_declare_identifier(gc, kind, ident_node);
let identifier = ident_node
.as_identifier()
.expect("a promoted function's id is an Identifier");
let decl = self
.sem_ctx
.get_declaration_decl(identifier)
.expect("a promoted function declaration always gets a decl");
self.function_context_mut()
.promoted_func_decls
.entry(identifier.name.get())
.or_insert(decl);
}
}
fn scan_directives<'ast, I>(&mut self, body: I) -> FoundDirectives<'ast>
where
I: IntoIterator<Item = &'ast Node<'ast>>,
{
let kw_use_strict = self.kw().ident_use_strict;
let kw_show_source = self.kw().ident_show_source;
let kw_hide_source = self.kw().ident_hide_source;
let kw_sensitive = self.kw().ident_sensitive;
let kw_inline = self.kw().ident_inline;
let kw_no_inline = self.kw().ident_no_inline;
let kw_builtin = self.kw().ident_builtin;
let mut directives = FoundDirectives::default();
for node in body {
let expr_st = match node {
Node::ExpressionStatement(e) => e,
_ => break,
};
let directive = expr_st.directive.get();
if directive == hermes_atom_table::INVALID_ATOM_BYTES {
break;
}
if directive == kw_use_strict {
directives.use_strict_node.get_or_insert(node);
} else if directive == kw_show_source
&& SourceVisibility::ShowSource > directives.source_visibility
{
directives.source_visibility = SourceVisibility::ShowSource;
} else if directive == kw_hide_source
&& SourceVisibility::HideSource > directives.source_visibility
{
directives.source_visibility = SourceVisibility::HideSource;
} else if directive == kw_sensitive
&& SourceVisibility::Sensitive > directives.source_visibility
{
directives.source_visibility = SourceVisibility::Sensitive;
}
if directive == kw_inline {
if directives.no_inline {
self.sm.warning_range(
Warning::Misc,
node.range(),
"Should not declare both 'inline' and 'noinline'.",
Subsystem::Unspecified,
);
directives.no_inline = false;
}
directives.always_inline = true;
}
if directive == kw_no_inline {
if directives.always_inline {
self.sm.warning_range(
Warning::Misc,
node.range(),
"Should not declare both 'inline' and 'noinline'.",
Subsystem::Unspecified,
);
directives.always_inline = false;
}
directives.no_inline = true;
}
if directive == kw_builtin {
directives.builtin = true;
}
}
directives
}
fn process_ambient_decls(&mut self, gc: &GCLock) {
assert!(
!self.global_scope.is_null(),
"global scope must be created when declaring ambient globals"
);
let ambient_decls = self.ambient_decls;
if ambient_decls.is_empty() {
return;
}
for program_node in ambient_decls {
let mut dh = DeclHoisting::default();
dh.visit_node(program_node.node(gc));
for vd in &dh.decls {
self.declare_ambient_global(*vd);
}
for fd in &dh.closures {
self.declare_ambient_global(*fd);
}
}
}
fn declare_ambient_global(&mut self, name: Atom) {
if self.binding_table.count(&name) == 0 {
let decl = self
.sem_ctx
.new_global(name, DeclKind::UndeclaredGlobalProperty);
self.binding_table.try_emplace_into_scope(
&self.global_scope,
name,
Binding::new(decl, None),
);
}
}
}
impl<'gc> VisitorMut<'gc> for SemanticResolver<'_, '_, '_, '_> {
fn call(
&mut self,
gc: &'gc GCLock<'_, '_>,
node: &'gc Node<'gc>,
path: Option<Path<'gc>>,
) -> TransformResult<&'gc Node<'gc>> {
if !self.inc_recursion_depth(node) {
return TransformResult::Unchanged;
}
let result = self.visit_node(gc, node, path);
self.dec_recursion_depth();
result
}
}
impl Drop for SemanticResolver<'_, '_, '_, '_> {
fn drop(&mut self) {
while self.binding_scopes.pop().is_some() {}
self.sm.disable_buffering();
}
}
#[derive(Default)]
struct DeclHoisting {
decls: Vec<Atom>,
closures: Vec<Atom>,
}
impl DeclHoisting {
fn collect_decls(&mut self, node: &Node) {
match node {
Node::VariableDeclarator(vd) => {
self.decls.push(identifier_name(vd.id));
}
Node::FunctionDeclaration(fd) => {
let id = fd
.id
.expect("ambient FunctionDeclaration must have a name");
self.closures.push(identifier_name(id));
}
_ => {}
}
}
}
impl<'gc> Visitor<'gc> for DeclHoisting {
fn visit_node(&mut self, node: &'gc Node<'gc>) {
self.collect_decls(node);
if matches!(
node,
Node::FunctionDeclaration(_)
| Node::FunctionExpression(_)
| Node::ArrowFunctionExpression(_)
) {
return;
}
node.visit_children(self);
}
}
fn identifier_name(node: &Node) -> Atom {
match node {
Node::Identifier(id) => id.name.get(),
_ => panic!(
"ambient declaration name is a {}, not an Identifier",
node.node_type_str()
),
}
}
fn make_strictness(strict: bool) -> Strictness {
if strict {
Strictness::StrictMode
} else {
Strictness::NonStrictMode
}
}
fn set_node_scope(node: &Node, scope: ScopeId) {
let id = Some(scope.sema_id());
match node {
Node::Program(n) => n.scope.set(id),
Node::FunctionExpression(n) => n.scope.set(id),
Node::ArrowFunctionExpression(n) => n.scope.set(id),
Node::FunctionDeclaration(n) => n.scope.set(id),
Node::ComponentDeclaration(n) => n.scope.set(id),
Node::HookDeclaration(n) => n.scope.set(id),
Node::ForInStatement(n) => n.scope.set(id),
Node::ForOfStatement(n) => n.scope.set(id),
Node::ForStatement(n) => n.scope.set(id),
Node::BlockStatement(n) => n.scope.set(id),
Node::StaticBlock(n) => n.scope.set(id),
Node::SwitchStatement(n) => n.scope.set(id),
Node::CatchClause(n) => n.scope.set(id),
Node::ClassDeclaration(n) => n.scope.set(id),
Node::ClassExpression(n) => n.scope.set(id),
_ => {
panic!("{} does not carry a scope decoration", node.node_type_str())
}
}
}
fn set_node_sem_info(node: &Node, sem_info: FunctionInfoId) {
let id: Option<SemaId> = Some(sem_info.sema_id());
match node {
Node::Program(n) => n.sem_info.set(id),
Node::FunctionExpression(n) => n.sem_info.set(id),
Node::ArrowFunctionExpression(n) => n.sem_info.set(id),
Node::FunctionDeclaration(n) => n.sem_info.set(id),
Node::ComponentDeclaration(n) => n.sem_info.set(id),
Node::HookDeclaration(n) => n.sem_info.set(id),
_ => panic!("{} is not a function-like node", node.node_type_str()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use hermes_ast::context::Context;
use hermes_ast::node::EmptyStatement;
use hermes_ast::node_child::NodeMetadata;
use hermes_support::location::{SMLoc, SMRange};
#[test]
fn recursion_depth_tracker_trips_once_at_the_nesting_limit() {
let mut ctx = Context::new();
let gc = ctx.lock();
let mut sem_ctx = SemContext::new(Keywords::new(&gc));
let mut sm = SourceErrorManager::new();
let buf = sm.add_buffer_bytes("depth.js", b"x");
let loc = SMLoc {
source: buf,
offset: 0,
};
let node = gc.alloc(Node::EmptyStatement(EmptyStatement::new(
NodeMetadata::new(SMRange {
start: loc,
end: loc,
}),
)));
{
let binding_table = sem_ctx.binding_table_rc();
let mut resolver = SemanticResolver::new(
&binding_table,
&mut sem_ctx,
&mut sm,
&[],
true,
);
for level in 1..AST_MAX_RECURSION_DEPTH {
assert!(
resolver.inc_recursion_depth(node),
"nesting level {level} must be allowed"
);
}
assert!(
!resolver.inc_recursion_depth(node),
"level {AST_MAX_RECURSION_DEPTH} must trip the limit"
);
assert!(!resolver.inc_recursion_depth(node));
resolver.dec_recursion_depth();
assert!(
!resolver.inc_recursion_depth(node),
"a spent budget must stay spent"
);
}
assert_eq!(sm.error_count(), 1);
}
#[test]
fn flow_range_size_is_97() {
use hermes_ast::node::NodeKind;
let count =
NodeKind::_Flow_Last as u32 - NodeKind::_Flow_First as u32 - 1;
assert_eq!(count, 97);
}
}