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