Skip to main content

kind_driver/
resolution.rs

1//! Transforms a single book into a book by
2//! reading it and it's dependencies. In the end
3//! it returns a desugared book of all of the
4//! depedencies.
5
6use 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
24/// The extension of kind2 files.
25const 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
38/// Tries to accumulate on a buffer all of the
39/// paths that exists (so we can just throw an
40/// error about ambiguous resolution to the user)
41fn 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
68/// Gets an identifier and tries to get all of the
69/// paths that it can refer into a single path. If
70/// multiple paths are found then we just throw an
71/// error about ambiguous paths.
72fn 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}