submilli_engine/typechecker/rules/
mod.rs1pub(crate) mod body_walk;
6mod capability_consistency;
7mod check_calls;
8mod check_discipline;
9mod control_flow;
10mod declarations;
11mod definite_assignment;
12mod doc_consistency;
13mod exported_docs;
14pub(crate) mod exported_signature_types;
15mod fallthrough;
16mod finalizers;
17mod main_required;
18mod missing_return;
19mod return_outside_function;
20mod super_call;
21mod unreachable;
22
23use crate::compiler_error::{CompileError, CompilerFailure, CompilerStage};
24use crate::{Diagnostic, ExportEntry, PackageDeclaration, Type, TypedAst, tree_height};
25
26use super::infer::module_symbols::ModuleSymbols;
27use declarations::TypeDeclarations;
28
29pub fn check(ta: &TypedAst) -> Result<Vec<Diagnostic>, CompileError> {
32 check_script(ta, &[])
33}
34
35pub fn check_script(
42 ta: &TypedAst,
43 dependencies: &[&PackageDeclaration],
44) -> Result<Vec<Diagnostic>, CompileError> {
45 tree_height::check_typed(ta, CompilerStage::Infer)?;
46 super::infer::check_declaration_types(dependencies.iter().copied())?;
47 let declarations = TypeDeclarations::for_script(ta, dependencies);
48 let mut diags = Vec::new();
49 run_rules(ta, &declarations, Source::Script, &mut diags).map_err(|fatal| CompileError {
50 diagnostics: diags.clone(),
51 fatal: Some(fatal),
52 })?;
53 Ok(diags)
54}
55
56pub fn capability_binding_type(
63 package: &PackageDeclaration,
64 dependencies: &[&PackageDeclaration],
65 ty: &Type,
66 path: &[String],
67) -> Option<Type> {
68 let declarations = TypeDeclarations::for_package(package, dependencies);
69 match capability_consistency::binding_target(&declarations, ty, path) {
70 capability_consistency::BindingTarget::Found(ty) => Some(ty),
71 capability_consistency::BindingTarget::Missing(_)
72 | capability_consistency::BindingTarget::Unresolved => None,
73 }
74}
75
76pub(in crate::typechecker) struct PackageModuleSurface<'a> {
77 pub(in crate::typechecker) symbols: &'a ModuleSymbols,
78 pub(in crate::typechecker) exports: &'a [ExportEntry],
79}
80
81pub(in crate::typechecker) fn check_package<'a>(
84 package_name: &str,
85 typed_ast: &TypedAst,
86 package: &PackageDeclaration,
87 resolver: super::infer::assignable::TypeResolver<'_>,
88 modules: impl IntoIterator<Item = PackageModuleSurface<'a>>,
89 dependencies: &[&PackageDeclaration],
90) -> Result<Vec<Diagnostic>, CompileError> {
91 tree_height::check_typed(typed_ast, CompilerStage::Infer)?;
92 let mut diags = Vec::new();
93 finalizers::run(typed_ast, package, resolver, &mut diags).map_err(|fatal| CompileError {
94 diagnostics: diags.clone(),
95 fatal: Some(fatal),
96 })?;
97 let exports = &typed_ast.exports;
98 let module_surfaces: Vec<_> = modules
99 .into_iter()
100 .map(|module| (module.symbols, module.exports))
101 .collect();
102 exported_signature_types::run_modules(package_name, "module", &module_surfaces, &mut diags);
103 exported_signature_types::run(package_name, "package", package, exports, &mut diags);
104 exported_docs::run(typed_ast, package, exports, &mut diags);
105 let declarations = TypeDeclarations::for_package(package, dependencies);
106 run_rules(typed_ast, &declarations, Source::Package, &mut diags)
107 .and_then(|()| check_discipline::run(typed_ast, &mut diags))
108 .map_err(|fatal| CompileError {
109 diagnostics: diags.clone(),
110 fatal: Some(fatal),
111 })?;
112 Ok(diags)
113}
114
115#[derive(Clone, Copy, PartialEq, Eq)]
116enum Source {
117 Script,
118 Package,
119}
120
121fn run_rules(
123 ta: &TypedAst,
124 declarations: &TypeDeclarations<'_>,
125 source: Source,
126 diags: &mut Vec<Diagnostic>,
127) -> Result<(), CompilerFailure> {
128 missing_return::run(ta, declarations, diags)?;
129 unreachable::run(ta, declarations, diags)?;
130 return_outside_function::run(ta, diags)?;
131 fallthrough::run(ta, declarations, diags)?;
132 definite_assignment::run(ta, diags)?;
133 super_call::run(ta, diags)?;
134 if source == Source::Script {
135 main_required::run(ta, diags);
137 }
138 doc_consistency::run(ta, diags)?;
139 capability_consistency::run(ta, declarations, diags)
140}
141
142#[cfg(test)]
143pub(crate) mod test_util {
144 use std::collections::BTreeMap;
145
146 use super::check_script;
147 use crate::{
148 Asi, Ast, Diagnostic, FileId, ModulePath, PackageDeclaration, Sources, Token, TokenKind,
149 TypedAst, infer, infer_package, lower_patterns, parse,
150 };
151
152 pub fn run(source: &str) -> Vec<Diagnostic> {
155 let with_main = format!("function main(): void {{ }}\n{source}");
156 pipeline(&with_main)
157 }
158
159 pub fn run_raw(source: &str) -> Vec<Diagnostic> {
160 pipeline(source)
161 }
162
163 pub fn compile_messages(source: &str) -> Vec<String> {
166 crate::typecheck(source, FileId(0))
167 .err()
168 .unwrap_or_default()
169 .into_iter()
170 .map(|diag| diag.message)
171 .collect()
172 }
173
174 pub fn run_lines(source: &str) -> Vec<(String, usize)> {
177 run_raw(source)
178 .into_iter()
179 .map(|diag| {
180 let line = source[..diag.span.start as usize].lines().count().max(1);
181 (diag.message, line)
182 })
183 .collect()
184 }
185
186 pub fn infer_script(source: &str) -> (TypedAst, Vec<Diagnostic>) {
189 infer_script_with(source, &[])
190 }
191
192 pub fn infer_script_with(
194 source: &str,
195 packages: &[PackageDeclaration],
196 ) -> (TypedAst, Vec<Diagnostic>) {
197 let ast = parse_source(source, FileId(0));
198 let dependencies = runtime_declarations();
199 let dependencies: Vec<_> = dependencies.iter().chain(packages).collect();
200 infer(source, "main", &ast, &dependencies)
201 }
202
203 pub fn run_package(
205 name: &str,
206 modules: &[(&str, &str)],
207 dependencies: &[PackageDeclaration],
208 ) -> (TypedAst, PackageDeclaration, Vec<Diagnostic>) {
209 let mut sources = Sources::new();
210 let parsed: Vec<(ModulePath, FileId, Ast)> = modules
211 .iter()
212 .map(|(module, source)| {
213 let file = sources.add((*module).to_string(), *source).unwrap();
214 let ast = lower_patterns(parse_source(source, file)).unwrap();
215 (ModulePath::from(*module), file, ast)
216 })
217 .collect();
218 let external: BTreeMap<String, PackageDeclaration> = runtime_declarations()
219 .into_iter()
220 .chain(dependencies.iter().cloned())
221 .map(|declaration| (declaration.package_name.clone(), declaration))
222 .collect();
223 infer_package(
224 name,
225 ModulePath::from("lib"),
226 parsed
227 .iter()
228 .map(|(module, file, ast)| (module.clone(), *file, ast))
229 .collect(),
230 &sources,
231 external,
232 BTreeMap::new(),
233 )
234 }
235
236 fn pipeline(source: &str) -> Vec<Diagnostic> {
237 let ast = parse_source(source, FileId(0));
238 let (prelude_defs, host_defs, _) =
239 crate::runtime::prelude::cached_runtime_package_declarations();
240 let mut packages = Vec::with_capacity(prelude_defs.len() + host_defs.len());
241 packages.extend(prelude_defs.iter());
242 packages.extend(host_defs.iter());
243 let (ta, mut diags) = infer(source, "main", &ast, &packages);
244 diags.extend(check_script(&ta, &packages).unwrap());
245 diags
246 }
247
248 fn parse_source(source: &str, file: FileId) -> Ast {
249 let mut asi = Asi::new(source, file);
250 let mut tokens: Vec<Token> = Vec::new();
251 loop {
252 let tok = asi.next_token();
253 let is_eof = matches!(tok.kind, TokenKind::Eof);
254 tokens.push(tok);
255 if is_eof {
256 break;
257 }
258 }
259 let lex_diags = asi.into_diagnostics();
260 assert!(
261 lex_diags.is_empty(),
262 "unexpected lexer diags: {lex_diags:?}"
263 );
264 let (ast, parse_diags) = parse(source, tokens, file);
265 assert!(
266 parse_diags.is_empty(),
267 "unexpected parser diags: {parse_diags:?}"
268 );
269 ast
270 }
271
272 fn runtime_declarations() -> Vec<PackageDeclaration> {
273 let (prelude_defs, host_defs, _) =
274 crate::runtime::prelude::cached_runtime_package_declarations();
275 prelude_defs
276 .iter()
277 .chain(host_defs)
278 .cloned()
279 .chain(crate::stdlib::stdlib_package_declarations())
280 .collect()
281 }
282}