logicaffeine_kernel/interface/match_compile.rs
1//! The equation compiler: surface patterns → the kernel's positional `Match` tree.
2//!
3//! The kernel's `Term::Match` is deliberately primitive. Its `cases` vector is indexed by
4//! CONSTRUCTOR, in the inductive's registration order, one case per constructor, each case a
5//! λ-telescope over that constructor's value fields. It has no patterns, no nesting, no
6//! wildcard and no notion of order among alternatives. That is the right primitive — every
7//! extra feature in an eliminator is another thing the trusted base has to be right about —
8//! but it is not how mathematics is written. Lean writes
9//!
10//! ```text
11//! def f : Nat → Nat → Nat
12//! | 0, _ => 1
13//! | _, 0 => 2
14//! | .succ _, .succ _ => 3
15//! ```
16//!
17//! and compiles it. This module is that compiler: Maranget/Augustsson column-based
18//! pattern-matrix compilation, with Lean's top-to-bottom first-match semantics, producing a
19//! tree of positional `Match` nodes the kernel already certifies.
20//!
21//! # What it guarantees
22//!
23//! * **Registration order, never source order.** A clause is placed in the slot of the
24//! constructor it NAMES. `Bool` is registered `true` then `false`; writing the `false` clause
25//! first must not silently compute the other function. For every equal-arity inductive —
26//! every enumeration — this is the difference between a correct program and a wrong one that
27//! type-checks.
28//! * **First match wins.** Overlapping equations are split into disjoint constructor cases by
29//! specialising the matrix top-to-bottom, which is the only reading under which `pick 0 0`
30//! is `1` rather than `2`.
31//! * **Totality.** A constructor no clause can reach is an error naming the missing case, not a
32//! silently-invented value and not a partial function. The kernel is total; so is its surface —
33//! and so is Lean's: `def classify : Nat → Nat | 0 => 0 | Nat.succ (Nat.succ k) => k` answers
34//! `error: missing cases: (Nat.succ Nat.zero)` (`Lean/Elab/Match.lean:991-992`).
35//! * **Numerals.** `When 0:` is the unary spelling of `0` in whatever inductive the column has —
36//! Lean's `expandNatValuePattern` (`Lean/Meta/Match/Match.lean:629`), except that the zero and
37//! the successor are derived from the declaration instead of hard-coded to `Nat`.
38//! * **Nothing is relaxed at the kernel.** This is a SURFACE pass. It emits exactly the
39//! `Term::Match` the kernel already accepts, one case per constructor at every node. A
40//! hand-written match with the wrong case count is still `WrongNumberOfCases`.
41//!
42//! # What it deliberately does not do
43//!
44//! The compiled tree is non-dependent: every node's motive is the CONSTANT `λ_:I. T` at the
45//! same result type `T`. That is what a surface equation set means (`f : A → B`, one result
46//! type for every branch). A genuinely dependent elimination is written with an explicit motive
47//! against `Term::Match` directly, which this module never touches.
48
49use super::spelled_names;
50use crate::type_checker::substitute;
51use crate::{
52 infer_type, is_subtype, normalize, surface_elaborate_against, Context, KernelError,
53 KernelResult, Term,
54};
55use std::collections::HashSet;
56
57/// The binder prefix for a field the compiler introduces.
58///
59/// The prefix alone does NOT make the name fresh. `«#fld0»` is a `«quoted name»`, which
60/// `scan_name` accepts everywhere an identifier is read, so a clause binder can be spelled
61/// exactly like one of these — and the variable and catch-all rules bind a pattern variable by
62/// SUBSTITUTING it, column by column, so a collision makes one column's substitution rewrite
63/// what the previous column's had just introduced. `When Pair a «#fld0»: Yield a.` returned
64/// the SECOND field: a well-typed term, certified by both kernels, denoting a different
65/// function. Freshness is therefore CHECKED, against every name the clause set and the
66/// constructor telescopes can spell — see [`State::next_name`].
67const FIELD_MARK: &str = "#fld";
68
69/// A surface pattern, exactly as a `When` clause spells it.
70#[derive(Clone, Debug, PartialEq, Eq)]
71pub enum Pattern {
72 /// `_` — matches anything, binds nothing.
73 Wildcard,
74 /// `k` — matches anything, binds the name to the whole matched value.
75 Var(String),
76 /// `Succ (Succ k)` — a constructor applied to one sub-pattern per VALUE field.
77 Ctor { name: String, args: Vec<Pattern> },
78 /// `0`, `2`, `17` — a NUMERAL, which is the unary spelling of that number in the inductive
79 /// the column has. Lean's `| 0 => … | 1 => …` (`Lean/Meta/Match/Match.lean:629`
80 /// `expandNatValuePattern`, reached through `isNatValueTransition` at :171) expands a `Nat`
81 /// literal pattern into `Nat.zero`/`Nat.succ` applications before the matrix ever sees it,
82 /// and so does this compiler — see `peano_shape`, which derives the pair from the
83 /// declaration rather than hard-coding `Nat`, so a user's own unary numeral type works too.
84 Numeral(u64),
85}
86
87impl Pattern {
88 /// Every name this pattern binds, left to right.
89 pub fn binders(&self) -> Vec<String> {
90 let mut out = Vec::new();
91 self.collect_binders(&mut out);
92 out
93 }
94
95 fn collect_binders(&self, out: &mut Vec<String>) {
96 match self {
97 Pattern::Wildcard | Pattern::Numeral(_) => {}
98 Pattern::Var(n) => out.push(n.clone()),
99 Pattern::Ctor { args, .. } => {
100 for a in args {
101 a.collect_binders(out);
102 }
103 }
104 }
105 }
106
107 /// How this pattern reads back, for a diagnostic.
108 pub fn render(&self) -> String {
109 match self {
110 Pattern::Wildcard => "_".to_string(),
111 Pattern::Numeral(n) => n.to_string(),
112 Pattern::Var(n) => n.clone(),
113 Pattern::Ctor { name, args } if args.is_empty() => name.clone(),
114 Pattern::Ctor { name, args } => {
115 let rendered: Vec<String> = args
116 .iter()
117 .map(|a| match a {
118 Pattern::Ctor { args, .. } if !args.is_empty() => {
119 format!("({})", a.render())
120 }
121 _ => a.render(),
122 })
123 .collect();
124 format!("{name} {}", rendered.join(" "))
125 }
126 }
127 }
128}
129
130/// One equation: one pattern per column, and the body those patterns' variables are free in.
131#[derive(Clone, Debug)]
132pub struct Clause {
133 pub patterns: Vec<Pattern>,
134 pub body: Term,
135}
136
137/// What the compiler noticed on the way through, beyond the term itself.
138#[derive(Clone, Debug, Default, PartialEq, Eq)]
139pub struct CompileReport {
140 /// Clause indices no input can ever select, because an earlier clause already covers
141 /// everything they do. First-match semantics makes them dead rather than wrong, so this is
142 /// information for the surface to surface.
143 ///
144 /// ★ WHAT LEAN DOES WITH THE SAME FACT, and it is not what the previous sentence here
145 /// claimed. Lean does NOT quietly accept a redundant alternative: `Lean/Elab/Match.lean:999`
146 /// calls `logError "redundant alternative"` for every index in `result.unusedAltIdxs`, and
147 /// the run on this box confirms it — `def h : Hue → Bool | .ruby => true | .jade => false |
148 /// _ => true` answers `error: redundant alternative`. The error is non-fatal (`#eval h
149 /// Hue.ruby` still prints `true`, so the declaration is added), and
150 /// `Lean/Elab/Match.lean:985-988` declares the option that turns it off:
151 /// `match.ignoreUnusedAlts`, default `false`.
152 ///
153 /// So the MEANING is the same in both compilers and only the default severity differs: LOGOS
154 /// behaves as Lean does under `set_option match.ignoreUnusedAlts true`. What is still missing
155 /// is the report reaching a reader — `literate_parser::parse_consider` discards this census,
156 /// so a dead clause is silent rather than merely non-fatal. That needs a warning channel on
157 /// `Command`, which this module does not own.
158 pub unreachable_clauses: Vec<usize>,
159}
160
161/// Compile a clause matrix against a list of scrutinees.
162///
163/// `result_type` is the match's motive at every node: a raw type, which the kernel wraps as the
164/// constant motive `λ_:I. result_type`.
165///
166/// Returns the compiled term. Use [`compile_clauses_reporting`] when the caller wants the
167/// unreachable-clause census as well.
168pub fn compile_clauses(
169 ctx: &Context,
170 scrutinees: &[Term],
171 result_type: &Term,
172 clauses: &[Clause],
173) -> KernelResult<Term> {
174 compile_clauses_reporting(ctx, scrutinees, result_type, clauses).map(|(t, _)| t)
175}
176
177/// [`compile_clauses`], plus what the compiler noticed.
178pub fn compile_clauses_reporting(
179 ctx: &Context,
180 scrutinees: &[Term],
181 result_type: &Term,
182 clauses: &[Clause],
183) -> KernelResult<(Term, CompileReport)> {
184 if clauses.is_empty() {
185 return Err(KernelError::CertificationError(
186 "a pattern match needs at least one clause".to_string(),
187 ));
188 }
189 for (i, c) in clauses.iter().enumerate() {
190 if c.patterns.len() != scrutinees.len() {
191 return Err(KernelError::CertificationError(format!(
192 "clause {} has {} pattern(s) but the match has {} scrutinee(s) — every equation \
193 must cover the same columns",
194 i + 1,
195 c.patterns.len(),
196 scrutinees.len()
197 )));
198 }
199 // A name bound TWICE in one clause is refused, as Lean refuses it. It is not a harmless
200 // shadow: each field is bound by SUBSTITUTING the clause's binder, so the second
201 // occurrence would find nothing left to replace and the body would silently read the
202 // FIRST field for both — a well-typed term denoting a different function. (Writing
203 // `When Cons x x:` to mean "the two fields are equal" needs a decidable equality and a
204 // guard; that is a different feature, not this one by accident.)
205 let mut seen: Vec<String> = Vec::new();
206 for pat in &c.patterns {
207 for b in pat.binders() {
208 if seen.contains(&b) {
209 return Err(KernelError::CertificationError(format!(
210 "clause {} binds `{b}` more than once. A pattern variable names ONE \
211 field; repeating it would silently read the first field for both.",
212 i + 1
213 )));
214 }
215 seen.push(b);
216 }
217 }
218 }
219
220 let rows: Vec<Row> = clauses
221 .iter()
222 .enumerate()
223 .map(|(origin, c)| Row {
224 pats: c.patterns.clone(),
225 body: c.body.clone(),
226 origin,
227 })
228 .collect();
229
230 let mut taken: HashSet<String> = HashSet::new();
231 for s in scrutinees {
232 spelled_names(s, &mut taken);
233 }
234 spelled_names(result_type, &mut taken);
235 for c in clauses {
236 for pat in &c.patterns {
237 taken.extend(pat.binders());
238 }
239 spelled_names(&c.body, &mut taken);
240 }
241
242 let mut state = State {
243 fresh: 0,
244 reached: vec![false; clauses.len()],
245 taken,
246 };
247 let term = compile(ctx, scrutinees, result_type, &rows, &[], &mut state)?;
248 let report = CompileReport {
249 unreachable_clauses: state
250 .reached
251 .iter()
252 .enumerate()
253 .filter(|(_, r)| !**r)
254 .map(|(i, _)| i)
255 .collect(),
256 };
257 Ok((term, report))
258}
259
260/// The types a pattern's binders take when it is matched against a value of `disc_ty`.
261///
262/// This is the same constructor-telescope walk the compiler itself does, exposed so a parser
263/// can put the binders in scope with their real types BEFORE it parses the clause body. Two
264/// computations of one fact is how a compiler and its surface drift apart; there is one here.
265pub fn pattern_binder_types(
266 ctx: &Context,
267 disc_ty: &Term,
268 pattern: &Pattern,
269) -> KernelResult<Vec<(String, Term)>> {
270 let mut out = Vec::new();
271 walk_binder_types(ctx, disc_ty, pattern, &mut out)?;
272 Ok(out)
273}
274
275fn walk_binder_types(
276 ctx: &Context,
277 disc_ty: &Term,
278 pattern: &Pattern,
279 out: &mut Vec<(String, Term)>,
280) -> KernelResult<()> {
281 match pattern {
282 // A numeral names a closed value, so like `_` it introduces no binder.
283 Pattern::Wildcard | Pattern::Numeral(_) => Ok(()),
284 Pattern::Var(n) => {
285 out.push((n.clone(), disc_ty.clone()));
286 Ok(())
287 }
288 Pattern::Ctor { name, args } => {
289 let ty = normalize(ctx, disc_ty);
290 let (ind, type_args) = split_type_app(&ty).ok_or_else(|| {
291 KernelError::NotAnInductive(format!(
292 "`{}` cannot be matched against the constructor pattern `{}`",
293 ty,
294 pattern.render()
295 ))
296 })?;
297 let ctor_ty = resolve_constructor(ctx, &ind, name, pattern)?;
298 let params = &type_args[..ctx.inductive_num_params(&ind).min(type_args.len())];
299 let fields = constructor_field_types(&ctor_ty, params, &[]);
300 if fields.len() != args.len() {
301 return Err(arity_error(name, fields.len(), args.len()));
302 }
303 // A DEPENDENT constructor's later field types mention its earlier fields. Each
304 // earlier field's value is recoverable from the sub-pattern that matched it
305 // (`pattern_to_term`); a `_` names nothing, so the dependency is left standing —
306 // scope types are the parser's convenience, and `compile` is the authority.
307 let mut bound: Vec<(String, Term)> = Vec::new();
308 for (i, sub) in args.iter().enumerate() {
309 let (orig, mut fty) = fields[i].clone();
310 for (prev, value) in &bound {
311 fty = substitute(&fty, prev, value);
312 }
313 walk_binder_types(ctx, &fty, sub, out)?;
314 if let Some(value) = pattern_to_term(sub) {
315 bound.push((orig, value));
316 }
317 let _ = i;
318 }
319 Ok(())
320 }
321 }
322}
323
324/// A clause's right-hand side, read at the result type the equations declare.
325///
326/// This is the stage that first knows "this body must have type R", in the scope the body is
327/// actually written in — `ctx` here carries the case's field binders. The kernel's `Match` rule
328/// elaborates its cases against nothing, because a case is a λ-telescope whose expected type it
329/// would have to reconstruct from the constructor; the compiler already has the result type at
330/// every leaf. In Lean's terms this is `OfNat` and friends at the expected type: `| true => 1`
331/// in a function declared `→ Cel` is the `Cel` whose field is `1`.
332///
333/// Best-effort by design, in two directions, and both matter:
334///
335/// * a body that ALREADY fits is returned untouched, so nothing that type-checks today is
336/// re-shaped, and the common case costs one `infer_type` rather than a full elaboration;
337/// * a body elaboration cannot reconcile — most often one naming the function being defined,
338/// whose `fix` binder is tied after compilation — is handed on UNCHANGED, so the mismatch is
339/// reported by the kernel against the real expected type. Elaboration may CHOOSE a term; it may
340/// never change what one means.
341fn elaborate_leaf(ctx: &Context, result_type: &Term, body: &Term) -> Term {
342 if matches!(infer_type(ctx, body), Ok(native) if is_subtype(ctx, &native, result_type)) {
343 return body.clone();
344 }
345 surface_elaborate_against(ctx, body, Some(result_type)).unwrap_or_else(|_| body.clone())
346}
347
348/// The VALUE a pattern denotes, when it denotes one: a variable is itself, a constructor
349/// pattern is its constructor applied to its sub-patterns' values, and `_` denotes nothing.
350fn pattern_to_term(pattern: &Pattern) -> Option<Term> {
351 match pattern {
352 // A numeral's value is known but its SPELLING is not — the constructors it expands into
353 // belong to the column's inductive, which this walk does not carry. `compile` expands it
354 // before any case is built; here the dependency is simply left standing, which is what
355 // `None` already means for `_`.
356 Pattern::Wildcard | Pattern::Numeral(_) => None,
357 Pattern::Var(n) => Some(Term::Var(n.clone())),
358 Pattern::Ctor { name, args } => {
359 let mut out = Term::Global(name.clone());
360 for a in args {
361 out = Term::App(Box::new(out), Box::new(pattern_to_term(a)?));
362 }
363 Some(out)
364 }
365 }
366}
367
368// ============================================================
369// THE MATRIX
370// ============================================================
371
372#[derive(Clone, Debug)]
373struct Row {
374 pats: Vec<Pattern>,
375 body: Term,
376 origin: usize,
377}
378
379struct State {
380 fresh: usize,
381 reached: Vec<bool>,
382 /// Every name the input can spell: the clause set's binders and bodies, the scrutinees, the
383 /// result type, and — added as each node resolves its inductive — the constructor
384 /// telescopes' own parameter names. A generated binder is stepped until it is outside this
385 /// set, which is what makes the substitutions capture-free by CHECK rather than by an
386 /// assumption about the lexer.
387 taken: HashSet<String>,
388}
389
390impl State {
391 fn next_name(&mut self) -> String {
392 loop {
393 let candidate = format!("{FIELD_MARK}{}", self.fresh);
394 self.fresh += 1;
395 if !self.taken.contains(&candidate) {
396 return candidate;
397 }
398 }
399 }
400}
401
402/// One step of the constructor path taken to reach a leaf, for the missing-case diagnostic.
403#[derive(Clone)]
404struct Step {
405 ctor: String,
406 arity: usize,
407}
408
409fn compile(
410 ctx: &Context,
411 cols: &[Term],
412 result_type: &Term,
413 rows: &[Row],
414 trail: &[Step],
415 state: &mut State,
416) -> KernelResult<Term> {
417 // No row survives: nothing in the clause set covers this shape. A total language cannot
418 // admit a partial function, so this is where Lean's "missing cases" error lives.
419 let Some(first) = rows.first() else {
420 return Err(KernelError::CertificationError(format!(
421 "this definition is not exhaustive: no equation covers `{}`. A LOGOS definition is a \
422 TOTAL function, so every constructor of the type being matched needs a clause — add \
423 one, or write `When _:` for the rest.",
424 render_trail(trail)
425 )));
426 };
427
428 // Every column consumed: first match wins, and this row is the first.
429 if cols.is_empty() {
430 state.reached[first.origin] = true;
431 return Ok(elaborate_leaf(ctx, result_type, &first.body));
432 }
433
434 // The VARIABLE rule: no row inspects this column's constructor, so the column is just a
435 // binding. Bind it and move on — splitting here would be sound but would fan the tree out
436 // over a distinction no equation makes.
437 // A NUMERAL inspects its column exactly as a constructor pattern does — it IS one, once the
438 // inductive is known — so it must not fall through to the variable rule.
439 if rows
440 .iter()
441 .all(|r| !matches!(r.pats[0], Pattern::Ctor { .. } | Pattern::Numeral(_)))
442 {
443 let bound: Vec<Row> = rows
444 .iter()
445 .map(|r| {
446 let body = match &r.pats[0] {
447 Pattern::Var(v) => substitute(&r.body, v, &cols[0]),
448 _ => r.body.clone(),
449 };
450 Row {
451 pats: r.pats[1..].to_vec(),
452 body,
453 origin: r.origin,
454 }
455 })
456 .collect();
457 return compile(ctx, &cols[1..], result_type, &bound, trail, state);
458 }
459
460 // The CONSTRUCTOR rule: split this column over the inductive's constructors, in the
461 // inductive's OWN order.
462 let scrutinee = &cols[0];
463 let disc_ty = normalize(ctx, &infer_type(ctx, scrutinee)?);
464 let (ind, type_args) = split_type_app(&disc_ty).ok_or_else(|| {
465 KernelError::NotAnInductive(format!(
466 "`{disc_ty}` is not an inductive type, so it cannot be matched on"
467 ))
468 })?;
469 let constructors: Vec<(String, Term)> = ctx
470 .get_constructors(&ind)
471 .into_iter()
472 .map(|(n, t)| (n.to_string(), t.clone()))
473 .collect();
474 if constructors.is_empty() {
475 return Err(KernelError::NotAnInductive(format!(
476 "`{ind}` has no constructors, so there is nothing to match"
477 )));
478 }
479
480 // An INDEXED discriminant is refused rather than approximated. The kernel types an indexed
481 // match by a different rule (`infer_indexed_match`): the motive abstracts the indices as
482 // well as the scrutinee, and coverage is over the constructors the indices still ALLOW, not
483 // over all of them. A clause list carries no motive and cannot say which indices it means,
484 // so compiling one into a plain positional match would emit a tree whose case count is right
485 // by luck. Say so instead — an indexed elimination is written against `Term::Match` with the
486 // motive supplied.
487 let num_params = ctx.inductive_num_params(&ind);
488 if type_args.len() > num_params {
489 return Err(KernelError::CertificationError(format!(
490 "`{ind}` is an INDEXED inductive ({} parameter(s), {} index argument(s) here). An \
491 indexed elimination needs an explicit motive over its indices, which a clause list \
492 cannot express — write the `match` directly with its `return` clause.",
493 num_params,
494 type_args.len() - num_params
495 )));
496 }
497
498 // NUMERALS BECOME CONSTRUCTOR PATTERNS HERE, where the column's inductive is finally known —
499 // Lean's `expandNatValuePattern` (`Lean/Meta/Match/Match.lean:629`), at the same point in the
500 // same algorithm. Everything below this line sees only `Ctor`/`Var`/`Wildcard`.
501 let expanded: Vec<Row> = rows
502 .iter()
503 .map(|r| match &r.pats[0] {
504 Pattern::Numeral(n) => {
505 let head = numeral_pattern(ctx, &ind, *n)?;
506 let mut pats = vec![head];
507 pats.extend_from_slice(&r.pats[1..]);
508 Ok(Row {
509 pats,
510 body: r.body.clone(),
511 origin: r.origin,
512 })
513 }
514 _ => Ok(r.clone()),
515 })
516 .collect::<KernelResult<Vec<Row>>>()?;
517 let rows: &[Row] = &expanded;
518
519 // Reject a clause that names something that is not a constructor of THIS inductive before
520 // any slotting happens — mis-slotting a foreign name is exactly the silent wrong answer the
521 // positional case vector used to allow.
522 for row in rows {
523 if let Pattern::Ctor { name, args } = &row.pats[0] {
524 let ctor_ty = resolve_constructor(ctx, &ind, name, &row.pats[0])?;
525 let fields = constructor_field_types(&ctor_ty, &type_args, &[]);
526 if fields.len() != args.len() {
527 return Err(arity_error(name, fields.len(), args.len()));
528 }
529 }
530 }
531
532 // A constructor's own field names are substituted into its later field TYPES, so they are
533 // names this node can spell too — seeded before any binder is generated for it.
534 for (_, ctor_ty) in &constructors {
535 for (field, fty) in constructor_field_types(ctor_ty, &type_args, &[]) {
536 state.taken.insert(field);
537 spelled_names(&fty, &mut state.taken);
538 }
539 }
540
541 let mut cases = Vec::with_capacity(constructors.len());
542 for (ctor_name, ctor_ty) in &constructors {
543 let fields = constructor_field_types(ctor_ty, &type_args, &[]);
544
545 // One fresh binder per value field, with its type closed into this case's scope: a
546 // dependent field type naming an EARLIER field is rewritten to that field's binder.
547 // The context is extended lazily so a nullary constructor — most nodes in most trees —
548 // costs no clone at all.
549 let mut binders: Vec<(String, Term)> = Vec::new();
550 let mut extended: Option<Context> = None;
551 for (i, (_, fty)) in fields.iter().enumerate() {
552 let mut closed = fty.clone();
553 for j in 0..i {
554 closed = substitute(&closed, &fields[j].0, &Term::Var(binders[j].0.clone()));
555 }
556 let name = state.next_name();
557 let base = extended.as_ref().unwrap_or(ctx);
558 extended = Some(base.extend(&name, closed.clone()));
559 binders.push((name, closed));
560 }
561 let inner_ctx: &Context = extended.as_ref().unwrap_or(ctx);
562
563 // `C type_args… field_binders…` — the value this case's scrutinee is known to be, which
564 // is what a variable or catch-all head pattern gets bound to.
565 let mut ctor_value = Term::Global(ctor_name.clone());
566 for ta in &type_args {
567 ctor_value = Term::App(Box::new(ctor_value), Box::new(ta.clone()));
568 }
569 for (name, _) in &binders {
570 ctor_value = Term::App(Box::new(ctor_value), Box::new(Term::Var(name.clone())));
571 }
572
573 // Specialise: keep the rows this constructor can still reach, in their original order.
574 let mut specialised: Vec<Row> = Vec::new();
575 for row in rows {
576 match &row.pats[0] {
577 Pattern::Ctor { name, args } if name == ctor_name => {
578 let mut pats = args.clone();
579 pats.extend_from_slice(&row.pats[1..]);
580 specialised.push(Row { pats, body: row.body.clone(), origin: row.origin });
581 }
582 Pattern::Ctor { .. } => {}
583 Pattern::Wildcard => {
584 let mut pats = vec![Pattern::Wildcard; binders.len()];
585 pats.extend_from_slice(&row.pats[1..]);
586 specialised.push(Row { pats, body: row.body.clone(), origin: row.origin });
587 }
588 Pattern::Var(v) => {
589 let mut pats = vec![Pattern::Wildcard; binders.len()];
590 pats.extend_from_slice(&row.pats[1..]);
591 specialised.push(Row {
592 pats,
593 body: substitute(&row.body, v, &ctor_value),
594 origin: row.origin,
595 });
596 }
597 // Unreachable by construction — the head column's numerals were rewritten into
598 // constructor patterns the moment `ind` was known, a few lines above. Reported
599 // rather than `unreachable!()`d: a kernel surface answers, it does not abort, and
600 // a future edit that moves the expansion gets a sentence instead of a panic.
601 Pattern::Numeral(n) => {
602 return Err(KernelError::CertificationError(format!(
603 "the numeral pattern `{n}` reached case specialisation for `{ind}` without being expanded into its unary spelling"
604 )))
605 }
606 }
607 }
608
609 let mut sub_cols: Vec<Term> = binders.iter().map(|(n, _)| Term::Var(n.clone())).collect();
610 sub_cols.extend_from_slice(&cols[1..]);
611
612 let mut sub_trail = trail.to_vec();
613 sub_trail.push(Step {
614 ctor: ctor_name.clone(),
615 arity: binders.len(),
616 });
617
618 let mut case = compile(
619 inner_ctx,
620 &sub_cols,
621 result_type,
622 &specialised,
623 &sub_trail,
624 state,
625 )?;
626 for (name, ty) in binders.iter().rev() {
627 case = Term::Lambda {
628 param: name.clone(),
629 param_type: Box::new(ty.clone()),
630 body: Box::new(case),
631 };
632 }
633 cases.push(case);
634 }
635
636 Ok(Term::Match {
637 discriminant: Box::new(scrutinee.clone()),
638 motive: Box::new(constant_motive(disc_ty, result_type, state)),
639 cases,
640 })
641}
642
643/// The CANONICAL motive for a non-dependent node: `λ_:I. T`, never the bare `T`.
644///
645/// The kernel's `Match` rule accepts both — it wraps a `Sort`-typed motive as this very
646/// constant before it checks the branches — so from inside the kernel the two are the same
647/// term. They are not the same DECLARATION. Only the λ records which inductive the node
648/// eliminates, and that fact is unrecoverable from a stored tree: the discriminant a compiled
649/// node splits on is a `Var`, whose type needs the local telescope that a consumer walking the
650/// term does not have. Program extraction reads the motive to enumerate the constructors, and a
651/// bare result type left it with none — an arm-less `match n { }` the kernel accepts and no
652/// backend can emit. The compiler holds `disc_ty` at every node already (it cannot split the
653/// column without it), so the richer form costs nothing and is stated once, here, rather than
654/// re-derived differently by each consumer.
655///
656/// The binder is `_` — the spelling [`crate::elaborate::infer_match_motive`] gives the same
657/// motive — stepped to a generated name in the one case where the result type has a free
658/// variable actually called `_`, so the constant can never capture.
659fn constant_motive(disc_ty: Term, result_type: &Term, state: &mut State) -> Term {
660 let param = if crate::type_checker::occurs_free(result_type, "_") {
661 state.next_name()
662 } else {
663 "_".to_string()
664 };
665 Term::Lambda {
666 param,
667 param_type: Box::new(disc_ty),
668 body: Box::new(result_type.clone()),
669 }
670}
671
672// ============================================================
673// CONSTRUCTOR SHAPE
674// ============================================================
675
676/// Split a type application into its head inductive's name and its arguments.
677/// `List A` → `("List", [A])`; `Nat` → `("Nat", [])`.
678fn split_type_app(ty: &Term) -> Option<(String, Vec<Term>)> {
679 let mut args = Vec::new();
680 let mut cur = ty;
681 while let Term::App(f, a) = cur {
682 args.push((**a).clone());
683 cur = f;
684 }
685 args.reverse();
686 match cur {
687 Term::Global(n) => Some((n.clone(), args)),
688 _ => None,
689 }
690}
691
692/// A constructor's VALUE fields: its `Π` telescope past the inductive's parameters, with the
693/// parameters instantiated by the discriminant's own type arguments.
694///
695/// This mirrors `type_checker::compute_case_type`, which is what the kernel will check the
696/// emitted case against. `extra` lets a caller pre-substitute names already bound.
697fn constructor_field_types(
698 ctor_ty: &Term,
699 type_args: &[Term],
700 extra: &[(String, Term)],
701) -> Vec<(String, Term)> {
702 let mut all: Vec<(String, Term)> = Vec::new();
703 let mut cur = ctor_ty;
704 while let Term::Pi {
705 param,
706 param_type,
707 body_type,
708 } = cur
709 {
710 all.push((param.clone(), (**param_type).clone()));
711 cur = body_type;
712 }
713 let split = type_args.len().min(all.len());
714 let params: Vec<String> = all[..split].iter().map(|(n, _)| n.clone()).collect();
715 all[split..]
716 .iter()
717 .map(|(n, t)| {
718 let mut ty = t.clone();
719 for (p, a) in params.iter().zip(type_args.iter()) {
720 if p != "_" {
721 ty = substitute(&ty, p, a);
722 }
723 }
724 for (n2, t2) in extra {
725 ty = substitute(&ty, n2, t2);
726 }
727 (n.clone(), ty)
728 })
729 .collect()
730}
731
732/// The unary spelling of `n` in `ind` — `2` over `Nat` is `Succ (Succ Zero)`.
733fn numeral_pattern(ctx: &Context, ind: &str, n: u64) -> KernelResult<Pattern> {
734 let (zero, succ) = peano_shape(ctx, ind)?;
735 let mut out = Pattern::Ctor {
736 name: zero,
737 args: Vec::new(),
738 };
739 for _ in 0..n {
740 out = Pattern::Ctor {
741 name: succ.clone(),
742 args: vec![out],
743 };
744 }
745 Ok(out)
746}
747
748/// The two constructors a numeral needs: the one with NO value fields, and the one with exactly
749/// ONE field of the inductive's own type.
750///
751/// Lean hard-codes `Nat.zero`/`Nat.succ` (`Lean/Meta/Match/Match.lean:629`). Deriving the pair
752/// from the DECLARATION costs nothing and is strictly more: a user who writes
753/// `A Tally is either Nought or a Tick with prev: Tally.` gets numeral patterns for free, and a
754/// type that is not unary-numeral-shaped is told so by name instead of silently mis-expanding
755/// into constructors that happen to be called `Zero` and `Succ`.
756fn peano_shape(ctx: &Context, ind: &str) -> KernelResult<(String, String)> {
757 let mut zero: Option<String> = None;
758 let mut succ: Option<String> = None;
759 let ctors = ctx.get_constructors(ind);
760 let shape_error = |why: &str| {
761 KernelError::CertificationError(format!(
762 "a numeral pattern is the unary spelling of a number, and `{ind}` has no unary numeral form: {why}. Write the constructors out, or match on a type with one field-free constructor and one that carries a single `{ind}`."
763 ))
764 };
765 for (name, ty) in &ctors {
766 let fields = constructor_field_types(ty, &[], &[]);
767 match fields.len() {
768 0 if zero.replace((*name).to_string()).is_none() => {}
769 1 if matches!(&fields[0].1, Term::Global(g) if g == ind)
770 && succ.replace((*name).to_string()).is_none() => {}
771 _ => {
772 return Err(shape_error(&format!(
773 "`{name}` takes {} field(s) and cannot be a zero or a successor",
774 fields.len()
775 )))
776 }
777 }
778 }
779 match (zero, succ) {
780 (Some(z), Some(s)) => Ok((z, s)),
781 (None, _) => Err(shape_error(
782 "it has no field-free constructor to be the zero",
783 )),
784 (_, None) => Err(shape_error(
785 "it has no constructor carrying a single value of itself",
786 )),
787 }
788}
789
790fn resolve_constructor(
791 ctx: &Context,
792 ind: &str,
793 name: &str,
794 pattern: &Pattern,
795) -> KernelResult<Term> {
796 match ctx.constructor_inductive(name) {
797 Some(owner) if owner == ind => Ok(ctx
798 .get_global(name)
799 .cloned()
800 .expect("a registered constructor always has a type")),
801 Some(owner) => Err(KernelError::CertificationError(format!(
802 "`{name}` is a constructor of `{owner}`, not of `{ind}` — the pattern `{}` cannot \
803 match a value of `{ind}`",
804 pattern.render()
805 ))),
806 None => Err(KernelError::CertificationError(format!(
807 "`{name}` is not a constructor of `{ind}`. Its constructors are: {}.",
808 ctx.get_constructors(ind)
809 .iter()
810 .map(|(n, _)| *n)
811 .collect::<Vec<_>>()
812 .join(", ")
813 ))),
814 }
815}
816
817fn arity_error(name: &str, expected: usize, found: usize) -> KernelError {
818 KernelError::CertificationError(format!(
819 "the constructor `{name}` takes {expected} field(s), but the pattern gives it {found}"
820 ))
821}
822
823/// The shape no clause covered, as a pattern a reader can act on.
824fn render_trail(trail: &[Step]) -> String {
825 let mut acc: Option<String> = None;
826 for step in trail.iter().rev() {
827 let mut parts: Vec<String> = Vec::new();
828 for i in 0..step.arity {
829 match (i, &acc) {
830 (0, Some(inner)) if inner.contains(' ') => parts.push(format!("({inner})")),
831 (0, Some(inner)) => parts.push(inner.clone()),
832 _ => parts.push("_".to_string()),
833 }
834 }
835 acc = Some(if parts.is_empty() {
836 step.ctor.clone()
837 } else {
838 format!("{} {}", step.ctor, parts.join(" "))
839 });
840 }
841 acc.unwrap_or_else(|| "_".to_string())
842}