Skip to main content

rl_lang/checker/
mod.rs

1//! Static type checker for rl - runs after parsing, before evaluation.
2//!
3//! The checker walks the AST and verifies:
4//! - Variable and constant declarations match their declared types
5//! - Binary/unary operators receive compatible operand types
6//! - Function calls receive the correct number and types of arguments
7//! - `return` types match the enclosing function's declared return type
8//! - `break` and `continue` only appear inside loops
9//! - Array elements are all the same type
10//!
11//! It also populates [`TypeChecker::hovers`] - a side-table of
12//! `(Span, markdown)` pairs used by the LSP hover provider.
13//!
14//! # Two-pass function checking
15//!
16//! [`TypeChecker::check`] does two passes over the statement list:
17//! first it pre-declares all top-level `FunctionDeclaration`s so they
18//! are visible to each other regardless of order, then it checks every
19//! statement body. This allows mutual recursion at the top level.
20
21pub mod operators;
22pub mod scope;
23pub mod statements;
24pub mod structs;
25pub mod types;
26
27use crate::{
28    ast::{
29        Ast,
30        statements::{Statement, StatementKind},
31    },
32    checker::structs::CheckType,
33    interpreter::evaluator::Evaluator,
34    utils::{
35        errors::{Error, Reason},
36        source::SourceFile,
37        span::Span,
38    },
39};
40use std::{
41    collections::{HashMap, HashSet},
42    path::PathBuf,
43};
44pub use structs::TypeChecker;
45
46impl Default for TypeChecker {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl TypeChecker {
53    pub fn new() -> Self {
54        // getting all stdlib modules
55        let root_module = Evaluator::default().with_stdlib().root_module;
56        let mut stdlib_fn_names = std::collections::HashSet::new();
57        collect_fn_names(&root_module, &mut stdlib_fn_names);
58
59        Self {
60            scopes: vec![HashMap::new()],
61            source_file: None,
62            root_module: Evaluator::default().with_stdlib().root_module,
63            errors: Vec::new(),
64            return_type_stack: Vec::new(),
65            loop_depth: 0,
66            stdlib_fn_names,
67            hovers: Vec::new(),
68            base_dir: None,
69            importing: Vec::new(),
70            imported: HashSet::new(),
71            ast_arena: Ast::new(),
72            records: HashMap::new(),
73            tags: HashMap::new(),
74        }
75    }
76
77    // functions for source file for ariadne
78    pub fn with_source_file(mut self, file: SourceFile) -> Self {
79        self.source_file = Some(file);
80        self
81    }
82    pub fn set_source_file(&mut self, file: SourceFile) {
83        self.source_file = Some(file);
84    }
85
86    pub fn with_base_dir(mut self, dir: impl Into<PathBuf>) -> Self {
87        self.base_dir = Some(dir.into());
88        self
89    }
90    pub fn with_ast_arena(mut self, arena: Ast) -> Self {
91        self.ast_arena = arena;
92        self
93    }
94
95    // runs check on every ast statement in the list and returns errors as list
96    pub fn check(&mut self, statements: &[Statement]) -> &[Error] {
97        for statement in statements {
98            if let StatementKind::FunctionDeclaration {
99                name,
100                params,
101                return_type,
102                ..
103            } = &statement.kind
104            {
105                let fn_type = CheckType::Function {
106                    params: params.iter().map(|p| p.param_type.clone()).collect(),
107                    return_type: return_type.clone(),
108                };
109                self.declare(name.clone(), fn_type, false, statement.span);
110            }
111            if let StatementKind::RecordDeclaration { name, fields } = &statement.kind {
112                self.records.insert(name.clone(), fields.clone());
113            }
114            if let StatementKind::TagDeclaration { name, variants } = &statement.kind {
115                self.tags.insert(name.clone(), variants.clone());
116            }
117        }
118        for statement in statements {
119            self.check_statement(statement);
120        }
121        &self.errors
122    }
123
124    // transforms arguments into Error type
125    // for message it accepts str and String types
126    pub fn err(&self, message: impl Into<String>, span: Span) -> Error {
127        let err = Error::at(Reason::Compile, message, span);
128        match &self.source_file {
129            Some(file) => err.with_source_file(file),
130            None => err,
131        }
132    }
133
134    // transforms the arguments into Error type via err() functions
135    // and pushes the error to the errors field
136    pub fn error(&mut self, message: impl Into<String>, span: Span) {
137        let err = self.err(message, span);
138        self.errors.push(err);
139    }
140    // same as error() but with helper
141    pub fn error_with_help(&mut self, message: impl Into<String>, span: Span, help: Option<&str>) {
142        let mut err = self.err(message, span);
143        if let Some(h) = help {
144            err = err.with_help(format!("did you mean `{}`?", h));
145        }
146        self.errors.push(err);
147    }
148
149    // adds markdown hover text for a source span
150    pub fn push_hover(&mut self, span: Span, text: impl Into<String>) {
151        self.hovers.push((span, text.into()));
152    }
153
154    // using find_fn_doc() in `crate::docs` find the current docs
155    // for the function and add the markdown hover for the span of fn
156    fn push_stdlib_hover(&mut self, path: &[String], span: Span) {
157        let fn_name = match path.last() {
158            Some(n) => n.as_str(),
159            None => return,
160        };
161        // get the module to handle std::io::print()
162        // and get print from std::io
163        let module = if path.len() >= 2 {
164            Some(path[path.len() - 2].as_str())
165        } else {
166            None
167        };
168
169        let text = match crate::docs::find_fn_doc(module, fn_name)
170            .or_else(|| crate::docs::find_fn_doc(None, fn_name))
171        {
172            Some((std_entry, func)) => format!(
173                "```rl\nstd::{}::{}\n```\n{}",
174                std_entry.name, func.signature, func.description
175            ),
176            None => format!("```rl\nfn {}(..)\n```\nstdlib function", fn_name),
177        };
178
179        self.push_hover(span, text);
180    }
181}
182
183/// Collects all function names from a stdlib [`Module`] tree into `out`.
184fn collect_fn_names(
185    module: &crate::interpreter::native::Module,
186    out: &mut std::collections::HashSet<String>,
187) {
188    for name in module.functions.keys() {
189        out.insert(name.clone());
190    }
191    for sub in module.submodules.values() {
192        collect_fn_names(sub, out);
193    }
194}