Skip to main content

ftml_solver/impls/
records.rs

1use std::{borrow::Cow, ops::ControlFlow};
2
3use ftml_ontology::{
4    domain::{
5        HasDeclarations, SharedDeclaration,
6        declarations::{AnyDeclarationRef, SharedSymbolLike, symbols::Symbol},
7        modules::ModuleLike,
8    },
9    terms::{ApplicationTerm, Argument, MaybeSequence, RecordFieldTerm, Term},
10};
11use ftml_solver_trace::SizedSolverRule;
12use ftml_uris::{DomainUriRef, Id, ModuleUri, SymbolUri, UriName};
13
14use crate::{
15    CheckRef,
16    rules::{InferenceRule, InhabitableRule, SimplificationRule, SubtypeRule},
17    split::SplitStrategy,
18};
19
20#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
21pub struct FieldRule;
22impl SizedSolverRule for FieldRule {
23    fn priority(&self) -> isize {
24        100_000
25    }
26    fn display(&self) -> Vec<crate::trace::Displayable> {
27        ftml_solver_trace::trace!("record field rule")
28    }
29}
30impl<Split: SplitStrategy> SimplificationRule<Split> for FieldRule {
31    fn applicable(&self, term: &Term) -> bool {
32        matches!(term, Term::Field(_))
33    }
34    fn apply<'t>(
35        &self,
36        mut checker: CheckRef<'t, '_, Split>,
37        term: &'t Term,
38    ) -> Result<Term, Option<ftml_ontology::terms::termpaths::TermPath>> {
39        let Term::Field(app) = term else {
40            unreachable!("by applicability")
41        };
42        let record = Record::from_term(&app.record, app.record_type.as_ref(), &mut checker)
43            .map_err(|_| None)?;
44        record.get_def(&app.key).ok_or(None)
45    }
46}
47impl<Split: SplitStrategy> InferenceRule<Split> for FieldRule {
48    fn applicable(&self, term: &Term) -> bool {
49        matches!(term, Term::Field(_))
50    }
51    fn infer<'t>(&self, mut checker: CheckRef<'t, '_, Split>, term: &'t Term) -> Option<Term> {
52        let Term::Field(app) = term else {
53            unreachable!("by applicability")
54        };
55        let record = Record::from_term(&app.record, app.record_type.as_ref(), &mut checker).ok()?;
56        record.get_tp(&app.key)
57    }
58}
59
60#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
61pub struct RecordRule;
62impl SizedSolverRule for RecordRule {
63    fn priority(&self) -> isize {
64        100_000
65    }
66    fn display(&self) -> Vec<crate::trace::Displayable> {
67        ftml_solver_trace::trace!("record rule")
68    }
69}
70impl RecordRule {
71    fn is_sub(sub: &AnyRecordType, sup: &AnyRecordType) -> bool {
72        match (sub, sup) {
73            (AnyRecordType::Structure(sub), AnyRecordType::Structure(sup)) => {
74                sup.domain.iter().all(|d| sub.domain.contains(d))
75            }
76            (AnyRecordType::Ext(sub), AnyRecordType::Structure(sup)) => {
77                sup.domain.iter().all(|d| {
78                    sub.elems.iter().any(|e| {
79                        if let AnyRecordType::Structure(sub) = e {
80                            sub.domain.contains(d)
81                        } else {
82                            false
83                        }
84                    })
85                })
86            }
87            _ => false,
88        }
89    }
90}
91impl<Split: SplitStrategy> InhabitableRule<Split> for RecordRule {
92    fn applicable(&self, term: &Term) -> bool {
93        AnyRecordType::may_be(term)
94    }
95    fn apply<'t>(&self, mut checker: CheckRef<'t, '_, Split>, term: &'t Term) -> Option<bool> {
96        AnyRecordType::from_term(term, &mut checker).ok()?;
97        Some(true)
98    }
99}
100impl<Split: SplitStrategy> SubtypeRule<Split> for RecordRule {
101    fn applicable(&self, checker: &CheckRef<'_, '_, Split>, sub: &Term, sup: &Term) -> bool {
102        AnyRecordType::may_be(sub) && AnyRecordType::may_be(sup)
103    }
104    fn apply<'t>(
105        &self,
106        mut checker: CheckRef<'t, '_, Split>,
107        sub: &'t Term,
108        sup: &'t Term,
109    ) -> Option<bool> {
110        let sub = AnyRecordType::from_term(sub, &mut checker).ok()?;
111        let sup = AnyRecordType::from_term(sup, &mut checker).ok()?;
112        if Self::is_sub(&sub, &sup) {
113            Some(true)
114        } else {
115            None
116        }
117    }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct RecordUniverse(pub Term);
122impl SizedSolverRule for RecordUniverse {
123    fn display(&self) -> Vec<crate::trace::Displayable> {
124        ftml_solver_trace::trace!(self.0.clone(), "is universe for records")
125    }
126}
127impl<Split: SplitStrategy> InferenceRule<Split> for RecordUniverse {
128    fn applicable(&self, term: &Term) -> bool {
129        AnyRecordType::may_be(term)
130    }
131    fn infer<'t>(&self, mut checker: CheckRef<'t, '_, Split>, term: &'t Term) -> Option<Term> {
132        fn is<Split: SplitStrategy>(checker: &CheckRef<Split>, term: &Term) -> bool {
133            match term {
134                Term::Symbol { uri, .. } => {
135                    matches!(
136                        checker
137                            .top
138                            .get_symbol_like(uri, |t| checker.prepare(t, None).1),
139                        Ok(SharedSymbolLike::MathStructure(_))
140                    )
141                }
142                Term::Application(app)
143                    if app.head.is(&*ftml_uris::metatheory::ANONYMOUS_RECORD) =>
144                {
145                    let [Argument::Sequence(MaybeSequence::Seq(omls))] = &*app.arguments else {
146                        return false;
147                    };
148                    omls.iter().all(|o| matches!(o, Term::Label { .. }))
149                }
150                Term::Application(app)
151                    if app.head.is(&*ftml_uris::metatheory::RECORD_TYPE_MERGE) =>
152                {
153                    let [Argument::Sequence(MaybeSequence::Seq(recs))] = &*app.arguments else {
154                        return false;
155                    };
156                    // TODO check that record is well-typed
157                    recs.iter().all(|r| is(checker, r))
158                }
159                _ => false,
160            }
161        }
162        if is(&checker, term) {
163            let r = AnyRecordType::from_term(term, &mut checker).ok()?;
164            if r.is_complete() {
165                match r {
166                    // return the first structure as the type
167                    AnyRecordType::Ext(e)
168                        if e.elems.iter().any(|e| matches!(e, AnyRecordType::Ano(_)))
169                            && e.elems
170                                .iter()
171                                .any(|e| matches!(e, AnyRecordType::Structure(_))) =>
172                    {
173                        let first = e.elems.iter().find_map(|e| {
174                            if let AnyRecordType::Structure(s) = e {
175                                s.rec_type.clone().into_top_symbol()
176                            } else {
177                                None
178                            }
179                        })?;
180                        Some(first.into())
181                    }
182                    _ => Some(self.0.clone()),
183                }
184            } else {
185                Some(self.0.clone())
186            }
187        } else {
188            None
189        }
190    }
191}
192
193pub struct Record {
194    term: Term,
195    typ: AnyRecordType,
196}
197impl Record {
198    #[inline]
199    pub const fn get_type(&self) -> &AnyRecordType {
200        &self.typ
201    }
202    pub fn from_term<'t, Split: SplitStrategy>(
203        t: &'t Term,
204        tp: Option<&'t Term>,
205        checker: &mut CheckRef<'t, '_, Split>,
206    ) -> Result<Self, Option<ModuleUri>> {
207        /*let t = checker
208        .simplify_until(t, |_, t| AnyRecordType::may_be(t))
209        .unwrap_or_else(|| Cow::Borrowed(t));*/
210        if let Some(tp) = tp
211            && checker.check_type(t, tp) != Some(true)
212        {
213            return Err(None);
214        }
215
216        let tp = /*if let Some(tp) = tp {
217            if checker.check_type(t, tp) != Some(true) {
218                return Err(None);
219            }
220            AnyRecordType::from_term(tp, checker)?
221        } else*/ {
222            let Some(tp) = checker.infer_type(t) else {
223                checker.failure("Could not determine record type");
224                return Err(None);
225            };
226            checker.scoped(|checker| AnyRecordType::from_term(&tp, checker))?
227        };
228        Ok(Self {
229            term: t.clone(),
230            typ: tp,
231        })
232    }
233
234    pub fn get_def(&self, field: &UriName) -> Option<Term> {
235        self.typ.get_def(&self.term, field)
236    }
237    pub fn get_tp(&self, field: &UriName) -> Option<Term> {
238        self.typ.get_tp(&self.term, field)
239    }
240}
241
242pub enum AnyRecordType {
243    Structure(ModuleType),
244    Ano(AnonymousRecord),
245    Ext(RecordExtension),
246}
247impl AnyRecordType {
248    pub fn is_complete(&self) -> bool {
249        match self {
250            Self::Structure(s) => s.is_complete(),
251            Self::Ano(a) => a.is_complete(),
252            Self::Ext(e) => e.is_complete(),
253        }
254    }
255    pub fn get_symbol(&self, name: &UriName) -> Option<SharedDeclaration<Symbol>> {
256        match self {
257            Self::Structure(mt) => mt
258                .fields
259                .iter()
260                .rev()
261                .find(|sd| name_fits(sd, name))
262                .cloned(),
263            Self::Ano(_) => None,
264            Self::Ext(e) => e
265                .elems
266                .iter()
267                .rev()
268                .filter_map(|e| {
269                    if let Self::Structure(m) = e {
270                        Some(m)
271                    } else {
272                        None
273                    }
274                })
275                .flat_map(|m| m.fields.iter().rev())
276                .find(|sd| name_fits(sd, name))
277                .cloned(),
278        }
279    }
280    pub fn may_be(term: &Term) -> bool {
281        match term {
282            Term::Symbol { .. } => true,
283            Term::Application(app) => {
284                (app.head.is(&*ftml_uris::metatheory::ANONYMOUS_RECORD)
285                    || app.head.is(&*ftml_uris::metatheory::RECORD_TYPE_MERGE))
286                    && matches!(&*app.arguments, [Argument::Sequence(MaybeSequence::Seq(_))])
287            }
288            _ => false,
289        }
290    }
291    pub fn from_term<'t, Split: SplitStrategy>(
292        t: &'t Term,
293        checker: &mut CheckRef<'t, '_, Split>,
294    ) -> Result<Self, Option<ModuleUri>> {
295        let t = checker
296            .simplify_until(t, |_, t| Self::may_be(t))
297            .unwrap_or_else(|| Cow::Borrowed(t));
298        match &*t {
299            Term::Symbol { uri, .. } => ModuleType::new(uri.clone(), checker).map(Self::Structure),
300            Term::Application(app) => checker.scoped(|checker| {
301                RecordExtension::from_app(app, checker)
302                    .map(Self::Ext)
303                    .or_else(|_| AnonymousRecord::from_app(app, checker).map(Self::Ano))
304            }),
305            _ => Err(None),
306        }
307    }
308
309    pub fn get_def(&self, record: &Term, field: &UriName) -> Option<Term> {
310        match self {
311            Self::Structure(s) => s.get_def(record, field),
312            Self::Ext(s) => s.get_def(record, field),
313            Self::Ano(s) => s.get_def(record, field),
314        }
315    }
316    pub fn get_tp(&self, record: &Term, field: &UriName) -> Option<Term> {
317        match self {
318            Self::Structure(s) => s.get_tp(record, field),
319            Self::Ext(s) => s.get_tp(record, field),
320            Self::Ano(s) => s.get_tp(record, field),
321        }
322    }
323}
324
325struct Field {
326    name: UriName,
327    id: Option<Id>,
328    tp: Option<Term>,
329    df: Option<Term>,
330}
331
332pub struct AnonymousRecord {
333    fields: Vec<Field>,
334}
335impl AnonymousRecord {
336    pub fn is_complete(&self) -> bool {
337        self.fields.iter().all(|f| f.df.is_some())
338    }
339    pub fn from_app<'t, Split: SplitStrategy>(
340        app: &'t ApplicationTerm,
341        _: &mut CheckRef<'t, '_, Split>,
342    ) -> Result<Self, Option<ModuleUri>> {
343        if !app.head.is(&*ftml_uris::metatheory::ANONYMOUS_RECORD) {
344            return Err(None);
345        }
346        let [Argument::Sequence(MaybeSequence::Seq(omls))] = &*app.arguments else {
347            return Err(None);
348        };
349        let mut fields = Vec::new();
350        for o in omls {
351            let Term::Label { name, df, tp } = o else {
352                return Err(None);
353            };
354            fields.push(Field {
355                name: name.clone(),
356                id: None,
357                tp: tp.as_ref().map(|t| (**t).clone()),
358                df: df.as_ref().map(|t| (**t).clone()),
359            });
360        }
361        Ok(Self { fields })
362    }
363
364    pub fn get_def(&self, _: &Term, field: &UriName) -> Option<Term> {
365        self.fields.iter().find_map(|f| {
366            if name_fits_i(field, &f.name, f.id.as_ref()) {
367                f.df.clone()
368            } else {
369                None
370            }
371        })
372    }
373    pub fn get_tp(&self, _: &Term, field: &UriName) -> Option<Term> {
374        self.fields.iter().find_map(|f| {
375            if name_fits_i(field, &f.name, f.id.as_ref()) {
376                f.tp.clone()
377            } else {
378                None
379            }
380        })
381    }
382}
383
384pub struct ModuleType {
385    //term: &'s Term,
386    rec_type: ModuleUri,
387    domain: Vec<ModuleUri>,
388    fields: Vec<SharedDeclaration<Symbol>>,
389}
390impl ModuleType {
391    pub fn is_complete(&self) -> bool {
392        self.fields.iter().all(|f| f.data.df.is_some())
393    }
394    pub fn new<Split: SplitStrategy>(
395        structure: SymbolUri,
396        checker: &mut CheckRef<Split>,
397    ) -> Result<Self, Option<ModuleUri>> {
398        let as_mod = structure.into_module();
399        let mut mods = Vec::new();
400        let mut err = None;
401        ModuleLike::topo_sort(vec![as_mod.clone()], &mut mods, |u| {
402            if let Ok(r) = checker.top.get_module_like(u) {
403                Some(r)
404            } else {
405                err = Some(u.clone());
406                None
407            }
408        });
409        if let Some(err) = err {
410            return Err(Some(err));
411        }
412        let mut fields = Vec::new();
413        let mut domain = Vec::new();
414        for m in mods {
415            domain.push(match m.domain_uri() {
416                DomainUriRef::Module(m) => m.clone(),
417                DomainUriRef::Symbol(s) => s.clone().into_module(),
418            });
419            if let ModuleLike::Morphism(m) = &m {
420                domain.push(m.domain.clone());
421            }
422            for d in m.declarations() {
423                if let AnyDeclarationRef::Symbol(sym) = d {
424                    unsafe {
425                        match &m {
426                            ModuleLike::Module(_) | ModuleLike::Nested(_) => (),
427                            ModuleLike::Structure(m) => fields.push(m.inherit_unsafe(sym)),
428                            ModuleLike::Extension(m) => fields.push(m.inherit_unsafe(sym)),
429                            ModuleLike::Morphism(m) => fields.push(m.inherit_unsafe(sym)),
430                        }
431                    };
432                }
433            }
434        }
435        Ok(Self {
436            rec_type: as_mod,
437            domain,
438            fields,
439        })
440    }
441    fn subst<'t>(&self, record: &Term, tm: &'t Term) -> Cow<'t, Term> {
442        tm.modify(|t| {
443            let Term::Symbol { uri, .. } = t else {
444                return None;
445            };
446            self.fields.iter().find_map(|s| {
447                if s.uri.equivalent(uri) {
448                    // SAFTEY: last names are valie
449                    let name = unsafe { s.uri.name.last().parse().unwrap_unchecked() };
450                    // SAFTEY: self.rec_type is a structure => nested URI
451                    let tp = unsafe { self.rec_type.clone().into_top_symbol().unwrap_unchecked() };
452                    Some(ControlFlow::Break(Term::Field(RecordFieldTerm::new(
453                        record.clone(),
454                        name,
455                        Some(tp.into()),
456                        None,
457                    ))))
458                } else {
459                    None
460                }
461            })
462        })
463    }
464    pub fn get_def(&self, record: &Term, field: &UriName) -> Option<Term> {
465        self.fields
466            .iter()
467            .rev()
468            .find(|f| name_fits(f, field))
469            .and_then(|f| {
470                f.data
471                    .df
472                    .checked_or_parsed()
473                    .map(|(t, _)| self.subst(record, &t).into_owned())
474            })
475    }
476    pub fn get_tp(&self, record: &Term, field: &UriName) -> Option<Term> {
477        self.fields
478            .iter()
479            .rev()
480            .find(|f| name_fits(f, field))
481            .and_then(|f| {
482                f.data
483                    .tp
484                    .checked_or_parsed()
485                    .map(|(t, _)| self.subst(record, &t).into_owned())
486            })
487    }
488}
489
490pub struct RecordExtension {
491    elems: Vec<AnyRecordType>,
492}
493impl RecordExtension {
494    pub fn is_complete(&self) -> bool {
495        self.elems.iter().all(|e| match e {
496            AnyRecordType::Structure(s) => s.fields.iter().all(|f| {
497                f.data.df.is_some()
498                    || self.elems.iter().rev().any(|e| match e {
499                        AnyRecordType::Structure(s) => s.fields.iter().any(|e| {
500                            name_fits_i(&f.uri.name, &e.uri.name, e.data.macroname.as_ref())
501                        }),
502                        AnyRecordType::Ano(a) => a.fields.iter().any(|nf| name_fits(f, &nf.name)),
503                        AnyRecordType::Ext(_) => unreachable!("eliminated by construction"),
504                    })
505            }),
506            AnyRecordType::Ano(a) => a.fields.iter().all(|f| {
507                f.df.is_some()
508                    || self.elems.iter().rev().any(|e| match e {
509                        AnyRecordType::Structure(s) => {
510                            s.fields.iter().any(|e| name_fits(e, &f.name))
511                        }
512                        AnyRecordType::Ano(a) => a.fields.iter().any(|nf| {
513                            nf.name == f.name
514                                || nf
515                                    .id
516                                    .as_ref()
517                                    .is_some_and(|id| f.id.as_ref().is_some_and(|f| f == id))
518                        }),
519                        AnyRecordType::Ext(_) => unreachable!("eliminated by construction"),
520                    })
521            }),
522            AnyRecordType::Ext(_) => unreachable!("eliminated by construction"),
523        })
524    }
525    pub fn from_app<'t, Split: SplitStrategy>(
526        app: &'t ApplicationTerm,
527        checker: &mut CheckRef<'t, '_, Split>,
528    ) -> Result<Self, Option<ModuleUri>> {
529        if !app.head.is(&*ftml_uris::metatheory::RECORD_TYPE_MERGE) {
530            return Err(None);
531        }
532        let [Argument::Sequence(MaybeSequence::Seq(recs))] = &*app.arguments else {
533            return Err(None);
534        };
535        let mut elems = Vec::new();
536        for r in recs {
537            match AnyRecordType::from_term(r, checker)? {
538                AnyRecordType::Ext(Self { elems: new }) => elems.extend(new),
539                AnyRecordType::Ano(AnonymousRecord { mut fields }) => {
540                    for f in &mut fields {
541                        if let Some(r) = elems
542                            .iter()
543                            .rev()
544                            .filter_map(|e| {
545                                if let AnyRecordType::Structure(m) = e {
546                                    Some(m)
547                                } else {
548                                    None
549                                }
550                            })
551                            .flat_map(|m| m.fields.iter().rev())
552                            .find(|s| name_fits(s, &f.name))
553                        {
554                            f.name = r.uri.name.clone();
555                            if let Some(m) = &r.data.macroname {
556                                f.id = Some(m.clone());
557                            }
558                        }
559                    }
560                    elems.push(AnyRecordType::Ano(AnonymousRecord { fields }));
561                }
562                o => elems.push(o),
563            }
564        }
565        // TODO check fields against base
566        Ok(Self { elems })
567    }
568    /*fn subst<'t>(&self, record: &Term, tm: &'t Term) -> Cow<'t, Term> {
569        self.base.subst(record, tm)
570    }*/
571
572    pub fn get_def(&self, record: &Term, field: &UriName) -> Option<Term> {
573        self.elems
574            .iter()
575            .enumerate()
576            .rev()
577            .find_map(|(idx, e)| match e {
578                AnyRecordType::Ano(e) => e.get_def(record, field),
579                AnyRecordType::Ext(_) => unreachable!("Filtered above"),
580                AnyRecordType::Structure(e) => e
581                    .fields
582                    .iter()
583                    .rev()
584                    .find(|f| name_fits(f, field))
585                    .and_then(|f| {
586                        f.data
587                            .df
588                            .checked_or_parsed()
589                            .map(|(t, _)| self.subst(record, &t, idx).into_owned())
590                    }),
591            })
592        //.find_map(|r| r.get_def(record, field))
593    }
594    pub fn get_tp(&self, record: &Term, field: &UriName) -> Option<Term> {
595        self.elems
596            .iter()
597            .enumerate()
598            .rev()
599            .find_map(|(idx, e)| match e {
600                AnyRecordType::Ano(e) => e.get_tp(record, field),
601                AnyRecordType::Ext(_) => unreachable!("Filtered above"),
602                AnyRecordType::Structure(e) => e
603                    .fields
604                    .iter()
605                    .rev()
606                    .find(|f| name_fits(f, field))
607                    .and_then(|f| {
608                        f.data
609                            .tp
610                            .checked_or_parsed()
611                            .map(|(t, _)| self.subst(record, &t, idx).into_owned())
612                    }),
613            })
614        //.find_map(|r| r.get_tp(record, field))
615    }
616    fn subst<'t>(&self, record: &Term, tm: &'t Term, idx: usize) -> Cow<'t, Term> {
617        tm.modify(|t| {
618            let Term::Symbol { uri, .. } = t else {
619                return None;
620            };
621            self.elems
622                .iter()
623                .take(idx + 1)
624                .flat_map(|e| {
625                    if let AnyRecordType::Structure(e) = e {
626                        either::Left(e.fields.iter().map(|f| (&e.rec_type, f)))
627                    } else {
628                        either::Right(std::iter::empty())
629                    }
630                })
631                .find_map(|(rec_type, s)| {
632                    if s.uri.equivalent(uri) {
633                        // SAFTEY: last names are valie
634                        let name = unsafe { s.uri.name.last().parse().unwrap_unchecked() };
635                        // SAFTEY: self.rec_type is a structure => nested URI
636                        let tp = unsafe { rec_type.clone().into_top_symbol().unwrap_unchecked() };
637                        Some(ControlFlow::Break(Term::Field(RecordFieldTerm::new(
638                            record.clone(),
639                            name,
640                            Some(tp.into()),
641                            None,
642                        ))))
643                    } else {
644                        None
645                    }
646                })
647        })
648    }
649}
650
651fn name_fits(s: &Symbol, name: &UriName) -> bool {
652    name_fits_i(name, s.uri.name(), s.data.macroname.as_ref())
653}
654fn name_fits_i(name: &UriName, symname: &UriName, macroname: Option<&Id>) -> bool {
655    macroname
656        .as_ref()
657        .is_some_and(|s| s.as_ref() == name.as_ref())
658        || symname
659            .as_ref()
660            .strip_suffix(name.as_ref())
661            .is_some_and(|r| r.is_empty() || r.ends_with('/'))
662}