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