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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
use arret_syntax::datum::DataStr;
use arret_syntax::span::Span;

use crate::hir::error::{Error, ErrorKind, Result};
use crate::hir::ns::Ident;
use crate::hir::ns::{NsDataIter, NsDatum};
use crate::hir::scope::{Binding, Scope};
use crate::hir::types::lower_poly;
use crate::hir::types::lower_polymorphic_var_list;
use crate::hir::util::{expect_ns_ident, expect_spanned_ns_ident};
use crate::ty;
use crate::ty::record;
use crate::ty::Ty;

enum LoweredRecordCons {
    Parameterised(Span, Ident, NsDataIter),
    Singleton(Span, Ident),
}

fn lower_record_field_decl(scope: &Scope<'_>, field_datum: NsDatum) -> Result<record::Field> {
    let datum_span = field_datum.span();
    let datum_description = field_datum.description();

    let (ident, poly) = match field_datum {
        NsDatum::Ident(_, ident) => (ident, Ty::Any.into()),
        NsDatum::Vector(span, vs) => {
            let mut data = vs.into_vec();

            if data.len() != 2 {
                return Err(Error::new(
                    span,
                    ErrorKind::ExpectedRecordFieldDecl(datum_description),
                ));
            }

            let poly = lower_poly(scope, data.pop().unwrap())?;
            let ident = expect_ns_ident(data.pop().unwrap(), "new record field name")?;

            (ident, poly)
        }
        other => {
            return Err(Error::new(
                other.span(),
                ErrorKind::ExpectedRecordFieldDecl(datum_description),
            ));
        }
    };

    Ok(record::Field::new(datum_span, ident.into_name(), poly))
}

/// Lowers either the type or value constructor for a `(defrecord)`
fn lower_record_cons_decl<F>(cons_datum: NsDatum, error_kind_cons: F) -> Result<LoweredRecordCons>
where
    F: Fn(&'static str) -> ErrorKind,
{
    let datum_description = cons_datum.description();

    match cons_datum {
        NsDatum::Ident(span, ident) => Ok(LoweredRecordCons::Singleton(span, ident)),
        NsDatum::List(span, vs) => {
            let mut param_data_iter = vs.into_vec().into_iter();

            if let Some(name_datum) = param_data_iter.next() {
                let (ident_span, ident) =
                    expect_spanned_ns_ident(name_datum, "new record constructor name")?;

                Ok(LoweredRecordCons::Parameterised(
                    ident_span,
                    ident,
                    param_data_iter,
                ))
            } else {
                Err(Error::new(span, error_kind_cons(datum_description)))
            }
        }
        other => Err(Error::new(other.span(), error_kind_cons(datum_description))),
    }
}

pub fn lower_record(
    outer_scope: &mut Scope<'_>,
    ty_cons_datum: NsDatum,
    value_cons_datum: NsDatum,
) -> Result<()> {
    use crate::hir::types::PolymorphicVar;
    use crate::ty::ty_args::TyArgs;

    let mut inner_scope = outer_scope.child();

    // Lower our type constructor
    let ty_cons_span = ty_cons_datum.span();
    let ty_cons_decl = lower_record_cons_decl(ty_cons_datum, ErrorKind::ExpectedRecordTyConsDecl)?;

    let (ty_ident_span, ty_ident, poly_vars) = match ty_cons_decl {
        LoweredRecordCons::Singleton(span, ident) => (span, ident, None),
        LoweredRecordCons::Parameterised(span, ident, param_data_iter) => {
            let poly_params =
                lower_polymorphic_var_list(outer_scope, &mut inner_scope, param_data_iter)?;

            (span, ident, Some(poly_params))
        }
    };

    // Lower our value destructor
    let value_cons_decl =
        lower_record_cons_decl(value_cons_datum, ErrorKind::ExpectedRecordValueConsDecl)?;

    let fields: Box<[record::Field]>;
    let (value_cons_ident_span, value_cons_ident) = match value_cons_decl {
        LoweredRecordCons::Singleton(_, _) => {
            todo!("singleton record values");
        }
        LoweredRecordCons::Parameterised(span, ident, param_data_iter) => {
            fields = param_data_iter
                .map(|field_datum| lower_record_field_decl(&inner_scope, field_datum))
                .collect::<Result<Box<_>>>()?;

            (span, ident)
        }
    };

    // Convert our lowered polymorphic vars to polymorphic parameters
    let poly_params_list = match poly_vars {
        Some(poly_vars) => {
            use crate::ty::var_usage::VarUsages;

            let mut var_usages = VarUsages::new();
            for field in fields.iter() {
                var_usages.add_poly_usages(field.ty_ref());
            }

            let poly_params_list = poly_vars
                .into_vec()
                .into_iter()
                .map(|poly_var| {
                    match poly_var {
                        PolymorphicVar::PVar(pvar) => {
                            if let Some(variance) = var_usages.pvar_variance(&pvar) {
                                Ok(record::PolyParam::PVar(variance, pvar))
                            } else {
                                Err(Error::new(
                                    pvar.span(),
                                    ErrorKind::UnusedPolyPurityParam(pvar),
                                ))
                            }
                        }
                        // It'd be nice to check if the param was used but it's been erased to
                        // `Pure` by this point
                        PolymorphicVar::Pure(span) => Ok(record::PolyParam::Pure(span)),
                        PolymorphicVar::TVar(tvar) => {
                            if let Some(variance) = var_usages.tvar_variance(&tvar) {
                                Ok(record::PolyParam::TVar(variance, tvar))
                            } else {
                                Err(Error::new(tvar.span(), ErrorKind::UnusedPolyTyParam(tvar)))
                            }
                        }
                        PolymorphicVar::TFixed(span, fixed_poly) => {
                            Ok(record::PolyParam::TFixed(span, fixed_poly))
                        }
                    }
                })
                .collect::<Result<Box<[record::PolyParam]>>>()?;

            Some(poly_params_list)
        }
        None => None,
    };

    let predicate_name: DataStr = format!("{}?", value_cons_ident.name()).into();
    let predicate_ident = Ident::new(value_cons_ident.ns_id(), predicate_name);

    let record_ty_cons = record::Cons::new(
        ty_cons_span,
        ty_ident.name().clone(),
        value_cons_ident.name().clone(),
        poly_params_list,
        fields,
    );

    for (idx, field) in record_ty_cons.fields().iter().enumerate() {
        if field.name().as_ref() != "_" {
            let accessor_name = format!("{}-{}", value_cons_ident.name(), field.name());
            let accessor_ident = Ident::new(value_cons_ident.ns_id(), accessor_name.into());

            outer_scope.insert_binding(
                field.span(),
                accessor_ident,
                Binding::FieldAccessor(record_ty_cons.clone(), idx),
            )?;
        }
    }

    outer_scope.insert_binding(
        value_cons_ident_span,
        predicate_ident,
        Binding::TyPred(ty::pred::TestTy::RecordClass(record_ty_cons.clone())),
    )?;

    outer_scope.insert_binding(
        value_cons_ident_span,
        value_cons_ident,
        Binding::RecordValueCons(record_ty_cons.clone()),
    )?;

    if record_ty_cons.is_singleton() {
        // We were used as a singleton; bind a type
        let record_instance = record::Instance::new(record_ty_cons, TyArgs::empty());
        outer_scope.insert_binding(ty_ident_span, ty_ident, Binding::Ty(record_instance.into()))?;
    } else {
        // We were used as a type constructor; bind a type constructor
        outer_scope.insert_binding(
            ty_ident_span,
            ty_ident,
            Binding::RecordTyCons(record_ty_cons),
        )?;
    };

    Ok(())
}