use hermes_ast::context::{GCLock, NodeRc};
use hermes_ast::node::{builder, BlockStatement, Node, NodeField, ReturnStatement};
use hermes_ast::node_child::{NodeList, NodeMetadata, Strictness};
use hermes_ast::visitor::{Path, TransformResult, VisitorMut};
use crate::check_implicit_return::may_reach_implicit_return;
use crate::ids::FunctionInfoId;
use crate::sem_context::{Binding, ConstructorKind, DeclKind};
use super::expressions::replacement_of;
use super::promoter::get_promoted_scoped_func_decls;
use super::unresolver::Unresolver;
use super::{
make_strictness, FoundDirectives, SemanticResolver, DEBUG_INFO_SETTING_ALL,
};
const ENABLE_ASYNC_GENERATORS: bool = false;
const ALLOW_RETURN_OUTSIDE_FUNCTION: bool = false;
const TYPED: bool = false;
enum FuncBuilder<'gc> {
Declaration(builder::FunctionDeclaration<'gc>),
Expression(builder::FunctionExpression<'gc>),
Arrow(builder::ArrowFunctionExpression<'gc>),
}
impl<'gc> FuncBuilder<'gc> {
fn from_node(node: &'gc Node<'gc>) -> FuncBuilder<'gc> {
match node {
Node::FunctionDeclaration(n) => FuncBuilder::Declaration(
builder::FunctionDeclaration::from_node(n),
),
Node::FunctionExpression(n) => FuncBuilder::Expression(
builder::FunctionExpression::from_node(n),
),
Node::ArrowFunctionExpression(n) => FuncBuilder::Arrow(
builder::ArrowFunctionExpression::from_node(n),
),
_ => panic!(
"sema: no function builder for {}",
node.node_type_str()
),
}
}
fn params(&mut self, params: NodeList<'gc>) {
match self {
FuncBuilder::Declaration(b) => b.params(params),
FuncBuilder::Expression(b) => b.params(params),
FuncBuilder::Arrow(b) => b.params(params),
}
}
fn body(&mut self, body: &'gc Node<'gc>) {
match self {
FuncBuilder::Declaration(b) => b.body(body),
FuncBuilder::Expression(b) => b.body(body),
FuncBuilder::Arrow(b) => b.body(body),
}
}
fn build(self, gc: &'gc GCLock) -> TransformResult<&'gc Node<'gc>> {
match self {
FuncBuilder::Declaration(b) => b.build(gc),
FuncBuilder::Expression(b) => b.build(gc),
FuncBuilder::Arrow(b) => b.build(gc),
}
}
}
pub(super) fn copy_location_from<'gc>(src: &Node<'gc>) -> NodeMetadata<'gc> {
NodeMetadata::new_with_debug(src.range(), src.metadata().debug_loc.get())
}
fn node_sem_info(node: &Node) -> FunctionInfoId {
let id = match node {
Node::Program(n) => n.sem_info.get(),
Node::FunctionExpression(n) => n.sem_info.get(),
Node::ArrowFunctionExpression(n) => n.sem_info.get(),
Node::FunctionDeclaration(n) => n.sem_info.get(),
Node::ComponentDeclaration(n) => n.sem_info.get(),
Node::HookDeclaration(n) => n.sem_info.get(),
_ => panic!("{} is not a function-like node", node.node_type_str()),
};
FunctionInfoId::from_sema_id(id.expect("semInfo must be set"))
}
fn function_like_params<'gc>(node: &'gc Node<'gc>) -> NodeList<'gc> {
match node {
Node::FunctionExpression(n) => n.params,
Node::ArrowFunctionExpression(n) => n.params,
Node::FunctionDeclaration(n) => n.params,
_ => panic!("invalid FunctionLikeNode: {}", node.node_type_str()),
}
}
pub(super) fn function_like_body<'gc>(node: &'gc Node<'gc>) -> &'gc Node<'gc> {
match node {
Node::FunctionExpression(n) => n.body,
Node::ArrowFunctionExpression(n) => n.body,
Node::FunctionDeclaration(n) => n.body,
_ => panic!("invalid FunctionLikeNode: {}", node.node_type_str()),
}
}
pub(super) fn is_generator(node: &Node) -> bool {
match node {
Node::FunctionExpression(n) => n.generator.get(),
Node::ArrowFunctionExpression(_) => false,
Node::FunctionDeclaration(n) => n.generator.get(),
Node::ComponentDeclaration(_) => false,
Node::HookDeclaration(_) => false,
_ => {
debug_assert!(
matches!(node, Node::Program(_)),
"invalid FunctionLikeNode"
);
false
}
}
}
fn is_async(node: &Node) -> bool {
match node {
Node::FunctionExpression(n) => n.r#async.get(),
Node::ArrowFunctionExpression(n) => n.r#async.get(),
Node::FunctionDeclaration(n) => n.r#async.get(),
Node::ComponentDeclaration(n) => n.r#async.get(),
Node::HookDeclaration(n) => n.r#async.get(),
_ => {
debug_assert!(
matches!(node, Node::Program(_)),
"invalid FunctionLikeNode"
);
false
}
}
}
fn is_method_definition(node: &Node) -> bool {
match node {
Node::Program(_) => false,
Node::FunctionExpression(n) => n.is_method_definition.get(),
Node::ArrowFunctionExpression(n) => n.is_method_definition.get(),
Node::FunctionDeclaration(n) => n.is_method_definition.get(),
Node::ComponentDeclaration(n) => n.is_method_definition.get(),
Node::HookDeclaration(n) => n.is_method_definition.get(),
_ => panic!("{} is not a function-like node", node.node_type_str()),
}
}
fn set_node_strictness(node: &Node, strictness: Strictness) {
match node {
Node::Program(n) => n.strictness.set(strictness),
Node::FunctionExpression(n) => n.strictness.set(strictness),
Node::ArrowFunctionExpression(n) => n.strictness.set(strictness),
Node::FunctionDeclaration(n) => n.strictness.set(strictness),
Node::ComponentDeclaration(n) => n.strictness.set(strictness),
Node::HookDeclaration(n) => n.strictness.set(strictness),
_ => panic!("{} is not a function-like node", node.node_type_str()),
}
}
impl<'bt, 'sc, 'sm, 'ad> SemanticResolver<'bt, 'sc, 'sm, 'ad> {
pub(super) fn visit_function_declaration<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
path: Option<Path<'gc>>,
) -> TransformResult<&'gc Node<'gc>> {
let func_decl = node
.as_function_declaration()
.expect("visit_function_declaration: not a FunctionDeclaration");
let hoisted_scope = self.cur_scope.expect("no active scope");
let hoisted_list =
&mut self.sem_ctx.scope_mut(hoisted_scope).hoisted_functions;
hoisted_list.push(NodeRc::from_node(gc, node));
let hoisted_idx = hoisted_list.len() - 1;
let id = func_decl.id.inspect(|id_node| {
assert!(
matches!(id_node, Node::Identifier(_)),
"FunctionDeclaration.id is not an Identifier"
);
});
let result =
self.visit_function_like(gc, node, id, path.map(|p| p.parent));
if let TransformResult::Changed(new_node) = &result {
self.sem_ctx.scope_mut(hoisted_scope).hoisted_functions
[hoisted_idx] = NodeRc::from_node(gc, new_node);
}
result
}
pub(super) fn visit_function_expression<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
path: Option<Path<'gc>>,
) -> TransformResult<&'gc Node<'gc>> {
let func_expr = node
.as_function_expression()
.expect("visit_function_expression: not a FunctionExpression");
let parent = path.map(|p| p.parent);
let ident_node = match func_expr.id {
Some(n) if matches!(n, Node::Identifier(_)) => Some(n),
_ => None,
};
let Some(ident_node) = ident_node else {
return self.visit_function_like(gc, node, None, parent);
};
let ident = ident_node
.as_identifier()
.expect("checked to be an Identifier above");
let name = ident.name.get();
let scope_state = self.enter_scope(Some(node), false);
let cur_scope = self.cur_scope.expect("just entered a scope");
let decl = self.sem_ctx.new_decl_in_scope_default(
name,
DeclKind::FunctionExprName,
cur_scope,
);
self.sem_ctx.set_declaration_decl(
ident_node.node_id(),
ident,
Some(decl),
);
self.binding_table.try_emplace(
name,
Binding::new(decl, Some(NodeRc::from_node(gc, ident_node))),
);
let result =
self.visit_function_like(gc, node, Some(ident_node), parent);
self.exit_scope(scope_state);
result
}
pub(super) fn visit_arrow_function_expression<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
path: Option<Path<'gc>>,
) -> TransformResult<&'gc Node<'gc>> {
let arrow = node.as_arrow_function_expression().expect(
"visit_arrow_function_expression: not an ArrowFunctionExpression",
);
let mut rewritten = false;
let rewritten_node: &'gc Node<'gc> = if self.compile()
&& arrow.expression.get()
{
let ret_stmt = gc.alloc(Node::ReturnStatement(
ReturnStatement::new(
copy_location_from(arrow.body),
Some(arrow.body),
),
));
let stmt_list = NodeList::from_iter(gc, [ret_stmt]);
let block_stmt =
gc.alloc(Node::BlockStatement(BlockStatement::new(
copy_location_from(arrow.body),
stmt_list,
true,
)));
let mut b = builder::ArrowFunctionExpression::from_node(arrow);
b.body(block_stmt);
let new_node = b.build_forced(gc);
new_node
.as_arrow_function_expression()
.expect("the arrow builder builds an arrow")
.expression
.set(false);
rewritten = true;
new_node
} else {
node
};
let result = self.visit_function_like(
gc,
rewritten_node,
None,
path.map(|p| p.parent),
);
let enclosing = self.cur_function_info();
self.sem_ctx.function_mut(enclosing).contains_arrow_functions = true;
let arrow_info = node_sem_info(rewritten_node);
let uses = self
.sem_ctx
.function(enclosing)
.contains_arrow_functions_using_arguments
|| self
.sem_ctx
.function(arrow_info)
.contains_arrow_functions_using_arguments
|| self.sem_ctx.function(arrow_info).uses_arguments;
self.sem_ctx
.function_mut(enclosing)
.contains_arrow_functions_using_arguments = uses;
match result {
TransformResult::Unchanged if rewritten => {
TransformResult::Changed(rewritten_node)
}
other => other,
}
}
fn visit_function_like<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
id: Option<&'gc Node<'gc>>,
parent: Option<&'gc Node<'gc>>,
) -> TransformResult<&'gc Node<'gc>> {
let mut cons_kind = ConstructorKind::None;
if let Some(Node::MethodDefinition(method)) = parent {
if method.kind.get() == self.kw().ident_constructor {
self.cur_class_context_mut().has_constructor = true;
cons_kind = if self.cur_class_is_derived(gc) {
ConstructorKind::Derived
} else {
ConstructorKind::Base
};
}
}
let parent_sem_info = self.cur_function_info();
let strict = self.sem_ctx.function(parent_sem_info).strict;
let custom_directives =
self.sem_ctx.function(parent_sem_info).custom_directives;
let func_state = self.enter_function(
gc,
node,
Some(parent_sem_info),
strict,
cons_kind,
custom_directives,
false,
);
let is_arrow = matches!(node, Node::ArrowFunctionExpression(_));
let new_can_ref_super = if is_arrow {
self.can_reference_super
} else {
is_method_definition(node)
};
let saved_can_ref_super = self.can_reference_super;
self.can_reference_super = new_can_ref_super;
let saved_forbid_arguments_as_identifier =
self.forbid_arguments_as_identifier;
self.forbid_arguments_as_identifier =
if is_arrow { saved_forbid_arguments_as_identifier } else { false };
let result = self.visit_function_like_in_function_context(gc, node, id);
self.forbid_arguments_as_identifier =
saved_forbid_arguments_as_identifier;
self.can_reference_super = saved_can_ref_super;
self.exit_function(func_state);
result
}
fn visit_function_like_in_function_context<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
id: Option<&'gc Node<'gc>>,
) -> TransformResult<&'gc Node<'gc>> {
if self.compile()
&& is_async(node)
&& is_generator(node)
&& !ENABLE_ASYNC_GENERATORS
{
self.sm
.error_range(node.range(), "async generators are unsupported");
}
let mut directives = FoundDirectives::default();
let body = function_like_body(node);
let block_body = match body {
Node::BlockStatement(_) => Some(body),
_ => None,
};
if let Some(bb) = block_body {
let bs = bb
.as_block_statement()
.expect("checked to be a BlockStatement above");
directives = self.scan_directives(bs.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();
set_node_strictness(
node,
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).custom_directives.always_inline =
directives.always_inline;
self.sem_ctx.function_mut(f).custom_directives.no_inline =
directives.no_inline;
self.sem_ctx.function_mut(f).custom_directives.builtin =
directives.builtin;
if let Some(id_node) = id {
let ident = id_node
.as_identifier()
.expect("a function-like id is always an Identifier");
let decl = self.sem_ctx.get_declaration_decl(ident);
self.sem_ctx.set_expression_decl(
id_node.node_id(),
ident,
decl,
);
self.validate_declaration_name(
gc,
DeclKind::FunctionExprName,
id_node,
);
}
if let Some(bb) = block_body {
let bs = bb
.as_block_statement()
.expect("checked to be a BlockStatement above");
if bs.is_lazy_function_body.get() {
let f = self.cur_function_info();
let cur = self.binding_table.current_scope();
self.sem_ctx.function_mut(f).binding_table_scope = cur;
self.sem_ctx.function_mut(f).contains_arrow_functions =
bs.contains_arrow_functions.get();
self.sem_ctx
.function_mut(f)
.contains_arrow_functions_using_arguments =
bs.may_contain_arrow_functions_using_arguments.get();
return TransformResult::Unchanged;
}
}
let mut simple_parameter_list = true;
let mut has_parameter_expressions = false;
let mut param_ids: Vec<&'gc Node<'gc>> = Vec::new();
for param in function_like_params(node).iter() {
simple_parameter_list &= !param.is_pattern();
has_parameter_expressions |= self
.extract_declared_idents_from_id(Some(param), &mut param_ids);
}
let f = self.cur_function_info();
self.sem_ctx.function_mut(f).simple_parameter_list =
simple_parameter_list;
self.sem_ctx.function_mut(f).has_parameter_expressions =
has_parameter_expressions;
if !simple_parameter_list {
if let Some(use_strict_node) = directives.use_strict_node {
self.sm.error_range(
use_strict_node.range(),
"'use strict' not allowed inside function with \
non-simple parameter list",
);
}
}
let unique_params = !simple_parameter_list
|| self.sem_ctx.function(self.cur_function_info()).strict
|| matches!(node, Node::ArrowFunctionExpression(_));
let mut has_parameter_named_arguments = false;
let mut b = FuncBuilder::from_node(node);
let saved_forbid_await_expression = self.forbid_await_expression;
self.forbid_await_expression = !is_async(node);
let saved_forbid_special_arguments =
self.forbid_special_arguments_reference;
self.forbid_special_arguments_reference =
if matches!(node, Node::ArrowFunctionExpression(_)) {
saved_forbid_special_arguments
} else {
false
};
if has_parameter_expressions {
let param_scope = self.enter_scope(None, false);
self.declare_params(
gc,
¶m_ids,
unique_params,
&mut has_parameter_named_arguments,
);
if !matches!(node, Node::ArrowFunctionExpression(_))
&& !has_parameter_named_arguments
{
let temporary_arguments_scope = self.enter_scope(None, false);
self.declare_arguments();
self.visit_params(gc, node, &mut b);
self.exit_scope(temporary_arguments_scope);
} else {
self.visit_params(gc, node, &mut b);
}
let scope = self.enter_scope(
None,
true,
);
self.visit_function_body_after_params_visited(
gc,
node,
&mut b,
block_body,
has_parameter_named_arguments,
);
self.exit_scope(scope);
self.exit_scope(param_scope);
} else {
let scope = self.enter_scope(
None,
true,
);
self.declare_params(
gc,
¶m_ids,
unique_params,
&mut has_parameter_named_arguments,
);
self.visit_params(gc, node, &mut b);
self.visit_function_body_after_params_visited(
gc,
node,
&mut b,
block_body,
has_parameter_named_arguments,
);
self.exit_scope(scope);
}
self.forbid_special_arguments_reference =
saved_forbid_special_arguments;
self.forbid_await_expression = saved_forbid_await_expression;
b.build(gc)
}
fn declare_params<'gc>(
&mut self,
gc: &'gc GCLock,
param_ids: &[&'gc Node<'gc>],
unique_params: bool,
has_parameter_named_arguments: &mut bool,
) {
for ¶m_id_node in param_ids {
let param_id = param_id_node
.as_identifier()
.expect("extractDeclaredIdentsFromID only pushes Identifiers");
let name = param_id.name.get();
if name == self.kw().ident_arguments {
*has_parameter_named_arguments = true;
}
if self.compile() && !TYPED && name == self.kw().ident_this {
self.sm.error_range(
param_id_node.range(),
"'this' parameter requires typed mode",
);
}
self.validate_declaration_name(
gc,
DeclKind::Parameter,
param_id_node,
);
let cur_scope = self.cur_scope.expect("no active scope");
let param_decl = self.sem_ctx.new_decl_in_scope_default(
name,
DeclKind::Parameter,
cur_scope,
);
self.sem_ctx.set_both_decl(
param_id_node.node_id(),
param_id,
Some(param_decl),
);
let prev_name = self.binding_table.find(&name);
let prev_in_cur_scope = match &prev_name {
Some(prev) => {
self.sem_ctx.decl(prev.decl).scope == Some(cur_scope)
}
None => false,
};
if prev_in_cur_scope {
if unique_params {
self.sm.error_range(
param_id_node.range(),
format!(
"cannot declare two parameters with the same \
name '{}'",
String::from_utf8_lossy(gc.bytes(name))
),
);
}
self.binding_table.put(
name,
Binding::new(
param_decl,
Some(NodeRc::from_node(gc, param_id_node)),
),
);
} else {
self.binding_table.try_emplace(
name,
Binding::new(
param_decl,
Some(NodeRc::from_node(gc, param_id_node)),
),
);
}
}
}
fn visit_params<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
b: &mut FuncBuilder<'gc>,
) {
let saved_is_formal_params = self.function_context().is_formal_params;
self.function_context_mut().is_formal_params = true;
let mut forbid_await_as_identifier = false;
if let Node::ArrowFunctionExpression(arrow) = node {
if self.forbid_await_as_identifier || arrow.r#async.get() {
forbid_await_as_identifier = true;
}
}
let saved_forbid_await = self.forbid_await_as_identifier;
self.forbid_await_as_identifier = forbid_await_as_identifier;
if let Some(new_params) = self.visit_node_list(
gc,
function_like_params(node),
node,
NodeField::params,
) {
b.params(new_params);
}
self.forbid_await_as_identifier = saved_forbid_await;
self.function_context_mut().is_formal_params = saved_is_formal_params;
}
fn visit_function_body_after_params_visited<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
b: &mut FuncBuilder<'gc>,
block_body: Option<&'gc Node<'gc>>,
has_parameter_named_arguments: bool,
) {
if DEBUG_INFO_SETTING_ALL {
let f = self.cur_function_info();
let cur = self.binding_table.current_scope();
self.sem_ctx.function_mut(f).binding_table_scope = cur;
}
let saved_forbid_await_as_identifier = self.forbid_await_as_identifier;
self.forbid_await_as_identifier = is_async(node);
self.process_collected_declarations(gc, node);
if block_body.is_some()
&& !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);
}
if !matches!(node, Node::ArrowFunctionExpression(_))
&& !has_parameter_named_arguments
{
let prev_arguments =
self.binding_table.find(&self.kw().ident_arguments);
let needs_declare = match &prev_arguments {
None => true,
Some(prev) => {
self.sem_ctx.decl(prev.decl).scope != self.cur_scope
}
};
if needs_declare {
self.declare_arguments();
}
}
let body = function_like_body(node);
let new_body = replacement_of(self.call(
gc,
body,
Some(Path::new(node, NodeField::body)),
));
if let Some(new_body) = new_body {
b.body(new_body);
}
let visited_body = new_body.unwrap_or(body);
if self.recursion_depth == 0 {
self.forbid_await_as_identifier = saved_forbid_await_as_identifier;
return;
}
let lex_scope = self
.sem_ctx
.function(self.cur_function_info())
.get_function_body_scope();
#[allow(clippy::overly_complex_bool_expr, clippy::collapsible_if)]
if false {
if self.sem_ctx.scope(lex_scope).local_eval
&& !self.sem_ctx.function(self.cur_function_info()).strict
{
let depth = self.sem_ctx.scope(lex_scope).depth;
Unresolver::run(self.sem_ctx, depth, node);
}
}
if self.sm.error_count() == 0 {
let f = self.cur_function_info();
self.sem_ctx.function_mut(f).may_reach_implicit_return =
may_reach_implicit_return(visited_body);
}
self.forbid_await_as_identifier = saved_forbid_await_as_identifier;
}
pub(super) fn visit_return_statement<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
if self.in_global_scope_context() && !ALLOW_RETURN_OUTSIDE_FUNCTION {
self.sm
.error_range(node.range(), "'return' not in a function");
}
node.visit_children_mut(gc, self)
}
pub(super) fn visit_node_list<'gc>(
&mut self,
gc: &'gc GCLock,
list: NodeList<'gc>,
parent: &'gc Node<'gc>,
field: NodeField,
) -> Option<NodeList<'gc>> {
let path = Path::new(parent, field);
let mut changed = false;
let mut result: Vec<&'gc Node<'gc>> = Vec::new();
for elem in list.iter() {
match replacement_of(self.call(gc, elem, Some(path))) {
Some(new_elem) => {
changed = true;
result.push(new_elem);
}
None => result.push(elem),
}
}
changed.then(|| NodeList::from_iter(gc, result))
}
}