pub struct TcKey { /* private fields */ }
Expand description
An inexpensive and simple indexing mechanism using during the type checking procedure.
It can that can be copied, checked for equality, and hashed, however, it is not ordered. A TcKey offers functions for relating them to other keys or types, symmetrically or asymmetrically, by creating constraints. These constraints only serve one purpose: They can be passed to the type checker’s crate::TypeChecker::impose() function.
§Example
There are several kinds of constraints that can be generated for a key. Assume the following data structures exist:
use rusttyc::{Variant, TcKey, TypeChecker, TcVar, TcErr, Partial, Arity};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MyVariant {
Top,
Numeric,
UInt,
U8,
String,
}
impl Variant for MyVariant {
type Err = String;
fn meet(lhs: Partial<Self>, rhs: Partial<Self>) -> Result<Partial<Self>, Self::Err> {
use MyVariant::*;
let variant = match (lhs.variant, rhs.variant) {
(Top, x) | (x, Top) => Ok(x),
(String, String) => Ok(String),
(String, _) | (_, String) => Err("string can only be met with string.".to_string()),
(Numeric, x) | (x, Numeric) => Ok(x), // x can only be Numeric, UInt, or U8.
(UInt, x) | (x, UInt) => Ok(x), // x can only be UInt or U8.
(U8, U8) => Ok(U8),
}?;
Ok(Partial { variant, least_arity: 0 })
}
fn top() -> Self {
Self::Top
}
fn arity(&self) -> Arity {
Arity::Fixed(0)
}
}
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
struct MyVar(u8);
impl TcVar for MyVar {}
The type checking procedure can then proceed as follows.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MyVariant {
Top,
Numeric,
UInt,
U8,
String,
}
impl Variant for MyVariant {
type Err = String;
fn meet(lhs: Partial<Self>, rhs: Partial<Self>) -> Result<Partial<Self>, Self::Err> {
use MyVariant::*;
let variant = match (lhs.variant, rhs.variant) {
(Top, x) | (x, Top) => Ok(x),
(String, String) => Ok(String),
(String, _) | (_, String) => Err("string can only be met with string.".to_string()),
(Numeric, x) | (x, Numeric) => Ok(x), // x can only be Numeric, UInt, or U8.
(UInt, x) | (x, UInt) => Ok(x), // x can only be UInt or U8.
(U8, U8) => Ok(U8),
}?;
Ok(Partial { variant, least_arity: 0 })
}
fn top() -> Self {
Self::Top
}
fn arity(&self) -> Arity {
Arity::Fixed(0)
}
}
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
struct MyVar(u8);
impl TcVar for MyVar {}
let mut tc: TypeChecker<MyVariant, MyVar> = TypeChecker::new();
let key1 = tc.new_term_key();
let key2 = tc.new_term_key();
let key3 = tc.new_term_key();
let key4 = tc.new_term_key();
tc.impose(key1.concretizes_explicit(MyVariant::Numeric))?; // key1 is numeric.
// key2 is at least as concrete as k1, i.e. it will also be numeric.
tc.impose(key2.concretizes(key1))?;
tc.impose(key3.equate_with(key2))?; // key3 is the same type as key2, i.e., numeric
let tt = tc.clone().type_check_preliminary()?;
assert_eq!(tt[&key1].variant, MyVariant::Numeric);
assert_eq!(tt[&key2].variant, MyVariant::Numeric);
assert_eq!(tt[&key3].variant, MyVariant::Numeric);
// we did not impose a constraint on it, yet, so its the top element.
assert_eq!(tt[&key4].variant, MyVariant::Top);
// Concretize key3 to be a UInt. Also affects key2 due to unification.
// key1 is unaffected because of the asymmetric relation between key1 and key2,
// which translates to an asymmetric relation between key1 and key3 as well.
tc.impose(key3.concretizes_explicit(MyVariant::UInt))?;
let tt = tc.clone().type_check_preliminary()?;
assert_eq!(tt[&key1].variant, MyVariant::Numeric);
assert_eq!(tt[&key2].variant, MyVariant::UInt);
assert_eq!(tt[&key3].variant, MyVariant::UInt);
assert_eq!(tt[&key4].variant, MyVariant::Top);
// key4 is more concrete than both key2 (and transitively key3), and key1, so it becomes a UInt.
tc.impose(key4.is_meet_of(key2, key1))?;
// Make key2 and key3 U8. key4 depends on them, so it becomes U8 as well. key1 is unaffected.
tc.impose(key2.concretizes_explicit(MyVariant::U8))?;
let key5 = tc.new_term_key();
tc.impose(key5.concretizes_explicit(MyVariant::String))?; // key5 is a string.
let tt = tc.clone().type_check_preliminary()?;
assert_eq!(tt[&key1].variant, MyVariant::Numeric);
assert_eq!(tt[&key2].variant, MyVariant::U8);
assert_eq!(tt[&key3].variant, MyVariant::U8);
assert_eq!(tt[&key4].variant, MyVariant::U8);
assert_eq!(tt[&key5].variant, MyVariant::String);
let key6 = tc.new_term_key();
// key6 is the meet of all other keys.
tc.impose(key6.is_meet_of_all(&[key1, key2, key3, key4, key5]))?;
let res = tc.type_check_preliminary();
// The meet of numeric types and strings will fail, so key6 cannot be resolved.
assert!(res.is_err());
Implementations§
Source§impl TcKey
impl TcKey
Sourcepub fn concretizes<V: ContextSensitiveVariant>(
self,
bound: Self,
) -> Constraint<V>
pub fn concretizes<V: ContextSensitiveVariant>( self, bound: Self, ) -> Constraint<V>
Connects two keys asymmetrically. Refining bound
refines self
whereas refining self
leaves bound
unaffected.
Examples found in repository?
123fn tc_expr(tc: &mut VarlessTypeChecker<Variant>, expr: &Expression) -> Result<TcKey, TcErr<Variant>> {
124 use Expression::*;
125 let key_result = tc.new_term_key(); // will be returned
126 match expr {
127 ConstInt(c) => {
128 let width = (128 - c.leading_zeros()).try_into().unwrap();
129 tc.impose(key_result.concretizes_explicit(Variant::Integer(width)))?;
130 }
131 ConstFixed(i, f) => {
132 let int_width = (64 - i.leading_zeros()).try_into().unwrap();
133 let frac_width = (64 - f.leading_zeros()).try_into().unwrap();
134 tc.impose(key_result.concretizes_explicit(Variant::Fixed(int_width, frac_width)))?;
135 }
136 ConstBool(_) => tc.impose(key_result.concretizes_explicit(Variant::Bool))?,
137 Conditional { cond, cons, alt } => {
138 let key_cond = tc_expr(tc, cond)?;
139 let key_cons = tc_expr(tc, cons)?;
140 let key_alt = tc_expr(tc, alt)?;
141 tc.impose(key_cond.concretizes_explicit(Variant::Bool))?;
142 tc.impose(key_result.is_meet_of(key_cons, key_alt))?;
143 }
144 PolyFn { name: _, param_constraints, args, returns } => {
145 // Note: The following line cannot be replaced by `vec![param_constraints.len(); tc.new_key()]` as this
146 // would copy the keys rather than creating new ones.
147 let params: Vec<(Option<Variant>, TcKey)> =
148 param_constraints.iter().map(|p| (*p, tc.new_term_key())).collect();
149 ¶ms;
150 for (arg_ty, arg_expr) in args {
151 let arg_key = tc_expr(tc, arg_expr)?;
152 match arg_ty {
153 ParamType::ParamId(id) => {
154 let (p_constr, p_key) = params[*id];
155 // We need to enforce that the parameter is more concrete than the passed argument and that the
156 // passed argument satisfies the constraints imposed on the parametric type.
157 tc.impose(p_key.concretizes(arg_key))?;
158 if let Some(c) = p_constr {
159 tc.impose(arg_key.concretizes_explicit(c))?;
160 }
161 }
162 ParamType::Abstract(at) => tc.impose(arg_key.concretizes_explicit(*at))?,
163 };
164 }
165 match returns {
166 ParamType::Abstract(at) => tc.impose(key_result.concretizes_explicit(*at))?,
167 ParamType::ParamId(id) => {
168 let (constr, key) = params[*id];
169 if let Some(c) = constr {
170 tc.impose(key_result.concretizes_explicit(c))?;
171 }
172 tc.impose(key_result.equate_with(key))?;
173 }
174 }
175 }
176 }
177 Ok(key_result)
178}
Sourcepub fn equate_with<V: ContextSensitiveVariant>(
self,
other: Self,
) -> Constraint<V>
pub fn equate_with<V: ContextSensitiveVariant>( self, other: Self, ) -> Constraint<V>
Equates two keys, i.e., they refer to the same type and are thus symmetrically connected. Refining one will refine the other as well.
Examples found in repository?
123fn tc_expr(tc: &mut VarlessTypeChecker<Variant>, expr: &Expression) -> Result<TcKey, TcErr<Variant>> {
124 use Expression::*;
125 let key_result = tc.new_term_key(); // will be returned
126 match expr {
127 ConstInt(c) => {
128 let width = (128 - c.leading_zeros()).try_into().unwrap();
129 tc.impose(key_result.concretizes_explicit(Variant::Integer(width)))?;
130 }
131 ConstFixed(i, f) => {
132 let int_width = (64 - i.leading_zeros()).try_into().unwrap();
133 let frac_width = (64 - f.leading_zeros()).try_into().unwrap();
134 tc.impose(key_result.concretizes_explicit(Variant::Fixed(int_width, frac_width)))?;
135 }
136 ConstBool(_) => tc.impose(key_result.concretizes_explicit(Variant::Bool))?,
137 Conditional { cond, cons, alt } => {
138 let key_cond = tc_expr(tc, cond)?;
139 let key_cons = tc_expr(tc, cons)?;
140 let key_alt = tc_expr(tc, alt)?;
141 tc.impose(key_cond.concretizes_explicit(Variant::Bool))?;
142 tc.impose(key_result.is_meet_of(key_cons, key_alt))?;
143 }
144 PolyFn { name: _, param_constraints, args, returns } => {
145 // Note: The following line cannot be replaced by `vec![param_constraints.len(); tc.new_key()]` as this
146 // would copy the keys rather than creating new ones.
147 let params: Vec<(Option<Variant>, TcKey)> =
148 param_constraints.iter().map(|p| (*p, tc.new_term_key())).collect();
149 ¶ms;
150 for (arg_ty, arg_expr) in args {
151 let arg_key = tc_expr(tc, arg_expr)?;
152 match arg_ty {
153 ParamType::ParamId(id) => {
154 let (p_constr, p_key) = params[*id];
155 // We need to enforce that the parameter is more concrete than the passed argument and that the
156 // passed argument satisfies the constraints imposed on the parametric type.
157 tc.impose(p_key.concretizes(arg_key))?;
158 if let Some(c) = p_constr {
159 tc.impose(arg_key.concretizes_explicit(c))?;
160 }
161 }
162 ParamType::Abstract(at) => tc.impose(arg_key.concretizes_explicit(*at))?,
163 };
164 }
165 match returns {
166 ParamType::Abstract(at) => tc.impose(key_result.concretizes_explicit(*at))?,
167 ParamType::ParamId(id) => {
168 let (constr, key) = params[*id];
169 if let Some(c) = constr {
170 tc.impose(key_result.concretizes_explicit(c))?;
171 }
172 tc.impose(key_result.equate_with(key))?;
173 }
174 }
175 }
176 }
177 Ok(key_result)
178}
Sourcepub fn concretizes_explicit<V: ContextSensitiveVariant>(
self,
bound: V,
) -> Constraint<V>
pub fn concretizes_explicit<V: ContextSensitiveVariant>( self, bound: V, ) -> Constraint<V>
Declares that self
is at least as concrete as bound
.
Examples found in repository?
123fn tc_expr(tc: &mut VarlessTypeChecker<Variant>, expr: &Expression) -> Result<TcKey, TcErr<Variant>> {
124 use Expression::*;
125 let key_result = tc.new_term_key(); // will be returned
126 match expr {
127 ConstInt(c) => {
128 let width = (128 - c.leading_zeros()).try_into().unwrap();
129 tc.impose(key_result.concretizes_explicit(Variant::Integer(width)))?;
130 }
131 ConstFixed(i, f) => {
132 let int_width = (64 - i.leading_zeros()).try_into().unwrap();
133 let frac_width = (64 - f.leading_zeros()).try_into().unwrap();
134 tc.impose(key_result.concretizes_explicit(Variant::Fixed(int_width, frac_width)))?;
135 }
136 ConstBool(_) => tc.impose(key_result.concretizes_explicit(Variant::Bool))?,
137 Conditional { cond, cons, alt } => {
138 let key_cond = tc_expr(tc, cond)?;
139 let key_cons = tc_expr(tc, cons)?;
140 let key_alt = tc_expr(tc, alt)?;
141 tc.impose(key_cond.concretizes_explicit(Variant::Bool))?;
142 tc.impose(key_result.is_meet_of(key_cons, key_alt))?;
143 }
144 PolyFn { name: _, param_constraints, args, returns } => {
145 // Note: The following line cannot be replaced by `vec![param_constraints.len(); tc.new_key()]` as this
146 // would copy the keys rather than creating new ones.
147 let params: Vec<(Option<Variant>, TcKey)> =
148 param_constraints.iter().map(|p| (*p, tc.new_term_key())).collect();
149 ¶ms;
150 for (arg_ty, arg_expr) in args {
151 let arg_key = tc_expr(tc, arg_expr)?;
152 match arg_ty {
153 ParamType::ParamId(id) => {
154 let (p_constr, p_key) = params[*id];
155 // We need to enforce that the parameter is more concrete than the passed argument and that the
156 // passed argument satisfies the constraints imposed on the parametric type.
157 tc.impose(p_key.concretizes(arg_key))?;
158 if let Some(c) = p_constr {
159 tc.impose(arg_key.concretizes_explicit(c))?;
160 }
161 }
162 ParamType::Abstract(at) => tc.impose(arg_key.concretizes_explicit(*at))?,
163 };
164 }
165 match returns {
166 ParamType::Abstract(at) => tc.impose(key_result.concretizes_explicit(*at))?,
167 ParamType::ParamId(id) => {
168 let (constr, key) = params[*id];
169 if let Some(c) = constr {
170 tc.impose(key_result.concretizes_explicit(c))?;
171 }
172 tc.impose(key_result.equate_with(key))?;
173 }
174 }
175 }
176 }
177 Ok(key_result)
178}
Sourcepub fn is_meet_of<V: ContextSensitiveVariant>(
self,
left: Self,
right: Self,
) -> Constraint<V>
pub fn is_meet_of<V: ContextSensitiveVariant>( self, left: Self, right: Self, ) -> Constraint<V>
Declares that self
is the meet of left
and right
.
This binds self
to both left
and right
asymmetrically.
Examples found in repository?
123fn tc_expr(tc: &mut VarlessTypeChecker<Variant>, expr: &Expression) -> Result<TcKey, TcErr<Variant>> {
124 use Expression::*;
125 let key_result = tc.new_term_key(); // will be returned
126 match expr {
127 ConstInt(c) => {
128 let width = (128 - c.leading_zeros()).try_into().unwrap();
129 tc.impose(key_result.concretizes_explicit(Variant::Integer(width)))?;
130 }
131 ConstFixed(i, f) => {
132 let int_width = (64 - i.leading_zeros()).try_into().unwrap();
133 let frac_width = (64 - f.leading_zeros()).try_into().unwrap();
134 tc.impose(key_result.concretizes_explicit(Variant::Fixed(int_width, frac_width)))?;
135 }
136 ConstBool(_) => tc.impose(key_result.concretizes_explicit(Variant::Bool))?,
137 Conditional { cond, cons, alt } => {
138 let key_cond = tc_expr(tc, cond)?;
139 let key_cons = tc_expr(tc, cons)?;
140 let key_alt = tc_expr(tc, alt)?;
141 tc.impose(key_cond.concretizes_explicit(Variant::Bool))?;
142 tc.impose(key_result.is_meet_of(key_cons, key_alt))?;
143 }
144 PolyFn { name: _, param_constraints, args, returns } => {
145 // Note: The following line cannot be replaced by `vec![param_constraints.len(); tc.new_key()]` as this
146 // would copy the keys rather than creating new ones.
147 let params: Vec<(Option<Variant>, TcKey)> =
148 param_constraints.iter().map(|p| (*p, tc.new_term_key())).collect();
149 ¶ms;
150 for (arg_ty, arg_expr) in args {
151 let arg_key = tc_expr(tc, arg_expr)?;
152 match arg_ty {
153 ParamType::ParamId(id) => {
154 let (p_constr, p_key) = params[*id];
155 // We need to enforce that the parameter is more concrete than the passed argument and that the
156 // passed argument satisfies the constraints imposed on the parametric type.
157 tc.impose(p_key.concretizes(arg_key))?;
158 if let Some(c) = p_constr {
159 tc.impose(arg_key.concretizes_explicit(c))?;
160 }
161 }
162 ParamType::Abstract(at) => tc.impose(arg_key.concretizes_explicit(*at))?,
163 };
164 }
165 match returns {
166 ParamType::Abstract(at) => tc.impose(key_result.concretizes_explicit(*at))?,
167 ParamType::ParamId(id) => {
168 let (constr, key) = params[*id];
169 if let Some(c) = constr {
170 tc.impose(key_result.concretizes_explicit(c))?;
171 }
172 tc.impose(key_result.equate_with(key))?;
173 }
174 }
175 }
176 }
177 Ok(key_result)
178}
Sourcepub fn is_meet_of_all<V: ContextSensitiveVariant>(
self,
elems: &[Self],
) -> Constraint<V>
pub fn is_meet_of_all<V: ContextSensitiveVariant>( self, elems: &[Self], ) -> Constraint<V>
Declares that self
is the meet of all elements contained in elems
.
This binds self
to all of these keys asymmetrically.
Sourcepub fn is_sym_meet_of<V: ContextSensitiveVariant>(
self,
left: Self,
right: Self,
) -> Constraint<V>
pub fn is_sym_meet_of<V: ContextSensitiveVariant>( self, left: Self, right: Self, ) -> Constraint<V>
Declares that self
is the symmetric meet of left
and right
.
This binds self
to both left
and right
symmetrically.
Sourcepub fn is_sym_meet_of_all<V: ContextSensitiveVariant>(
self,
elems: &[Self],
) -> Constraint<V>
pub fn is_sym_meet_of_all<V: ContextSensitiveVariant>( self, elems: &[Self], ) -> Constraint<V>
Declares that self
is the symmetric meet of all elements contained in elems
.
This binds self
to all of these keys symmetrically.