Skip to content

Commit eec1c36

Browse files
committed
Add test for multi-substitutor
1 parent 57179a1 commit eec1c36

File tree

1 file changed

+138
-29
lines changed

1 file changed

+138
-29
lines changed

src/typing/unifyable_cell.rs

Lines changed: 138 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ impl<'s, T: Debug, Unif: UnifierTop> Drop for Substitutor<'s, T, Unif> {
201201
fn drop(&mut self) {
202202
if !std::thread::panicking() && self.ready_constraints.take().is_some() {
203203
panic!(
204-
"Substitutor dropped while still holding Ready Constraints! These should have been resolved using [Unifier::execute_ready_constraints]"
204+
"Substitutor dropped while still holding Ready Constraints! These should have been resolved using [Substitutor::execute_ready_constraints]"
205205
);
206206
}
207207
}
@@ -222,6 +222,25 @@ impl<'s, T: Debug, Unif: UnifierTop> Substitutor<'s, T, Unif> {
222222
self.ready_constraints
223223
.set(DelayedConstraint::add_to_list(long_list, constraints));
224224
}
225+
226+
/// See [UnifierTop::delayed_constraint]
227+
///
228+
/// We bubble delayed constraints up to the top of the call stack,
229+
/// because if we immediately execute any delayed constraint that becomes ready,
230+
/// we might blow out the stack.
231+
///
232+
/// You may call [Substitutor::execute_ready_constraints] multiple times, but certainly you must make sure to call it before the [UnifierTop] is dropped.
233+
fn execute_ready_constraints(&self, unif: &Unif) {
234+
// During of a delayed constraint, other constraints may of course become ready.
235+
// We retry delayed constraints in a stack-like manner, as my intuition tells me this is more efficient.
236+
while let Some(mut first_constraint) = self.ready_constraints.take() {
237+
self.ready_constraints.set(first_constraint.next.take());
238+
239+
if let Err(resolution_err) = (first_constraint.f)(unif) {
240+
resolution_err.add_delayed_constraint(first_constraint);
241+
}
242+
}
243+
}
225244
}
226245

227246
impl<'s, T: Debug, Unif: UnifierTop> SubstitutorInterior<'s, T, Unif> {
@@ -328,7 +347,7 @@ pub struct SubTree<T>(usize, PhantomData<T>);
328347
/// going through the trouble with &mut refs is not worth it. Passing it along the call stack is also no bueno,
329348
/// we'd have to pass the unifier itself, plus whatever extra data the user wants to attach to it. Lots of complexity for nothing.
330349
///
331-
/// Times we've been through the `&mut Substitutor` dead-end thus far: 3
350+
/// Times we've been through the `&mut Substitutor` dead-end thus far: 4
332351
pub trait Unifier<'slf, 's: 'slf, T: Debug + Clone + 's>: UnifierTop + Sized + 's {
333352
/// You should declare a [Substitutor] field for each [UniCell]`<T>` you wish to support. Return it here.
334353
fn get_substitutor(&'slf self) -> &'slf Substitutor<'s, T, Self>;
@@ -560,38 +579,24 @@ pub trait Unifier<'slf, 's: 'slf, T: Debug + Clone + 's>: UnifierTop + Sized + '
560579
Err(None) => false,
561580
}
562581
}
563-
564-
/// See [UnifierTop::delayed_constraint]
565-
///
566-
/// We bubble delayed constraints up to the top of the call stack,
567-
/// because if we immediately execute any delayed constraint that becomes ready,
568-
/// we might blow out the stack.
569-
fn execute_ready_constraints(&'slf self) {
570-
let subs = self.get_substitutor();
571-
// During of a delayed constraint, other constraints may of course become ready.
572-
// We retry delayed constraints in a stack-like manner, as my intuition tells me this is more efficient.
573-
while let Some(mut first_constraint) = subs.ready_constraints.take() {
574-
subs.ready_constraints.set(first_constraint.next.take());
575-
576-
if let Err(resolution_err) = (first_constraint.f)(self) {
577-
resolution_err.add_delayed_constraint(first_constraint);
578-
}
579-
}
580-
}
581582
}
582583

583584
pub trait UnifierTop: Sized {
584-
/// When using [UnifierTop::delayed_constraint],
585-
/// you must also call [Unifier::execute_ready_constraints] once, or multiple times.
586-
fn delayed_constraint<'slf2, 's: 'slf2>(
587-
&'slf2 self,
588-
mut f: impl for<'slf> FnMut(&'slf Self) -> Result<(), ResolveError<'slf, 's, Self>> + 's,
585+
/// You must call [UnifierTop::execute_ready_constraints] after creating delayed_constraints,
586+
/// as not immediately resolved delayed constraints don't immediately get resolved the moment they become eligible.
587+
fn delayed_constraint<'slf, 's: 'slf>(
588+
&'slf self,
589+
mut f: impl for<'fn_slf> FnMut(&'fn_slf Self) -> Result<(), ResolveError<'fn_slf, 's, Self>>
590+
+ 's,
589591
) {
590592
if let Err(not_found_var) = f(self) {
591593
// May be a not_found_var from a different Substitutor
592594
not_found_var.add_delayed_constraint(Box::new(DelayedConstraint { next: None, f }));
593595
}
594596
}
597+
/// This method is provided as a reminder that [Substitutor::execute_ready_constraints] must be called for all [Substitutor]s that are part of this [UnifierTop].
598+
/// You may call [UnifierTop::execute_ready_constraints] multiple times, but certainly you must make sure to call it before the [UnifierTop] is dropped.
599+
fn execute_ready_constraints(&self);
595600
}
596601

597602
/// Fancy trick! [Rust Forum - Creating a DST struct with a dyn FnMut](https://users.rust-lang.org/t/creating-a-dst-struct-with-a-dyn-fnmut/137256/3).
@@ -701,23 +706,42 @@ impl PeanoType {
701706
}
702707
}
703708

709+
#[derive(Debug, Clone)]
710+
enum SecondType {
711+
None,
712+
OnePeano(UniCell<PeanoType>),
713+
TwoPeano(UniCell<PeanoType>, UniCell<PeanoType>),
714+
}
715+
716+
impl SecondType {
717+
#[allow(clippy::declare_interior_mutable_const)]
718+
pub const UNKNOWN: UniCell<SecondType> = UniCell::<SecondType>::UNKNOWN;
719+
}
720+
704721
#[derive(Debug)]
705722
struct PeanoUnifier<'s> {
706-
substitutor: Substitutor<'s, PeanoType, Self>,
723+
peano_subs: Substitutor<'s, PeanoType, Self>,
724+
second_subs: Substitutor<'s, SecondType, Self>,
707725
}
708726

709727
impl<'s> PeanoUnifier<'s> {
710728
pub fn new() -> Self {
711729
Self {
712-
substitutor: Substitutor::new(),
730+
peano_subs: Substitutor::new(),
731+
second_subs: Substitutor::new(),
713732
}
714733
}
715734
}
716735

717-
impl<'s> UnifierTop for PeanoUnifier<'s> {}
736+
impl<'s> UnifierTop for PeanoUnifier<'s> {
737+
fn execute_ready_constraints(&self) {
738+
self.peano_subs.execute_ready_constraints(self);
739+
self.second_subs.execute_ready_constraints(self);
740+
}
741+
}
718742
impl<'slf, 's: 'slf> Unifier<'slf, 's, PeanoType> for PeanoUnifier<'s> {
719743
fn get_substitutor(&'slf self) -> &'slf Substitutor<'s, PeanoType, Self> {
720-
&self.substitutor
744+
&self.peano_subs
721745
}
722746
fn unify_subtrees(&'slf self, a: &'s PeanoType, b: &'s PeanoType) -> UnifyResult {
723747
match (a, b) {
@@ -755,6 +779,57 @@ impl<'slf, 's: 'slf> Unifier<'slf, 's, PeanoType> for PeanoUnifier<'s> {
755779
}
756780
}
757781
}
782+
impl<'slf, 's: 'slf> Unifier<'slf, 's, SecondType> for PeanoUnifier<'s> {
783+
fn get_substitutor(&'slf self) -> &'slf Substitutor<'s, SecondType, Self> {
784+
&self.second_subs
785+
}
786+
fn unify_subtrees(&'slf self, a: &'s SecondType, b: &'s SecondType) -> UnifyResult {
787+
match (a, b) {
788+
(SecondType::None, SecondType::None) => UnifyResult::Success,
789+
(SecondType::OnePeano(a), SecondType::OnePeano(b)) => self.unify(a, b),
790+
(SecondType::TwoPeano(a1, a2), SecondType::TwoPeano(b1, b2)) => {
791+
self.unify(a1, b1) & self.unify(a2, b2)
792+
}
793+
_ => UnifyResult::Failure,
794+
}
795+
}
796+
fn set_subtrees(&'slf self, a: &'s SecondType, b: SecondType) -> UnifyResult {
797+
match (a, b) {
798+
(SecondType::None, SecondType::None) => UnifyResult::Success,
799+
(SecondType::OnePeano(a), SecondType::OnePeano(b)) => self.set_cell(a, b),
800+
(SecondType::TwoPeano(a1, a2), SecondType::TwoPeano(b1, b2)) => {
801+
self.set_cell(a1, b1) & self.set_cell(a2, b2)
802+
}
803+
_ => UnifyResult::Failure,
804+
}
805+
}
806+
fn contains_subtree(&'slf self, _in_obj: &SecondType, _subtree: SubTree<SecondType>) -> bool {
807+
false // SecondType doesn't recurse
808+
}
809+
fn fully_substitute_recurse(
810+
&'slf self,
811+
known: &'s SecondType,
812+
) -> Option<ResolveError<'slf, 's, Self>> {
813+
match known {
814+
SecondType::None => None,
815+
SecondType::OnePeano(a) => self.fully_substitute(a).err(),
816+
SecondType::TwoPeano(a, b) => {
817+
let a = self.fully_substitute(a).err();
818+
let b = self.fully_substitute(b).err();
819+
a.or(b)
820+
}
821+
}
822+
}
823+
fn clone_known(&'slf self, known: &'s SecondType) -> SecondType {
824+
match known {
825+
SecondType::None => SecondType::None,
826+
SecondType::OnePeano(a) => SecondType::OnePeano(self.clone_unify(a)),
827+
SecondType::TwoPeano(a, b) => {
828+
SecondType::TwoPeano(self.clone_unify(a), self.clone_unify(b))
829+
}
830+
}
831+
}
832+
}
758833

759834
impl UniCell<PeanoType> {
760835
pub fn clone_prototype(&self) -> Self {
@@ -1180,6 +1255,40 @@ mod tests {
11801255
}
11811256
}
11821257

1258+
#[test]
1259+
fn test_multi_substitutor() {
1260+
let a = SecondType::UNKNOWN;
1261+
let b = SecondType::UNKNOWN;
1262+
1263+
let substitutor = PeanoUnifier::new();
1264+
1265+
substitutor
1266+
.set(
1267+
&a,
1268+
SecondType::TwoPeano(mk_peano_cell(1), PeanoType::UNKNOWN),
1269+
)
1270+
.unwrap();
1271+
substitutor
1272+
.set(
1273+
&b,
1274+
SecondType::TwoPeano(PeanoType::UNKNOWN, mk_peano_cell(2)),
1275+
)
1276+
.unwrap();
1277+
1278+
substitutor.unify(&a, &b).unwrap();
1279+
1280+
let a = substitutor.fully_substitute(&a).unwrap();
1281+
let b = substitutor.fully_substitute(&b).unwrap();
1282+
1283+
let_unwrap!(SecondType::TwoPeano(a1, a2), a);
1284+
let_unwrap!(SecondType::TwoPeano(b1, b2), b);
1285+
1286+
assert_eq!(a1.unwrap().count(), 1);
1287+
assert_eq!(a2.unwrap().count(), 2);
1288+
assert_eq!(b1.unwrap().count(), 1);
1289+
assert_eq!(b2.unwrap().count(), 2);
1290+
}
1291+
11831292
/// Just a stress test to cover all possible code paths. To check under miri that everything is alright.
11841293
#[test]
11851294
fn stress_test_for_miri() {

0 commit comments

Comments
 (0)