Skip to content

Commit 7c26566

Browse files
committed
Add in match diagnostics
1 parent ef07ec1 commit 7c26566

File tree

10 files changed

+184
-54
lines changed

10 files changed

+184
-54
lines changed

benchmark.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import os
2+
import subprocess
3+
4+
last = 0
5+
def new_name(s):
6+
global last
7+
last += 1
8+
return s + str(last)
9+
10+
long = ""
11+
with open("scratch.sml", 'r') as f:
12+
text = f.read()
13+
lines = len(text.splitlines())
14+
total = 0
15+
16+
while total < 10000:
17+
s = text
18+
s = s.replace('merge', new_name('merge'))
19+
s = s.replace('type_check', new_name('type_check'))
20+
long += s
21+
total += lines
22+
23+
open('long.sml', 'w').write(long)
24+
subprocess.run(["cargo", "build", "--release"])
25+
bench = subprocess.run(["target/release/sml-driver.exe", "--measure", "long.sml"], capture_output=True)
26+
print(bench.stdout.decode("utf-8"))
27+
os.remove('long.sml')

crates/sml-core/src/elaborate.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ pub struct Context<'a> {
129129
tyvar_rank: usize,
130130

131131
pub(crate) arena: &'a CoreArena<'a>,
132-
elab_errors: Vec<ElabError>,
132+
pub elab_errors: Vec<ElabError>,
133133
unification_errors: Vec<CantUnify<'a>>,
134134
}
135135

@@ -337,6 +337,8 @@ pub enum ErrorKind {
337337
Rebound(Symbol),
338338
Escape(Symbol),
339339
Arity(usize, usize),
340+
Redundant,
341+
Inexhaustive,
340342
Message,
341343
}
342344

crates/sml-core/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,13 @@ impl<'ar> Expr<'ar> {
125125
_ => false,
126126
}
127127
}
128+
129+
pub fn as_symbol(&self) -> Symbol {
130+
match &self.expr {
131+
ExprKind::Var(s) => *s,
132+
_ => panic!("BUG: Expr::as_symbol()"),
133+
}
134+
}
128135
}
129136

130137
impl<'ar> Pat<'ar> {

crates/sml-core/src/match_compile.rs

Lines changed: 90 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,23 @@
1414
//! * `vars` contains the current "in-scope" destructured variables
1515
1616
use super::*;
17-
use elaborate::Context;
17+
use elaborate::{Context, ElabError, ErrorKind};
1818
use std::collections::{HashMap, HashSet, VecDeque};
1919

2020
pub type Var<'a> = (Symbol, &'a Type<'a>);
2121

2222
pub fn case<'a>(
23-
ctx: &Context<'a>,
23+
ctx: &mut Context<'a>,
2424
scrutinee: Expr<'a>,
2525
ret_ty: &'a Type<'a>,
2626
rules: Vec<Rule<'a>>,
2727
span: Span,
2828
) -> Expr<'a> {
2929
let test = ctx.fresh_var();
3030
let pats = rules.iter().map(|r| vec![r.pat]).collect();
31-
let (mut decls, rules) = preflight(ctx, rules);
31+
32+
let mut diags = MatchDiags::with_capacity(span, rules.len());
33+
let (mut decls, rules) = preflight(ctx, rules, &mut diags);
3234

3335
decls.push(Decl::Val(Rule {
3436
pat: ctx.arena.pat_var(test, scrutinee.ty),
@@ -45,7 +47,8 @@ pub fn case<'a>(
4547
};
4648

4749
let mut facts = Facts::default();
48-
let expr = mat.compile(&mut facts);
50+
let expr = mat.compile(&mut facts, &mut diags);
51+
diags.emit_diagnostics(ctx);
4952
Expr::new(
5053
ctx.arena.exprs.alloc(ExprKind::Let(decls, expr)),
5154
expr.ty,
@@ -54,15 +57,16 @@ pub fn case<'a>(
5457
}
5558

5659
pub fn fun<'a>(
57-
ctx: &Context<'a>,
60+
ctx: &mut Context<'a>,
5861
ret_ty: &'a Type<'a>,
5962
vars: Vec<Var<'a>>,
6063
pats: Vec<Vec<Pat<'a>>>,
6164
rules: Vec<Rule<'a>>,
6265
span: Span,
6366
) -> Expr<'a> {
6467
let test = ctx.fresh_var();
65-
let (decls, rules) = preflight(ctx, rules);
68+
let mut diags = MatchDiags::with_capacity(span, rules.len());
69+
let (decls, rules) = preflight(ctx, rules, &mut diags);
6670

6771
let rec = SortedRecord::new_unchecked(
6872
vars.iter()
@@ -88,7 +92,8 @@ pub fn fun<'a>(
8892
vars,
8993
};
9094

91-
let expr = mat.compile(&mut facts);
95+
let expr = mat.compile(&mut facts, &mut diags);
96+
diags.emit_diagnostics(ctx);
9297
Expr::new(
9398
ctx.arena.exprs.alloc(ExprKind::Let(decls, expr)),
9499
expr.ty,
@@ -117,7 +122,11 @@ pub fn fun<'a>(
117122
/// case x of
118123
/// ...
119124
/// ```
120-
fn preflight<'a>(ctx: &Context<'a>, rules: Vec<Rule<'a>>) -> (Vec<Decl<'a>>, Vec<Rule<'a>>) {
125+
fn preflight<'a>(
126+
ctx: &Context<'a>,
127+
rules: Vec<Rule<'a>>,
128+
diags: &mut MatchDiags,
129+
) -> (Vec<Decl<'a>>, Vec<Rule<'a>>) {
121130
let mut finished = Vec::new();
122131
let mut decls = Vec::new();
123132
for Rule { pat, expr } in rules {
@@ -158,7 +167,9 @@ fn preflight<'a>(ctx: &Context<'a>, rules: Vec<Rule<'a>>) -> (Vec<Decl<'a>>, Vec
158167
finished.push(Rule {
159168
pat,
160169
expr: ctx.arena.expr_var(name, ty),
161-
})
170+
});
171+
172+
diags.renamed.push((expr.span, name));
162173
}
163174
(decls, finished)
164175
}
@@ -218,6 +229,42 @@ impl Facts {
218229
}
219230
}
220231

232+
pub struct MatchDiags {
233+
span: Span,
234+
// mapping from match arm index to renamed/abstracted arms
235+
renamed: Vec<(Span, Symbol)>,
236+
// Which arms were reached
237+
reached: HashSet<Symbol>,
238+
// Did we emit a `raise Match`?
239+
inexhaustive: bool,
240+
}
241+
242+
impl MatchDiags {
243+
pub fn with_capacity(span: Span, capacity: usize) -> MatchDiags {
244+
MatchDiags {
245+
span,
246+
renamed: Vec::with_capacity(capacity),
247+
reached: HashSet::with_capacity(capacity),
248+
inexhaustive: false,
249+
}
250+
}
251+
252+
pub fn emit_diagnostics(self, ctx: &mut Context<'_>) {
253+
for (sp, sym) in self.renamed {
254+
if !self.reached.contains(&sym) {
255+
ctx.elab_errors
256+
.push(ElabError::new(sp, "unreachable match arm").kind(ErrorKind::Redundant));
257+
}
258+
}
259+
if self.inexhaustive {
260+
ctx.elab_errors.push(
261+
ElabError::new(self.span, "inexhaustive `case` expression")
262+
.kind(ErrorKind::Redundant),
263+
);
264+
}
265+
}
266+
}
267+
221268
pub struct Matrix<'a, 'ctx> {
222269
// ref to the context so we can allocate stuff in arenas
223270
ctx: &'ctx Context<'a>,
@@ -264,7 +311,12 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
264311
/// Deconstruct a record or tuple pattern, binding each field to a fresh
265312
/// variable, and flattening all of the record patterns in the first column
266313
/// [{a, b, c}, ...] --> [a, b, c, ...]
267-
fn record_rule(&self, facts: &mut Facts, fields: &SortedRecord<Pat<'a>>) -> Expr<'a> {
314+
fn record_rule(
315+
&self,
316+
facts: &mut Facts,
317+
diags: &mut MatchDiags,
318+
fields: &SortedRecord<Pat<'a>>,
319+
) -> Expr<'a> {
268320
// This part is a little tricky. We need to generate a fresh variable
269321
// for every field in the pattern
270322
let mut vars = self.vars.clone();
@@ -310,7 +362,7 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
310362
mat.vars = vars;
311363
self.ctx
312364
.arena
313-
.let_derecord(record, base.0, mat.compile(facts))
365+
.let_derecord(record, base.0, mat.compile(facts, diags))
314366
}
315367

316368
/// This is basically the same thing as the record rule, but for data
@@ -320,6 +372,7 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
320372
fn specialize(
321373
&self,
322374
facts: &mut Facts,
375+
diags: &mut MatchDiags,
323376
head: &Constructor,
324377
arity: Option<Var<'a>>,
325378
) -> Expr<'a> {
@@ -352,11 +405,11 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
352405
}
353406
}
354407
// mat.test = mat.vars[0].0;
355-
mat.compile(facts)
408+
mat.compile(facts, diags)
356409
}
357410

358411
/// Generate a case expression for the data constructors in the first column
359-
fn sum_rule(&self, facts: &mut Facts) -> Expr<'a> {
412+
fn sum_rule(&self, facts: &mut Facts, diags: &mut MatchDiags) -> Expr<'a> {
360413
// Generate the set of constructors appearing in the column
361414
let mut set = HashMap::new();
362415
let mut type_arity = 0;
@@ -374,7 +427,7 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
374427
for (con, arg_ty) in set {
375428
let fresh = self.ctx.fresh_var();
376429
let mut f = facts.clone();
377-
let expr = self.specialize(&mut f, con, arg_ty.map(|ty| (fresh, ty)));
430+
let expr = self.specialize(&mut f, diags, con, arg_ty.map(|ty| (fresh, ty)));
378431

379432
let arg = match arg_ty {
380433
Some(ty) => Some(self.ctx.arena.pat_var(fresh, ty)),
@@ -391,7 +444,7 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
391444
// If we don't have an exhaustive match, generate a default matrix
392445
if !exhaustive {
393446
let pat = self.mk_wild(self.pats[0][0].ty);
394-
let expr = self.default_matrix(facts);
447+
let expr = self.default_matrix(facts, diags);
395448
rules.push(Rule { pat, expr });
396449
}
397450

@@ -409,7 +462,12 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
409462
/// constructors. We want to select all of the rows in the first column
410463
/// that will match the constructor `head` (e.g. `head` itself, and
411464
/// wildcard)
412-
fn specialize_const(&self, facts: &mut Facts, head: &Const) -> Expr<'a> {
465+
fn specialize_const(
466+
&self,
467+
facts: &mut Facts,
468+
diags: &mut MatchDiags,
469+
head: &Const,
470+
) -> Expr<'a> {
413471
let mut mat = self.shallow();
414472
for (idx, row) in self.pats.iter().enumerate() {
415473
match &row[0].pat {
@@ -422,11 +480,11 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
422480
mat.pats.push(new_row);
423481
}
424482
mat.vars.remove(0);
425-
mat.compile(facts)
483+
mat.compile(facts, diags)
426484
}
427485

428486
/// Generate a case expression for the data constructors in the first column
429-
fn const_rule(&self, facts: &mut Facts) -> Expr<'a> {
487+
fn const_rule(&self, facts: &mut Facts, diags: &mut MatchDiags) -> Expr<'a> {
430488
// Generate the set of constructors appearing in the column
431489
let mut set = HashSet::new();
432490
for row in &self.pats {
@@ -444,7 +502,7 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
444502
for con in set {
445503
// Clone facts so we don't polute other branches with identical bound
446504
let mut f = facts.clone();
447-
let expr = self.specialize_const(&mut f, con);
505+
let expr = self.specialize_const(&mut f, diags, con);
448506

449507
let pat = Pat::new(
450508
self.ctx.arena.pats.alloc(PatKind::Const(*con)),
@@ -455,7 +513,7 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
455513
}
456514

457515
let pat = self.mk_wild(self.pats[0][0].ty);
458-
let expr = self.default_matrix(facts);
516+
let expr = self.default_matrix(facts, diags);
459517
rules.push(Rule { pat, expr });
460518

461519
Expr::new(
@@ -469,7 +527,7 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
469527
}
470528

471529
/// Compute the "default" matrix
472-
fn default_matrix(&self, facts: &mut Facts) -> Expr<'a> {
530+
fn default_matrix(&self, facts: &mut Facts, diags: &mut MatchDiags) -> Expr<'a> {
473531
let mut mat = self.shallow();
474532
mat.vars.remove(0);
475533

@@ -480,11 +538,11 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
480538
}
481539
}
482540
// mat.test = mat.vars[0].0;
483-
mat.compile(facts)
541+
mat.compile(facts, diags)
484542
}
485543

486544
/// Compile a [`Matrix`] into a source-level expression
487-
fn compile(&mut self, facts: &mut Facts) -> Expr<'a> {
545+
fn compile(&mut self, facts: &mut Facts, diags: &mut MatchDiags) -> Expr<'a> {
488546
if self.pats.is_empty() {
489547
// We have an in-exhaustive case expression
490548
// TODO: Emit better diagnostics
@@ -499,6 +557,8 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
499557
Span::zero(),
500558
);
501559

560+
diags.inexhaustive = true;
561+
502562
Expr::new(
503563
self.ctx.arena.exprs.alloc(ExprKind::Raise(matchh)),
504564
self.ret_ty,
@@ -530,6 +590,8 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
530590
})),
531591
};
532592

593+
diags.reached.insert(self.rules[0].expr.as_symbol());
594+
533595
// TODO: Check types just in case
534596
Expr::new(
535597
self.ctx
@@ -543,9 +605,9 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
543605
// There is at least one non-wild pattern in the matrix somewhere
544606
for row in &self.pats {
545607
match &row[0].pat {
546-
PatKind::Record(fields) => return self.record_rule(facts, fields),
547-
PatKind::App(_, _) => return self.sum_rule(facts),
548-
PatKind::Const(_) => return self.const_rule(facts),
608+
PatKind::Record(fields) => return self.record_rule(facts, diags, fields),
609+
PatKind::App(_, _) => return self.sum_rule(facts, diags),
610+
PatKind::Const(_) => return self.const_rule(facts, diags),
549611
PatKind::Wild | PatKind::Var(_) => continue,
550612
}
551613
}
@@ -571,9 +633,9 @@ impl<'a, 'ctx> Matrix<'a, 'ctx> {
571633
}
572634
// Swap variables as well
573635
self.vars.swap(0, col);
574-
self.compile(facts)
636+
self.compile(facts, diags)
575637
} else {
576-
self.default_matrix(facts)
638+
self.default_matrix(facts, diags)
577639
}
578640
}
579641
}

crates/sml-core/src/monomorphize.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![allow(dead_code)]
22
use super::*;
33

4+
#[derive(Default)]
45
pub struct Cache<'a> {
56
defs: HashMap<Symbol, CacheEntry<'a>>,
67
names: Vec<HashMap<Symbol, Symbol>>,

crates/sml-driver/src/main.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,9 @@ fn main() {
133133
let owned_arena = sml_core::arenas::OwnedCoreArena::new();
134134
let borrow = owned_arena.borrow();
135135

136-
let args = std::env::args();
137-
if args.len() > 1 {
138-
let ArgParse { builder, files } = ArgParse::parse(args);
139-
let mut compiler = builder.build(&borrow);
136+
let ArgParse { builder, files } = ArgParse::parse(std::env::args());
137+
let mut compiler = builder.build(&borrow);
138+
if !files.is_empty() {
140139
for f in files {
141140
let file = std::fs::read_to_string(&f).unwrap();
142141
compiler.run(&file);
@@ -148,7 +147,6 @@ fn main() {
148147
}
149148

150149
println!("SomewhatML (c) 2020");
151-
let mut compiler = CompilerBuilder::default().verbosity(2).build(&borrow);
152150
loop {
153151
let mut buffer = String::new();
154152
print!("repl: ");

0 commit comments

Comments
 (0)