#![allow(clippy::unwrap_used)]
use diag_lang::{Diagnostic, Span};
use intern_lang::{Interner, Symbol};
use symbol_lang::{SymbolTable, duplicate_definition, unresolved_name};
enum Event {
Define(Symbol, Span),
Use(Symbol, Span),
EnterBlock,
ExitBlock,
}
fn resolve(events: &[Event], interner: &Interner) -> Vec<Diagnostic> {
let mut table: SymbolTable<Span> = SymbolTable::new();
let mut diagnostics = Vec::new();
for event in events {
match event {
Event::Define(name, span) => {
if let Some(&first) = table.lookup_local(*name) {
let text = interner.resolve(*name).unwrap_or("?");
diagnostics.push(duplicate_definition(text, *span, first));
}
table.define(*name, *span);
}
Event::Use(name, span) => {
if table.lookup(*name).is_none() {
let text = interner.resolve(*name).unwrap_or("?");
diagnostics.push(unresolved_name(text, *span));
}
}
Event::EnterBlock => table.enter_scope(),
Event::ExitBlock => {
table.exit_scope();
}
}
}
diagnostics
}
#[test]
fn test_well_formed_program_has_no_diagnostics() {
let mut interner = Interner::new();
let x = interner.intern("x");
let y = interner.intern("y");
let program = [
Event::Define(x, Span::new(0, 1)),
Event::EnterBlock,
Event::Define(y, Span::new(2, 3)),
Event::Use(x, Span::new(4, 5)), Event::Use(y, Span::new(6, 7)), Event::ExitBlock,
];
let diagnostics = resolve(&program, &interner);
assert!(diagnostics.is_empty());
}
#[test]
fn test_use_after_scope_exit_is_unresolved() {
let mut interner = Interner::new();
let y = interner.intern("y");
let program = [
Event::EnterBlock,
Event::Define(y, Span::new(0, 1)),
Event::ExitBlock,
Event::Use(y, Span::new(5, 6)),
];
let diagnostics = resolve(&program, &interner);
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].message(), "cannot find `y` in this scope");
assert_eq!(diagnostics[0].primary().span(), Span::new(5, 6));
}
#[test]
fn test_duplicate_in_same_scope_is_reported_with_both_sites() {
let mut interner = Interner::new();
let x = interner.intern("x");
let program = [
Event::Define(x, Span::new(0, 1)),
Event::Define(x, Span::new(8, 9)),
];
let diagnostics = resolve(&program, &interner);
assert_eq!(diagnostics.len(), 1);
assert_eq!(
diagnostics[0].message(),
"the name `x` is defined multiple times"
);
assert_eq!(diagnostics[0].primary().span(), Span::new(8, 9));
assert_eq!(diagnostics[0].secondary()[0].span(), Span::new(0, 1));
}
#[test]
fn test_shadowing_in_inner_scope_is_not_a_duplicate() {
let mut interner = Interner::new();
let x = interner.intern("x");
let program = [
Event::Define(x, Span::new(0, 1)),
Event::EnterBlock,
Event::Define(x, Span::new(4, 5)),
Event::ExitBlock,
];
let diagnostics = resolve(&program, &interner);
assert!(diagnostics.is_empty());
}