1use std::borrow::{Borrow, Cow};
2
3use ftml_ontology::terms::{
4 ApplicationTerm, Argument, BindingTerm, BoundArgument, ComponentVar, MaybeSequence, Term,
5 Variable,
6};
7use ftml_uris::Id;
8use smallvec::SmallVec;
9
10use crate::{
11 CheckRef, Checker,
12 rules::unknowns::beta_unknowns,
13 split::SplitStrategy,
14 utils::{Merge, MutableRefList},
15};
16
17const PREFIX: &str = "SOLVE!";
18
19pub trait TermExtSolvable {
20 fn is_solvable(&self) -> Option<&Id>;
21 fn has_solvable(&self) -> bool;
22 fn solvables(&self) -> SmallVec<&Variable, 2>;
23}
24
25pub fn is_solvable_id(name: &Id) -> bool {
49 name.as_ref().starts_with(PREFIX)
50 && name.as_ref().as_bytes()[PREFIX.len()..]
51 .iter()
52 .all(u8::is_ascii_digit)
53}
54
55pub fn is_solvable_var(var: &Variable) -> Option<&Id> {
56 let Variable::Name { name, .. } = var else {
57 return None;
58 };
59 if is_solvable_id(name) {
60 Some(name)
61 } else {
62 None
63 }
64}
65
66impl TermExtSolvable for Term {
67 fn is_solvable(&self) -> Option<&Id> {
68 if let Self::Var { variable, .. } = self {
69 is_solvable_var(variable)
70 } else if let Self::Application(app) = self
71 && let Self::Var { variable, .. } = &app.head
72 {
73 is_solvable_var(variable)
74 } else {
75 None
76 }
77 }
78 fn has_solvable(&self) -> bool {
79 self.has_free_such_that(|v| is_solvable_var(v).is_some())
80 }
81 fn solvables(&self) -> SmallVec<&Variable, 2> {
82 self.free_variables()
83 .into_iter()
84 .filter(|s| is_solvable_var(s).is_some())
85 .collect()
86 }
87}
88
89#[derive(Clone)]
90pub enum BoundedValue {
91 None,
92 Solved(Term),
93 Bounded(Option<Term>, Option<Term>),
94}
95impl std::fmt::Debug for BoundedValue {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 match self {
98 Self::None => f.write_str("(None)"),
99 Self::Solved(t) => t.debug_short().fmt(f),
100 Self::Bounded(a, b) => write!(
101 f,
102 "({:?} <= _ <= {:?})",
103 a.as_ref().map(Term::debug_short),
104 b.as_ref().map(Term::debug_short)
105 ),
106 }
107 }
108}
109
110#[derive(Clone)]
111pub struct Solvable {
112 pub(crate) name: Id,
113 solution: BoundedValue,
114 context: Vec<ComponentVar>,
115 tp: BoundedValue,
116}
117impl Solvable {
118 pub(crate) fn new(name: Id, context: impl Iterator<Item = ComponentVar>) -> Self {
119 Self {
120 name,
121 solution: BoundedValue::None,
122 tp: BoundedValue::None,
123 context: context.collect(),
124 }
125 }
126
127 pub const fn solution(&self) -> Option<&Term> {
128 if let BoundedValue::Solved(t) = &self.solution {
129 Some(t)
130 } else {
131 None
132 }
133 }
134}
135impl std::fmt::Debug for Solvable {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 struct CtxWrap<'i>(&'i [ComponentVar]);
138 impl std::fmt::Debug for CtxWrap<'_> {
139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 let mut first = true;
141 for ComponentVar { var, tp, df } in self.0 {
142 if !first {
143 f.write_str(", ")?;
144 }
145 first = false;
146 var.name().fmt(f)?;
147 if let Some(tp) = tp {
148 write!(f, " : {:?}", tp.debug_short())?;
149 }
150 if let Some(df) = df {
151 write!(f, " := {:?}", df.debug_short())?;
152 }
153 }
154 Ok(())
155 }
156 }
157 write!(
158 f,
159 "{} := {{{:?}}} {:?} (: {:?})",
160 self.name,
161 CtxWrap(&self.context),
162 self.solution,
163 self.tp
164 )
165 }
166}
167impl PartialEq for Solvable {
168 #[inline]
169 fn eq(&self, other: &Self) -> bool {
170 self.name == other.name
171 }
172}
173impl Eq for Solvable {}
174impl Borrow<Id> for Solvable {
175 #[inline]
176 fn borrow(&self) -> &Id {
177 &self.name
178 }
179}
180impl std::hash::Hash for Solvable {
181 #[inline]
182 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
183 self.name.hash(state);
184 }
185}
186
187#[derive(Default, Debug, Clone, PartialEq, Eq)]
188pub struct Solutions(pub(crate) rustc_hash::FxHashSet<Solvable>);
189impl Merge for Solutions {
190 fn merge(&mut self, other: Self) {
191 for e in other.0 {
192 self.0.remove(&e);
193 self.0.insert(e);
194 }
195 }
196}
197impl std::hash::Hash for Solutions {
198 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
199 for s in self.0.iter() {
200 s.hash(state);
201 }
202 }
203}
204
205impl MutableRefList<'_, Solutions> {
206 #[inline]
207 fn get_solvable<'s>(&'s self, name: &Id) -> Option<&'s Solvable> {
208 fn get_i<'s>(
209 slf: &'s MutableRefList<Solutions>,
210 name: &Id,
211 first: Option<&Id>,
212 ) -> Option<&'s Solvable> {
213 let s = slf.find(|s| s.0.get(name))?;
214 if let BoundedValue::Solved(s) = &s.solution
215 && let Some(s) = s.is_solvable()
216 && first != Some(s)
217 {
218 get_i(slf, s, Some(first.unwrap_or(name)))
219 } else {
220 Some(s)
221 }
222 }
223 get_i(self, name, None)
224 }
225
226 fn set_solution(&mut self, name: Id, solution: Term) {
227 let (tp, context) = self
228 .get_solvable(&name)
229 .map_or((BoundedValue::None, Vec::new()), |e| {
230 (e.tp.clone(), e.context.clone())
231 });
232 self.0.remove(&name);
233
234 let ne = Solvable {
235 name,
236 solution: BoundedValue::Solved(solution),
237 tp,
238 context,
239 };
240 self.0.insert(ne);
241 }
242 fn set_type(&mut self, name: Id, tp: Term) {
243 let (solution, context) = self
244 .get_solvable(&name)
245 .map_or((BoundedValue::None, Vec::new()), |e| {
246 (e.solution.clone(), e.context.clone())
247 });
248 let ne = Solvable {
249 name,
250 solution,
251 tp: BoundedValue::Solved(tp),
252 context,
253 };
254
255 self.0.remove(&ne);
256 self.0.insert(ne);
257 }
258 #[must_use]
259 pub fn subst(&self, term: Term) -> Term {
260 fn subst_i(slf: &MutableRefList<Solutions>, term: Term) -> Term {
261 let freevars = term.free_variables();
284 if freevars.is_empty() {
285 drop(freevars);
286 return term;
287 }
288 let mut ret = smallvec::SmallVec::<_, 2>::new();
289 for v in slf.iter().flat_map(|m| m.0.iter()) {
290 if let BoundedValue::Solved(tm) = &v.solution
291 && freevars.iter().any(|f| f.name() == v.name.as_ref())
292 {
293 ret.push((v.name.to_string(), slf.subst(tm.clone()))); }
295 }
296 drop(freevars);
297
298 match &term / ret.as_slice() {
300 Cow::Borrowed(_) => term,
301 Cow::Owned(t) if t.has_solvable() => {
302 tracing::trace!(
303 "substituted {:?}\n => {:?}",
304 term.debug_short(),
305 t.debug_short()
306 );
307 subst_i(slf, t)
308 }
309 Cow::Owned(t) => t,
310 }
311 }
312 beta_unknowns(subst_i(self, term))
313 }
314}
315
316impl<Split: SplitStrategy> Checker<Split> {
317 pub(crate) fn new_solvable(&self) -> Id {
318 let i = self
319 .implicits
320 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
321 unsafe { format!("{PREFIX}{i}").parse().unwrap_unchecked() }
323 }
324}
325
326fn apply_solvable(name: Id, ctx: impl ExactSizeIterator<Item = Variable>) -> Term {
327 let head: Term = Variable::Name {
328 name,
329 notated: None,
330 }
331 .into();
332 if ctx.len() == 0 {
333 head
334 } else {
335 Term::Application(ApplicationTerm::new(
336 head,
337 Box::new([Argument::Sequence(MaybeSequence::Seq(
338 ctx.map(Into::into).collect(),
339 ))]),
340 None,
341 ))
342 }
343}
344
345impl<Split: SplitStrategy> CheckRef<'_, '_, Split> {
346 #[must_use]
347 pub fn new_solvable(&mut self) -> Term {
348 let name = self.top.new_solvable();
349 self.add_solvable(name.clone());
350 apply_solvable(name, self.context.as_ref().iter().map(|cv| cv.var.clone()))
351 }
352 pub(crate) fn solve_equality(&mut self, unk: &Id, solution: &Term) -> Option<bool> {
353 self.comment(format!("solving unknown {unk}"));
354 let Some(unks) = self.get_solvable(unk) else {
355 self.failure("Unknown unknown!");
356 return Some(false);
357 };
358 let solution = self.subst(solution.clone());
359
360 if let BoundedValue::Solved(tm) = &unks.solution {
361 let tm = tm.clone();
362 let ctx = unks
363 .context
364 .iter()
365 .cloned()
366 .map(cleanup_cv)
367 .collect::<Vec<_>>();
368 self.comment("already solved");
369 let solution = if ctx.is_empty() {
370 solution
371 } else {
372 Term::Bound(BindingTerm::new(
373 (*ftml_uris::metatheory::BIND_UNKNOWNS).clone().into(),
374 Box::new([
375 BoundArgument::BoundSeq(MaybeSequence::Seq(ctx.into_boxed_slice())),
376 BoundArgument::Simple(solution),
377 ]),
378 None,
379 ))
380 };
381 return self.scoped(|slf| slf.check_equality(&solution, &tm));
382 }
383 self.solve(unk.clone(), solution)?;
384 Some(true)
385 }
386
387 pub(crate) fn solve_upper_bound(&mut self, unk: &Id, bound: &Term) -> Option<bool> {
388 tracing::debug!("Solving upper bound");
389 self.comment(format!("solving boundaries of unknown {unk}"));
390 let Some(unks) = self.get_solvable(unk) else {
391 self.failure("Unknown unknown!");
392 return Some(false);
393 };
394 let bound = self.subst(bound.clone());
395
396 if let BoundedValue::Solved(tm) = &unks.solution {
397 let tm = tm.clone();
398 let ctx = unks
399 .context
400 .iter()
401 .cloned()
402 .map(cleanup_cv)
403 .collect::<Vec<_>>();
404 self.comment("already solved");
405 let bound = if ctx.is_empty() {
406 bound
407 } else {
408 Term::Bound(BindingTerm::new(
409 (*ftml_uris::metatheory::BIND_UNKNOWNS).clone().into(),
410 Box::new([
411 BoundArgument::BoundSeq(MaybeSequence::Seq(ctx.into_boxed_slice())),
412 BoundArgument::Simple(bound),
413 ]),
414 None,
415 ))
416 };
417 return self.scoped(|slf| slf.check_subtype(&tm, &bound));
418 }
419 self.solve(unk.clone(), bound)?;
445 Some(true)
446 }
447
448 pub(crate) fn solve_lower_bound(&mut self, unk: &Id, bound: &Term) -> Option<bool> {
449 tracing::debug!("Solving lower bound");
450 self.comment(format!("solving boundaries of unknown {unk}"));
451 let Some(unks) = self.get_solvable(unk) else {
452 self.failure("Unknown unknown!");
453 return Some(false);
454 };
455
456 let bound = self.subst(bound.clone());
457
458 if let BoundedValue::Solved(tm) = &unks.solution {
461 let tm = tm.clone();
462 let ctx = unks
463 .context
464 .iter()
465 .cloned()
466 .map(cleanup_cv)
467 .collect::<Vec<_>>();
468 self.comment("already solved");
469 let bound = if ctx.is_empty() {
470 bound
471 } else {
472 Term::Bound(BindingTerm::new(
473 (*ftml_uris::metatheory::BIND_UNKNOWNS).clone().into(),
474 Box::new([
475 BoundArgument::BoundSeq(MaybeSequence::Seq(ctx.into_boxed_slice())),
476 BoundArgument::Simple(bound),
477 ]),
478 None,
479 ))
480 };
481 return self.scoped(|slf| slf.check_subtype(&bound, &tm));
482 }
483 self.solve(unk.clone(), bound)?;
484 Some(true)
485 }
486
487 #[inline]
488 pub(crate) fn merge_solutions(&mut self, solutions: Solutions) {
489 self.solutions.merge(solutions);
490 }
491 fn add_solvable(&mut self, name: Id) {
492 self.solutions.0.insert(Solvable::new(
493 name,
494 self.context.as_ref().iter().map(|v| v.clone().into_owned()),
495 ));
496 }
497
498 pub fn get_solution(&self, name: &Id) -> Option<Term> {
499 self.get_solvable(name)
500 .and_then(Solvable::solution)
501 .map(|t| self.subst(t.clone()))
502 }
503
504 fn get_solvable<'s>(&'s self, name: &Id) -> Option<&'s Solvable> {
505 self.solutions.get_solvable(name)
506 }
533
534 pub(crate) fn get_solvable_type(&mut self, name: &Id) -> Term {
535 let ctx = if let Some(s) = self.get_solvable(name) {
536 if let BoundedValue::Solved(t) = &s.tp {
537 return t.clone();
538 }
539 Some(
540 s.context
541 .iter()
542 .cloned()
543 .map(cleanup_cv)
544 .collect::<Vec<_>>(),
545 )
546 } else {
547 None
548 };
549 let ctx = ctx.unwrap_or(Vec::new());
550 let tp_name = self.top.new_solvable();
551 self.solutions
552 .0
553 .insert(Solvable::new(tp_name.clone(), ctx.iter().cloned()));
554 let tp: Term = if ctx.is_empty() {
555 tp_name.into()
556 } else {
557 let body = apply_solvable(tp_name, ctx.iter().map(|cv| cv.var.clone()));
558 Term::Bound(BindingTerm::new(
559 (*ftml_uris::metatheory::BIND_UNKNOWNS).clone().into(),
560 Box::new([
561 BoundArgument::BoundSeq(MaybeSequence::Seq(ctx.into_boxed_slice())),
562 BoundArgument::Simple(body),
563 ]),
564 None,
565 ))
566 };
567 self.solve_type(name.clone(), tp.clone());
568 tp
569 }
570
571 fn solve(&mut self, name: Id, solution: Term) -> Option<()> {
572 let Some((ctx, tp)) = self.get_solvable(&name).map(|s| {
573 (
574 s.context
575 .iter()
576 .cloned()
577 .map(cleanup_cv)
578 .collect::<Vec<_>>(),
579 (), )
581 }) else {
582 self.failure("Unknown not found");
583 return None;
584 };
585 let solution = self.subst(solution);
586 if solution.has_free_such_that(|v| v.name() == name.as_ref()) {
587 tracing::debug!("Circular solution! {:?}", solution.debug_short());
588 self.failure(format!("Circular solution: {:?}", solution.debug_short()));
589 return None;
590 }
591 let solution = if ctx.is_empty() {
592 solution
593 } else {
594 Term::Bound(BindingTerm::new(
595 (*ftml_uris::metatheory::BIND_UNKNOWNS).clone().into(),
596 Box::new([
597 BoundArgument::BoundSeq(MaybeSequence::Seq(ctx.into_boxed_slice())),
598 BoundArgument::Simple(solution),
599 ]),
600 None,
601 ))
602 };
603 tracing::debug!("solving {name} as {:?}", solution.debug_short());
604 self.comment(format!("Solved {name} as {:?}", solution.debug_short()));
605 self.solutions.set_solution(name, solution);
610 Some(())
611 }
612 fn solve_type(&mut self, name: Id, tp: Term) {
613 let tp = self.subst(tp);
614 self.solutions.set_type(name, tp);
615 }
616
617 pub(crate) fn subst(&self, term: Term) -> Term {
618 self.solutions.subst(term) }
620}
621
622fn cleanup_cv(cv: ComponentVar) -> ComponentVar {
623 ComponentVar {
624 var: cv.var,
625 tp: None,
626 df: None,
627 }
628}