diff options
| -rw-r--r-- | examples/equality.makkai | 42 | ||||
| -rw-r--r-- | src/checker.rs | 2 | ||||
| -rw-r--r-- | src/checker_set.rs | 167 | ||||
| -rw-r--r-- | src/checker_signature.rs | 67 | ||||
| -rw-r--r-- | src/checker_state.rs | 9 | ||||
| -rw-r--r-- | src/parser.rs | 4 |
6 files changed, 175 insertions, 116 deletions
diff --git a/examples/equality.makkai b/examples/equality.makkai index ac668c7..5e9a0f9 100644 --- a/examples/equality.makkai +++ b/examples/equality.makkai @@ -2,31 +2,37 @@ let set Empty = variant[] let set Unit = record {} let element pt : Unit = {} -let set Three = variant [ zero : Unit | one : Unit | two : Unit ] +let set Two = variant [ zero : Unit | one : Unit] let signature SetWithEquivRelation = theory { Carrier :: Set, Relation :: (x : set-of(Carrier)) (y: set-of(Carrier)) -> Set, Reflexive :: (x : set-of(Carrier)) -> <set-of(Relation x x)>, - Symmetric :: (x : set-of(Carrier)) (y : set-of(Carrier)) (r : set-of(Relation x y)) -> <set-of(Relation y x)>, - Transitive :: (x : set-of(Carrier)) (y : set-of(Carrier)) (z : set-of(Carrier)) (r : set-of(Relation x y)) (s : set-of(Relation z z)) -> <set-of(Relation y z)> + Symmetric :: (x : set-of(Carrier)) (y : set-of(Carrier)) (r : set-of(Relation x y)) + -> <set-of(Relation y x)>, + Transitive :: (x : set-of(Carrier)) (y : set-of(Carrier)) (z : set-of(Carrier)) + (r : set-of(Relation x y)) (s : set-of(Relation y z)) + -> <set-of(Relation x z)> } -let instance eqThree :: SetWithEquivRelation = { - .Carrier = Three :: Set, - .Relation = for (x: Three) (y: Three), - case x of [ zero. z => case y of [ zero. w => Unit :: Set | one. w => Empty :: Set | two. w => Empty :: Set ] - | one. z => case y of [ zero. w => Empty :: Set | one. w => Unit :: Set | two. w => Empty :: Set ] - | two. z => case y of [ zero. w => Empty :: Set | one. w => Empty :: Set | two. w => Unit :: Set ] ], - .Reflexive = for (x: Three), case x of [ zero. z => <pt> | one. z => <pt> | two. z => <pt> ], - .Symmetric = for (x: Three) (y: Three) (r: set-of(Relation x y)), - case x of [ zero. z => case y of [ zero. z => <r> | one. z => <r> | two. z => <r> ] - | one. z => case y of [ zero. z => <r> | one. z => <r> | two. z => <r> ] - | two. z => case y of [ zero. z => <r> | one. z => <r> | two. z => <r> ] - ], - .Transitive = for (x: Three)(y: Three)(z: Three)(r: set-of(Relation x y))(s: set-of(Relation y z)), <pt> +let instance eqTwo :: SetWithEquivRelation = { + .Carrier = Two :: Set, + .Relation = for (x: Two) (y: Two), + case x of [ zero. _ => case y of [ zero. _ => Unit :: Set | one. _ => Empty :: Set ] + | one. _ => case y of [ zero. _ => Empty :: Set | one. _ => Unit :: Set ] ], + .Reflexive = for (x: Two), case x of [ zero. _ => <pt> | one. _ => <pt> ], + .Symmetric = for (x: Two) (y: Two) (r: set-of(Relation x y)), + case x of [ zero. _ => case y of [ zero. _ => <r> | one. _ => <r> ] + | one. _ => case y of [ zero. _ => <r> | one. _ => <r> ] ], + .Transitive = for (x: Two)(y: Two)(z: Two)(r: set-of(Relation x y))(s: set-of(Relation y z)), + case x of [ zero. _ => case y of + [ zero. _ => case z of [ zero. _ => <r> | one. _ => <s> ] + | one. _ => case z of [ zero. _ => <pt>| one. _ => <r> ] ] + | one. _ => case y of + [ zero. _ => case z of [ zero. _ => <r> | one. _ => <pt>] + | one. _ => case z of [ zero. _ => <s> | one. _ => <r> ] ] ] } -// let set Diagonal = record { x : set-of(eqThree .Carrier), y : set-of(eqThree .Carrier), equal : set-of((eqThree .Relation) x y) } +let set Diagonal = record { x : set-of(eqTwo .Carrier), y : set-of(eqTwo .Carrier), equal : set-of((eqTwo .Relation) x y) } -// let element oneEqualsOne : Diagonal = { .x = one. pt, .y = one. pt, .equal = pt } +let element oneEqualsOne : Diagonal = { .x = one. pt, .y = one. pt, .equal = pt } diff --git a/src/checker.rs b/src/checker.rs index f94fccc..7b06c8b 100644 --- a/src/checker.rs +++ b/src/checker.rs @@ -28,7 +28,7 @@ impl CheckerState { Decl::Element { name, element, set } => { self.assert_unbound_element(name)?; let set = self.check_set(set)?; - let element = self.check_element(element.into(), &set)?; + let element = self.check_element(element.into(), Some(&set))?; self.add_element(name.clone(), element.into(), set) } Decl::Signature { name, signature } => { diff --git a/src/checker_set.rs b/src/checker_set.rs index 83d97a1..f13f686 100644 --- a/src/checker_set.rs +++ b/src/checker_set.rs @@ -13,7 +13,7 @@ impl CheckerState { let mut ctx = self.clone(); let field_set = fields.iter().map(|f| &f.name).collect::<HashSet<&String>>(); if field_set.len() != fields.len() { - todo!("duplicate fields"); + return Err(CheckerError::DuplicateFieldsSet(set.clone())); } let fields = fields .into_iter() @@ -29,6 +29,11 @@ impl CheckerState { Ok(Set::Record(fields)) } Set::Variant(fields) => { + let field_set = fields.iter().map(|f| &f.name).collect::<HashSet<&String>>(); + if field_set.len() != fields.len() { + return Err(CheckerError::DuplicateFieldsSet(set.clone())); + } + let fields = fields .into_iter() .map(|Field { name, carries }| { @@ -76,43 +81,59 @@ impl CheckerState { } } - #[instrument(skip(self), level = "debug", fields(%element, %set))] - pub fn check_element(&self, element: &Element, set: &Set) -> Result<Element, CheckerError> { + #[instrument(skip(self), level = "debug", fields(%element, set=%set.map(|s| s.to_string()).unwrap_or_default()))] + pub fn check_element( + &self, + element: &Element, + set: Option<&Set>, + ) -> Result<Element, CheckerError> { match element { Element::Literal(lit) => { let value = element.clone(); // we may infer the type from the element - match lit { - Literal::Int(_) => { - self._check_literal_set_helper(value, set, Set::BuiltIn(BuiltIn::Int))?; - } - Literal::Nat(_) => { - self._check_literal_set_helper(value, set, Set::BuiltIn(BuiltIn::Nat))?; - } - Literal::Str(_) => { - self._check_literal_set_helper(value, set, Set::BuiltIn(BuiltIn::Str))?; - } - Literal::Bool(_) => { - self._check_literal_set_helper(value, set, Set::BuiltIn(BuiltIn::Bool))?; - } - Literal::Float(_) => { - self._check_literal_set_helper(value, set, Set::BuiltIn(BuiltIn::Float))?; + if let Some(set) = set { + match lit { + Literal::Int(_) => { + self._check_literal_set_helper(value, set, Set::BuiltIn(BuiltIn::Int))?; + } + Literal::Nat(_) => { + self._check_literal_set_helper(value, set, Set::BuiltIn(BuiltIn::Nat))?; + } + Literal::Str(_) => { + self._check_literal_set_helper(value, set, Set::BuiltIn(BuiltIn::Str))?; + } + Literal::Bool(_) => { + self._check_literal_set_helper( + value, + set, + Set::BuiltIn(BuiltIn::Bool), + )?; + } + Literal::Float(_) => { + self._check_literal_set_helper( + value, + set, + Set::BuiltIn(BuiltIn::Float), + )?; + } } } Ok(element.clone().into()) } Element::Var(v) => { let lookup = self.lookup_element(&v)?; - let container = self.check_set(&lookup.container)?; // TODO: necessary why? - // we have previously done the work to discover the type of - // this element, so what we're claiming now must match! - if !self.equal(set, &container) { - return Err(CheckerError::WrongSetForElement { - value: element.clone().into(), - claimed: set.clone(), - real: container, - }); + if let Some(set) = set { + let container = self.check_set(&lookup.container)?; + // we have previously done the work to discover the type of + // this element, so what we're claiming now must match! + if !self.equal(set, &container) { + return Err(CheckerError::WrongSetForElement { + value: element.clone().into(), + claimed: set.clone(), + real: container, + }); + } } // If we found a formal binding, we have no value to report. // This is the ONLY source of Var as a return value for @@ -125,21 +146,53 @@ impl CheckerState { } } Element::Record(assignations) => { - let rej = |reason| CheckerError::ElementDoesNotBelong { - element: element.clone(), - claimed: set.clone(), - reason, + // there's a very short path here for constructing {} : record {} + if assignations.is_empty() { + let element = Element::Record(Vec::new()); + if let Some(set) = set { + if !self.equal(set, &Set::Record(Vec::new())) { + return Err(CheckerError::ElementDoesNotBelong { + element, + claimed: set.clone(), + reason: "the set is not the empty record".to_string(), + }); + }; + }; + return Ok(element); + } + // from here on assignations is non-empty + + // look up the owner for each tag + let owners = assignations + .iter() + .map(|ea| self.lookup_record_field(&ea.name).map(|x| &x.owner)) + .collect::<Result<Vec<_>, _>>()?; + + // make sure they're all the same + let owner = owners[0]; + if !owners[1..].into_iter().all(|x| self.equal(*x, owner)) { + return Err(CheckerError::ElementInconsistentFieldChoice( + element.clone(), + )); }; - // make sure we are filling a record - let fields = if let Set::Record(fields) = set { - Ok(fields) - } else { - Err(rej("element is a record instance".to_string())) - }?; + // if in addition we know the set, make sure it agrees + if let Some(set) = set + && !self.equal(set, owner) + { + return Err(CheckerError::WrongSetForElement { + value: element.clone().into(), + claimed: set.clone(), + real: owner.clone(), + }); + } + + let Set::Record(fields) = owner else { + panic!("invariant violation: looking up field owners did not retrieve a record") + }; let mut set_fnames_sorted: Vec<String> = - fields.iter().map(|x| x.name.clone()).collect(); + fields.iter().map(|f| f.name.clone()).collect(); set_fnames_sorted.sort(); let mut element_fnames_sorted: Vec<String> = @@ -148,11 +201,15 @@ impl CheckerState { // make sure that we are correctly filling the record if set_fnames_sorted != element_fnames_sorted { - return Err(rej(format!( - "expected [{}] but found [{}]", - set_fnames_sorted.join(", "), - element_fnames_sorted.join(", "), - ))); + return Err(CheckerError::ElementDoesNotBelong { + element: element.clone(), + claimed: owner.clone(), + reason: format!( + "expected [{}] but found [{}]", + set_fnames_sorted.join(", "), + element_fnames_sorted.join(", "), + ), + }); } let assignations = assignations @@ -176,7 +233,7 @@ impl CheckerState { .expect("we have already checked that all fields are present"); let f_s = ctx.check_set(f_s)?; - let f_e = ctx.check_element(f_e, &f_s)?; + let f_e = ctx.check_element(f_e, Some(&f_s))?; ctx.add_element(f_n.clone(), f_e.clone().into(), f_s)?; Ok(ElemAssign { @@ -200,7 +257,9 @@ impl CheckerState { } = self.lookup_record_field(&field)?; // enforce the correct typing of the claimed result - if !self.equal(set, field_set) { + if let Some(set) = set + && !self.equal(set, field_set) + { return Err(CheckerError::WrongSetForElement { value: element.clone().into(), claimed: set.clone(), @@ -209,7 +268,7 @@ impl CheckerState { } // enforce the correct typing of the element - let inner = self.check_element(inner, owner_set)?; + let inner = self.check_element(inner, Some(owner_set))?; // Unfortunately we still have to do something nasty here to // obtain the data @@ -250,7 +309,9 @@ impl CheckerState { } = self.lookup_variant_field(&field)?; // enforce the correct typing of the claimed result - if !self.equal(set, owner_set) { + if let Some(set) = set + && !self.equal(set, owner_set) + { return Err(CheckerError::WrongSetForElement { value: element.clone().into(), claimed: set.clone(), @@ -259,7 +320,7 @@ impl CheckerState { } // enforce the correct typing of the element - let element = self.check_element(inner, field_set)?; + let element = self.check_element(inner, Some(field_set))?; Ok(Element::Inject { element: Box::new(element), field: field.clone(), @@ -311,7 +372,7 @@ impl CheckerState { // scrutinee must be of the same set that all the arms are // implying, in particular this implies that the following holds // `inner : self.lookup_variant_field(field).field_set` - let scrutinee = self.check_element(scrutinee, owner)?; + let scrutinee = self.check_element(scrutinee, Some(owner))?; // which variant are we, if any let matching: Option<(String, Element)> = match scrutinee { @@ -359,9 +420,9 @@ impl CheckerState { { let canonical = ctx.make_element_definition(binding_name, inner.clone(), binding_set)?; - let this_set = ctx.check_set(set)?; + let this_set = set.map(|set| ctx.check_set(set)).transpose()?; - let output = ctx.check_element((&arm.body).into(), &this_set)?; + let output = ctx.check_element((&arm.body).into(), this_set.as_ref())?; if matches!(computed_output, Some(_)) { panic!( "invariant violation: we somehow matched multiple arms in case analysis" @@ -386,8 +447,8 @@ impl CheckerState { owner.clone(), )?; }; - let this_set = ctx.check_set(set)?; - let body = ctx.check_element((&arm.body).into(), &this_set)?; + let this_set = set.map(|set| ctx.check_set(set)).transpose()?; + let body = ctx.check_element((&arm.body).into(), this_set.as_ref())?; CaseArm { tag: arm.tag.clone(), diff --git a/src/checker_signature.rs b/src/checker_signature.rs index 9d0bffe..1ce8bc8 100644 --- a/src/checker_signature.rs +++ b/src/checker_signature.rs @@ -1,6 +1,6 @@ use crate::ast::*; use crate::checker_state::*; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::iter::zip; use tracing::instrument; @@ -80,6 +80,11 @@ impl CheckerState { }) } Signature::Theory(fields) => { + let field_set = fields.iter().map(|f| &f.name).collect::<HashSet<&String>>(); + if field_set.len() != fields.len() { + return Err(CheckerError::DuplicateFieldsSignature(signature.clone())); + } + let mut ctx = self.clone(); let temp_name = ctx.make_unique_name(); let mut new_fields = Vec::new(); @@ -132,7 +137,7 @@ impl CheckerState { } } Instance::ElementCoerce(element) => { - if let Some(signature) = signature { + let set = if let Some(signature) = signature { let Signature::FromSet(set) = signature else { return Err(CheckerError::WrongSignatureForInstance { value: instance.clone().into(), @@ -140,26 +145,13 @@ impl CheckerState { claimed: signature.clone(), }); }; - println!("{self}"); - let element = self.check_element(element, set)?; - Ok(Instance::ElementCoerce(element)) + Some(set) } else { - // todo!("how do we handle check_element without a set?"); - println!("THISISATTODO"); - // quick hack: - let element = if let Element::Var(v) = element { - let lookup = self.lookup_element(&v)?; - if let ElementValue::Concrete(ref x) = lookup.value { - x.clone() - } else { - element.clone() - } - } else { - element.clone() - }; + None + }; - Ok(Instance::ElementCoerce(element.clone())) - } + let element = self.check_element(element, set)?; + Ok(Instance::ElementCoerce(element)) } Instance::Var(v) => { // Exactly the same discipline as for Element::Var, see there @@ -337,7 +329,7 @@ impl CheckerState { required: required_field_names_sorted, }); } - let scrutinee = self.check_element(scrutinee, owner)?; + let scrutinee = self.check_element(scrutinee, Some(owner))?; let matching: Option<(String, Element)> = match scrutinee { Element::Inject { @@ -376,12 +368,8 @@ impl CheckerState { { let canonical = ctx.make_element_definition(binding_name, inner.clone(), binding_set)?; - let this_signature = if let Some(signature) = signature { - let signature = ctx.check_signature(signature)?; - Some(signature) - } else { - None - }; + let this_signature = + signature.map(|s| ctx.check_signature(s)).transpose()?; let output = ctx.check_instance((&arm.body).into(), this_signature.as_ref())?; @@ -409,13 +397,8 @@ impl CheckerState { owner.clone(), )?; }; - let this_signature = if let Some(signature) = signature { - let signature = ctx.check_signature(signature)?; - Some(signature) - } else { - None - }; - + let this_signature = + signature.map(|s| ctx.check_signature(s)).transpose()?; let body = ctx.check_instance((&arm.body).into(), this_signature.as_ref())?; CaseArm { @@ -683,7 +666,7 @@ impl CheckerState { let checked = zip(params.iter(), args.iter()) .map(|(p, a)| { let p_set = ctx.check_set(&p.set)?; - let a = ctx.check_element(a, &p_set)?; + let a = ctx.check_element(a, Some(&p_set))?; ctx.add_element(p.name.clone(), a.clone().into(), p_set)?; Ok(a) }) @@ -697,13 +680,13 @@ impl CheckerState { Instance::Project { field, .. } => { Ok(Some(self.lookup_signature_field(field)?.field.clone())) } - // All arms of a stuck Case share a signature by the case - // elimination typing rule, and we've already expanded the body, so - // we can pick any arm. - - // TODO! this is wrong! - Instance::Case { arms, .. } => self - ._stuck_subject_signature(&arms.first().expect("we don't allow bottom type").body), + // until we properly support motives there's nothing we can really do here + Instance::Case { .. } => { + let msg = format!( + "this type checker has no motives yet, and was called upon to infer the signature of {inst}, which leads with a `case`, and so has no option but to fail" + ); + Err(CheckerError::Unimplemented(msg)) + } Instance::ElementCoerce(_) | Instance::For { .. } | Instance::Record(_) diff --git a/src/checker_state.rs b/src/checker_state.rs index 77abbdd..42f5089 100644 --- a/src/checker_state.rs +++ b/src/checker_state.rs @@ -19,6 +19,9 @@ pub enum CheckerError { #[display("The following functionality is unimplemented: {_0}")] Unimplemented(String), + #[display("The set contains dulplicate fiels: {_0}")] + DuplicateFieldsSet(Set), + #[display("Element {value} claimed to belong to {claimed} but actually belongs to {real}")] WrongSetForElement { value: ElementValue, @@ -33,9 +36,15 @@ pub enum CheckerError { reason: String, }, + #[display("Record construction involves incosistent field choice {_0}")] + ElementInconsistentFieldChoice(Element), + #[display("Case analysis {_0} does not have consistent set for scrutinee")] ElementInconsistentCaseScrutineeSet(Element), + #[display("The signature contains dulplicate fiels: {_0}")] + DuplicateFieldsSignature(Signature), + #[display("Case analysis {_0} does not have consistent set for scrutinee")] InstanceInconsistentCaseScrutineeSet(Instance), diff --git a/src/parser.rs b/src/parser.rs index 73e8dd3..8dbc31e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -57,13 +57,13 @@ parser! { // ==================================================================== rule lower_ident() -> String - = !keyword() s:$(['a'..='z'] ident_tail()*) { s.to_string() } + = !keyword() s:$(['_' | 'a'..='z'] ident_tail()*) { s.to_string() } rule upper_ident() -> String = !keyword() s:$(['A'..='Z'] ident_tail()*) { s.to_string() } rule any_ident() -> String - = !keyword() s:$(['a'..='z' | 'A'..='Z'] ident_tail()*) { s.to_string() } + = !keyword() s:$(['_' | 'a'..='z' | 'A'..='Z'] ident_tail()*) { s.to_string() } rule elem_var() -> String = lower_ident() rule inst_var() -> String = lower_ident() |
