aboutsummaryrefslogtreecommitdiff
path: root/src/bidirectional.rs
blob: 493ae2fb7411ca8fe62d32b26ddab740b03874bc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// Simple bidirectional type checking

use crate::ast::*;

/// Checking judgement: takes an expression and a type to check against and calls out to `infer` as needed.
pub fn check(context: &Context, expression: Expression, target: &Type) -> Result<(), String> {
    match expression {
        // fall through to inference mode
        Expression::Annotation { expr, kind } => {
            let result = infer(context, Expression::Annotation { expr, kind })?;
            return match subtype(&result, &target) {
                true => Ok(()),
                false => Err(format!("inferred type {result} does not match target {target}"))
            }
        },
        // Bt-CheckInfer
        Expression::Constant { term } => match subtype(&convert(&term)?, &target) {
            true => Ok(()),
            false => Err(format!("constant is of wrong type, expected {target}"))
            // false => Ok(()) // all our constants are Empty for now
        },
        // Bt-CheckInfer
        Expression::Variable { id } => match context.get(&id) {
            Some(term) if subtype(&convert(term)?, &target) => Ok(()),
            Some(_) => Err(format!("variable {id} is of wrong type")),
            None => Err(format!("failed to find variable {id} in context"))
        },
        // Bt-Abs
        Expression::Abstraction { param, func } => match target {
            Type::Function { from, to } => {
                let mut context = context.clone();
                context.insert(param, default(from)?);
                return check(&context, *func, &to);
            },
            _ => Err(format!("attempting to check an abstraction with a non-function type {target}"))
        },
        // fall through to inference mode
        Expression::Application { func, arg } => {
            let result = &infer(context, Expression::Application { func, arg })?;
            return match subtype(&result, &target) {
                true => Ok(()),
                false => Err(format!("inferred type {result} does not match {target}"))
            }
        },
        // T-If
        Expression::Conditional { if_cond, if_then, if_else } => {
            check(context, *if_cond, &Type::Boolean)?;
            check(context, *if_then, &target)?;
            check(context, *if_else, &target)?;
            return Ok(());
        }
    }
}

/// Inference judgement: takes an expression and attempts to infer the associated type.
pub fn infer(context: &Context, expression: Expression) -> Result<Type, String> {
    match expression {
        // Bt-Ann
        Expression::Annotation { expr, kind } => check(context, *expr, &kind).map(|x| kind),
        // Bt-True / Bt-False / etc
        Expression::Constant { term } => convert(&term),
        // Bt-Var
        Expression::Variable { id } => match context.get(&id) {
            Some(term) => infer(&Context::new(), Expression::Constant { term: term.clone() }),
            None => Err(format!("failed to find variable in context {context:?}"))
        },
        // Bt-App
        Expression::Application { func, arg } => match infer(context, *func)? {
            Type::Function { from, to } => check(context, *arg, &*from).map(|x| *to),
            _ => Err(format!("application abstraction is not a function type"))
        },
        // inference from an abstraction is always an error
        // we could try and infer the func without adding the parameter to scope:
        // but this is overwhelmingly likely to be an error, so just report it now.
        Expression::Abstraction { param, func } =>
            Err(format!("attempting to infer from an abstraction")),
        // idk
        Expression::Conditional { if_cond, if_then, if_else } => {
            check(context, *if_cond, &Type::Boolean)?;
            let if_then = infer(context, *if_then)?;
            let if_else = infer(context, *if_else)?;
            if subtype(&if_then, &if_else) && subtype(&if_else, &if_then) {
                Ok(if_then) // fixme: should be the join
            } else {
                Err(format!("if clauses of different types: {if_then} and {if_else}"))
            }
        }
    }
}

/// The subtyping relation between any two types.
pub fn subtype(is: &Type, of: &Type) -> bool {
    match (is, of) {
        (Type::Record(is_fields), Type::Record(of_fields)) => {
            // width, depth, and permutation
            for (key, of_value) in of_fields {
                match is_fields.get(key) {
                    Some(is_value) => {
                        if !subtype(is_value, of_value) {
                            return false;
                        }
                    }
                    None => return false
                }
            }
            return true;
        },
        (Type::Enum(is_variants), Type::Enum(of_variants)) => false, // fixme
        (Type::Function { from: is_from, to: is_to },
         Type::Function { from: of_from, to: of_to }) => {
            subtype(of_from, is_from) && subtype(is_to, of_to)
        },
        (Type::Natural, Type::Integer) => true, // obviously not, but let's pretend
        (_, Type::Empty) => true,
        (Type::Error, _) => true,
        (_, _) if is == of => true,
        (_, _) => false
    }
}