use brink_ir::hir::visit::{self, HirVisitor};
use brink_ir::{
BlockStmt, Diagnostic, DiagnosticCode, ElseBranch, Expr, FileId, HirFile, IfStmt,
ResolutionMap, Stmt,
};
use crate::determinism::LookupSet;
pub fn check(files: &[(FileId, &HirFile)], resolutions: &ResolutionMap) -> Vec<Diagnostic> {
let resolved: LookupSet<(FileId, rowan::TextRange)> =
resolutions.iter().map(|r| (r.file, r.range)).collect();
let mut out = Vec::new();
for &(file, hir) in files {
let mut v = NoneDeclVisitor {
file,
resolved: &resolved,
diagnostics: &mut out,
};
for var in &hir.variables {
if var.annotation.is_none() {
v.check_init(&var.name.text, Some(&var.value));
}
}
for c in &hir.constants {
if c.annotation.is_none() {
v.check_init(&c.name.text, Some(&c.value));
}
}
visit::visit(hir, &mut v);
}
out
}
struct NoneDeclVisitor<'a> {
file: FileId,
resolved: &'a LookupSet<(FileId, rowan::TextRange)>,
diagnostics: &'a mut Vec<Diagnostic>,
}
impl NoneDeclVisitor<'_> {
fn check_init(&mut self, decl_name: &str, init: Option<&Expr>) {
let Some(Expr::Path(p)) = init else { return };
let [seg] = p.segments.as_slice() else {
return;
};
if seg.text != "none" || self.resolved.contains(&(self.file, p.range)) {
return;
}
self.diagnostics.push(Diagnostic {
file: self.file,
range: p.range,
message: format!(
"{}: `{decl_name}` is declared from a bare `none`, which carries no \
element type — initialize from `some(x)` or an Option-returning \
verb (`find`/`get`/`pop`/…) instead",
DiagnosticCode::E107.title(),
),
code: DiagnosticCode::E107,
});
}
fn check_block_stmts(&mut self, stmts: &[BlockStmt]) {
for s in stmts {
match s {
BlockStmt::TempDecl(t) => {
if t.annotation.is_none() {
self.check_init(&t.name.text, t.value.as_ref());
}
}
BlockStmt::If(i) => self.check_if_stmt(i),
BlockStmt::While(w) => self.check_block_stmts(&w.body),
BlockStmt::For(f) => self.check_block_stmts(&f.body),
BlockStmt::Assignment(_)
| BlockStmt::Return(_)
| BlockStmt::ExprStmt(_)
| BlockStmt::Break(_)
| BlockStmt::Continue(_)
| BlockStmt::Await(_) => {}
}
}
}
fn check_if_stmt(&mut self, i: &IfStmt) {
self.check_block_stmts(&i.body);
match &i.else_branch {
Some(ElseBranch::ElseIf(inner)) => self.check_if_stmt(inner),
Some(ElseBranch::Else(stmts)) => self.check_block_stmts(stmts),
None => {}
}
}
}
impl HirVisitor for NoneDeclVisitor<'_> {
fn enter_stmt(&mut self, stmt: &Stmt) {
match stmt {
Stmt::TempDecl(t) => {
if t.annotation.is_none() {
self.check_init(&t.name.text, t.value.as_ref());
}
}
Stmt::LogicBlock(lb) => self.check_block_stmts(&lb.stmts),
_ => {}
}
}
}