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 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 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 if let Some(tp) = tp
211 && checker.check_type(t, tp) != Some(true)
212 {
213 return Err(None);
214 }
215
216 let tp = {
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 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 let name = unsafe { s.uri.name.last().parse().unwrap_unchecked() };
450 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 Ok(Self { elems })
567 }
568 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 }
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 }
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 let name = unsafe { s.uri.name.last().parse().unwrap_unchecked() };
635 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}