use std::collections::HashSet;
use hermes_ast::context::{GCLock, NodeRc};
use hermes_ast::node::Node;
use hermes_ast::visitor::Visitor;
use hermes_ast::NodeId;
use hermes_support::manager::SourceErrorManager;
use hermes_support::persistent_scoped_map::{PersistentScopedMap, Scope};
use crate::decl_collector::{DeclCollector, ScopeDecls};
use crate::ids::FunctionInfoId;
use crate::sem_context::{Atom, DeclKind, SemContext};
use super::declarations::extract_declared_idents_from_id;
use super::functions::function_like_body;
use super::SemanticResolver;
type PromoterBindingTable = PersistentScopedMap<Atom, bool>;
struct ScopedFunctionPromoter<'ast, 'g_ast, 'g_ctx, 'd, 'sc, 'sm, 'tb> {
gc: &'ast GCLock<'g_ast, 'g_ctx>,
decls: &'d DeclCollector,
sem_ctx: &'sc SemContext,
sm: &'sm mut SourceErrorManager,
promoted_func_decls: Vec<NodeRc>,
func_names: HashSet<Atom>,
func_decls: HashSet<NodeId>,
binding_table: &'tb PromoterBindingTable,
}
impl<'ast, 'd, 'sc, 'sm, 'tb>
ScopedFunctionPromoter<'ast, '_, '_, 'd, 'sc, 'sm, 'tb>
{
fn run(
&mut self,
func_node: &'ast Node<'ast>,
func_sem_info: FunctionInfoId,
) {
let binding_scope = Scope::new(self.binding_table);
let decls = self.decls.scoped_func_decls();
for node in decls {
let node = node.node(self.gc);
let func_decl = match node {
Node::FunctionDeclaration(fd) => fd,
_ => panic!(
"cast<FunctionDeclarationNode> failed: scoped func decl \
is a {}",
node.node_type_str()
),
};
let id = func_decl
.id
.expect("cast<IdentifierNode>(funcDecl->_id) on a nameless \
scoped function declaration");
self.func_names.insert(identifier_name(id));
self.func_decls.insert(node.node_id());
}
self.process_parameters(func_sem_info);
self.process_declarations(func_node);
if matches!(func_node, Node::Program(_)) {
func_node.visit_children(self);
} else {
let body = function_like_body(func_node);
debug_assert!(
matches!(body, Node::BlockStatement(_)),
"getBlockStatement: expression-bodied function"
);
body.visit_children(self);
}
drop(binding_scope);
}
fn visit_scope(&mut self, node: &'ast Node<'ast>) {
let binding_scope = Scope::new(self.binding_table);
self.process_declarations(node);
node.visit_children(self);
drop(binding_scope);
}
fn process_parameters(&self, func_sem_info: FunctionInfoId) {
let sem_ctx = self.sem_ctx;
let param_scope = sem_ctx.function(func_sem_info).get_parameter_scope();
for &decl_id in &sem_ctx.scope(param_scope).decls {
let decl = sem_ctx.decl(decl_id);
if decl.kind == DeclKind::Parameter {
let name = decl.name;
if self.func_names.contains(&name) {
self.binding_table.try_emplace(name, true);
}
}
}
}
fn process_declarations(&mut self, scope: &Node) {
let collector = self.decls;
let decls: &ScopeDecls =
match collector.scope_decls_for_node(scope.node_id()) {
Some(d) => d,
None => return,
};
let mut idents: Vec<&Node> = Vec::new();
let mut found_decls: Vec<&NodeRc> = Vec::new();
for node_ref in decls {
let node = node_ref.node(self.gc);
if matches!(
node,
Node::TypeAlias(_) | Node::TSTypeAliasDeclaration(_)
) {
continue;
}
if matches!(node, Node::FunctionDeclaration(_)) {
if self.func_decls.contains(&node.node_id()) {
found_decls.push(node_ref);
}
continue;
}
idents.clear();
let decl_kind = self.extract_declared_idents(node, &mut idents);
if !decl_kind.is_let_like() || decl_kind == DeclKind::ES5Catch {
continue;
}
for id_node in &idents {
let name = identifier_name(id_node);
if self.func_names.contains(&name) {
self.binding_table.try_emplace(name, true);
}
}
}
if found_decls.is_empty() {
return;
}
for func_decl_ref in found_decls {
let node = func_decl_ref.node(self.gc);
let func_decl = match node {
Node::FunctionDeclaration(fd) => fd,
_ => panic!(
"cast<FunctionDeclarationNode> failed: found decl is a {}",
node.node_type_str()
),
};
self.func_decls.remove(&node.node_id());
if let Some(id) = func_decl.id {
if !self
.binding_table
.lookup(&identifier_name(id))
.unwrap_or_default()
{
self.promoted_func_decls.push(func_decl_ref.clone());
}
}
}
}
fn extract_declared_idents<'a>(
&mut self,
node: &'a Node<'a>,
idents: &mut Vec<&'a Node<'a>>,
) -> DeclKind {
if let Node::VariableDeclaration(var_declaration) = node {
for declarator in var_declaration.declarations.iter() {
let vd = match declarator {
Node::VariableDeclarator(vd) => vd,
_ => panic!(
"cast<VariableDeclaratorNode> failed: {}",
declarator.node_type_str()
),
};
extract_declared_idents_from_id(self.sm, Some(vd.id), idents);
}
let kind = var_declaration.kind.get();
return if kind == self.sem_ctx.kw.ident_var {
DeclKind::Var
} else if kind == self.sem_ctx.kw.ident_let {
DeclKind::Let
} else {
DeclKind::Const
};
}
if let Node::FunctionDeclaration(fd) = node {
extract_declared_idents_from_id(self.sm, fd.id, idents);
return DeclKind::ScopedFunction;
}
if let Node::HookDeclaration(hd) = node {
extract_declared_idents_from_id(self.sm, Some(hd.id), idents);
return DeclKind::ScopedFunction;
}
if let Node::ComponentDeclaration(cd) = node {
extract_declared_idents_from_id(self.sm, Some(cd.id), idents);
return DeclKind::ScopedFunction;
}
if let Node::ClassDeclaration(cd) = node {
extract_declared_idents_from_id(self.sm, cd.id, idents);
return DeclKind::Class;
}
if let Node::CatchClause(catch_clause) = node {
extract_declared_idents_from_id(
self.sm,
catch_clause.param,
idents,
);
return if matches!(catch_clause.param, Some(Node::Identifier(_))) {
DeclKind::ES5Catch
} else {
DeclKind::Catch
};
}
let id = match node {
Node::ImportDeclaration(id) => id,
_ => panic!(
"cast<ImportDeclarationNode> failed: unexpected scope decl \
kind {}",
node.node_type_str()
),
};
for spec in id.specifiers.iter() {
match spec {
Node::ImportSpecifier(is) => {
extract_declared_idents_from_id(
self.sm,
Some(is.local),
idents,
);
}
Node::ImportDefaultSpecifier(ids) => {
extract_declared_idents_from_id(
self.sm,
Some(ids.local),
idents,
);
}
_ => {
let ins = match spec {
Node::ImportNamespaceSpecifier(ins) => ins,
_ => panic!(
"cast<ImportNamespaceSpecifierNode> failed: {}",
spec.node_type_str()
),
};
extract_declared_idents_from_id(
self.sm,
Some(ins.local),
idents,
);
}
}
}
DeclKind::Import
}
}
impl<'ast> Visitor<'ast>
for ScopedFunctionPromoter<'ast, '_, '_, '_, '_, '_, '_>
{
fn visit_node(&mut self, node: &'ast Node<'ast>) {
match node {
Node::Program(_)
| Node::FunctionExpression(_)
| Node::ArrowFunctionExpression(_)
| Node::FunctionDeclaration(_)
| Node::ComponentDeclaration(_)
| Node::HookDeclaration(_) => {}
Node::SwitchStatement(_)
| Node::BlockStatement(_)
| Node::ForStatement(_)
| Node::ForInStatement(_)
| Node::ForOfStatement(_)
| Node::WithStatement(_)
| Node::CatchClause(_) => self.visit_scope(node),
_ => node.visit_children(self),
}
}
}
fn identifier_name(node: &Node) -> Atom {
node.as_identifier()
.expect("cast<IdentifierNode> failed: not an Identifier")
.name
.get()
}
pub(super) fn get_promoted_scoped_func_decls<'ast>(
resolver: &mut SemanticResolver<'_, '_, '_, '_>,
gc: &'ast GCLock,
func_node: &'ast Node<'ast>,
) -> Vec<NodeRc> {
let func_sem_info = resolver.cur_function_info();
let decls = resolver
.function_stack
.last()
.expect("no active function context")
.decls
.as_ref()
.expect("FunctionContext without a DeclCollector");
if decls.scoped_func_decls().is_empty() {
return Vec::new();
}
let sem_ctx: &SemContext = resolver.sem_ctx;
let sm: &mut SourceErrorManager = resolver.sm;
let binding_table = PromoterBindingTable::new();
let mut promoter = ScopedFunctionPromoter {
gc,
decls,
sem_ctx,
sm,
promoted_func_decls: Vec::new(),
func_names: HashSet::new(),
func_decls: HashSet::new(),
binding_table: &binding_table,
};
promoter.run(func_node, func_sem_info);
promoter.promoted_func_decls
}