Skip to content

Commit aeadb26

Browse files
committed
Use new pattern match compiler and coverage checker
1 parent 462c717 commit aeadb26

34 files changed

+1567
-468
lines changed

fathom/src/core.rs

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
33
use std::fmt;
44

5-
use crate::env::{Index, Level};
5+
use crate::env::{EnvLen, Index, Level};
66
use crate::source::{Span, StringId};
7+
use scoped_arena::Scope;
78

89
pub mod binary;
910
pub mod pretty;
@@ -205,6 +206,10 @@ pub enum Term<'arena> {
205206
}
206207

207208
impl<'arena> Term<'arena> {
209+
pub fn error(span: impl Into<Span>) -> Self {
210+
Self::Prim(span.into(), Prim::ReportedError)
211+
}
212+
208213
/// Get the source span of the term.
209214
pub fn span(&self) -> Span {
210215
match self {
@@ -279,6 +284,153 @@ impl<'arena> Term<'arena> {
279284
pub fn is_error(&self) -> bool {
280285
matches!(self, Term::Prim(_, Prim::ReportedError))
281286
}
287+
288+
// TODO: Add a new `Weaken` variant to `core::Term` instead of eagerly traversing the term?
289+
// See [Andras Kovacs’ staged language](https://github.com/AndrasKovacs/staged/blob/9e381eb162f44912d70fb843c4ca6567b0d1683a/demo/Syntax.hs#L52) for an example
290+
pub fn shift(&self, scope: &'arena Scope<'arena>, amount: EnvLen) -> Term<'arena> {
291+
self.shift_inner(scope, Index::last(), amount)
292+
}
293+
294+
/// Increment all `LocalVar`s greater than or equal to `min` by `amount`
295+
fn shift_inner(
296+
&self,
297+
scope: &'arena Scope<'arena>,
298+
mut min: Index,
299+
amount: EnvLen,
300+
) -> Term<'arena> {
301+
// Skip traversing and rebuilding the term if it would make no change. Increases sharing.
302+
if amount == EnvLen::new() {
303+
return self.clone();
304+
}
305+
306+
match self {
307+
Term::LocalVar(span, var) if *var >= min => Term::LocalVar(*span, *var + amount),
308+
Term::LocalVar(..)
309+
| Term::ItemVar(..)
310+
| Term::MetaVar(..)
311+
| Term::InsertedMeta(..)
312+
| Term::Prim(..)
313+
| Term::ConstLit(..)
314+
| Term::Universe(..) => self.clone(),
315+
Term::Ann(span, expr, r#type) => Term::Ann(
316+
*span,
317+
scope.to_scope(expr.shift_inner(scope, min, amount)),
318+
scope.to_scope(r#type.shift_inner(scope, min, amount)),
319+
),
320+
Term::Let(span, name, def_type, def_expr, body) => Term::Let(
321+
*span,
322+
*name,
323+
scope.to_scope(def_type.shift_inner(scope, min, amount)),
324+
scope.to_scope(def_expr.shift_inner(scope, min, amount)),
325+
scope.to_scope(body.shift_inner(scope, min.prev(), amount)),
326+
),
327+
Term::FunType(span, plicity, name, input, output) => Term::FunType(
328+
*span,
329+
*plicity,
330+
*name,
331+
scope.to_scope(input.shift_inner(scope, min, amount)),
332+
scope.to_scope(output.shift_inner(scope, min.prev(), amount)),
333+
),
334+
Term::FunLit(span, plicity, name, body) => Term::FunLit(
335+
*span,
336+
*plicity,
337+
*name,
338+
scope.to_scope(body.shift_inner(scope, min.prev(), amount)),
339+
),
340+
Term::FunApp(span, plicity, fun, arg) => Term::FunApp(
341+
*span,
342+
*plicity,
343+
scope.to_scope(fun.shift_inner(scope, min, amount)),
344+
scope.to_scope(arg.shift_inner(scope, min, amount)),
345+
),
346+
Term::RecordType(span, labels, types) => Term::RecordType(
347+
*span,
348+
labels,
349+
scope.to_scope_from_iter(types.iter().map(|r#type| {
350+
let ret = r#type.shift_inner(scope, min, amount);
351+
min = min.prev();
352+
ret
353+
})),
354+
),
355+
Term::RecordLit(span, labels, exprs) => Term::RecordLit(
356+
*span,
357+
labels,
358+
scope.to_scope_from_iter(
359+
exprs
360+
.iter()
361+
.map(|expr| expr.shift_inner(scope, min, amount)),
362+
),
363+
),
364+
Term::RecordProj(span, head, label) => Term::RecordProj(
365+
*span,
366+
scope.to_scope(head.shift_inner(scope, min, amount)),
367+
*label,
368+
),
369+
Term::ArrayLit(span, terms) => Term::ArrayLit(
370+
*span,
371+
scope.to_scope_from_iter(
372+
terms
373+
.iter()
374+
.map(|term| term.shift_inner(scope, min, amount)),
375+
),
376+
),
377+
Term::FormatRecord(span, labels, terms) => Term::FormatRecord(
378+
*span,
379+
labels,
380+
scope.to_scope_from_iter(terms.iter().map(|term| {
381+
let ret = term.shift_inner(scope, min, amount);
382+
min = min.prev();
383+
ret
384+
})),
385+
),
386+
Term::FormatCond(span, name, format, pred) => Term::FormatCond(
387+
*span,
388+
*name,
389+
scope.to_scope(format.shift_inner(scope, min, amount)),
390+
scope.to_scope(pred.shift_inner(scope, min.prev(), amount)),
391+
),
392+
Term::FormatOverlap(span, labels, terms) => Term::FormatOverlap(
393+
*span,
394+
labels,
395+
scope.to_scope_from_iter(terms.iter().map(|term| {
396+
let ret = term.shift_inner(scope, min, amount);
397+
min = min.prev();
398+
ret
399+
})),
400+
),
401+
Term::ConstMatch(span, scrut, branches, default) => Term::ConstMatch(
402+
*span,
403+
scope.to_scope(scrut.shift_inner(scope, min, amount)),
404+
scope.to_scope_from_iter(
405+
branches
406+
.iter()
407+
.map(|(r#const, term)| (*r#const, term.shift_inner(scope, min, amount))),
408+
),
409+
default.map(|(name, term)| {
410+
(
411+
name,
412+
scope.to_scope(term.shift_inner(scope, min.prev(), amount)) as &_,
413+
)
414+
}),
415+
),
416+
}
417+
}
418+
419+
/// Returns `true` if `self` can be evaluated in a single step.
420+
/// Used as a heuristic to prevent increase in runtime when expanding pattern matches
421+
pub fn is_atomic(&self) -> bool {
422+
match self {
423+
Term::ItemVar(_, _)
424+
| Term::LocalVar(_, _)
425+
| Term::MetaVar(_, _)
426+
| Term::InsertedMeta(_, _, _)
427+
| Term::Universe(_)
428+
| Term::Prim(_, _)
429+
| Term::ConstLit(_, _) => true,
430+
Term::RecordProj(_, head, _) => head.is_atomic(),
431+
_ => false,
432+
}
433+
}
282434
}
283435

284436
macro_rules! def_prims {
@@ -599,6 +751,21 @@ pub enum Const {
599751
Ref(usize),
600752
}
601753

754+
impl Const {
755+
/// Return the number of inhabitants of `self`.
756+
/// `None` represents infinity
757+
pub fn num_inhabitants(&self) -> Option<u128> {
758+
match self {
759+
Const::Bool(_) => Some(2),
760+
Const::U8(_, _) | Const::S8(_) => Some(1 << 8),
761+
Const::U16(_, _) | Const::S16(_) => Some(1 << 16),
762+
Const::U32(_, _) | Const::S32(_) => Some(1 << 32),
763+
Const::U64(_, _) | Const::S64(_) => Some(1 << 64),
764+
Const::F32(_) | Const::F64(_) | Const::Pos(_) | Const::Ref(_) => None,
765+
}
766+
}
767+
}
768+
602769
impl PartialEq for Const {
603770
fn eq(&self, other: &Self) -> bool {
604771
match (*self, *other) {

fathom/src/core/semantics.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ pub enum Value<'arena> {
5353
}
5454

5555
impl<'arena> Value<'arena> {
56+
pub const ERROR: Self = Self::Stuck(Head::Prim(Prim::ReportedError), Vec::new());
57+
5658
pub fn prim(prim: Prim, params: impl IntoIterator<Item = ArcValue<'arena>>) -> Value<'arena> {
5759
let params = params
5860
.into_iter()
@@ -79,6 +81,13 @@ impl<'arena> Value<'arena> {
7981
pub fn is_error(&self) -> bool {
8082
matches!(self, Value::Stuck(Head::Prim(Prim::ReportedError), _))
8183
}
84+
85+
pub fn as_record_type(&self) -> Option<&Telescope<'arena>> {
86+
match self {
87+
Value::RecordType(_, telescope) => Some(telescope),
88+
_ => None,
89+
}
90+
}
8291
}
8392

8493
/// The head of a [stuck value][Value::Stuck].

fathom/src/env.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
//! and [`SliceEnv`], but when we need to copy environments often, we use a
1818
//! [`SharedEnv`] to increase the amount of sharing at the expense of locality.
1919
20-
use std::fmt;
20+
use std::{fmt, ops::Add};
2121

2222
/// Underlying variable representation.
2323
type RawVar = u16;
@@ -56,6 +56,13 @@ impl Index {
5656
}
5757
}
5858

59+
impl Add<EnvLen> for Index {
60+
type Output = Self;
61+
fn add(self, rhs: EnvLen) -> Self::Output {
62+
Self(self.0 + rhs.0) // FIXME: check overflow?
63+
}
64+
}
65+
5966
impl fmt::Debug for Index {
6067
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6168
write!(f, "Index(")?;
@@ -152,6 +159,10 @@ impl EnvLen {
152159
Level(self.0)
153160
}
154161

162+
pub fn next(&self) -> EnvLen {
163+
Self(self.0 + 1) // FIXME: check overflow?
164+
}
165+
155166
/// Push an entry onto the environment.
156167
pub fn push(&mut self) {
157168
self.0 += 1; // FIXME: check overflow?

fathom/src/surface/distillation.rs

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -336,11 +336,33 @@ impl<'interner, 'arena, 'env> Context<'interner, 'arena, 'env> {
336336
core::Const::Ref(number) => self.check_number_literal(number),
337337
},
338338
core::Term::ConstMatch(_span, head_expr, branches, default_branch) => {
339-
if let Some((then_expr, else_expr)) = match_if_then_else(branches, *default_branch)
339+
if let Some(((then_name, then_expr), (else_name, else_expr))) =
340+
match_if_then_else(branches, *default_branch)
340341
{
341342
let cond_expr = self.check(head_expr);
342-
let then_expr = self.check(then_expr);
343-
let else_expr = self.check(else_expr);
343+
344+
let then_expr = match then_name {
345+
None => self.check(then_expr),
346+
Some(name) => {
347+
let name = self.freshen_name(name, then_expr);
348+
self.push_local(name);
349+
let then_expr = self.check(then_expr);
350+
self.pop_local();
351+
then_expr
352+
}
353+
};
354+
355+
let else_expr = match else_name {
356+
None => self.check(else_expr),
357+
Some(name) => {
358+
let name = self.freshen_name(name, else_expr);
359+
self.push_local(name);
360+
let else_expr = self.check(else_expr);
361+
self.pop_local();
362+
else_expr
363+
}
364+
};
365+
344366
return Term::If(
345367
(),
346368
self.scope.to_scope(cond_expr),
@@ -399,11 +421,11 @@ impl<'interner, 'arena, 'env> Context<'interner, 'arena, 'env> {
399421
match core_term {
400422
core::Term::ItemVar(_span, var) => match self.get_item_name(*var) {
401423
Some(name) => Term::Name((), name),
402-
None => todo!("misbound variable"), // TODO: error?
424+
None => panic!("misbound item variable: {var:?}"),
403425
},
404426
core::Term::LocalVar(_span, var) => match self.get_local_name(*var) {
405427
Some(name) => Term::Name((), name),
406-
None => todo!("misbound variable"), // TODO: error?
428+
None => panic!("Unbound local variable: {var:?}"),
407429
},
408430
core::Term::MetaVar(_span, var) => match self.get_hole_name(*var) {
409431
Some(name) => Term::Hole((), name),
@@ -683,10 +705,33 @@ impl<'interner, 'arena, 'env> Context<'interner, 'arena, 'env> {
683705
core::Const::Ref(number) => self.synth_number_literal(number, core::Prim::RefType),
684706
},
685707
core::Term::ConstMatch(_span, head_expr, branches, default_expr) => {
686-
if let Some((then_expr, else_expr)) = match_if_then_else(branches, *default_expr) {
708+
if let Some(((then_name, then_expr), (else_name, else_expr))) =
709+
match_if_then_else(branches, *default_expr)
710+
{
687711
let cond_expr = self.check(head_expr);
688-
let then_expr = self.synth(then_expr);
689-
let else_expr = self.synth(else_expr);
712+
713+
let then_expr = match then_name {
714+
None => self.synth(then_expr),
715+
Some(name) => {
716+
let name = self.freshen_name(name, then_expr);
717+
self.push_local(name);
718+
let then_expr = self.synth(then_expr);
719+
self.pop_local();
720+
then_expr
721+
}
722+
};
723+
724+
let else_expr = match else_name {
725+
None => self.synth(else_expr),
726+
Some(name) => {
727+
let name = self.freshen_name(name, else_expr);
728+
self.push_local(name);
729+
let else_expr = self.synth(else_expr);
730+
self.pop_local();
731+
else_expr
732+
}
733+
};
734+
690735
return Term::If(
691736
(),
692737
self.scope.to_scope(cond_expr),
@@ -813,15 +858,24 @@ impl<'interner, 'arena, 'env> Context<'interner, 'arena, 'env> {
813858
}
814859
}
815860

861+
#[allow(clippy::type_complexity)]
816862
fn match_if_then_else<'arena>(
817863
branches: &'arena [(Const, core::Term<'arena>)],
818864
default_branch: Option<(Option<StringId>, &'arena core::Term<'arena>)>,
819-
) -> Option<(&'arena core::Term<'arena>, &'arena core::Term<'arena>)> {
865+
) -> Option<(
866+
(Option<Option<StringId>>, &'arena core::Term<'arena>),
867+
(Option<Option<StringId>>, &'arena core::Term<'arena>),
868+
)> {
820869
match (branches, default_branch) {
821-
([(Const::Bool(false), else_expr), (Const::Bool(true), then_expr)], None)
822-
// TODO: Normalise boolean branches when elaborating patterns
823-
| ([(Const::Bool(true), then_expr)], Some((_, else_expr)))
824-
| ([(Const::Bool(false), else_expr)], Some((_, then_expr))) => Some((then_expr, else_expr)),
870+
([(Const::Bool(false), else_expr), (Const::Bool(true), then_expr)], None) => {
871+
Some(((None, then_expr), (None, else_expr)))
872+
}
873+
([(Const::Bool(true), then_expr)], Some((name, else_expr))) => {
874+
Some(((None, then_expr), (Some(name), else_expr)))
875+
}
876+
([(Const::Bool(false), else_expr)], Some((name, then_expr))) => {
877+
Some(((Some(name), then_expr), (None, else_expr)))
878+
}
825879
_ => None,
826880
}
827881
}

0 commit comments

Comments
 (0)