lutra-compiler 0.5.1

Compiler for Lutra query language
Documentation
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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
use std::cell::RefCell;
use std::collections::HashMap;

use crate::diagnostic::{Diagnostic, DiagnosticCode, WithErrorInfo};
use crate::pr;
use crate::{Result, Span};
use crate::{printer, utils};

use super::TypeResolver;

#[derive(Debug)]
pub struct Scope {
    pub(super) id: usize,

    pub(super) kind: ScopeKind,
    pub(super) names: append_only_vec::AppendOnlyVec<ScopedKind>,

    pub(super) ty_var_constraints: RefCell<Vec<TyVarConstraint>>,
}

#[derive(Debug)]
pub enum ScopeKind {
    /// Scope that does not allow references to parent scopes.
    /// When closed, all type must be inferred.
    /// Used for variable stmts and functions with generic params.
    Isolated,

    /// Scope that allows references to parent scopes.
    /// Used for function bodies, match case blocks.
    Nested,
}

#[derive(Debug, Clone)]
pub enum ScopedKind {
    Param {
        ty: pr::Ty,
    },
    Local {
        ty: pr::Ty,
    },
    LocalTy {
        ty: pr::Ty,
    },

    /// Type parameter to a function.
    ///
    /// For example, `T` is a type param:
    /// ```lt
    /// func twice <T> (x: T): {T, T} -> {x: T, x}
    /// ```
    TyParam {
        name: String,
        domain: pr::TyDomain,
    },

    /// Type variable - something that we don't yet know the type of,
    /// but we will (probably) be able to infer it later on.
    ///
    /// For example, here:
    /// ```lt
    /// let x: int32 = 5
    /// ```
    /// ... `5` is initially assigned a type variable. Later on, when validating
    /// the type annotation, it infer that it must be `int32`.
    TyVar(TyVar),
}

pub type TyVarId = usize;

#[derive(Debug, Clone)]
pub struct TyVar {
    pub name_hint: Option<String>,

    pub span: Option<Span>,
}

#[derive(Debug)]
pub enum TyVarConstraint {
    IsTy(TyVarId, pr::Ty),
    Equals(TyVarId, TyVarId),
    InDomain(TyVarId, pr::TyDomain),
}

#[derive(Debug, strum::AsRefStr)]
pub enum Named<'a> {
    Expr(&'a pr::Expr),
    Ty {
        ty: &'a pr::Ty,
        is_framed: bool,
        framed_label: Option<&'a str>,
    },
    Module,
    Scoped(&'a ScopedKind),
}

/// A reference to a type-like objects
#[derive(Debug, Clone)]
pub enum TyRef<'a> {
    /// Concrete type (cannot be an ident)
    Ty(&'a pr::Ty),

    /// Reference to type param: a placeholder which must support a few different concrete types.
    /// Contains offset in scope.
    Param(usize),

    /// Reference to type variable: a placeholder which will inferred to be some non-variable type
    /// when scope is finalized. Contains scope id and offset in scope.
    #[allow(dead_code)] // TODO: actually use scope_id when fetching the scope
    Var(usize, usize),
}

// impl<'a> TyRef<'a> {
//     #[allow(dead_code)]
//     fn into_owned(self) -> TyRef<'static> {
//         match self {
//             TyRef::Ty(v) => TyRef::Ty(Cow::Owned(v.as_ref().clone())),
//             TyRef::Param(v) => TyRef::Param(v),
//             TyRef::Var(s, o) => TyRef::Var(s, o),
//         }
//     }
// }

impl Scope {
    pub fn new(id: usize, kind: ScopeKind) -> Self {
        Self {
            id,
            kind,
            names: Default::default(),
            ty_var_constraints: RefCell::new(Vec::new()),
        }
    }

    /// Returns true for scopes that should store type variables.
    /// Location of where ty vars are store determines when they will be finalized.
    pub fn for_ty_vars(&self) -> bool {
        matches!(self.kind, ScopeKind::Isolated)
    }

    pub fn insert_type_params(&mut self, type_params: &[pr::TyParam]) {
        for gtp in type_params {
            let scoped = ScopedKind::TyParam {
                name: gtp.name.clone(),
                domain: gtp.domain.clone(),
            };
            self.names.push(scoped);
        }
    }

    pub fn insert_params(&mut self, func: &pr::Func) -> Result<(), Vec<Diagnostic>> {
        let mut d = Vec::new();

        for param in &func.params {
            let Some(ty) = param.ty.clone() else {
                d.push(
                    Diagnostic::new_custom("missing type annotations").with_span(Some(param.span)),
                );
                continue;
            };

            let scoped = ScopedKind::Param { ty };
            self.names.push(scoped);
        }

        if d.is_empty() { Ok(()) } else { Err(d) }
    }

    #[must_use]
    pub fn insert_type_var(
        &self,
        name_hint: Option<String>,
        span: Span,
        mut domain: pr::TyDomain,
    ) -> usize {
        let type_arg = TyVar {
            name_hint,
            span: Some(span),
        };
        let var_id = self.names.push(ScopedKind::TyVar(type_arg));

        // overwrite span to point to this var instead of the original domain
        if let pr::TyDomain::TupleHasFields(fields) = &mut domain {
            for f in fields {
                f.span = span;
            }
        }

        if !matches!(domain, pr::TyDomain::Open) {
            self.ty_var_constraints
                .borrow_mut()
                .push(TyVarConstraint::InDomain(var_id, domain));
        }
        var_id
    }

    pub fn insert_local(&mut self, ty: pr::Ty) -> usize {
        let local = ScopedKind::Local { ty };
        self.names.push(local)
    }

    pub fn insert_local_ty(&mut self, ty: pr::Ty) -> usize {
        let local = ScopedKind::LocalTy { ty };
        self.names.push(local)
    }

    pub fn infer_type_var(&self, id: TyVarId, ty: pr::Ty) {
        tracing::debug!("inferring {id:?} is {}", crate::printer::print_ty(&ty));

        let mut constraints = self.ty_var_constraints.borrow_mut();
        constraints.push(TyVarConstraint::IsTy(id, ty));
    }

    pub fn infer_type_var_in_domain(&self, id: TyVarId, domain: pr::TyDomain) {
        tracing::debug!("inferring {id:?} in domain {domain:?}");

        let mut constraints = self.ty_var_constraints.borrow_mut();
        constraints.push(TyVarConstraint::InDomain(id, domain));
    }

    pub fn infer_type_vars_equal(&self, a: TyVarId, b: TyVarId) {
        tracing::debug!("inferring equality between {a:?} and {b:?}");

        let mut constraints = self.ty_var_constraints.borrow_mut();
        constraints.push(TyVarConstraint::Equals(a, b));
    }
}

impl<'a> TypeResolver<'a> {
    pub fn get_ty_var_scope(&self) -> &Scope {
        let mut stack = self.scopes.iter().rev();
        stack.find(|s| s.for_ty_vars()).unwrap()
    }
    pub fn get_ty_var_scope_mut(&mut self) -> &mut Scope {
        let mut stack = self.scopes.iter_mut().rev();
        stack.find(|s| s.for_ty_vars()).unwrap()
    }

    /// Get definition from within the current scope.
    ///
    /// Does not mutate the current scope or module structure.
    pub(super) fn get_ref(&'a self, target: &pr::Ref) -> Result<Named<'a>> {
        tracing::trace!("get_ident: {target:?}");

        match target {
            pr::Ref::Global(tgt_fq) => {
                let def = self.root_mod.get(tgt_fq);
                match &def.unwrap_or_else(|| panic!("cannot find {tgt_fq}")).kind {
                    pr::DefKind::Expr(expr) => Ok(Named::Expr(&expr.value)),
                    pr::DefKind::Ty(def) => Ok(Named::Ty {
                        ty: &def.ty,
                        is_framed: def.is_framed,
                        framed_label: def.framed_label.as_deref(),
                    }),

                    pr::DefKind::Module(_) => Ok(Named::Module),

                    pr::DefKind::Unresolved(_) | pr::DefKind::Import(_) => unreachable!(),
                }
            }

            pr::Ref::Local { scope, offset } => {
                let scope = self
                    .scopes
                    .iter()
                    .find(|s| s.id == *scope)
                    .ok_or_else(|| panic!("cannot find scope: {scope}"))
                    .unwrap();
                Ok(Named::Scoped(&scope.names[*offset]))
            }
        }
    }

    pub fn get_ty_param(&self, param_id: usize) -> (&String, &pr::TyDomain) {
        let scope = self.scopes.last().unwrap();
        let scoped = &scope.names[param_id];
        let ScopedKind::TyParam { name, domain } = scoped else {
            panic!()
        };
        (name, domain)
    }

    pub fn get_ty_var(&self, id: TyVarId) -> &TyVar {
        let scoped = &self.get_ty_var_scope().names[id];
        let ScopedKind::TyVar(var) = scoped else {
            panic!()
        };
        var
    }

    /// Resolves type identifiers. Does not resolve identifiers in contained types.
    pub fn get_ty_mat<'t>(&'t self, ty: &'t pr::Ty) -> Result<TyRef<'t>> {
        let pr::TyKind::Ident(_) = &ty.kind else {
            return Ok(TyRef::Ty(ty));
        };

        let target = ty.target.as_ref().unwrap();
        let named = self.get_ref(target).with_span(ty.span)?;

        match named {
            Named::Ty {
                is_framed: true, ..
            } => {
                // reference to a framed type: don't inline, return ident
                Ok(TyRef::Ty(ty))
            }

            Named::Ty {
                is_framed: false,
                ty,
                ..
            } => {
                // reference to a type: all ok

                // but refed ty might be an ident too: recurse!
                self.get_ty_mat(ty)
            }
            Named::Scoped(scoped) => {
                let pr::Ref::Local { scope, offset } = target else {
                    panic!()
                };
                match scoped {
                    ScopedKind::LocalTy { ty } => self.get_ty_mat(ty),
                    ScopedKind::TyParam { .. } => Ok(TyRef::Param(*offset)),
                    ScopedKind::TyVar(_) => {
                        // return type var
                        Ok(TyRef::Var(*scope, *offset))
                    }

                    ScopedKind::Param { ty, .. } => Err(err_name_kind("a type", "a value")
                        .push_hint(format!("got param of type `{}`", printer::print_ty(ty)))
                        .with_span(ty.span)),
                    ScopedKind::Local { ty, .. } => Err(err_name_kind("a type", "a value")
                        .push_hint(format!("got local var of type `{}`", printer::print_ty(ty)))
                        .with_span(ty.span)),
                }
            }
            Named::Expr(_) => Err(err_name_kind("a type", "a value").with_span(ty.span)),
            Named::Module => Err(err_name_kind("a type", "a module").with_span(ty.span)),
        }
    }

    /// Add type's params into scope as type variables.
    pub fn introduce_ty_into_scope(&mut self, ty: pr::Ty, span: Span) -> (pr::Ty, Vec<pr::Ty>) {
        let pr::TyKind::Func(mut ty_func) = ty.kind else {
            return (ty, Vec::new());
        };

        // TODO: recurse? There might be type params deeper in the type.

        if ty_func.ty_params.is_empty() {
            return (
                pr::Ty {
                    kind: pr::TyKind::Func(ty_func),
                    ..ty
                },
                Vec::new(),
            );
        }

        let mut mapping = HashMap::new();
        let mut ty_args = Vec::with_capacity(ty_func.ty_params.len());

        let scope = self.get_ty_var_scope_mut();
        tracing::debug!(
            "introducing generics for ty_func={} into scope {}",
            crate::printer::print_ty(&pr::Ty::new(ty_func.clone())),
            scope.id,
        );
        for (gtp_position, gtp) in ty_func.ty_params.drain(..).enumerate() {
            let gtp_ref = pr::Ref::Local {
                scope: ty.scope_id.unwrap(), // original scope of the type
                offset: gtp_position,
            };

            let mut ty_arg_ident = pr::Ty::new(
                // name does not matter here, it is just for error messages
                pr::Path::new(vec![gtp.name.clone()]),
            );
            let offset = scope.insert_type_var(Some(gtp.name), span, gtp.domain);
            ty_arg_ident.target = Some(pr::Ref::Local {
                scope: scope.id, // current scope
                offset,
            });

            mapping.insert(gtp_ref, ty_arg_ident.clone());
            ty_args.push(ty_arg_ident);
        }

        let ty = pr::Ty {
            kind: pr::TyKind::Func(ty_func),
            ..ty
        };
        (utils::TypeReplacer::on_ty(ty, mapping), ty_args)
    }

    pub fn introduce_ty_var(&self, domain: pr::TyDomain, span: Span) -> pr::Ty {
        let scope = self.get_ty_var_scope();
        let var_id = scope.insert_type_var(None, span, domain);

        pr::Ty {
            span: Some(span),
            target: Some(pr::Ref::Local {
                scope: scope.id,
                offset: var_id,
            }),
            ..pr::Ty::new(pr::TyKind::Ident(pr::Path::from_name("_")))
        }
    }
}

pub(super) fn err_name_kind(expected: &str, found: &str) -> Diagnostic {
    Diagnostic::new(
        format!("expected {expected}, found {found}"),
        DiagnosticCode::NAME_KIND,
    )
}