1pub 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 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 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 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 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 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 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 pub fn push_hover(&mut self, span: Span, text: impl Into<String>) {
151 self.hovers.push((span, text.into()));
152 }
153
154 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 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
183fn 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}