Skip to main content

flams_lsp/
state.rs

1use std::{hint::unreachable_unchecked, path::Path};
2
3use crate::Entry;
4use async_lsp::{ClientSocket, LanguageClient, lsp_types as lsp};
5use flams_ftml::FtmlResult;
6use flams_math_archives::{
7    Archive, MathArchive,
8    backend::{AnyBackend, GlobalBackend, HTMLData, TemporaryBackend},
9    source_files::SourceEntry,
10};
11use flams_stex::{
12    OutputCont, RusTeX,
13    quickparse::stex::{DiagnosticLevel, STeXDiagnostic, STeXParseData, STeXParseDataI},
14};
15use flams_utils::{
16    impossible,
17    //prelude::HMap,
18    sourcerefs::{LSPLineCol, StringRange},
19};
20use ftml_ontology::{
21    domain::{declarations::symbols::Symbol, modules::Module},
22    narrative::{
23        documents::Document,
24        elements::{DocumentTerm, LogicalParagraph, VariableDeclaration},
25    },
26    terms::TermContainer,
27    utils::RefTree,
28};
29use ftml_solver::results::{
30    CheckResult, ContentCheckResult, DocumentCheckResult, SymbolCheckResult,
31};
32use ftml_uris::DocumentUri;
33use smallvec::SmallVec;
34
35use crate::{
36    ClientExt, LSPStore, ProgressCallbackServer, annotations::to_diagnostic,
37    documents::LSPDocument, verbalizations::VerbalizationTrie,
38};
39
40#[derive(Clone)]
41pub enum DocData {
42    Doc(LSPDocument),
43    Data(STeXParseData, bool),
44}
45impl DocData {
46    pub fn merge(&mut self, other: Self) {
47        fn merge_a(from: &mut STeXParseDataI, to: &mut STeXParseDataI) {
48            to.dependencies = std::mem::take(&mut from.dependencies);
49            /*for dep in std::mem::take(&mut from.dependents) {
50              to.dependents.insert(dep);
51            }*/
52            for d in std::mem::take(&mut from.diagnostics) {
53                to.diagnostics.insert(d);
54            }
55        }
56        match (self, other) {
57            (Self::Doc(d1), Self::Doc(d2)) => {
58                //merge_a(&mut d1.annotations.lock(),&mut d2.annotations.lock());
59                *d1 = d2;
60            }
61            (Self::Doc(d1), Self::Data(d2, _)) => {
62                merge_a(&mut d2.lock(), &mut d1.annotations.lock());
63            }
64            (d2 @ Self::Data(_, _), Self::Doc(d1)) => {
65                {
66                    let Self::Data(d2, _) = d2 else { impossible!() };
67                    merge_a(&mut d2.lock(), &mut d1.annotations.lock());
68                }
69                *d2 = Self::Doc(d1);
70            }
71            (d1 @ Self::Data(_, false), Self::Data(d2, true)) => {
72                {
73                    let Self::Data(_, _) = d1 else { impossible!() };
74                    //merge_a(&mut d1.lock(),&mut d2.lock());
75                }
76                *d1 = Self::Data(d2, true);
77            }
78            (Self::Data(d1, _), Self::Data(d2, _)) => {
79                merge_a(&mut d2.lock(), &mut d1.lock());
80            }
81        }
82    }
83}
84
85#[derive(Clone, Debug, Hash, PartialEq, Eq)]
86pub enum UrlOrFile {
87    Url(lsp::Url),
88    File(std::sync::Arc<Path>),
89}
90impl UrlOrFile {
91    pub fn name(&self) -> &str {
92        match self {
93            Self::Url(u) => u.path().split('/').next_back().unwrap_or(""),
94            Self::File(p) => p.file_name().and_then(|s| s.to_str()).unwrap_or(""),
95        }
96    }
97}
98impl From<lsp::Url> for UrlOrFile {
99    fn from(value: lsp::Url) -> Self {
100        match value.to_file_path() {
101            Ok(p) => Self::File(p.into()),
102            Err(()) => {
103                tracing::error!("Not a file uri: {value}");
104                Self::Url(value)
105            }
106        }
107    }
108}
109impl Into<lsp::Url> for UrlOrFile {
110    fn into(self) -> lsp::Url {
111        match self {
112            Self::Url(u) => u,
113            Self::File(p) => lsp::Url::from_file_path(p).expect("this shouldn't happen"),
114        }
115    }
116}
117impl std::fmt::Display for UrlOrFile {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match self {
120            Self::Url(u) => u.fmt(f),
121            Self::File(p) => p.display().fmt(f),
122        }
123    }
124}
125
126#[derive(Default, Clone)]
127pub struct LSPState {
128    pub documents: triomphe::Arc<crate::LMap<UrlOrFile, DocData>>, //parking_lot::RwLock<HMap<UrlOrFile, DocData>>>,
129    rustex: triomphe::Arc<std::sync::OnceLock<RusTeX>>,
130    pub(crate) verbalizations: VerbalizationTrie, //backend: TemporaryBackend,
131}
132impl LSPState {
133    #[inline]
134    #[must_use]
135    pub fn backend(&self) -> &TemporaryBackend {
136        let AnyBackend::Temp(t) = flams_system::backend::backend() else {
137            panic!("this is a bug")
138        };
139        t
140    }
141
142    #[must_use]
143    #[inline]
144    pub fn rustex(&self) -> &RusTeX {
145        self.rustex.get_or_init(|| {
146            RusTeX::get().unwrap_or_else(|()| {
147                tracing::error!("Could not initialize RusTeX");
148                panic!("Could not initialize RusTeX")
149            })
150        })
151    }
152
153    pub fn build_html(&self, uri: &UrlOrFile, client: &mut ClientSocket) -> Option<DocumentUri> {
154        let Some(DocData::Doc(doc)) = self.documents.read().get(uri).cloned() else {
155            return None;
156        };
157        let UrlOrFile::File(path) = uri else {
158            return None;
159        }; //.to_file_path().ok()?;
160        let doc_uri = doc.document_uri().cloned()?;
161        if doc.html_up_to_date() {
162            return Some(doc_uri);
163        };
164        doc.relative_path()?;
165        let engine = self
166            .rustex()
167            .builder()
168            .set_sourcerefs(true)
169            .set_font_debug_info(true);
170        let engine = doc.with_text(|text| engine.set_string(path, text))?;
171        ProgressCallbackServer::with(
172            client.clone(),
173            format!("Building {}", uri.name()),
174            None,
175            move |progress| {
176                let out = ClientOutput(std::cell::RefCell::new(progress));
177                let (mut res, old) = engine.set_output(out).run();
178                doc.set_html_up_to_date();
179                {
180                    let mut lock = doc.annotations.lock();
181                    for (fnt, dt) in &res.font_data {
182                        if dt.web.is_none() {
183                            lock.diagnostics.insert(STeXDiagnostic {
184                                level: DiagnosticLevel::Warning,
185                                message: format!("Unknown web font for {fnt}"),
186                                range: StringRange::default(),
187                            });
188                            for (glyph, char) in &dt.missing.inner {
189                                lock.diagnostics.insert(STeXDiagnostic {
190                                    level: DiagnosticLevel::Warning,
191                                    message: format!("unknown unicode character for glyph {char} ({glyph}) in font {fnt}"),
192                                    range: StringRange::default()
193                                });
194                            }
195                        }
196                    }
197                }
198                //let progress: ClientOutput = old.take_output().unwrap_or_else(|| unreachable!());
199                if let Some((e, ft)) = &mut res.error {
200                    let mut done = None;
201                    for ft in std::mem::take(ft) {
202                        let url = UrlOrFile::File(ft.file.into());
203                        if url == *uri {
204                            done = Some(StringRange {
205                                start: LSPLineCol::new(ft.line, 0),
206                                end: LSPLineCol::new(ft.line, ft.col),
207                            });
208                        } else if let Some(dc) = self.documents.read().get(&url) {
209                            let data = match dc {
210                                DocData::Data(d, _) => d,
211                                DocData::Doc(d) => &d.annotations,
212                            };
213                            let mut lock = data.lock();
214                            lock.diagnostics.insert(STeXDiagnostic {
215                                level: DiagnosticLevel::Error,
216                                message: format!("RusTeX Error: {e}"),
217                                range: StringRange {
218                                    start: LSPLineCol::new(ft.line, 0),
219                                    end: LSPLineCol::new(ft.line, ft.col),
220                                },
221                            });
222                            let _ = client.publish_diagnostics(lsp::PublishDiagnosticsParams {
223                                uri: url.clone().into(),
224                                version: None,
225                                diagnostics: lock.diagnostics.iter().map(to_diagnostic).collect(),
226                            });
227                        }
228                    }
229                    let mut lock = doc.annotations.lock();
230                    lock.check = None;
231                    lock.diagnostics.insert(STeXDiagnostic {
232                        level: DiagnosticLevel::Error,
233                        message: format!("RusTeX Error: {e}"),
234                        range: done.unwrap_or_default(),
235                    });
236                    let _ = client.publish_diagnostics(lsp::PublishDiagnosticsParams {
237                        uri: uri.clone().into(),
238                        version: None,
239                        diagnostics: lock.diagnostics.iter().map(to_diagnostic).collect(),
240                    });
241                    drop(lock);
242                    None
243                } else {
244                    let html = res.to_string();
245                    //let rel_path = doc.relative_path().unwrap_or_else(|| unreachable!());
246                    match flams_system::logging::ignore_traces(|| {
247                        flams_ftml::build_ftml(
248                            &AnyBackend::Temp(self.backend().clone()),
249                            &html,
250                            doc_uri.clone(),
251                        )
252                    }) {
253                        Ok(FtmlResult {
254                            doc: docresult,
255                            ftml,
256                            css,
257                            body,
258                            inner_offset,
259                            ..
260                        }) => {
261                            /*tracing::warn!(
262                                "Adding HTML for {}\nSanity check: {:?}\n{:#?}",
263                                docresult.document.uri,
264                                &docresult.data[0..140],
265                                docresult.document
266                            );*/
267                            self.backend().add_html(
268                                docresult.document.uri.clone(),
269                                HTMLData {
270                                    html: ftml,
271                                    css,
272                                    body,
273                                    inner_offset: inner_offset as _,
274                                    refs: docresult.data,
275                                },
276                            );
277                            self.backend()
278                                .add_triples(&docresult.document.uri, docresult.triples);
279                            self.backend().add_document(docresult.document.clone());
280                            for m in &docresult.modules {
281                                self.backend().add_module(m.clone());
282                            }
283                            old.memorize(self.rustex());
284                            let mut checker = ftml_solver::Checker::<
285                                ftml_solver::split::SingleThreadedSplit,
286                            >::new(AnyBackend::Temp(
287                                self.backend().clone(),
288                            ));
289                            let _ = checker.add_modules(docresult.modules);
290                            let Ok((logs, mods)) = checker
291                                .check_document(&docresult.document)
292                                .inspect_err(|m| {
293                                    let _ =
294                                        client.publish_diagnostics(lsp::PublishDiagnosticsParams {
295                                            uri: uri.clone().into(),
296                                            version: None,
297                                            diagnostics: vec![to_diagnostic(&STeXDiagnostic {
298                                                level: DiagnosticLevel::Error,
299                                                message: format!("Module {m} not found"),
300                                                range: StringRange::default(),
301                                            })],
302                                        });
303                                })
304                            else {
305                                return Some(doc_uri);
306                            };
307                            let mut lock = doc.annotations.lock();
308                            lock.diagnostics
309                                .0
310                                .extend(check_diagnostics(&logs, (&docresult.document, &mods)));
311                            lock.check = Some(logs);
312                            if !lock.diagnostics.is_empty() {
313                                let _ = client.publish_diagnostics(lsp::PublishDiagnosticsParams {
314                                    uri: uri.clone().into(),
315                                    version: None,
316                                    diagnostics: lock
317                                        .diagnostics
318                                        .iter()
319                                        .map(to_diagnostic)
320                                        .collect(),
321                                });
322                            }
323                            drop(lock);
324                            for m in mods {
325                                self.backend().add_module(m.clone());
326                            }
327                            Some(doc_uri)
328                        }
329                        Err(e) => {
330                            let mut lock = doc.annotations.lock();
331                            lock.check = None;
332                            lock.diagnostics.insert(STeXDiagnostic {
333                                level: DiagnosticLevel::Error,
334                                message: format!("FTML Error: {e}"),
335                                range: StringRange::default(),
336                            });
337                            let _ = client.publish_diagnostics(lsp::PublishDiagnosticsParams {
338                                uri: uri.clone().into(),
339                                version: None,
340                                diagnostics: lock.diagnostics.iter().map(to_diagnostic).collect(),
341                            });
342                            drop(lock);
343                            None
344                        }
345                    }
346                }
347            },
348        )
349    }
350
351    #[inline]
352    pub fn build_html_and_notify(&self, uri: &UrlOrFile, mut client: ClientSocket) {
353        if let Some(uri) = self.build_html(uri, &mut client) {
354            client.html_result(&uri);
355        }
356    }
357
358    pub fn relint_dependents(self, path: std::sync::Arc<Path>) { /*
359        let docs = self.documents.read();
360        let mut deps = vec![UrlOrFile::File(path.clone())];
361        for (k,v) in docs.iter() {
362        if matches!(k,UrlOrFile::File(p) if p == path) { continue }
363        let d = match v {
364        DocData::Doc(d) => &d.annotations,
365        DocData::Data(d,_) => d
366        };
367        let lock = d.lock();
368        if lock.dependencies.contains(&path) {
369        deps.push(k.clone());
370        }
371        } */
372    }
373    /*
374    fn relint_dependents_i(&mut self,path:std::sync::Arc<Path>,&mut v:Vec<UrlOrFile>) {
375      let docs = self.documents.read();
376      let mut deps = vec![UrlOrFile::File(path.clone())];
377      for (k,v) in docs.iter() {
378        if matches!(k,UrlOrFile::File(p) if p == path) { continue }
379        let d = match v {
380          DocData::Doc(d) => &d.annotations,
381          DocData::Data(d,_) => d
382        };
383        let lock = d.lock();
384        if lock.dependencies.contains(&path) {
385          deps.push(k.clone());
386        }
387      }
388    } */
389
390    pub fn load_mathhubs(&self, client: ClientSocket) {
391        let (_, t) = ftml_ontology::utils::time::measure(move || {
392            let mut files = Vec::new();
393
394            for a in GlobalBackend.all_archives().iter() {
395                if let Archive::Local(a) = a {
396                    let mut v = Vec::new();
397                    a.with_sources(|d| {
398                        for e in d.dfs() {
399                            match e {
400                                SourceEntry::File(f) => {
401                                    let uri = match DocumentUri::from_archive_relpath(
402                                        a.uri().clone(),
403                                        f.relative_path.as_ref(),
404                                    ) {
405                                        Ok(u) => u,
406                                        Err(e) => {
407                                            tracing::error!("{e}");
408                                            continue;
409                                        }
410                                    };
411                                    v.push((
412                                        f.relative_path
413                                            .steps()
414                                            .fold(a.source_dir(), |p, s| p.join(s))
415                                            .into(),
416                                        uri,
417                                    ))
418                                }
419                                _ => {}
420                            }
421                        }
422                    });
423                    files.push((a.id().clone(), v))
424                }
425            }
426
427            ProgressCallbackServer::with(
428                client,
429                "Linting MathHub".to_string(),
430                Some(files.len() as _),
431                move |progress| {
432                    self.load_all(
433                        files
434                            .into_iter()
435                            .map(|(id, v)| {
436                                progress.update(id.to_string(), Some(1));
437                                v
438                            })
439                            .flatten(),
440                        |file, data| {
441                            let lock = data.lock();
442                            if !lock.diagnostics.is_empty() {
443                                if let Ok(uri) = lsp::Url::from_file_path(&file) {
444                                    let _ = progress.client().clone().publish_diagnostics(
445                                        lsp::PublishDiagnosticsParams {
446                                            uri,
447                                            version: None,
448                                            diagnostics: lock
449                                                .diagnostics
450                                                .iter()
451                                                .map(to_diagnostic)
452                                                .collect(),
453                                        },
454                                    );
455                                }
456                            }
457                        },
458                    );
459                },
460            );
461        });
462        tracing::info!("Linting mathhubs finished after {t}");
463    }
464
465    pub fn load_all<I: IntoIterator<Item = (std::sync::Arc<Path>, DocumentUri)>>(
466        &self,
467        iter: I,
468        mut and_then: impl FnMut(&std::sync::Arc<Path>, &STeXParseData),
469    ) {
470        let mut ndocs = crate::HMap::default();
471        let verbalizations = VerbalizationTrie::default();
472        let mut vlock = verbalizations.lock();
473        let mut state = LSPStore::<true>::new(&mut ndocs, Some(&mut vlock), &[], false);
474        for (p, uri) in iter {
475            let p = UrlOrFile::File(p);
476            if !state.map.contains_key(&p) {
477                let UrlOrFile::File(p) = p else {
478                    // SAFETY: we just constructed it such
479                    unsafe { unreachable_unchecked() }
480                };
481                if let Some(ret) = state.load(p.as_ref(), &uri) {
482                    and_then(&p, &ret);
483                    let p = UrlOrFile::File(p);
484                    match state.map.entry(p) {
485                        Entry::Vacant(e) => {
486                            e.insert(DocData::Data(ret, true));
487                        }
488                        Entry::Occupied(mut e) => {
489                            e.get_mut().merge(DocData::Data(ret, true));
490                        }
491                    }
492                }
493            }
494        }
495        drop(vlock);
496        self.verbalizations.merge(verbalizations);
497        /*{
498            use radix_trie::TrieCommon;
499            use std::fmt::Write;
500            let lck = self.verbalizations.0.lock();
501            let mut s = String::new();
502            for (k, v) in lck.iter() {
503                writeln!(s, " - {k}: {v:?}");
504            }
505            drop(lck);
506            tracing::warn!("{s}");
507        }*/
508        let docs = &mut self.documents.write();
509        for (k, v) in ndocs {
510            match docs.entry(k) {
511                Entry::Vacant(e) => {
512                    e.insert(v);
513                }
514                Entry::Occupied(mut e) => {
515                    e.get_mut().merge(v);
516                }
517            }
518        }
519    }
520
521    /*
522    pub fn load<const FULL: bool>(
523        &self,
524        p: std::sync::Arc<Path>,
525        uri: &DocumentUri,
526        and_then: impl FnOnce(&STeXParseData),
527    ) {
528        //let Some(lsp_uri) = lsp::Url::from_file_path(p).ok() else {return};
529        let lsp_uri = UrlOrFile::File(p);
530        let UrlOrFile::File(path) = &lsp_uri else {
531            unreachable!()
532        };
533        let mut docs = self.documents.write();
534        if docs.get(&lsp_uri).is_some() {
535            return;
536        }
537        let mut vlock = self.verbalizations.lock();
538        let mut state = LSPStore::<'_, '_, FULL>::new(&mut docs, Some(&mut vlock), true);
539        if let Some(ret) = state.load(path, uri) {
540            drop(state);
541            drop(vlock);
542            and_then(&ret);
543            match docs.entry(lsp_uri) {
544                Entry::Vacant(e) => {
545                    e.insert(DocData::Data(ret, FULL));
546                }
547                Entry::Occupied(mut e) => {
548                    e.get_mut().merge(DocData::Data(ret, FULL));
549                }
550            }
551        }
552    }
553     */
554
555    #[allow(clippy::let_underscore_future)]
556    pub fn insert(&self, uri: UrlOrFile, doctext: String) {
557        let doc = self.documents.read().get(&uri).as_deref().cloned();
558        match doc {
559            Some(DocData::Doc(doc)) => {
560                if doc.set_text(doctext) {
561                    doc.compute_annots(self.clone(), false);
562                }
563            }
564            _ => {
565                let doc = LSPDocument::new(doctext, uri.clone());
566                if doc.has_annots() {
567                    doc.compute_annots(self.clone(), false);
568                }
569                match self.documents.write().entry(uri) {
570                    Entry::Vacant(e) => {
571                        e.insert(DocData::Doc(doc));
572                    }
573                    Entry::Occupied(mut e) => {
574                        e.get_mut().merge(DocData::Doc(doc));
575                    }
576                }
577            }
578        }
579    }
580
581    #[must_use]
582    pub fn get(&self, uri: &UrlOrFile) -> Option<LSPDocument> {
583        if let Some(DocData::Doc(doc)) = self.documents.read().get(uri) {
584            Some(doc.clone())
585        } else {
586            None
587        }
588    }
589
590    pub fn force_get(&self, uri: &UrlOrFile) -> Option<LSPDocument> {
591        if let Some(DocData::Doc(doc)) = self.documents.read().get(uri) {
592            return Some(doc.clone());
593        }
594        let UrlOrFile::File(f) = uri else { return None };
595        let Some(s) = std::fs::read_to_string(f).ok() else {
596            return None;
597        };
598        self.insert(uri.clone(), s);
599        self.get(uri)
600    }
601}
602
603struct ClientOutput(std::cell::RefCell<ProgressCallbackServer>);
604impl OutputCont for ClientOutput {
605    fn message(&self, _: String) {}
606    fn errmessage(&self, text: String) {
607        let _ = self
608            .0
609            .borrow_mut()
610            .client_mut()
611            .show_message(lsp::ShowMessageParams {
612                typ: lsp::MessageType::ERROR,
613                message: text,
614            });
615    }
616    fn file_open(&self, text: String) {
617        self.0.borrow().update(text, None);
618    }
619    fn file_close(&self, _text: String) {}
620    fn write_16(&self, _text: String) {}
621    fn write_17(&self, _text: String) {}
622    fn write_18(&self, _text: String) {}
623    fn write_neg1(&self, _text: String) {}
624    fn write_other(&self, _text: String) {}
625
626    #[inline]
627    fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> {
628        self
629    }
630}
631
632fn check_diagnostics(
633    res: &DocumentCheckResult,
634    src: (&Document, &[Module]),
635) -> impl Iterator<Item = STeXDiagnostic> {
636    use either_of::EitherOf5 as E;
637    res.checks.iter().flat_map(|cr| match cr {
638        CheckResult::Missing(u) => E::A(std::iter::once(STeXDiagnostic {
639            level: DiagnosticLevel::Error,
640            message: format!("Module {u} not found"),
641            range: StringRange::default(),
642        })),
643        CheckResult::Variable(e, r) => {
644            let vd = src.0.get_as::<VariableDeclaration>(e.name());
645            if vd.is_none() {
646                tracing::error!("Variable {e} not found!");
647            }
648            E::B(symbol_check_result(
649                r,
650                e.name().as_ref(),
651                vd.as_ref().map(|v| &v.data.tp),
652                vd.as_ref().map(|v| &v.data.df),
653            ))
654        }
655        CheckResult::Proof(uri, res) if res.iter().any(|r| !r.success()) => {
656            let prf = src.0.get_as::<LogicalParagraph>(uri.name());
657            E::A(std::iter::once(STeXDiagnostic {
658                level: DiagnosticLevel::Error,
659                message: format!("Checking proof {uri} failed"),
660                range: if let Some(prf) = prf {
661                    conv_range(prf.source)
662                } else {
663                    StringRange::default()
664                },
665            }))
666        }
667        CheckResult::Proof(..) => E::C(std::iter::empty()),
668        CheckResult::Term { uri, inferred, .. } => {
669            if inferred.is_some() {
670                E::C(std::iter::empty())
671            } else {
672                let term = src.0.get_as::<DocumentTerm>(uri.name());
673                if term.is_none() {
674                    tracing::error!("Term {uri} not found!");
675                }
676                let range = term.map(|t| conv_range(t.term.source)).unwrap_or_default();
677                E::A(std::iter::once(STeXDiagnostic {
678                    level: DiagnosticLevel::Info,
679                    message: format!("checking term {} failed", uri.name()),
680                    range,
681                }))
682            }
683        }
684        CheckResult::Content(ccr) => match ccr {
685            ContentCheckResult::Symbol(uri, res) => {
686                let uri = uri.as_simple_module();
687                let sym = src
688                    .1
689                    .iter()
690                    .find(|m| m.uri == uri.module)
691                    .and_then(|m| m.get_as::<Symbol>(uri.name()));
692                if sym.is_none() {
693                    tracing::error!("Symbol {uri} not found!");
694                }
695                E::D(
696                    symbol_check_result(
697                        res,
698                        uri.name().as_ref(),
699                        sym.as_ref().map(|v| &v.data.tp),
700                        sym.as_ref().map(|v| &v.data.df),
701                    )
702                    .collect::<SmallVec<_, 1>>()
703                    .into_iter(),
704                )
705            }
706        },
707        CheckResult::Module { checks, .. } => E::E(checks.iter().flat_map(|ccr| match ccr {
708            ContentCheckResult::Symbol(uri, res) => {
709                let uri = uri.as_simple_module();
710                let sym = src
711                    .1
712                    .iter()
713                    .find(|m| m.uri == uri.module)
714                    .and_then(|m| m.get_as::<Symbol>(uri.name()));
715                if sym.is_none() {
716                    tracing::error!("Symbol {uri} not found!");
717                }
718                symbol_check_result(
719                    res,
720                    uri.name().as_ref(),
721                    sym.as_ref().map(|v| &v.data.tp),
722                    sym.as_ref().map(|v| &v.data.df),
723                )
724                .collect::<SmallVec<_, 1>>()
725                .into_iter()
726            }
727        })),
728    })
729}
730
731fn symbol_check_result(
732    result: &SymbolCheckResult,
733    name: &str,
734    tp: Option<&TermContainer>,
735    df: Option<&TermContainer>,
736) -> impl Iterator<Item = STeXDiagnostic> + use<> {
737    use either_of::Either::{Left as A, Right as B};
738    match result {
739        SymbolCheckResult::TypeOnly { result } => {
740            if result.success {
741                A(std::iter::empty())
742            } else {
743                let range = tp.map(|v| conv_range(v.source)).unwrap_or_default();
744                B(std::iter::once(STeXDiagnostic {
745                    level: DiagnosticLevel::Info,
746                    message: format!("checking type of {name} failed"),
747                    range,
748                }))
749            }
750        }
751        SymbolCheckResult::DefiniensOnly { inferred, .. } => {
752            if inferred.is_some() {
753                A(std::iter::empty())
754            } else {
755                let range = df.map(|v| conv_range(v.source)).unwrap_or_default();
756                B(std::iter::once(STeXDiagnostic {
757                    level: DiagnosticLevel::Info,
758                    message: format!("checking definiens of {name} failed ({range:?})"),
759                    range,
760                }))
761            }
762        }
763        SymbolCheckResult::Both { matches: None, .. } => {
764            let range = tp.map(|v| conv_range(v.source)).unwrap_or_default();
765            B(std::iter::once(STeXDiagnostic {
766                level: DiagnosticLevel::Info,
767                message: format!("checking type of {name} failed ({range:?})"),
768                range,
769            }))
770        }
771        SymbolCheckResult::Both {
772            matches: Some(matches),
773            ..
774        } => {
775            if matches.success {
776                return A(std::iter::empty());
777            }
778            let df_range = df.map(|v| conv_range(v.source)).unwrap_or_default();
779            B(std::iter::once(STeXDiagnostic {
780                level: DiagnosticLevel::Info,
781                message: format!("checking definiens of {name} failed"),
782                range: df_range,
783            }))
784        }
785    }
786}
787
788const fn conv_range(
789    ftml_ontology::utils::SourceRange { start, end }: ftml_ontology::utils::SourceRange,
790) -> StringRange<LSPLineCol> {
791    StringRange {
792        start: LSPLineCol::new(start.line, start.col),
793        end: LSPLineCol::new(end.line, end.col),
794    }
795}