use super::{Diagnostic, DiagnosticCtx};
use crate::{
LintLevel,
cfa::{self, FlowNodeKind},
};
use bumpalo::collections::Vec as BumpVec;
use lspt::{DiagnosticSeverity, DiagnosticTag};
use wat_syntax::{
AmberNode, SyntaxKind, TextRange,
ast::{AstNode, Instr, support},
};
const DIAGNOSTIC_CODE: &str = "unreachable";
pub fn check(diagnostics: &mut Vec<Diagnostic>, ctx: &mut DiagnosticCtx, lint_level: LintLevel, node: AmberNode) {
let severity = match lint_level {
LintLevel::Allow => return,
LintLevel::Hint => DiagnosticSeverity::Hint,
LintLevel::Warn => DiagnosticSeverity::Warning,
LintLevel::Deny => DiagnosticSeverity::Error,
};
let cfg = cfa::analyze(ctx.db, ctx.document, node.to_ptr());
let mut ranges = BumpVec::<TextRange>::new_in(ctx.bump);
cfg.graph.raw_nodes().iter().for_each(|raw_node| {
if !raw_node.weight.unreachable {
return;
}
match &raw_node.weight.kind {
FlowNodeKind::BasicBlock(bb) => {
bb.0.iter().for_each(|instr| {
let Some(instr) = instr.ptr.to_node(ctx.module) else {
return;
};
let current = instr.text_range();
if let Some(last) = ranges.last_mut() {
if instr
.prev_siblings()
.next()
.is_some_and(|prev| last.contains_range(prev.text_range()))
{
*last = last.cover(current);
} else if current.contains_range(*last) {
if instr
.amber()
.children_by_kind(Instr::can_cast)
.next()
.is_none_or(|first| last.contains_range(first.text_range()))
{
*last = current;
} else if let Some(instr_name) = instr.amber().tokens_by_kind(SyntaxKind::INSTR_NAME).next()
{
ranges.push(instr_name.text_range());
}
} else if !last.contains_range(current) {
ranges.push(current);
}
} else if instr.has_child_or_token_by_kind(Instr::can_cast)
&& let Some(instr_name) = support::token(&instr, SyntaxKind::INSTR_NAME)
{
ranges.push(instr_name.text_range());
} else {
ranges.push(current);
}
});
}
FlowNodeKind::BlockEntry(entry) => {
let Some(node) = entry.to_node(ctx.module) else {
return;
};
if let Some((prev, last)) = node.prev_siblings().next().zip(ranges.last_mut())
&& last.contains_range(prev.text_range())
{
*last = last.cover(node.text_range());
} else {
ranges.push(node.text_range());
}
}
_ => {}
}
});
diagnostics.extend(ranges.into_iter().map(|range| Diagnostic {
range,
severity,
code: DIAGNOSTIC_CODE.into(),
message: "unreachable code".into(),
tags: Some(vec![DiagnosticTag::Unnecessary]),
..Default::default()
}));
ctx.bump.reset();
}