1use core::fmt;
7use fxhash::FxHashSet;
8use kind_pass::expand::expand_module;
9use kind_pass::expand::uses::expand_uses;
10use std::error::Error;
11use std::fs;
12use std::path::{Path, PathBuf};
13use std::rc::Rc;
14use strsim::jaro;
15
16use kind_pass::unbound::{self, UnboundCollector};
17use kind_report::data::Diagnostic;
18use kind_tree::concrete::visitor::Visitor;
19use kind_tree::concrete::{Book, Module, TopLevel};
20use kind_tree::symbol::{Ident, QualifiedIdent};
21
22use crate::{diagnostic::DriverDiagnostic, session::Session};
23
24const EXT: &str = "kind2";
26
27#[derive(Debug)]
28pub struct ResolutionError;
29
30impl fmt::Display for ResolutionError {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 write!(f, "resolution error")
33 }
34}
35
36impl Error for ResolutionError {}
37
38fn accumulate_neighbour_paths(
42 ident: &QualifiedIdent,
43 raw_path: &Path,
44) -> Result<Option<PathBuf>, Box<dyn Diagnostic>> {
45 let mut canon_path = raw_path.to_path_buf();
46 let mut dir_file_path = canon_path.clone();
47 let dir_path = canon_path.clone();
48
49 canon_path.set_extension(EXT);
50
51 dir_file_path.push("_");
52 dir_file_path.set_extension(EXT);
53
54 if canon_path.exists() && dir_path.exists() && canon_path.is_file() && dir_path.is_dir() {
55 Err(Box::new(DriverDiagnostic::MultiplePaths(
56 ident.clone(),
57 vec![canon_path, dir_path],
58 )))
59 } else if canon_path.is_file() {
60 Ok(Some(canon_path))
61 } else if dir_file_path.is_file() {
62 Ok(Some(dir_file_path))
63 } else {
64 Ok(None)
65 }
66}
67
68fn ident_to_path(
73 root: &Path,
74 ident: &QualifiedIdent,
75 search_on_parent: bool,
76) -> Result<Option<PathBuf>, Box<dyn Diagnostic>> {
77 let name = ident.to_string();
78 let segments = name.as_str().split('.').collect::<Vec<&str>>();
79 let mut raw_path = root.to_path_buf();
80
81 raw_path.push(PathBuf::from(segments.join("/")));
82
83 match accumulate_neighbour_paths(ident, &raw_path) {
84 Ok(None) if search_on_parent => {
85 raw_path.pop();
86 accumulate_neighbour_paths(ident, &raw_path)
87 }
88 rest => rest,
89 }
90}
91
92fn try_to_insert_new_name<'a>(
93 failed: &mut bool,
94 session: &'a Session,
95 ident: QualifiedIdent,
96 book: &'a mut Book,
97) -> bool {
98 if let Some(first_occorence) = book.names.get(ident.to_string().as_str()) {
99 let err = Box::new(DriverDiagnostic::DefinedMultipleTimes(
100 first_occorence.clone(),
101 ident,
102 ));
103
104 session.diagnostic_sender.send(err).unwrap();
105 *failed = true;
106 false
107 } else {
108 book.names.insert(ident.to_string(), ident);
109 true
110 }
111}
112
113fn module_to_book<'a>(
114 failed: &mut bool,
115 session: &'a Session,
116 module: Module,
117 book: &'a mut Book,
118) -> FxHashSet<String> {
119 let mut public_names = FxHashSet::default();
120
121 for entry in module.entries {
122 match entry {
123 TopLevel::SumType(sum) => {
124 let name = sum.name.to_string();
125
126 public_names.insert(name.clone());
127
128 for cons in &sum.constructors {
129 let mut cons_ident = sum.name.add_segment(cons.name.to_str());
130 cons_ident.range = cons.name.range;
131 if try_to_insert_new_name(failed, session, cons_ident.clone(), book) {
132 let cons_name = cons_ident.to_string();
133 public_names.insert(cons_name.clone());
134 book.meta.insert(cons_name, cons.extract_book_info(&sum));
135 }
136 }
137
138 if try_to_insert_new_name(failed, session, sum.name.clone(), book) {
139 book.meta.insert(name.clone(), sum.extract_book_info());
140 book.entries.insert(name, TopLevel::SumType(sum));
141 }
142 }
143 TopLevel::RecordType(rec) => {
144 let name = rec.name.to_string();
145 public_names.insert(name.clone());
146 book.meta.insert(name.clone(), rec.extract_book_info());
147
148 try_to_insert_new_name(failed, session, rec.name.clone(), book);
149
150 let cons_ident = rec.name.add_segment(rec.constructor.to_str());
151 public_names.insert(cons_ident.to_string());
152 book.meta.insert(
153 cons_ident.to_string(),
154 rec.extract_book_info_of_constructor(),
155 );
156
157 try_to_insert_new_name(failed, session, cons_ident, book);
158
159 book.entries.insert(name.clone(), TopLevel::RecordType(rec));
160 }
161 TopLevel::Entry(entr) => {
162 let name = entr.name.to_string();
163
164 try_to_insert_new_name(failed, session, entr.name.clone(), book);
165 public_names.insert(name.clone());
166 book.meta.insert(name.clone(), entr.extract_book_info());
167 book.entries.insert(name, TopLevel::Entry(entr));
168 }
169 }
170 }
171
172 public_names
173}
174
175fn parse_and_store_book_by_identifier(
176 session: &mut Session,
177 ident: &QualifiedIdent,
178 book: &mut Book,
179) -> bool {
180 if book.entries.contains_key(ident.to_string().as_str()) {
181 return false;
182 }
183
184 match ident_to_path(&session.root, ident, true) {
185 Ok(Some(path)) => parse_and_store_book_by_path(session, &path, book, false),
186 Ok(None) => false,
187 Err(err) => {
188 session.diagnostic_sender.send(err).unwrap();
189 true
190 }
191 }
192}
193
194fn read_file(session: &mut Session, path: &Path) -> Option<String> {
195 match fs::read_to_string(path) {
196 Ok(res) => Some(res),
197 Err(_) => {
198 session
199 .diagnostic_sender
200 .send(Box::new(DriverDiagnostic::CannotFindFile(
201 path.to_str().unwrap().to_string(),
202 )))
203 .unwrap();
204 None
205 }
206 }
207}
208
209fn parse_and_store_book_by_path(session: &mut Session, path: &PathBuf, book: &mut Book, immediate: bool) -> bool {
210 if !path.exists() {
211 let err = Box::new(DriverDiagnostic::CannotFindFile(
212 path.to_str().unwrap().to_string(),
213 ));
214
215 session.diagnostic_sender.send(err).unwrap();
216 return true;
217 }
218
219 let canon_path = &fs::canonicalize(path).unwrap();
220
221 if session.loaded_paths_map.contains_key(canon_path) {
222 return false;
223 }
224
225 let Some(input) = read_file(session, path) else { return true };
226
227 let ctx_id = session.book_counter;
228 session.add_path(Rc::new(fs::canonicalize(path).unwrap()), input.clone());
229
230 let tx = session.diagnostic_sender.clone();
231
232 let (mut module, mut failed) = kind_parser::parse_book(tx.clone(), ctx_id, &input);
233
234 expand_uses(&mut module, tx.clone());
235 expand_module(tx.clone(), &mut module);
236
237 let mut state = UnboundCollector::new(tx.clone(), false);
238 state.visit_module(&mut module);
239
240 module_to_book(&mut failed, session, module, book);
241
242 for idents in state.unbound_top_level.values() {
243 let fst = idents.iter().next().unwrap();
244
245 if immediate && session.show_immediate_deps {
246 println!("{}", fst);
247 }
248
249 if !book.names.contains_key(&fst.to_string()) {
250 failed |= parse_and_store_book_by_identifier(session, fst, book);
251 }
252 }
253
254 failed
255}
256
257pub fn get_unbound_variables(session: &mut Session, path: &Path) -> Option<Vec<String>> {
258 let tx = session.diagnostic_sender.clone();
259
260 let Some(input) = read_file(session, path) else { return None };
261
262 let (mut module, _) = kind_parser::parse_book(tx.clone(), 0, &input);
263
264 expand_uses(&mut module, tx.clone());
265 expand_module(tx.clone(), &mut module);
266
267 let mut state = UnboundCollector::new(tx.clone(), false);
268 state.visit_module(&mut module);
269
270 Some(state.unbound_top_level.keys().cloned().collect())
271}
272
273
274fn unbound_variable(session: &mut Session, book: &Book, idents: &[Ident]) {
275 let mut similar_names = book
276 .names
277 .keys()
278 .map(|x| (jaro(x, idents[0].to_str()).abs(), x))
279 .filter(|x| x.0 > 0.8)
280 .collect::<Vec<_>>();
281
282 similar_names.sort_by(|x, y| x.0.total_cmp(&y.0));
283
284 let err = Box::new(DriverDiagnostic::UnboundVariable(
285 idents.to_vec(),
286 similar_names.iter().take(5).map(|x| x.1.clone()).collect(),
287 ));
288
289 session.diagnostic_sender.send(err).unwrap();
290}
291
292pub fn parse_and_store_book(session: &mut Session, path: &PathBuf) -> anyhow::Result<Book> {
293 let mut book = Book::default();
294 if parse_and_store_book_by_path(session, path, &mut book, true) {
295 Err(ResolutionError.into())
296 } else {
297 Ok(book)
298 }
299}
300
301pub fn check_unbound_top_level(session: &mut Session, book: &mut Book) -> anyhow::Result<()> {
302 let mut failed = false;
303
304 let (unbound_names, unbound_tops) =
305 unbound::get_book_unbound(session.diagnostic_sender.clone(), book, true);
306
307 for unbound in unbound_tops.values() {
308 let res: Vec<Ident> = unbound
309 .iter()
310 .filter(|x| !x.generated)
311 .map(|x| x.to_ident())
312 .collect();
313
314 if !res.is_empty() {
315 unbound_variable(session, book, &res);
316 failed = true;
317 }
318 }
319
320 for unbound in unbound_names.values() {
321 unbound_variable(session, book, unbound);
322 failed = true;
323 }
324
325 if failed {
326 Err(ResolutionError.into())
327 } else {
328 Ok(())
329 }
330}