Struct TypeChecker

Source
pub struct TypeChecker<V: ContextSensitiveVariant, Var: TcVar> { /* private fields */ }
Expand description

The TypeChecker is the main interaction point for the type checking procedure.

The TypeChecker is the main interaction point for the type checking procedure. It provides functionality to obtain TcKeys, manage variables, impose constraints, and generate a type table.

  • TcKey represent different entities during the type checking procedure such as variables or terms.
  • TcVar represent variables in the external structure, e.g. in the AST that is type checked. A variable has exactly one associated key, even if it occurs several times in the external data structure. The type checker manages keys for variables.
  • Constraints represent dependencies between keys and types. They can only be created using TcKeys.
  • Variant is an external data type that constitutes a potentially unresolved type.
  • TcErr is a wrapper for Variant::Err that contains additional information on what went wrong.
  • TypeTable maps keys to Constructable::Type if Variant implements Constructable.
  • PreliminaryTypeTable maps keys to Preliminary. This is only useful if Variant does not implement Constructable

§Process

In the first step after creation, the TypeChecker generates keys and collects information. It may already resolve some constraints and reveal contradictions. This prompts it to return a negative result with a TcErr. When all information is collected, it resolves them and generates a type table that contains the least restrictive Variant type for each registered key.

§Note:

The absence of errors does not entail that a constraint can be satisfied. Only the successful generation of a type table indicates that all constraints can be satisfied.

§Example

See crate documentation.

Implementations§

Source§

impl<V: Variant> TypeChecker<V, NoVars>

Source

pub fn without_vars() -> VarlessTypeChecker<V>

Instantiates a new, empty type checker that does not require variables.

Examples found in repository?
examples/parametrized_function.rs (line 184)
180fn main() {
181    // Build an expression to type-check.
182    let expr = build_complex_expression_type_checks();
183    // Create an empty type checker.
184    let mut tc: VarlessTypeChecker<Variant> = TypeChecker::without_vars();
185    // Type check the expression.
186    let res = tc_expr(&mut tc, &expr).and_then(|key| tc.type_check().map(|tt| (key, tt)));
187    match res {
188        Ok((key, tt)) => {
189            let res_type = tt[&key];
190            // Expression `if true then 2.7^3 + 4.3 else 3` should yield type Fixed(3, 3) because the addition requires a
191            // Fixed(2,3) and a Fixed(3,3), which results in a Fixed(3, 3).
192            // Constructing an actual type yields Type::FixedPointI64F64.
193            assert_eq!(res_type, Type::FixedPointI64F64);
194        }
195        Err(_) => panic!("Unexpected type error!"),
196    }
197}
Source§

impl<V: Variant, Var: TcVar> TypeChecker<V, Var>

Source

pub fn new() -> Self

Creates a new, empty type checker.

Source§

impl<V: ContextSensitiveVariant, Var: TcVar> TypeChecker<V, Var>

Source

pub fn with_context(context: V::Context) -> Self

Creates a new, empty type checker with the given context.

Source

pub fn new_term_key(&mut self) -> TcKey

Generates a new key representing a term.

Examples found in repository?
examples/parametrized_function.rs (line 125)
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            &params;
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}
Source

pub fn get_var_key(&mut self, var: &Var) -> TcKey

Manages keys for variables. It checks if var already has an associated key. If so, it returns the key. Otherwise, it generates a new one.

Source

pub fn get_child_key( &mut self, parent: TcKey, nth: usize, ) -> Result<TcKey, TcErr<V>>

Provides a key to the nth child of the type behind parent. This imposes the restriction that parent resolves to a type that has at least an arity of nth. If this imposition immediately leads to a contradiction, a TcErr is returned. Contradictions due to this constraint may only occur later when resolving further constraints. Calling this function several times on a parent with the same nth results in the same key.

Source

pub fn impose(&mut self, constr: Constraint<V>) -> Result<(), TcErr<V>>

Imposes a constraint on keys. They can be obtained by using the associated functions of TcKey. Returns a TcErr if the constraint immediately reveals a contradiction.

Examples found in repository?
examples/parametrized_function.rs (line 129)
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            &params;
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}
Source

pub fn lift_into(&mut self, variant: V, sub_types: Vec<TcKey>) -> TcKey

Lifts a collection of keys as children into a certain recursive variant.

Source

pub fn lift_partially( &mut self, variant: V, sub_types: Vec<Option<TcKey>>, ) -> TcKey

Lifts a collection of keys as subset of children into a certain recursive variant.

Source

pub fn all_keys(&self) -> impl Iterator<Item = TcKey> + '_

Returns an iterator over all keys currently present in the type checking procedure.

Source

pub fn update_context<F>(&mut self, update: F)
where F: FnOnce(&mut V::Context),

Updates the current context.

Applies the update function to the context of the current typechecker. The function may mutate the context.

§Warning

This will not change any call retroactively, i.e. it only applies to future meet and equate calls. Proceed with caution.

Source

pub fn type_check_preliminary(self) -> Result<PreliminaryTypeTable<V>, TcErr<V>>

Finalizes the type check procedure without constructing a full type table. Refer to TypeChecker::type_check if Variant implements Constructable. Calling this function indicates that all relevant information was passed on to the type checker. It will attempt to resolve all constraints and return a type table mapping each registered key to its minimally constrained Variants. For recursive types, the respective Preliminary provides access to crate::TcKeys refering to their children. If any constrained caused a contradiction, it will return a TcErr containing information about it.

Source§

impl<V, Var: TcVar> TypeChecker<V, Var>
where V: Constructable,

Source

pub fn type_check(self) -> Result<TypeTable<V>, TcErr<V>>

Finalizes the type check procedure. Calling this function indicates that all relevant information was passed on to the type checker. It will attempt to resolve all constraints and return a type table mapping each registered key to its minimally constrained, constructed type, i.e. Constructable::Type. Refer to TypeChecker::type_check_preliminary() if Variant does not implement Constructable. If any constrained caused a contradiction, it will return a TcErr containing information about it.

Examples found in repository?
examples/parametrized_function.rs (line 186)
180fn main() {
181    // Build an expression to type-check.
182    let expr = build_complex_expression_type_checks();
183    // Create an empty type checker.
184    let mut tc: VarlessTypeChecker<Variant> = TypeChecker::without_vars();
185    // Type check the expression.
186    let res = tc_expr(&mut tc, &expr).and_then(|key| tc.type_check().map(|tt| (key, tt)));
187    match res {
188        Ok((key, tt)) => {
189            let res_type = tt[&key];
190            // Expression `if true then 2.7^3 + 4.3 else 3` should yield type Fixed(3, 3) because the addition requires a
191            // Fixed(2,3) and a Fixed(3,3), which results in a Fixed(3, 3).
192            // Constructing an actual type yields Type::FixedPointI64F64.
193            assert_eq!(res_type, Type::FixedPointI64F64);
194        }
195        Err(_) => panic!("Unexpected type error!"),
196    }
197}

Trait Implementations§

Source§

impl<V: Clone + ContextSensitiveVariant, Var: Clone + TcVar> Clone for TypeChecker<V, Var>
where V::Context: Clone,

Source§

fn clone(&self) -> TypeChecker<V, Var>

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<V: Debug + ContextSensitiveVariant, Var: Debug + TcVar> Debug for TypeChecker<V, Var>
where V::Context: Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<V: Variant, Var: TcVar> Default for TypeChecker<V, Var>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<V, Var> Freeze for TypeChecker<V, Var>

§

impl<V, Var> RefUnwindSafe for TypeChecker<V, Var>

§

impl<V, Var> Send for TypeChecker<V, Var>
where <V as ContextSensitiveVariant>::Context: Send, Var: Send, V: Send,

§

impl<V, Var> Sync for TypeChecker<V, Var>
where <V as ContextSensitiveVariant>::Context: Sync, Var: Sync, V: Sync,

§

impl<V, Var> Unpin for TypeChecker<V, Var>
where <V as ContextSensitiveVariant>::Context: Unpin, Var: Unpin, V: Unpin,

§

impl<V, Var> UnwindSafe for TypeChecker<V, Var>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.