use std::collections::hash_map::Entry;
use hermes_ast::context::{GCLock, NodeRc};
use hermes_ast::node::{builder, BlockStatement, Node, NodeField, TryStatement};
use hermes_ast::node_child::NodeList;
use hermes_ast::visitor::{Path, TransformResult, VisitorMut};
use hermes_support::diag::Subsystem;
use super::declarations::atom_str;
use super::expressions::replacement_of;
use super::functions::copy_location_from;
use super::unresolver::Unresolver;
use super::{Label, SemanticResolver};
pub(super) fn label_index_of(node: &Node) -> u32 {
match node {
Node::WhileStatement(n) => n.label_index.get(),
Node::DoWhileStatement(n) => n.label_index.get(),
Node::ForInStatement(n) => n.label_index.get(),
Node::ForOfStatement(n) => n.label_index.get(),
Node::ForStatement(n) => n.label_index.get(),
Node::SwitchStatement(n) => n.label_index.get(),
Node::BreakStatement(n) => n.label_index.get(),
Node::ContinueStatement(n) => n.label_index.get(),
Node::LabeledStatement(n) => n.label_index.get(),
_ => {
unreachable!("{} carries no label decoration", node.node_type_str())
}
}
}
enum ForInOfBuilder<'gc> {
In(builder::ForInStatement<'gc>),
Of(builder::ForOfStatement<'gc>),
}
impl<'gc> ForInOfBuilder<'gc> {
fn from_node(node: &'gc Node<'gc>) -> ForInOfBuilder<'gc> {
match node {
Node::ForInStatement(n) => {
ForInOfBuilder::In(builder::ForInStatement::from_node(n))
}
Node::ForOfStatement(n) => {
ForInOfBuilder::Of(builder::ForOfStatement::from_node(n))
}
_ => unreachable!("visitForInOf on a {}", node.node_type_str()),
}
}
fn left(&mut self, left: &'gc Node<'gc>) {
match self {
ForInOfBuilder::In(b) => b.left(left),
ForInOfBuilder::Of(b) => b.left(left),
}
}
fn right(&mut self, right: &'gc Node<'gc>) {
match self {
ForInOfBuilder::In(b) => b.right(right),
ForInOfBuilder::Of(b) => b.right(right),
}
}
fn body(&mut self, body: &'gc Node<'gc>) {
match self {
ForInOfBuilder::In(b) => b.body(body),
ForInOfBuilder::Of(b) => b.body(body),
}
}
fn build(self, gc: &'gc GCLock) -> TransformResult<&'gc Node<'gc>> {
match self {
ForInOfBuilder::In(b) => b.build(gc),
ForInOfBuilder::Of(b) => b.build(gc),
}
}
}
fn for_in_of_children<'gc>(
node: &'gc Node<'gc>,
) -> (&'gc Node<'gc>, &'gc Node<'gc>, &'gc Node<'gc>) {
match node {
Node::ForInStatement(n) => (n.left, n.right, n.body),
Node::ForOfStatement(n) => (n.left, n.right, n.body),
_ => unreachable!("visitForInOf on a {}", node.node_type_str()),
}
}
fn set_label_index(node: &Node, index: u32) {
match node {
Node::WhileStatement(n) => n.label_index.set(index),
Node::DoWhileStatement(n) => n.label_index.set(index),
Node::ForInStatement(n) => n.label_index.set(index),
Node::ForOfStatement(n) => n.label_index.set(index),
Node::ForStatement(n) => n.label_index.set(index),
Node::SwitchStatement(n) => n.label_index.set(index),
Node::LabeledStatement(n) => n.label_index.set(index),
_ => {
unreachable!("{} carries no label decoration", node.node_type_str())
}
}
}
struct LoopState {
current_loop: Option<NodeRc>,
current_loop_or_switch: Option<NodeRc>,
}
impl SemanticResolver<'_, '_, '_, '_> {
fn allocate_label_for(&mut self, node: &Node) {
let f = self.cur_function_info();
let index = self.sem_ctx.function_mut(f).allocate_label();
set_label_index(node, index);
}
fn enter_loop<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> LoopState {
let node_rc = NodeRc::from_node(gc, node);
let fc = self.function_context_mut();
LoopState {
current_loop: fc.current_loop.replace(node_rc.clone()),
current_loop_or_switch: fc.current_loop_or_switch.replace(node_rc),
}
}
fn exit_loop(&mut self, state: LoopState) {
let fc = self.function_context_mut();
fc.current_loop_or_switch = state.current_loop_or_switch;
fc.current_loop = state.current_loop;
}
pub(super) fn visit_switch_statement<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let sw = node
.as_switch_statement()
.expect("visit_switch_statement: not a SwitchStatement");
let discriminant = replacement_of(self.call(
gc,
sw.discriminant,
Some(Path::new(node, NodeField::discriminant)),
));
if self.recursion_depth == 0 {
let mut b = builder::SwitchStatement::from_node(sw);
if let Some(v) = discriminant {
b.discriminant(v);
}
return b.build(gc);
}
self.allocate_label_for(node);
let saved_switch = self
.function_context_mut()
.current_loop_or_switch
.replace(NodeRc::from_node(gc, node));
let scope_state = self.enter_scope(Some(node), false);
self.process_collected_declarations(gc, node);
let mut b = builder::SwitchStatement::from_node(sw);
if let Some(v) = discriminant {
b.discriminant(v);
}
if let Some(cases) =
self.visit_node_list(gc, sw.cases, node, NodeField::cases)
{
b.cases(cases);
}
let result = b.build(gc);
self.exit_scope(scope_state);
self.function_context_mut().current_loop_or_switch = saved_switch;
result
}
pub(super) fn visit_for_in_of<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
self.allocate_label_for(node);
let loop_state = self.enter_loop(gc, node);
let scope_state = self.enter_scope(Some(node), false);
self.process_collected_declarations(gc, node);
let result = self.visit_for_in_of_children(gc, node);
self.exit_scope(scope_state);
self.exit_loop(loop_state);
result
}
fn visit_for_in_of_children<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let (left, right, body) = for_in_of_children(node);
let mut b = ForInOfBuilder::from_node(node);
let left_node =
match self.call(gc, left, Some(Path::new(node, NodeField::left))) {
TransformResult::Changed(v) => {
b.left(v);
v
}
TransformResult::Unchanged => left,
other => unreachable!(
"the resolver never removes or expands a child: {other:?}"
),
};
if self.recursion_depth == 0 {
return b.build(gc);
}
if let Node::VariableDeclaration(vd) = left_node {
debug_assert_eq!(
vd.declarations.iter().count(),
1,
"for-in/for-of must have a single binding"
);
let Some(Node::VariableDeclarator(declarator)) =
vd.declarations.iter().next()
else {
panic!("for-in/for-of binding is not a VariableDeclarator")
};
if let Some(init) = declarator.init {
let is_for_in = matches!(node, Node::ForInStatement(_));
let strict =
self.sem_ctx.function(self.cur_function_info()).strict;
let is_var = vd.kind.get() == self.kw().ident_var;
if declarator.id.is_pattern() {
self.sm.error_range(
init.range(),
"destructuring declaration cannot be initialized \
in for-in/for-of loop",
);
} else if !(is_for_in && !strict && is_var) {
self.sm.error_range(
init.range(),
"for-in/for-of variable declaration may not be \
initialized",
);
}
}
} else {
self.validate_assignment_target(left_node);
}
if let Some(v) = replacement_of(self.call(
gc,
right,
Some(Path::new(node, NodeField::right)),
)) {
b.right(v);
}
if let Some(v) = replacement_of(self.call(
gc,
body,
Some(Path::new(node, NodeField::body)),
)) {
b.body(v);
}
b.build(gc)
}
pub(super) fn visit_for_statement<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
self.allocate_label_for(node);
let loop_state = self.enter_loop(gc, node);
let scope_state = self.enter_scope(Some(node), false);
self.process_collected_declarations(gc, node);
let result = node.visit_children_mut(gc, self);
self.exit_scope(scope_state);
self.exit_loop(loop_state);
result
}
pub(super) fn visit_while_like<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
self.allocate_label_for(node);
let loop_state = self.enter_loop(gc, node);
let result = node.visit_children_mut(gc, self);
self.exit_loop(loop_state);
result
}
pub(super) fn visit_labeled_statement<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let labeled = node
.as_labeled_statement()
.expect("visit_labeled_statement: not a LabeledStatement");
self.allocate_label_for(node);
let mut target_stmt: &'gc Node<'gc> = node;
{
let mut cur_stmt: &'gc Node<'gc> = node;
while let Node::LabeledStatement(cur_labeled) = cur_stmt {
if cur_labeled.body.is_loop_statement() {
target_stmt = cur_labeled.body;
break;
}
cur_stmt = cur_labeled.body;
}
}
debug_assert!(
target_stmt.is_loop_statement()
|| matches!(target_stmt, Node::LabeledStatement(_)),
"invalid target statement detected for label"
);
let Node::Identifier(id) = labeled.label else {
panic!(
"LabeledStatement.label is a {}, not an Identifier",
labeled.label.node_type_str()
)
};
let name = id.name.get();
let mut inserted = true;
let mut prev_declaration: Option<NodeRc> = None;
match self.function_context_mut().label_map.entry(name) {
Entry::Occupied(e) => {
inserted = false;
prev_declaration = Some(e.get().declaration_node.clone());
}
Entry::Vacant(e) => {
e.insert(Label {
declaration_node: NodeRc::from_node(gc, labeled.label),
target_statement: NodeRc::from_node(gc, target_stmt),
});
}
}
if let Some(prev_declaration) = prev_declaration {
self.sm.error_range(
labeled.label.range(),
format!("label '{}' is already defined", atom_str(gc, name)),
);
self.sm.note_range(
prev_declaration.node(gc).range(),
"previous definition",
Subsystem::Unspecified,
);
}
let result = node.visit_children_mut(gc, self);
if inserted {
self.function_context_mut().label_map.remove(&name);
}
result
}
pub(super) fn visit_break_statement<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let brk = node
.as_break_statement()
.expect("visit_break_statement: not a BreakStatement");
if let Some(label_node) = brk.label {
let name = label_identifier_name(label_node);
let target = self
.function_context()
.label_map
.get(&name)
.map(|l| l.target_statement.clone());
match target {
Some(target) => {
brk.label_index.set(label_index_of(target.node(gc)));
}
None => {
self.sm.error_range(
label_node.range(),
format!(
"label '{}' is not defined",
atom_str(gc, name)
),
);
}
}
} else {
let target = self.function_context().current_loop_or_switch.clone();
match target {
Some(target) => {
brk.label_index.set(label_index_of(target.node(gc)));
}
None => {
self.sm.error_range(
node.range(),
"'break' not within a loop or a switch",
);
}
}
}
node.visit_children_mut(gc, self)
}
pub(super) fn visit_continue_statement<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let cont = node
.as_continue_statement()
.expect("visit_continue_statement: not a ContinueStatement");
if let Some(label_node) = cont.label {
let name = label_identifier_name(label_node);
let found = self.function_context().label_map.get(&name).map(|l| {
(l.target_statement.clone(), l.declaration_node.clone())
});
match found {
Some((target, declaration_node)) => {
if target.node(gc).is_loop_statement() {
cont.label_index.set(label_index_of(target.node(gc)));
} else {
self.sm.error_range(
label_node.range(),
format!(
"'continue' label '{}' is not a loop label",
atom_str(gc, name)
),
);
self.sm.note_range(
declaration_node.node(gc).range(),
"label defined here",
Subsystem::Unspecified,
);
}
}
None => {
self.sm.error_range(
label_node.range(),
format!(
"label '{}' is not defined",
atom_str(gc, name)
),
);
}
}
} else {
let target = self.function_context().current_loop.clone();
match target {
Some(target) => {
cont.label_index.set(label_index_of(target.node(gc)));
}
None => {
self.sm.error_range(
node.range(),
"'continue' not within a loop",
);
}
}
}
node.visit_children_mut(gc, self)
}
pub(super) fn visit_with_statement<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let with = node
.as_with_statement()
.expect("visit_with_statement: not a WithStatement");
if self.compile() {
self.sm
.error(node.range().start, "with statement is not supported");
}
let mut b = builder::WithStatement::from_node(with);
if let Some(v) = replacement_of(self.call(
gc,
with.object,
Some(Path::new(node, NodeField::object)),
)) {
b.object(v);
}
let body = match self.call(
gc,
with.body,
Some(Path::new(node, NodeField::body)),
) {
TransformResult::Changed(v) => {
b.body(v);
v
}
TransformResult::Unchanged => with.body,
other => unreachable!(
"the resolver never removes or expands a child: {other:?}"
),
};
let cur_scope = self
.cur_scope
.expect("a WithStatement is always inside some scope");
let depth = self.sem_ctx.scope(cur_scope).depth;
Unresolver::run(self.sem_ctx, depth + 1, body);
b.build(gc)
}
pub(super) fn visit_try_statement<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let try_statement = node
.as_try_statement()
.expect("visit_try_statement: not a TryStatement");
let rewritten: Option<&'gc Node<'gc>> = if self.compile() {
match (try_statement.handler, try_statement.finalizer) {
(Some(handler), Some(_)) => {
let nested_meta = copy_location_from(node);
let mut nested_range = nested_meta.range.get();
nested_range.end = handler.range().end;
nested_meta.range.set(nested_range);
let nested_try =
gc.alloc(Node::TryStatement(TryStatement::new(
nested_meta,
try_statement.block,
Some(handler),
None,
)));
let new_block =
gc.alloc(Node::BlockStatement(BlockStatement::new(
copy_location_from(nested_try),
NodeList::from_iter(gc, [nested_try]),
false,
)));
let mut b =
builder::TryStatement::from_node(try_statement);
b.block(new_block);
b.handler(None);
Some(b.build_forced(gc))
}
_ => None,
}
} else {
None
};
let node = rewritten.unwrap_or(node);
match node.visit_children_mut(gc, self) {
TransformResult::Unchanged => match rewritten {
Some(n) => TransformResult::Changed(n),
None => TransformResult::Unchanged,
},
other => other,
}
}
pub(super) fn visit_catch_clause<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let scope_state = self.enter_scope(Some(node), false);
self.process_collected_declarations(gc, node);
let result = node.visit_children_mut(gc, self);
self.exit_scope(scope_state);
result
}
pub(super) fn visit_match_statement<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
if self.compile() {
self.sm
.error_range(node.range(), "match statements are unsupported");
}
node.visit_children_mut(gc, self)
}
}
fn label_identifier_name(label_node: &Node) -> hermes_ast::node_child::NodeLabel {
match label_node {
Node::Identifier(id) => id.name.get(),
_ => panic!(
"break/continue label is a {}, not an Identifier",
label_node.node_type_str()
),
}
}