1pub(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;
16pub(crate) mod 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, PackageDeclaration, Type, TypedAst, tree_height};
25
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, true, &mut diags).map_err(|fatal| {
49 CompileError {
50 diagnostics: diags.clone(),
51 fatal: Some(fatal),
52 }
53 })?;
54 Ok(diags)
55}
56
57pub fn check_script_submilli(
59 ta: &TypedAst,
60 dependencies: &[&PackageDeclaration],
61) -> Result<Vec<Diagnostic>, CompileError> {
62 tree_height::check_typed(ta, CompilerStage::Infer)?;
63 super::infer::check_declaration_types(dependencies.iter().copied())?;
64 let declarations = TypeDeclarations::for_script(ta, dependencies);
65 let mut diags = Vec::new();
66 run_rules(ta, &declarations, Source::Script, false, &mut diags).map_err(|fatal| {
67 CompileError {
68 diagnostics: diags.clone(),
69 fatal: Some(fatal),
70 }
71 })?;
72 Ok(diags)
73}
74
75pub fn capability_binding_type(
82 package: &PackageDeclaration,
83 dependencies: &[&PackageDeclaration],
84 ty: &Type,
85 path: &[String],
86) -> Option<Type> {
87 let declarations = TypeDeclarations::for_package(package, dependencies);
88 match capability_consistency::binding_target(&declarations, ty, path) {
89 capability_consistency::BindingTarget::Found(ty) => Some(ty),
90 capability_consistency::BindingTarget::Missing(_)
91 | capability_consistency::BindingTarget::Unresolved => None,
92 }
93}
94
95pub fn check_package(
98 typed_ast: &TypedAst,
99 package: &PackageDeclaration,
100 dependencies: &[&PackageDeclaration],
101) -> Result<Vec<Diagnostic>, CompileError> {
102 check_package_rules(
103 typed_ast,
104 package,
105 dependencies,
106 true,
107 &crate::type_size::TypeLimits::default(),
108 )
109}
110
111pub fn check_package_submilli(
113 typed_ast: &TypedAst,
114 package: &PackageDeclaration,
115 dependencies: &[&PackageDeclaration],
116) -> Result<Vec<Diagnostic>, CompileError> {
117 check_package_rules(
118 typed_ast,
119 package,
120 dependencies,
121 false,
122 &crate::type_size::TypeLimits::default(),
123 )
124}
125
126pub(in crate::typechecker) fn check_package_rules(
127 typed_ast: &TypedAst,
128 package: &PackageDeclaration,
129 dependencies: &[&PackageDeclaration],
130 include_typescript: bool,
131 limits: &crate::type_size::TypeLimits,
132) -> Result<Vec<Diagnostic>, CompileError> {
133 tree_height::check_typed(typed_ast, CompilerStage::Infer)?;
134 let root_module = typed_ast.package_root_module.as_ref().ok_or_else(|| {
135 CompileError::from(super::invariant_failure(
136 "package TypedAst lacks its root module path",
137 ))
138 })?;
139 super::infer::check_declaration_types(
140 std::iter::once(package).chain(dependencies.iter().copied()),
141 )?;
142 let mut registry = super::infer::type_registry::TypeRegistry::new();
143 for declaration in std::iter::once(package).chain(dependencies.iter().copied()) {
144 for symbol in declaration
145 .runtime_types
146 .values()
147 .chain(declaration.types.values())
148 {
149 registry.insert_borrowed(symbol);
150 }
151 }
152 let namespace = super::infer::type_namespace::TypeNamespace::new();
153 let constraints = Default::default();
154 let resolver = super::infer::assignable::TypeResolver {
155 types: &namespace,
156 registry: ®istry,
157 limits,
158 strict_subtype: false,
159 generic_constraints: &constraints,
160 };
161 let mut diags = Vec::new();
162 finalizers::run(typed_ast, package, resolver, &mut diags).map_err(|fatal| CompileError {
163 diagnostics: diags.clone(),
164 fatal: Some(fatal),
165 })?;
166 let exports = &typed_ast.exports;
167 exported_signature_types::run_modules(
168 &package.package_name,
169 "module",
170 typed_ast,
171 package,
172 root_module,
173 &mut diags,
174 );
175 exported_signature_types::run(
176 &package.package_name,
177 "package",
178 package,
179 exports,
180 &mut diags,
181 );
182 exported_docs::run(typed_ast, package, exports, &mut diags);
183 let declarations = TypeDeclarations::for_package(package, dependencies);
184 run_rules(
185 typed_ast,
186 &declarations,
187 Source::Package,
188 include_typescript,
189 &mut diags,
190 )
191 .and_then(|()| check_discipline::run(typed_ast, &mut diags))
192 .map_err(|fatal| CompileError {
193 diagnostics: diags.clone(),
194 fatal: Some(fatal),
195 })?;
196 Ok(diags)
197}
198
199#[derive(Clone, Copy, PartialEq, Eq)]
200enum Source {
201 Script,
202 Package,
203}
204
205fn run_rules(
207 ta: &TypedAst,
208 declarations: &TypeDeclarations<'_>,
209 source: Source,
210 include_typescript: bool,
211 diags: &mut Vec<Diagnostic>,
212) -> Result<(), CompilerFailure> {
213 if include_typescript {
214 missing_return::run(ta, declarations, diags)?;
215 unreachable::run(ta, declarations, diags)?;
216 return_outside_function::run(ta, diags)?;
217 fallthrough::run(ta, declarations, diags)?;
218 definite_assignment::run(ta, diags)?;
219 }
220 super_call::run(ta, diags)?;
221 if source == Source::Script {
222 main_required::run(ta, diags);
224 }
225 doc_consistency::run(ta, diags)?;
226 capability_consistency::run(ta, declarations, diags)
227}
228
229#[cfg(test)]
230pub(crate) mod test_util {
231 use std::collections::BTreeMap;
232
233 use super::check_script;
234 use crate::{
235 Asi, Ast, Diagnostic, FileId, ModulePath, PackageDeclaration, Sources, Token, TokenKind,
236 TypedAst, infer, infer_package, lower_patterns, parse,
237 };
238
239 pub fn run(source: &str) -> Vec<Diagnostic> {
242 let with_main = format!("function main(): void {{ }}\n{source}");
243 pipeline(&with_main)
244 }
245
246 pub fn run_raw(source: &str) -> Vec<Diagnostic> {
247 pipeline(source)
248 }
249
250 pub fn compile_messages(source: &str) -> Vec<String> {
253 crate::typecheck(source, FileId(0))
254 .err()
255 .unwrap_or_default()
256 .into_iter()
257 .map(|diag| diag.message)
258 .collect()
259 }
260
261 pub fn run_lines(source: &str) -> Vec<(String, usize)> {
264 run_raw(source)
265 .into_iter()
266 .map(|diag| {
267 let line = source[..diag.span.start as usize].lines().count().max(1);
268 (diag.message, line)
269 })
270 .collect()
271 }
272
273 pub fn infer_script(source: &str) -> (TypedAst, Vec<Diagnostic>) {
276 infer_script_with(source, &[])
277 }
278
279 pub fn infer_script_with(
281 source: &str,
282 packages: &[PackageDeclaration],
283 ) -> (TypedAst, Vec<Diagnostic>) {
284 let ast = parse_source(source, FileId(0));
285 let dependencies = runtime_declarations();
286 let dependencies: Vec<_> = dependencies.iter().chain(packages).collect();
287 infer(source, "main", &ast, &dependencies)
288 }
289
290 pub fn run_package(
292 name: &str,
293 modules: &[(&str, &str)],
294 dependencies: &[PackageDeclaration],
295 ) -> (TypedAst, PackageDeclaration, Vec<Diagnostic>) {
296 let mut sources = Sources::new();
297 let parsed: Vec<(ModulePath, FileId, Ast)> = modules
298 .iter()
299 .map(|(module, source)| {
300 let file = sources.add((*module).to_string(), *source).unwrap();
301 let ast = lower_patterns(parse_source(source, file)).unwrap();
302 (ModulePath::from(*module), file, ast)
303 })
304 .collect();
305 let external: BTreeMap<String, PackageDeclaration> = runtime_declarations()
306 .into_iter()
307 .chain(dependencies.iter().cloned())
308 .map(|declaration| (declaration.package_name.clone(), declaration))
309 .collect();
310 infer_package(
311 name,
312 ModulePath::from("lib"),
313 parsed
314 .iter()
315 .map(|(module, file, ast)| (module.clone(), *file, ast))
316 .collect(),
317 &sources,
318 external,
319 BTreeMap::new(),
320 )
321 }
322
323 fn pipeline(source: &str) -> Vec<Diagnostic> {
324 let ast = parse_source(source, FileId(0));
325 let (prelude_defs, host_defs, _) =
326 crate::runtime::prelude::cached_runtime_package_declarations();
327 let mut packages = Vec::with_capacity(prelude_defs.len() + host_defs.len());
328 packages.extend(prelude_defs.iter());
329 packages.extend(host_defs.iter());
330 let (ta, mut diags) = infer(source, "main", &ast, &packages);
331 diags.extend(check_script(&ta, &packages).unwrap());
332 diags
333 }
334
335 fn parse_source(source: &str, file: FileId) -> Ast {
336 let mut asi = Asi::new(source, file);
337 let mut tokens: Vec<Token> = Vec::new();
338 loop {
339 let tok = asi.next_token();
340 let is_eof = matches!(tok.kind, TokenKind::Eof);
341 tokens.push(tok);
342 if is_eof {
343 break;
344 }
345 }
346 let lex_diags = asi.into_diagnostics();
347 assert!(
348 lex_diags.is_empty(),
349 "unexpected lexer diags: {lex_diags:?}"
350 );
351 let (ast, parse_diags) = parse(source, tokens, file);
352 assert!(
353 parse_diags.is_empty(),
354 "unexpected parser diags: {parse_diags:?}"
355 );
356 ast
357 }
358
359 fn runtime_declarations() -> Vec<PackageDeclaration> {
360 let (prelude_defs, host_defs, _) =
361 crate::runtime::prelude::cached_runtime_package_declarations();
362 prelude_defs
363 .iter()
364 .chain(host_defs)
365 .cloned()
366 .chain(crate::stdlib::stdlib_package_declarations())
367 .collect()
368 }
369}
370
371#[cfg(test)]
372mod public_entry_tests {
373 use super::test_util::{infer_script, run_package};
374 use super::{check_package, check_package_submilli, check_script, check_script_submilli};
375
376 #[test]
377 fn submilli_script_check_omits_typescript_rules() {
378 let (typed_ast, inference) =
379 infer_script("function main(): void { return; const value = 1; }");
380 assert!(
381 !inference
382 .iter()
383 .any(|diagnostic| diagnostic.severity == crate::Severity::Error),
384 "{inference:?}"
385 );
386 let full = check_script(&typed_ast, &[]).unwrap();
387 assert!(
388 full.iter()
389 .any(|diagnostic| diagnostic.message.contains("unreachable"))
390 );
391 assert!(check_script_submilli(&typed_ast, &[]).unwrap().is_empty());
392 let (without_main, _) = infer_script("function helper(): void {}");
393 assert!(
394 check_script_submilli(&without_main, &[])
395 .unwrap()
396 .iter()
397 .any(|diagnostic| diagnostic.message.contains("main"))
398 );
399 }
400
401 #[test]
402 fn package_entry_needs_only_tree_and_declarations() {
403 let (typed_ast, package, inference) = run_package(
404 "@test/public-rules",
405 &[
406 ("lib", "export function entry(): void {}"),
407 ("other", "export function helper(): void {}"),
408 ],
409 &[],
410 );
411 assert!(
412 !inference
413 .iter()
414 .any(|diagnostic| diagnostic.severity == crate::Severity::Error),
415 "{inference:?}"
416 );
417 assert_eq!(
418 check_package(&typed_ast, &package, &[]).unwrap(),
419 check_package_submilli(&typed_ast, &package, &[]).unwrap()
420 );
421 assert_eq!(
422 typed_ast.package_root_module,
423 Some(crate::ModulePath::from("lib"))
424 );
425 let mut without_root = typed_ast.clone();
426 without_root.package_root_module = None;
427 assert!(check_package(&without_root, &package, &[]).is_err());
428 }
429
430 #[test]
431 fn package_entry_checks_private_module_signature_types() {
432 let (typed_ast, package, _) = run_package(
433 "@test/private-type",
434 &[
435 ("lib", "export function entry(): void {}"),
436 (
437 "other",
438 "interface Hidden { value: string } export function expose(value: Hidden): void {}",
439 ),
440 ],
441 &[],
442 );
443 let diagnostics = check_package_submilli(&typed_ast, &package, &[]).unwrap();
444 assert!(
445 diagnostics
446 .iter()
447 .any(|diagnostic| diagnostic.message.contains("private type `Hidden`")),
448 "{diagnostics:?}"
449 );
450 }
451}