1use std::path::PathBuf;
9
10use bock_air::{lower_module, resolve_names_with_registry, ModuleRegistry, NodeIdGen, SymbolTable};
11use bock_errors::{Diagnostic, DiagnosticBag};
12use bock_lexer::Lexer;
13use bock_parser::Parser;
14use bock_source::SourceMap;
15use bock_types::{seed_imports, FnType, PrimitiveType, Strictness, Type, TypeChecker};
16
17pub struct CheckResult {
19 pub source_map: SourceMap,
22 pub file_id: bock_errors::FileId,
24 pub diagnostics: Vec<Diagnostic>,
26}
27
28#[must_use]
35pub fn check_document(path: PathBuf, content: String) -> CheckResult {
36 let mut source_map = SourceMap::new();
37 let file_id = source_map.add_file(path, content);
38 let mut diagnostics: Vec<Diagnostic> = Vec::new();
39
40 let source_file = source_map.get_file(file_id);
44
45 let mut lexer = Lexer::new(source_file);
47 let tokens = lexer.tokenize();
48 push_all(&mut diagnostics, lexer.diagnostics());
49
50 if has_errors(&diagnostics) {
51 return CheckResult {
52 source_map,
53 file_id,
54 diagnostics,
55 };
56 }
57
58 let mut parser = Parser::new(tokens, source_file);
60 let module = parser.parse_module();
61 push_all(&mut diagnostics, parser.diagnostics());
62
63 if has_errors(&diagnostics) {
64 return CheckResult {
65 source_map,
66 file_id,
67 diagnostics,
68 };
69 }
70
71 let registry = ModuleRegistry::new();
73 let mut symbols = SymbolTable::new();
74 let resolve_diags = resolve_names_with_registry(&module, &mut symbols, ®istry);
75 push_all(&mut diagnostics, &resolve_diags);
76
77 if has_errors(&diagnostics) {
78 return CheckResult {
79 source_map,
80 file_id,
81 diagnostics,
82 };
83 }
84
85 let id_gen = NodeIdGen::new();
87 let mut air_module = lower_module(&module, &id_gen, &symbols);
88
89 let mut checker = TypeChecker::new();
91 register_builtins(&mut checker);
92 seed_imports(&mut checker, &module.imports, ®istry);
93 checker.check_module(&mut air_module);
94 push_all(&mut diagnostics, &checker.diags);
95
96 let ownership_diags = bock_types::analyze_ownership(&air_module);
98 push_all(&mut diagnostics, &ownership_diags);
99
100 let strictness = Strictness::Development;
101 let effect_diags = bock_types::track_effects(&air_module, strictness);
102 push_all(&mut diagnostics, &effect_diags);
103
104 let capability_diags = bock_types::verify_capabilities(&air_module, strictness);
105 push_all(&mut diagnostics, &capability_diags);
106
107 CheckResult {
108 source_map,
109 file_id,
110 diagnostics,
111 }
112}
113
114fn push_all(acc: &mut Vec<Diagnostic>, bag: &DiagnosticBag) {
115 for diag in bag.iter() {
116 acc.push(diag.clone());
117 }
118}
119
120fn has_errors(diagnostics: &[Diagnostic]) -> bool {
121 diagnostics
122 .iter()
123 .any(|d| d.severity == bock_errors::Severity::Error)
124}
125
126fn register_builtins(checker: &mut TypeChecker) {
133 let io_fn_ty = Type::Function(FnType {
134 params: vec![Type::Primitive(PrimitiveType::String)],
135 ret: Box::new(Type::Primitive(PrimitiveType::Void)),
136 effects: vec![],
137 });
138 for name in ["print", "println", "debug"] {
139 checker.env.define(name, io_fn_ty.clone());
140 }
141
142 let assert_ty = Type::Function(FnType {
143 params: vec![Type::Primitive(PrimitiveType::Bool)],
144 ret: Box::new(Type::Primitive(PrimitiveType::Void)),
145 effects: vec![],
146 });
147 checker.env.define("assert", assert_ty);
148
149 let expect_ty = Type::Function(FnType {
150 params: vec![Type::Error],
151 ret: Box::new(Type::Error),
152 effects: vec![],
153 });
154 checker.env.define("expect", expect_ty);
155
156 let never_fn_ty = Type::Function(FnType {
157 params: vec![],
158 ret: Box::new(Type::Primitive(PrimitiveType::Never)),
159 effects: vec![],
160 });
161 for name in ["todo", "unreachable"] {
162 checker.env.define(name, never_fn_ty.clone());
163 }
164
165 let constructor_ty = Type::Function(FnType {
166 params: vec![Type::Error],
167 ret: Box::new(Type::Error),
168 effects: vec![],
169 });
170 for name in ["Ok", "Err", "Some"] {
171 checker.env.define(name, constructor_ty.clone());
172 }
173 checker.env.define("None", Type::Error);
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn valid_empty_module_has_no_errors() {
182 let src = "module m\n";
183 let result = check_document(PathBuf::from("test.bock"), src.to_string());
184 let errors: Vec<_> = result
185 .diagnostics
186 .iter()
187 .filter(|d| d.severity == bock_errors::Severity::Error)
188 .collect();
189 assert!(errors.is_empty(), "unexpected errors: {errors:#?}");
190 }
191
192 #[test]
193 fn syntax_error_produces_diagnostic() {
194 let src = "module m\nlet x 1\n";
196 let result = check_document(PathBuf::from("test.bock"), src.to_string());
197 assert!(
198 result
199 .diagnostics
200 .iter()
201 .any(|d| d.severity == bock_errors::Severity::Error),
202 "expected at least one error diagnostic",
203 );
204 }
205}