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 for module in modules {
99 exported_signature_types::run_module(
100 package_name,
101 "module",
102 module.symbols,
103 module.exports,
104 &mut diags,
105 );
106 }
107 exported_signature_types::run(package_name, "package", package, exports, &mut diags);
108 exported_docs::run(typed_ast, package, exports, &mut diags);
109 let declarations = TypeDeclarations::for_package(package, dependencies);
110 run_rules(typed_ast, &declarations, Source::Package, &mut diags)
111 .and_then(|()| check_discipline::run(typed_ast, &mut diags))
112 .map_err(|fatal| CompileError {
113 diagnostics: diags.clone(),
114 fatal: Some(fatal),
115 })?;
116 Ok(diags)
117}
118
119#[derive(Clone, Copy, PartialEq, Eq)]
120enum Source {
121 Script,
122 Package,
123}
124
125fn run_rules(
127 ta: &TypedAst,
128 declarations: &TypeDeclarations<'_>,
129 source: Source,
130 diags: &mut Vec<Diagnostic>,
131) -> Result<(), CompilerFailure> {
132 missing_return::run(ta, declarations, diags)?;
133 unreachable::run(ta, declarations, diags)?;
134 return_outside_function::run(ta, diags)?;
135 fallthrough::run(ta, declarations, diags)?;
136 definite_assignment::run(ta, diags)?;
137 super_call::run(ta, diags)?;
138 if source == Source::Script {
139 main_required::run(ta, diags);
141 }
142 doc_consistency::run(ta, diags)?;
143 capability_consistency::run(ta, declarations, diags)
144}
145
146#[cfg(test)]
147mod test_util {
148 use std::collections::BTreeMap;
149
150 use super::check_script;
151 use crate::{
152 Asi, Ast, Diagnostic, FileId, ModulePath, PackageDeclaration, Sources, Token, TokenKind,
153 TypedAst, infer, infer_package, lower_patterns, parse,
154 };
155
156 pub fn run(source: &str) -> Vec<Diagnostic> {
159 let with_main = format!("function main(): void {{ }}\n{source}");
160 pipeline(&with_main)
161 }
162
163 pub fn run_raw(source: &str) -> Vec<Diagnostic> {
164 pipeline(source)
165 }
166
167 pub fn run_lines(source: &str) -> Vec<(String, usize)> {
170 run_raw(source)
171 .into_iter()
172 .map(|diag| {
173 let line = source[..diag.span.start as usize].lines().count().max(1);
174 (diag.message, line)
175 })
176 .collect()
177 }
178
179 pub fn infer_script(source: &str) -> (TypedAst, Vec<Diagnostic>) {
182 infer_script_with(source, &[])
183 }
184
185 pub fn infer_script_with(
187 source: &str,
188 packages: &[PackageDeclaration],
189 ) -> (TypedAst, Vec<Diagnostic>) {
190 let ast = parse_source(source, FileId(0));
191 let dependencies = runtime_declarations();
192 let dependencies: Vec<_> = dependencies.iter().chain(packages).collect();
193 infer(source, "main", &ast, &dependencies)
194 }
195
196 pub fn run_package(
198 name: &str,
199 modules: &[(&str, &str)],
200 dependencies: &[PackageDeclaration],
201 ) -> (TypedAst, PackageDeclaration, Vec<Diagnostic>) {
202 let mut sources = Sources::new();
203 let parsed: Vec<(ModulePath, FileId, Ast)> = modules
204 .iter()
205 .map(|(module, source)| {
206 let file = sources.add((*module).to_string(), *source).unwrap();
207 let ast = lower_patterns(parse_source(source, file)).unwrap();
208 (ModulePath::from(*module), file, ast)
209 })
210 .collect();
211 let external: BTreeMap<String, PackageDeclaration> = runtime_declarations()
212 .into_iter()
213 .chain(dependencies.iter().cloned())
214 .map(|declaration| (declaration.package_name.clone(), declaration))
215 .collect();
216 infer_package(
217 name,
218 ModulePath::from("lib"),
219 parsed
220 .iter()
221 .map(|(module, file, ast)| (module.clone(), *file, ast))
222 .collect(),
223 &sources,
224 external,
225 BTreeMap::new(),
226 )
227 }
228
229 fn pipeline(source: &str) -> Vec<Diagnostic> {
230 let ast = parse_source(source, FileId(0));
231 let (prelude_defs, host_defs, _) =
232 crate::runtime::prelude::cached_runtime_package_declarations();
233 let mut packages = Vec::with_capacity(prelude_defs.len() + host_defs.len());
234 packages.extend(prelude_defs.iter());
235 packages.extend(host_defs.iter());
236 let (ta, mut diags) = infer(source, "main", &ast, &packages);
237 diags.extend(check_script(&ta, &packages).unwrap());
238 diags
239 }
240
241 fn parse_source(source: &str, file: FileId) -> Ast {
242 let mut asi = Asi::new(source, file);
243 let mut tokens: Vec<Token> = Vec::new();
244 loop {
245 let tok = asi.next_token();
246 let is_eof = matches!(tok.kind, TokenKind::Eof);
247 tokens.push(tok);
248 if is_eof {
249 break;
250 }
251 }
252 let lex_diags = asi.into_diagnostics();
253 assert!(
254 lex_diags.is_empty(),
255 "unexpected lexer diags: {lex_diags:?}"
256 );
257 let (ast, parse_diags) = parse(source, tokens, file);
258 assert!(
259 parse_diags.is_empty(),
260 "unexpected parser diags: {parse_diags:?}"
261 );
262 ast
263 }
264
265 fn runtime_declarations() -> Vec<PackageDeclaration> {
266 let (prelude_defs, host_defs, _) =
267 crate::runtime::prelude::cached_runtime_package_declarations();
268 prelude_defs
269 .iter()
270 .chain(host_defs)
271 .cloned()
272 .chain(crate::stdlib::stdlib_package_declarations())
273 .collect()
274 }
275}