use std::collections::HashSet;
use hermes_ast::node::{
MatchStatement, Node, SwitchStatement, TryStatement,
};
use hermes_ast::node_child::NodeList;
#[derive(Default)]
struct TerminationResult {
target_labels: HashSet<u32>,
}
impl TerminationResult {
const K_NEXT_STATEMENT_LABEL: u32 = u32::MAX - 2;
fn may_execute_next_statement(&self) -> bool {
self.target_labels.contains(&Self::K_NEXT_STATEMENT_LABEL)
}
fn must_execute_next_statement(&self) -> bool {
self.target_labels.len() == 1 && self.may_execute_next_statement()
}
fn must_terminate(&self) -> bool {
self.target_labels.is_empty()
}
fn make_single_label(label_index: u32) -> TerminationResult {
let mut result = TerminationResult::default();
result.target_labels.insert(label_index);
result
}
fn make_next_statement() -> TerminationResult {
Self::make_single_label(Self::K_NEXT_STATEMENT_LABEL)
}
fn make_must_terminate() -> TerminationResult {
TerminationResult::default()
}
}
struct CheckImplicitReturn;
impl CheckImplicitReturn {
fn check_termination(&self, node: &Node) -> TerminationResult {
match node {
Node::BlockStatement(block) => {
self.check_termination_statement_list(block.body)
}
Node::IfStatement(if_statement) => {
let mut consequent_res =
self.check_termination(if_statement.consequent);
if let Some(alternate) = if_statement.alternate {
let alternate_res = self.check_termination(alternate);
consequent_res
.target_labels
.extend(alternate_res.target_labels);
} else {
consequent_res
.target_labels
.insert(TerminationResult::K_NEXT_STATEMENT_LABEL);
}
consequent_res
}
Node::ForStatement(n) => self
.check_termination_loop_or_labeled_statement(
n.label_index.get(),
n.body,
false,
),
Node::ForInStatement(n) => self
.check_termination_loop_or_labeled_statement(
n.label_index.get(),
n.body,
false,
),
Node::ForOfStatement(n) => self
.check_termination_loop_or_labeled_statement(
n.label_index.get(),
n.body,
false,
),
Node::WhileStatement(n) => self
.check_termination_loop_or_labeled_statement(
n.label_index.get(),
n.body,
false,
),
Node::DoWhileStatement(n) => self
.check_termination_loop_or_labeled_statement(
n.label_index.get(),
n.body,
true,
),
Node::LabeledStatement(n) => self
.check_termination_loop_or_labeled_statement(
n.label_index.get(),
n.body,
true,
),
Node::SwitchStatement(n) => {
self.check_termination_switch_statement(n)
}
Node::TryStatement(n) => self.check_termination_try_statement(n),
Node::MatchStatement(n) => {
self.check_termination_match_statement(n)
}
Node::ReturnStatement(_) => {
TerminationResult::make_must_terminate()
}
Node::ThrowStatement(_) => {
TerminationResult::make_must_terminate()
}
Node::ContinueStatement(n) => {
TerminationResult::make_single_label(n.label_index.get())
}
Node::BreakStatement(n) => {
TerminationResult::make_single_label(n.label_index.get())
}
Node::WithStatement(n) => self.check_termination(n.body),
Node::DebuggerStatement(_)
| Node::EmptyStatement(_)
| Node::ExpressionStatement(_) => {
TerminationResult::make_next_statement()
}
_ => {
debug_assert!(
!node.is_statement(),
"unhandled statement in statement list"
);
TerminationResult::make_next_statement()
}
}
}
fn check_termination_statement_list(
&self,
stmts: NodeList<'_>,
) -> TerminationResult {
let mut result = TerminationResult::default();
for stmt in stmts.iter() {
result
.target_labels
.remove(&TerminationResult::K_NEXT_STATEMENT_LABEL);
let stmt_res = self.check_termination(stmt);
let may_execute_next_statement =
stmt_res.may_execute_next_statement();
result.target_labels.extend(stmt_res.target_labels);
if !may_execute_next_statement {
return result;
}
}
result
.target_labels
.insert(TerminationResult::K_NEXT_STATEMENT_LABEL);
result
}
fn check_termination_loop_or_labeled_statement(
&self,
label_index: u32,
body: &Node,
must_execute: bool,
) -> TerminationResult {
let mut may_execute_next_statement = !must_execute;
let mut body_res = self.check_termination(body);
if body_res.target_labels.remove(&label_index) {
may_execute_next_statement = true;
}
if may_execute_next_statement {
body_res
.target_labels
.insert(TerminationResult::K_NEXT_STATEMENT_LABEL);
}
body_res
}
fn check_termination_try_statement(
&self,
node: &TryStatement<'_>,
) -> TerminationResult {
debug_assert!(
node.handler.is_some() || node.finalizer.is_some(),
"try statement must have a handler or a finalizer"
);
let mut inner_res = self.check_termination(node.block);
if let Some(handler) = node.handler {
let catch_clause = handler
.as_catch_clause()
.expect("a TryStatement handler is a CatchClause");
let catch_res = self.check_termination(catch_clause.body);
inner_res.target_labels.extend(catch_res.target_labels);
}
let Some(finalizer) = node.finalizer else {
return inner_res;
};
self.check_termination_finalizer(inner_res, finalizer)
}
fn check_termination_finalizer(
&self,
mut try_res: TerminationResult,
finalizer: &Node,
) -> TerminationResult {
let finally_res = self.check_termination(finalizer);
if finally_res.must_terminate() {
return finally_res;
}
if try_res.must_terminate() && finally_res.must_execute_next_statement()
{
return try_res;
}
try_res.target_labels.extend(finally_res.target_labels);
try_res
}
fn check_termination_switch_statement(
&self,
node: &SwitchStatement<'_>,
) -> TerminationResult {
let mut result = TerminationResult::default();
let mut found_default = false;
for child in node.cases.iter() {
result
.target_labels
.remove(&TerminationResult::K_NEXT_STATEMENT_LABEL);
let switch_case = child
.as_switch_case()
.expect("a SwitchStatement case is a SwitchCase");
if switch_case.test.is_none() {
found_default = true;
}
let case_res =
self.check_termination_statement_list(switch_case.consequent);
result.target_labels.extend(case_res.target_labels);
}
let found_explicit_break =
result.target_labels.remove(&node.label_index.get());
if found_explicit_break || !found_default {
result
.target_labels
.insert(TerminationResult::K_NEXT_STATEMENT_LABEL);
}
result
}
fn is_irrefutable_match_pattern(pattern: &Node) -> bool {
let mut pattern = pattern;
while let Node::MatchAsPattern(as_pattern) = pattern {
pattern = as_pattern.pattern;
}
matches!(
pattern,
Node::MatchWildcardPattern(_) | Node::MatchBindingPattern(_)
)
}
fn check_termination_match_statement(
&self,
node: &MatchStatement<'_>,
) -> TerminationResult {
let mut result = TerminationResult::default();
let mut found_irrefutable = false;
for child in node.cases.iter() {
let match_case = child
.as_match_statement_case()
.expect("a MatchStatement case is a MatchStatementCase");
let case_res = self.check_termination(match_case.body);
result.target_labels.extend(case_res.target_labels);
if match_case.guard.is_none()
&& Self::is_irrefutable_match_pattern(match_case.pattern)
{
found_irrefutable = true;
break;
}
}
if !found_irrefutable {
result
.target_labels
.insert(TerminationResult::K_NEXT_STATEMENT_LABEL);
}
result
}
}
pub(crate) fn may_reach_implicit_return(body: &Node) -> bool {
let visitor = CheckImplicitReturn;
if !matches!(body, Node::BlockStatement(_)) {
return false;
}
let result = visitor.check_termination(body);
debug_assert!(
result.target_labels.is_empty() || result.must_execute_next_statement(),
"all user-declared labels must be removed by the end of the function"
);
result.may_execute_next_statement()
}