diff options
author | Joel Kronqvist <joel.kronqvist@iki.fi> | 2025-08-18 13:38:57 +0300 |
---|---|---|
committer | Joel Kronqvist <joel.kronqvist@iki.fi> | 2025-08-18 13:38:57 +0300 |
commit | 6ce77dfc296c24d43ac1e3b2daffec0a7e485891 (patch) | |
tree | c59adb488bef01a396340c7f4df8f52c00fefd20 | |
parent | 57c6323c15e257d4620f9d02a54e704d25bd2084 (diff) | |
download | myslip-6ce77dfc296c24d43ac1e3b2daffec0a7e485891.tar.gz myslip-6ce77dfc296c24d43ac1e3b2daffec0a7e485891.zip |
refactor: change conversions to use a stricter function with deduplicated code
-rw-r--r-- | src/type/check.rs | 17 | ||||
-rw-r--r-- | src/type/conversion.rs | 492 |
2 files changed, 109 insertions, 400 deletions
diff --git a/src/type/check.rs b/src/type/check.rs index a54ffc2..f1c4a2b 100644 --- a/src/type/check.rs +++ b/src/type/check.rs @@ -330,7 +330,9 @@ impl SExp { } match &ty { None => ty = Some(arm.infer_type(newctx)?), - Some(t) => ty = Some(t.least_general_supertype(&arm.infer_type(newctx.clone())?)), + Some(t) => if arm.infer_type(newctx)? != *t { + return Err(OtherError); + }, } } if !has_wildcard { @@ -346,8 +348,15 @@ impl SExp { let opertype = (*op).infer_type(ctx.clone())?; let argstype = (*l).infer_list_type(ctx)?; + // makes [x] x + let argstype = match argstype { + List(v) if v.get(0).is_some() && v.get(1).is_none() => + v.get(0).unwrap().clone(), + t => t, + }; + let conv_args = match (opertype.clone(), argstype.clone()) { - (Arrow(from, _), a) => match a.clone().into_type(&*from) { + (Arrow(from, _), a) => match a.clone().convert(&*from) { Ok(s) => Ok(s), Err(()) => Err(InvalidArgList { arglist: (**l).clone(), @@ -370,9 +379,9 @@ impl SExp { opertype.infer_generics(&conv_args)? }; - match (opertype, argstype) { + match (opertype, conv_args) { (Arrow(a, b), c) => { - if c.aka(&*a) { + if c == *a { Ok(*b) } else { Err(InvalidArgList { diff --git a/src/type/conversion.rs b/src/type/conversion.rs index 1d14971..844632f 100644 --- a/src/type/conversion.rs +++ b/src/type/conversion.rs @@ -4,309 +4,86 @@ use std::collections::HashMap; impl Type { - /// Checks if this type is known as the given other type, - /// ie. checks if there exists an implicit conversion from - /// one to the other. - /// - /// This is needed for list to vec conversions. - /// ```rust - /// use myslip::r#type::{Type::*, util::*}; - /// assert!(List(vec![Integer, Integer, Integer]).aka(&vecof(Integer))); - /// ``` - /// - /// Preferrably this should also work with generics: - /// ```rust - /// use myslip::r#type::{Type::*, util::*}; - /// assert!(List(vec![Integer, Integer, Integer]).aka(&vecof(vt("T")))); - /// assert!(List(vec![Boolean, Boolean, Boolean]).aka(&vecof(vt("T")))); - /// ``` - /// - /// Also, because quotes and vectors have their operators hanging - /// with them, having a conversion (Vector/Quote X) -> X is convenient. - /// ```rust - /// use myslip::r#type::{Type::*, util::*}; - /// assert!( - /// List(vec![QuoteTy, List(vec![Integer, Integer])]).aka(&vecof(Integer)), - /// ); - /// assert!( - /// List(vec![List(vec![QuoteTy, List(vec![Integer, Integer])])]).aka(&vecof(Integer)), - /// ); - /// ``` - /// - /// But of course it also should know when to fail: - /// ```rust - /// use myslip::r#type::{Type::*, util::*}; - /// assert!(!List(vec![Integer, Boolean]).aka(&vecof(Integer))); - /// assert!(!List(vec![Integer, Boolean]).aka(&vecof(Boolean))); - /// ``` - /// - /// Generics might be dangerous: - /// ```rust - /// use myslip::r#type::{Type::*, util::*}; - /// assert!(arr(Boolean, Boolean).aka(&arr(vt("T"), vt("T")))); - /// assert!( - /// List(vec![Integer, Integer]) - /// .aka(&List(vec![vt("T"), vt("T")])) - /// ); - /// assert!( - /// List(vec![Integer, Boolean]) - /// .aka(&List(vec![vt("T"), vt("H")])) - /// ); - /// assert!( - /// !List(vec![Integer, Boolean]) - /// .aka(&List(vec![vt("T"), vt("T")])) - /// ); - /// assert!( - /// !List(vec![Integer, Boolean]) - /// .aka(&List(vec![vt("T"), vt("T")])) - /// ); - /// assert!(!List(vec![Integer, Boolean, vt("X")]).aka(&vecof(vt("T")))); - /// ``` - pub fn aka(&self, known_as: &Type) -> bool { - self.least_general_supertype(known_as) == *known_as - } - pub fn least_general_supertype(&self, other: &Type) -> Type { - let (ty, ctx) = self.lgst_ctx(other, Vec::new()); + /// Tries to convert this type into another type. + /// + /// The following conversions are required: + /// 0. self == other => self + /// 1. (T T ... T T) -> (T ...) + /// 2. self = concrete and other = variable type => self, + /// 3. arrow types and lists are mapped + pub fn convert(self, other: &Type) -> Result<Type, ()> { + let (ty, ctx) = self.convert_ctx(other)?; let mut checks = HashMap::new(); for (name, ty1) in ctx { if let Some(ty2) = checks.insert(name, ty1.clone()) { if ty2 != ty1 { - return vt("?"); + return Err(()); } } } - ty + Ok(ty) } - fn lgst_ctx( - &self, - other: &Type, - mut ctx: Vec<(String, Type)> - ) -> (Type, Vec<(String, Type)>) { + fn convert_ctx( + self, + other: &Type + ) -> Result<(Type, Vec<(String, Type)>), ()> { - match (self, other) { - - (a, b) if a == b => (a.clone(), ctx), - - (a, VarType(name)) => { - ctx.push((name.clone(), a.clone())); - (vt(name), ctx) - }, - - (List(v), b) if v.len() == 1 && *&v[0].aka(b) => (b.clone(), ctx), + let mut ctx = vec![]; - (List(v1), x) if !x.is_quoted_or_vectored_list() - && (v1.get(0) == Some(&VecType) || v1.get(0) == Some(&QuoteTy)) - && v1.get(1) != None => - { - match (v1.get(1), x) { - (Some(a), b) => { - let res = a.lgst_ctx(&b, ctx); - res - }, - _ => panic!("unreachable") - } - }, + match (self, other) { - (VecOf(_), VecOf(b)) => { - match &**b { - VarType(s) => (vecof(vt(s)), ctx), - _ => (vt("T"), ctx) - } - }, + (a, b) if a == *b => Ok((a, ctx)), - (Arrow(a1, a2), Arrow(b1, b2)) => { - let (t1, newctx) = a1.lgst_ctx(b1, ctx); - ctx = newctx; - let (t2, newctx) = a2.lgst_ctx(b2, ctx); - ctx = newctx; - (arr(t1, t2), ctx) + (ty, VarType(name)) => if ty.is_concrete().is_ok() { + ctx.push((name.clone(), ty.clone())); + Ok((ty, ctx)) + } else { + Err(()) }, - (List(v1), List(v2)) if v1.len() == v2.len() => { - let mut res = vec![]; - for (a, b) in v1.into_iter().zip(v2) { - let (ty, newctx) = a.lgst_ctx(b, ctx.clone()); - res.push(ty); - ctx = newctx; - } - (List(res), ctx) - }, - - (List(v1), List(v2)) => match v1.get(0).or(v2.get(0)) { - Some(first) => { - let mut res = first.clone(); - for ty in v1.into_iter().chain(v2) { - let (ty, newctx) = res.lgst_ctx(ty, ctx.clone()); - res = ty; - ctx = newctx; - } - (vecof(res), ctx) - }, - None => panic!("unreachable - types would be equal") - }, - - (List(v), VecOf(b)) => { - let mut res = *b.clone(); - for ty in v { - let (ty, newctx) = res.lgst_ctx(ty, ctx.clone()); - res = ty; - ctx = newctx; - } - (vecof(res), ctx) - }, - (VecOf(b), List(v)) => { - let mut res = *b.clone(); - for ty in v { - let (ty, newctx) = res.lgst_ctx(ty, ctx.clone()); - res = ty; - ctx = newctx; + let mut res = vec![]; + for (t1, t2) in v1.into_iter().zip(v2) { + let (t, newctx) = t1.convert_ctx(t2)?; + ctx.extend(newctx); + res.push(t); } - (vecof(res), ctx) + Ok((List(res), ctx)) }, - _ => (vt("T"), ctx) - - } - - } - - /// Tries to convert this type into a subtype of the other. - /// - /// Currently the most important conversion is from list to vec. - /// ```rust - /// use myslip::r#type::{Type::*, util::*}; - /// - /// assert_eq!(Integer.into_type(&Boolean), Err(())); - /// assert_eq!(Integer.into_type(&Integer), Ok(Integer)); - /// assert_eq!(Integer.into_type(&vt("T")), Ok(Integer)); - /// assert_eq!( - /// List(vec![Integer, Integer]).into_type(&vecof(Integer)), - /// Ok(vecof(Integer)) - /// ); - /// assert_eq!( - /// List(vec![Integer, Integer]).into_type(&vecof(vt("T"))), - /// Ok(vecof(Integer)) - /// ); - /// assert_eq!( - /// List(vec![Integer, Boolean]).into_type(&vecof(vt("T"))), - /// Ok(vecof(vt("T"))) - /// ); - /// assert_eq!(List(vec![Boolean, Integer, Integer]).into_type(&List(vec![Boolean, vt("T"), vt("T")])), Ok(List(vec![Boolean, Integer, Integer]))) - /// ``` - /// - /// Though the conversion from (a) to a is also convenient: - /// ```rust - /// use myslip::r#type::{Type::*, util::*}; - /// - /// assert_eq!(List(vec![Integer]).into_type(&Integer), Ok(Integer)); - /// ``` - /// - /// Also, because quotes and vectors have their operators hanging - /// with them, having a conversion (Vector/Quote X) -> X is convenient. - /// ```rust - /// use myslip::r#type::{Type::*, util::*}; - /// assert_eq!( - /// List(vec![VecType, vecof(Integer)]).into_type(&vecof(Integer)), - /// Ok(vecof(Integer)) - /// ); - /// assert_eq!( - /// List(vec![List(vec![VecType, vecof(Integer)])]).into_type(&vecof(Integer)), - /// Ok(vecof(Integer)) - /// ); - /// ``` - /// - /// Now this also needs to be able to hand generics: - /// ```rust - /// use myslip::r#type::{Type::*, util::*}; - /// assert_eq!( - /// arr(Boolean, Boolean) - /// .into_type(&arr(vt("T"), vt("T"))), - /// Ok(arr(Boolean, Boolean)) - /// ); - /// assert_eq!( - /// arr(Boolean, Integer) - /// .into_type(&arr(vt("T"), vt("H"))), - /// Ok(arr(Boolean, Integer)) - /// ); - /// assert_eq!( - /// arr(Boolean, Integer) - /// .into_type(&arr(vt("T"), vt("T"))), - /// Err(()) - /// ); - /// assert_eq!( - /// List(vec![Integer, Integer]) - /// .into_type(&List(vec![vt("T"), vt("T")])), - /// Ok(List(vec![Integer, Integer])) - /// ); - /// assert!( - /// List(vec![Integer, Boolean]) - /// .into_type(&List(vec![vt("T"), vt("T")])) - /// .is_err() - /// ); - /// assert!( - /// List(vec![Integer, Boolean, vt("X")]) - /// .into_type(&vecof(vt("T"))) - /// .is_err() - /// ); - /// ``` - pub fn into_type(self, other: &Type) -> Result<Type, ()> { - if !self.aka(other) { - return Err(()); - } - - match (&self, other) { - - (a, b) if a == b => Ok(self), - - (_, VarType(_)) => Ok(self), - - (List(x), b) if x.len() == 1 && *&x[0].aka(b) => Ok(x[0].clone().into_type(b)?), - (Arrow(a1, a2), Arrow(b1, b2)) => { - let t1 = a1.clone().into_type(b1)?; - let t2 = a2.clone().into_type(b2)?; - Ok(arr(t1, t2)) + let (t1, newctx) = a1.convert_ctx(b1)?; + ctx.extend(newctx); + let (t2, newctx) = a2.convert_ctx(b2)?; + ctx.extend(newctx); + Ok((arr(t1, t2), ctx)) }, - (List(v), x) if !x.is_quoted_or_vectored_list() - && (v.get(0) == Some(&VecType) || v.get(0) == Some(&QuoteTy)) - && v.get(1) != None => - { - v[1].clone().into_type(x) + (List(v), VecOf(ty)) => match v.get(0) { + Some(t) => { + let (convt, newctx) = t.clone().convert_ctx(ty)?; + ctx.extend(newctx); + for t in v { + let (sconvt, newctx) = t.convert_ctx(ty)?; + if sconvt != convt { + return Err(()); + } else { + ctx.extend(newctx); + } + } + Ok((vecof(convt), ctx)) + }, + None => Err(()), // questionable? could be useful to convert? }, - (VecOf(a), VecOf(b)) => Ok(vecof(a.clone().into_type(&b)?)), - - (List(v1), List(v2)) if v1.len() == v2.len() => { - let mut res = vec![]; - for (t1, t2) in v1.into_iter().zip(v2) { - res.push(t1.clone().into_type(&t2)?); - } - Ok(List(res)) + (VecOf(t1), VecOf(t2)) => { + let (convt, ctx) = t1.convert_ctx(t2)?; + Ok((vecof(convt), ctx)) }, - (List(v), VecOf(b)) => match v.get(0) { - Some(first) => { - let cand = v.into_iter() - .fold(first.clone(), |a, b| a.least_general_supertype(b)); - if cand.aka(b) { - Ok(vecof(cand)) - } else { - Err(()) - } - }, - None => if NilType.aka(b) { - Ok(vecof(NilType)) - } else { - Err(()) - }, - }, - _ => Err(()) - } } @@ -318,155 +95,78 @@ mod tests { use crate::r#type::{Type::*, util::*}; #[test] - fn test_basic_general_supertypes() { - + fn test_conversion_case0() { + assert_eq!(Integer.convert(&Integer), Ok(Integer)); + assert_eq!(Boolean.convert(&Boolean), Ok(Boolean)); assert_eq!( - Integer.least_general_supertype(&Integer), - Integer, - "if two types are equal, they get the same supertype" + List(vec![Integer, Boolean]) + .convert(&List(vec![Integer, Boolean])), + Ok(List(vec![Integer, Boolean])) ); assert_eq!( - Integer.least_general_supertype(&Boolean), - vt("T"), - "if two types have nothing in common, vt(\"t\") is returned" - ); - - } - - #[test] - fn test_compound_general_supertypes() { - - assert_eq!( - List(vec![Integer, Integer]) - .least_general_supertype( - &List(vec![Integer, Integer]) - ), - List(vec![Integer, Integer]) + vecof(Integer).convert(&vecof(Integer)), + Ok(vecof(Integer)) ); + assert_eq!(vt("T").convert(&vt("T")), Ok(vt("T"))); - assert_eq!( + assert!(Integer.convert(&Boolean).is_err()); + assert!(Boolean.convert(&Integer).is_err()); + assert!( List(vec![Integer, Boolean]) - .least_general_supertype( - &List(vec![Integer, Integer]) - ), - List(vec![Integer, vt("T")]) + .convert(&List(vec![Integer, Integer])) + .is_err() ); - - assert_eq!( - vecof(Integer) - .least_general_supertype( - &vecof(Integer) - ), + assert!( vecof(Integer) + .convert(&vecof(Boolean)) + .is_err() ); - - assert_eq!( - vecof(Integer) - .least_general_supertype( - &vecof(Boolean) - ), - vt("T") - ); - - assert_eq!( - List(vec![Integer, Boolean]).least_general_supertype( - &List(vec![Boolean, Integer]) - ), - List(vec![vt("T"), vt("T")]) - ); - - assert_eq!( - vecof(Integer).least_general_supertype(&vecof(vt("T"))), - vecof(vt("T")) - ); - + assert!(vt("T").convert(&vt("H")).is_err()); } #[test] - fn test_removing_quote_and_vec() { - assert_eq!( - List(vec![List(vec![VecType, NilType])]).least_general_supertype(&NilType), - NilType - ); + fn test_conversion_case1() { assert_eq!( - List(vec![VecType, NilType]).least_general_supertype(&NilType), - NilType + List(vec![Integer]).convert(&vecof(Integer)), + Ok(vecof(Integer)) ); assert_eq!( - List(vec![QuoteTy, NilType]).least_general_supertype(&NilType), - NilType - ); - assert_eq!( - List(vec![VecType, vecof(Boolean)]) - .least_general_supertype( - &vecof(Boolean) - ), - vecof(Boolean) - ); - assert_eq!( - List(vec![List(vec![VecType, vecof(Boolean)])]) - .least_general_supertype( - &vecof(Boolean) - ), - vecof(Boolean) - ); - assert_eq!( - List(vec![QuoteTy, List(vec![Integer, Boolean])]) - .least_general_supertype( - &List(vec![Integer, Boolean]) - ), - List(vec![Integer, Boolean]) + List(vec![Integer]).convert(&vecof(vt("T"))), + Ok(vecof(Integer)) ); } #[test] - fn test_conversion_in_general_supertypes() { - - assert_eq!( - List(vec![Integer, Integer]).least_general_supertype( - &vecof(Integer) - ), - vecof(Integer) - ); - + fn test_conversion_case2() { + assert_eq!(Integer.convert(&vt("T")), Ok(Integer)); + assert_eq!(Boolean.convert(&vt("T")), Ok(Boolean)); assert_eq!( - List(vec![Integer, Integer]).least_general_supertype( - &List(vec![Integer, Integer, Integer]) - ), - vecof(Integer) + List(vec![Integer, Integer]) + .convert(&List(vec![vt("T"), vt("T")])), + Ok(List(vec![Integer, Integer])) ); - assert_eq!( - List(vec![Integer, Boolean]).least_general_supertype( - &List(vec![Integer, Boolean, Integer]) - ), - vecof(vt("T")) + List(vec![Integer, Boolean]) + .convert(&List(vec![vt("T"), vt("H")])), + Ok(List(vec![Integer, Boolean])) ); - + assert_eq!(vecof(Integer).convert(&vecof(vt("T"))), Ok(vecof(Integer))); assert_eq!( - List(vec![Boolean, Integer]).least_general_supertype( - &vecof(Integer) - ), - vecof(vt("T")) + arr(arr(vecof(Integer), Integer), arr(vecof(Integer), Integer)) + .convert(&arr(vt("T"), vt("T"))), + Ok(arr(arr(vecof(Integer), Integer), arr(vecof(Integer), Integer))) ); - assert_eq!( - List(vec![VecType, vecof(Integer)]).least_general_supertype( - &vecof(vt("T")) - ), - vecof(vt("T")) + assert!( + List(vec![Integer, Boolean]) + .convert(&List(vec![vt("T"), vt("T")])) + .is_err() ); - - assert_eq!( - List(vec![ - List(vec![VecType, vecof(Integer)]), - List(vec![VecType, vecof(Integer)]) - ]).least_general_supertype( - &List(vec![vecof(vt("T")), vecof(vt("T"))]) - ), - List(vec![vecof(vt("T")), vecof(vt("T"))]) + assert!( + arr(arr(vecof(Integer), Integer), arr(vecof(Boolean), Integer)) + .convert(&arr(vt("T"), vt("T"))) + .is_err() ); - } } |