Skip to main content

logicaffeine_kernel/
elaborate.rs

1//! R4 — the elaborator: metavariables, unification, and implicit-argument inference.
2//!
3//! The layer between the surface language and the trusted kernel. A user writes `id 0`
4//! and means `id Nat 0`; `length xs` and means `length Nat xs`. The elaborator fills the
5//! gaps: it inserts METAVARIABLES (`?A`, …) for the omitted/implicit arguments and SOLVES
6//! them by UNIFICATION against the types it infers, producing a fully-explicit kernel
7//! `Term` that `infer_type` then re-checks. Nothing here is trusted — elaboration only
8//! *constructs* a term; the kernel still certifies it.
9//!
10//! A metavariable is represented as a `Term::Var` whose name starts with `?` (a character
11//! no real binder uses), so the whole machinery rides on the existing `Term` with no new
12//! variant. Scope: first-order unification with the occurs-check, and implicit-argument
13//! insertion driven by an explicit implicit/explicit mask — the core that makes the
14//! surface usable. (Higher-order / pattern unification is the next layer.)
15
16use std::collections::HashMap;
17
18use crate::error::{KernelError, KernelResult};
19use crate::term::Term;
20use crate::type_checker::substitute;
21use crate::{infer_type, normalize, Context};
22
23/// Reserved head marking a surface anonymous constructor `⟨e₁, …, eₙ⟩` (E3). The parser
24/// emits `⟨anon⟩ e₁ … eₙ`; the elaborator (this module) rewrites it to the sole constructor
25/// of the expected inductive and then discards the marker — it NEVER reaches a kernel term.
26/// The name contains `⟨`/`⟩`, characters the identifier lexer cannot produce, so it can
27/// never collide with a user global. Like the `?`-prefixed metavariables above, this rides
28/// on the existing `Term` with no new variant.
29pub const ANON_CTOR_MARKER: &str = "⟨anon⟩";
30
31/// Reserved head marking surface dot notation `receiver.field` (E4). The parser emits
32/// `⟨proj⟩ receiver field` (the field name carried as a `Global`); the elaborator rewrites it
33/// to the projection `H_field params… receiver` and discards the marker. Collision-proof for
34/// the same reason as [`ANON_CTOR_MARKER`].
35pub const DOT_MARKER: &str = "⟨proj⟩";
36
37/// A surface-sugar application recognized by its reserved marker head.
38enum Sugar<'a> {
39    /// `⟨e₁, …, eₙ⟩` — an anonymous constructor with these component terms.
40    AnonCtor(Vec<&'a Term>),
41    /// `receiver.field` — a projection.
42    Dot(&'a Term, &'a str),
43}
44
45/// Recognize a marker-headed surface-sugar application (anonymous constructor or dot
46/// notation), returning its shape, or `None` for an ordinary term. Decomposing the spine
47/// here means a bare nullary `⟨⟩` (just the marker global) and a nested one are both caught.
48fn as_surface_sugar(term: &Term) -> Option<Sugar<'_>> {
49    // Fast path (the common case): walk to the spine head WITHOUT allocating and bail unless
50    // it is a marker — so an ordinary term costs only a pointer chase per `elaborate_in` call.
51    let mut head = term;
52    while let Term::App(f, _) = head {
53        head = f;
54    }
55    let is_anon = matches!(head, Term::Global(n) if n == ANON_CTOR_MARKER);
56    let is_dot = matches!(head, Term::Global(n) if n == DOT_MARKER);
57    if !is_anon && !is_dot {
58        return None;
59    }
60    // A marker head — now decompose the (rare) spine.
61    let mut args: Vec<&Term> = Vec::new();
62    let mut cur = term;
63    while let Term::App(f, a) = cur {
64        args.push(a);
65        cur = f;
66    }
67    args.reverse();
68    if is_anon {
69        Some(Sugar::AnonCtor(args))
70    } else if let [_, Term::Global(field)] = args.as_slice() {
71        Some(Sugar::Dot(args[0], field))
72    } else {
73        None
74    }
75}
76
77/// Elaborate a marker-headed surface-sugar term to a fully-explicit kernel term paired with
78/// its inferred type. Shared by [`elaborate_in`] (so NESTED sugar inside a field or receiver
79/// resolves with its expected type) and [`elab_surface`] (the top-level surface entry).
80fn elaborate_sugar(
81    ctx: &Context,
82    mctx: &mut MetaCtx,
83    sugar: Sugar<'_>,
84    expected: Option<&Term>,
85) -> KernelResult<(Term, Term)> {
86    let result = match sugar {
87        Sugar::AnonCtor(comps) => {
88            let expected = expected.ok_or_else(|| {
89                KernelError::CertificationError(
90                    "anonymous constructor `⟨…⟩` needs a known expected type to choose its \
91                     inductive — annotate it or use it where a type is expected"
92                        .to_string(),
93                )
94            })?;
95            let comps: Vec<Term> = comps.into_iter().cloned().collect();
96            elaborate_anon_ctor(ctx, mctx, expected, &comps)?
97        }
98        Sugar::Dot(receiver, field) => elaborate_dot(ctx, mctx, receiver, field)?,
99    };
100    let ty = infer_type(ctx, &result)?;
101    Ok((result, ty))
102}
103
104/// The metavariable context: fresh-name supply + the substitution being solved.
105#[derive(Debug, Default, Clone)]
106pub struct MetaCtx {
107    solutions: HashMap<String, Term>,
108    counter: usize,
109}
110
111impl MetaCtx {
112    pub fn new() -> Self {
113        MetaCtx::default()
114    }
115
116    /// A fresh, unsolved metavariable `?n`.
117    pub fn fresh(&mut self) -> Term {
118        let m = Term::Var(format!("?{}", self.counter));
119        self.counter += 1;
120        m
121    }
122
123    /// The solution recorded for metavariable `name`, if any.
124    pub fn solution(&self, name: &str) -> Option<&Term> {
125        self.solutions.get(name)
126    }
127
128    /// Directly bind metavariable `name := term`, WITHOUT normalizing `term`. Used when
129    /// the term is already known well-typed and should be kept structured (e.g. a resolved
130    /// typeclass instance `list_inst Nat (mk Nat Zero)`, which must not be δ-unfolded into
131    /// its body). The kernel re-checks the assembled term regardless.
132    pub fn solve(&mut self, name: &str, term: Term) {
133        self.solutions.insert(name.to_string(), term);
134    }
135}
136
137/// Whether `name` denotes a metavariable (the `?`-prefix convention).
138pub fn is_meta(name: &str) -> bool {
139    name.starts_with('?')
140}
141
142/// Substitute every solved metavariable throughout `term` (transitively, since a solution
143/// may itself mention other metavariables — the occurs-check keeps this terminating).
144pub fn instantiate(term: &Term, mctx: &MetaCtx) -> Term {
145    match term {
146        Term::Var(name) if is_meta(name) => match mctx.solutions.get(name) {
147            Some(sol) => instantiate(sol, mctx),
148            None => term.clone(),
149        },
150        Term::Var(_) | Term::Global(_) | Term::Sort(_) | Term::Lit(_) | Term::Hole => term.clone(),
151        Term::Const { name, levels } => {
152            Term::Const { name: name.clone(), levels: levels.clone() }
153        }
154        Term::Pi { param, param_type, body_type } => Term::Pi {
155            param: param.clone(),
156            param_type: Box::new(instantiate(param_type, mctx)),
157            body_type: Box::new(instantiate(body_type, mctx)),
158        },
159        Term::Lambda { param, param_type, body } => Term::Lambda {
160            param: param.clone(),
161            param_type: Box::new(instantiate(param_type, mctx)),
162            body: Box::new(instantiate(body, mctx)),
163        },
164        Term::App(f, a) => {
165            Term::App(Box::new(instantiate(f, mctx)), Box::new(instantiate(a, mctx)))
166        }
167        Term::Match { discriminant, motive, cases } => Term::Match {
168            discriminant: Box::new(instantiate(discriminant, mctx)),
169            motive: Box::new(instantiate(motive, mctx)),
170            cases: cases.iter().map(|c| instantiate(c, mctx)).collect(),
171        },
172        Term::Fix { name, body } => {
173            Term::Fix { name: name.clone(), body: Box::new(instantiate(body, mctx)) }
174        }
175        Term::MutualFix { defs, index } => Term::MutualFix {
176            defs: defs.iter().map(|(n, b)| (n.clone(), instantiate(b, mctx))).collect(),
177            index: *index,
178        },
179        Term::Let { name, ty, value, body } => Term::Let {
180            name: name.clone(),
181            ty: Box::new(instantiate(ty, mctx)),
182            value: Box::new(instantiate(value, mctx)),
183            body: Box::new(instantiate(body, mctx)),
184        },
185    }
186}
187
188/// Whether the metavariable `m` occurs in `term` — the occurs-check that keeps the
189/// solution acyclic (`?m := f ?m` would loop).
190fn occurs(m: &str, term: &Term) -> bool {
191    match term {
192        Term::Var(name) => name == m,
193        Term::Global(_) | Term::Sort(_) | Term::Lit(_) | Term::Hole | Term::Const { .. } => false,
194        Term::Pi { param_type, body_type, .. } => occurs(m, param_type) || occurs(m, body_type),
195        Term::Lambda { param_type, body, .. } => occurs(m, param_type) || occurs(m, body),
196        Term::App(f, a) => occurs(m, f) || occurs(m, a),
197        Term::Match { discriminant, motive, cases } => {
198            occurs(m, discriminant) || occurs(m, motive) || cases.iter().any(|c| occurs(m, c))
199        }
200        Term::Fix { body, .. } => occurs(m, body),
201        Term::MutualFix { defs, .. } => defs.iter().any(|(_, b)| occurs(m, b)),
202        Term::Let { ty, value, body, .. } => {
203            occurs(m, ty) || occurs(m, value) || occurs(m, body)
204        }
205    }
206}
207
208/// `instantiate` then weak-head-normalize — the form unification compares.
209fn resolve(ctx: &Context, mctx: &MetaCtx, t: &Term) -> Term {
210    normalize(ctx, &instantiate(t, mctx))
211}
212
213/// First-order unification of `a` and `b`, solving metavariables into `mctx`. Returns
214/// whether they were made equal. A metavariable unifies with anything (after the
215/// occurs-check); everything else decomposes structurally.
216pub fn unify(ctx: &Context, mctx: &mut MetaCtx, a: &Term, b: &Term) -> bool {
217    unify_in(ctx, mctx, &[], a, b)
218}
219
220/// Unify `a` and `b` under a LOCAL CONTEXT `lctx` (`(name, type)` of the bound variables
221/// in scope). The local context enables higher-order PATTERN (Miller) unification:
222/// `?M x̄ =?= t`, where `?M` is a metavariable applied to distinct bound variables `x̄`,
223/// is solved by `?M := λx̄. t`. The top-level [`unify`] runs with an empty context, so its
224/// behavior — and every existing caller — is the first-order one.
225pub fn unify_in(
226    ctx: &Context,
227    mctx: &mut MetaCtx,
228    lctx: &[(String, Term)],
229    a: &Term,
230    b: &Term,
231) -> bool {
232    let a = resolve(ctx, mctx, a);
233    let b = resolve(ctx, mctx, b);
234
235    if let Term::Var(n) = &a {
236        if is_meta(n) {
237            return assign(ctx, mctx, n, &b);
238        }
239    }
240    if let Term::Var(n) = &b {
241        if is_meta(n) {
242            return assign(ctx, mctx, n, &a);
243        }
244    }
245
246    // Higher-order PATTERN unification: `?M x̄ =?= t` ⇒ `?M := λx̄. t`.
247    if let Some(result) = try_pattern(mctx, lctx, &a, &b) {
248        return result;
249    }
250    if let Some(result) = try_pattern(mctx, lctx, &b, &a) {
251        return result;
252    }
253
254    // η-unification: `λx:A. body =?= t` (with `t` not a λ) ⇒ `body =?= t x` under the
255    // binder, and symmetrically — so a function and its η-expansion unify.
256    match (&a, &b) {
257        (Term::Lambda { param, param_type, body }, other)
258        | (other, Term::Lambda { param, param_type, body })
259            if !matches!(other, Term::Lambda { .. }) =>
260        {
261            let applied = Term::App(Box::new(other.clone()), Box::new(Term::Var(param.clone())));
262            let mut lctx2 = lctx.to_vec();
263            lctx2.push((param.clone(), (**param_type).clone()));
264            return unify_in(ctx, mctx, &lctx2, body, &applied);
265        }
266        _ => {}
267    }
268
269    match (&a, &b) {
270        (Term::Sort(u), Term::Sort(v)) => u.equiv(v),
271        (Term::Global(x), Term::Global(y)) => x == y,
272        (Term::Var(x), Term::Var(y)) => x == y,
273        (Term::Lit(x), Term::Lit(y)) => x == y,
274        (Term::Hole, Term::Hole) => true,
275        (Term::App(f1, a1), Term::App(f2, a2)) => {
276            unify_in(ctx, mctx, lctx, f1, f2) && unify_in(ctx, mctx, lctx, a1, a2)
277        }
278        (
279            Term::Pi { param: p1, param_type: t1, body_type: b1 },
280            Term::Pi { param: p2, param_type: t2, body_type: b2 },
281        ) => unify_binder(ctx, mctx, lctx, t1, b1, p1, t2, b2, p2),
282        (
283            Term::Lambda { param: p1, param_type: t1, body: b1 },
284            Term::Lambda { param: p2, param_type: t2, body: b2 },
285        ) => unify_binder(ctx, mctx, lctx, t1, b1, p1, t2, b2, p2),
286        (
287            Term::Const { name: n1, levels: l1 },
288            Term::Const { name: n2, levels: l2 },
289        ) => n1 == n2 && l1.len() == l2.len() && l1.iter().zip(l2.iter()).all(|(x, y)| x.equiv(y)),
290        _ => false,
291    }
292}
293
294/// Try to solve a Miller pattern `a = ?M x̄ =?= b`. Returns `None` if `a` is not a pattern
295/// (the caller falls through to structural unification), `Some(true)` if solved, and
296/// `Some(false)` if it IS a pattern but unsolvable (occurs-check / an out-of-scope
297/// variable on the right). The arguments `x̄` must be DISTINCT bound variables of `lctx`,
298/// and `b` may mention only those variables (plus globals/metavariables).
299fn try_pattern(
300    mctx: &mut MetaCtx,
301    lctx: &[(String, Term)],
302    a: &Term,
303    b: &Term,
304) -> Option<bool> {
305    let (head, args) = spine(a);
306    if args.is_empty() {
307        return None;
308    }
309    let meta = match &head {
310        Term::Var(m) if is_meta(m) && mctx.solution(m).is_none() => m.clone(),
311        _ => return None,
312    };
313    // Every argument must be a distinct bound variable of the local context.
314    let mut var_names: Vec<String> = Vec::new();
315    for arg in &args {
316        match arg {
317            Term::Var(v) if !is_meta(v) && lctx.iter().any(|(n, _)| n == v) => {
318                if var_names.iter().any(|u| u == v) {
319                    return None; // a repeated argument — not a pattern
320                }
321                var_names.push(v.clone());
322            }
323            _ => return None, // a non-variable argument — not a pattern
324        }
325    }
326    // It IS a pattern; now decide whether it is solvable.
327    if occurs(&meta, b) {
328        return Some(false); // `?M` occurs in `t` — cyclic
329    }
330    if !pattern_rhs_in_scope(b, &var_names, &mut Vec::new()) {
331        return Some(false); // `t` mentions a variable we cannot abstract
332    }
333    // Solve `?M := λx̄. t`, taking each binder's type from the local context.
334    let mut sol = b.clone();
335    for name in var_names.iter().rev() {
336        let ty = lctx
337            .iter()
338            .find(|(n, _)| n == name)
339            .map(|(_, t)| t.clone())
340            .unwrap_or(Term::Hole);
341        sol = Term::Lambda { param: name.clone(), param_type: Box::new(ty), body: Box::new(sol) };
342    }
343    mctx.solve(&meta, sol);
344    Some(true)
345}
346
347/// Whether every FREE variable of `t` (tracking binders in `bound`) is either a
348/// metavariable, bound inside `t`, or one of the `allowed` pattern arguments — the
349/// condition for `λallowed. t` to be well-scoped.
350fn pattern_rhs_in_scope(t: &Term, allowed: &[String], bound: &mut Vec<String>) -> bool {
351    match t {
352        Term::Var(v) => is_meta(v) || bound.iter().any(|b| b == v) || allowed.iter().any(|a| a == v),
353        Term::Sort(_) | Term::Global(_) | Term::Lit(_) | Term::Hole | Term::Const { .. } => true,
354        Term::App(f, a) => {
355            pattern_rhs_in_scope(f, allowed, bound) && pattern_rhs_in_scope(a, allowed, bound)
356        }
357        Term::Pi { param, param_type, body_type } => {
358            if !pattern_rhs_in_scope(param_type, allowed, bound) {
359                return false;
360            }
361            bound.push(param.clone());
362            let ok = pattern_rhs_in_scope(body_type, allowed, bound);
363            bound.pop();
364            ok
365        }
366        Term::Lambda { param, param_type, body } => {
367            if !pattern_rhs_in_scope(param_type, allowed, bound) {
368                return false;
369            }
370            bound.push(param.clone());
371            let ok = pattern_rhs_in_scope(body, allowed, bound);
372            bound.pop();
373            ok
374        }
375        Term::Fix { name, body } => {
376            bound.push(name.clone());
377            let ok = pattern_rhs_in_scope(body, allowed, bound);
378            bound.pop();
379            ok
380        }
381        Term::MutualFix { defs, .. } => {
382            for (n, _) in defs {
383                bound.push(n.clone());
384            }
385            let ok = defs.iter().all(|(_, b)| pattern_rhs_in_scope(b, allowed, bound));
386            for _ in defs {
387                bound.pop();
388            }
389            ok
390        }
391        Term::Match { discriminant, motive, cases } => {
392            pattern_rhs_in_scope(discriminant, allowed, bound)
393                && pattern_rhs_in_scope(motive, allowed, bound)
394                && cases.iter().all(|c| pattern_rhs_in_scope(c, allowed, bound))
395        }
396        Term::Let { name, ty, value, body } => {
397            if !pattern_rhs_in_scope(ty, allowed, bound)
398                || !pattern_rhs_in_scope(value, allowed, bound)
399            {
400                return false;
401            }
402            bound.push(name.clone());
403            let ok = pattern_rhs_in_scope(body, allowed, bound);
404            bound.pop();
405            ok
406        }
407    }
408}
409
410/// Decompose an application spine `head a0 a1 …` into its head and arguments (in order).
411fn spine(t: &Term) -> (Term, Vec<Term>) {
412    let mut args = Vec::new();
413    let mut cur = t;
414    while let Term::App(f, a) = cur {
415        args.push((**a).clone());
416        cur = f;
417    }
418    args.reverse();
419    (cur.clone(), args)
420}
421
422/// Unify two binders (`Π`/`λ`): their domains, then their bodies α-renamed to a common
423/// binder name — under a local context EXTENDED with that binder, so pattern unification
424/// can fire on metavariables applied to it.
425#[allow(clippy::too_many_arguments)]
426fn unify_binder(
427    ctx: &Context,
428    mctx: &mut MetaCtx,
429    lctx: &[(String, Term)],
430    dom1: &Term,
431    body1: &Term,
432    p1: &str,
433    dom2: &Term,
434    body2: &Term,
435    p2: &str,
436) -> bool {
437    if !unify_in(ctx, mctx, lctx, dom1, dom2) {
438        return false;
439    }
440    let body2 = if p1 == p2 {
441        body2.clone()
442    } else {
443        substitute(body2, p2, &Term::Var(p1.to_string()))
444    };
445    let mut ext = lctx.to_vec();
446    ext.push((p1.to_string(), dom1.clone()));
447    unify_in(ctx, mctx, &ext, body1, &body2)
448}
449
450/// Solve metavariable `m := t` (or, if already solved, unify the existing solution with
451/// `t`). The occurs-check rejects a cyclic solution.
452fn assign(ctx: &Context, mctx: &mut MetaCtx, m: &str, t: &Term) -> bool {
453    if let Some(sol) = mctx.solutions.get(m).cloned() {
454        return unify(ctx, mctx, &sol, t);
455    }
456    let t = instantiate(t, mctx);
457    // `?m := ?m` is trivially satisfied; a deeper occurrence is a cycle.
458    if matches!(&t, Term::Var(n) if n == m) {
459        return true;
460    }
461    if occurs(m, &t) {
462        return false;
463    }
464    mctx.solutions.insert(m.to_string(), t);
465    true
466}
467
468/// Elaborate `term` (which may contain `Hole`s) against an optional `expected` type,
469/// solving the holes by unification. Returns the elaborated term and its type (both still
470/// containing metavariables until `instantiate`d).
471pub fn elaborate(
472    ctx: &Context,
473    mctx: &mut MetaCtx,
474    term: &Term,
475    expected: Option<&Term>,
476) -> KernelResult<(Term, Term)> {
477    elaborate_in(ctx, mctx, &[], term, expected)
478}
479
480/// Like [`elaborate`] but under a LOCAL CONTEXT `lctx` of bound variables. `ctx` is the
481/// kernel context already EXTENDED with those same variables (so `infer_type` sees them
482/// at the leaves), while `lctx` is the parallel `(name, type)` list the unifier uses for
483/// higher-order PATTERN unification. Descending under a `λ` extends both — which is how a
484/// metavariable applied to a bound variable (a motive `?P n`) gets solved in a body.
485pub fn elaborate_in(
486    ctx: &Context,
487    mctx: &mut MetaCtx,
488    lctx: &[(String, Term)],
489    term: &Term,
490    expected: Option<&Term>,
491) -> KernelResult<(Term, Term)> {
492    // Surface sugar `⟨…⟩` / `receiver.field` is dispatched FIRST, so a nested occurrence in a
493    // field or receiver resolves here too — with the expected type flowing in from the
494    // enclosing constructor domain (`⟨⟨a, b⟩, c⟩`) or projection.
495    if let Some(sugar) = as_surface_sugar(term) {
496        return elaborate_sugar(ctx, mctx, sugar, expected);
497    }
498    match term {
499        Term::Hole => {
500            let m = mctx.fresh();
501            let ty = expected.cloned().unwrap_or_else(|| mctx.fresh());
502            Ok((m, ty))
503        }
504        Term::App(f, a) => {
505            let (f_elab, f_ty) = elaborate_in(ctx, mctx, lctx, f, None)?;
506            let f_ty = resolve(ctx, mctx, &f_ty);
507            match f_ty {
508                Term::Pi { param, param_type, body_type } => {
509                    let (a_elab, a_ty) = elaborate_in(ctx, mctx, lctx, a, Some(&param_type))?;
510                    if !unify_in(ctx, mctx, lctx, &a_ty, &param_type) {
511                        return Err(KernelError::TypeMismatch {
512                            expected: format!("{}", instantiate(&param_type, mctx)),
513                            found: format!("{}", instantiate(&a_ty, mctx)),
514                        });
515                    }
516                    let result_ty = substitute(&body_type, &param, &a_elab);
517                    // Reconcile the result with the expected type — this is where a motive
518                    // `?P n` is solved against the application's actual type `Vec n`.
519                    if let Some(exp) = expected {
520                        unify_in(ctx, mctx, lctx, &result_ty, exp);
521                    }
522                    Ok((Term::App(Box::new(f_elab), Box::new(a_elab)), result_ty))
523                }
524                other => Err(KernelError::NotAFunction(format!("{}", other))),
525            }
526        }
527        Term::Lambda { param, param_type, body } => {
528            // Descend under the binder, extending BOTH the kernel context (for leaf type
529            // inference) and the unification context (for pattern unification). The body's
530            // expected type is the expected `Π`'s codomain, α-renamed to this binder.
531            let body_expected = match expected.map(|e| resolve(ctx, mctx, e)) {
532                Some(Term::Pi { param: ep, body_type: ecod, .. }) => Some(if ep == *param {
533                    (*ecod).clone()
534                } else {
535                    substitute(&ecod, &ep, &Term::Var(param.clone()))
536                }),
537                _ => None,
538            };
539            let ext_ctx = ctx.extend(param, (**param_type).clone());
540            let mut ext_lctx = lctx.to_vec();
541            ext_lctx.push((param.clone(), (**param_type).clone()));
542            let (body_elab, body_ty) =
543                elaborate_in(&ext_ctx, mctx, &ext_lctx, body, body_expected.as_ref())?;
544            Ok((
545                Term::Lambda {
546                    param: param.clone(),
547                    param_type: param_type.clone(),
548                    body: Box::new(body_elab),
549                },
550                Term::Pi {
551                    param: param.clone(),
552                    param_type: param_type.clone(),
553                    body_type: Box::new(body_ty),
554                },
555            ))
556        }
557        _ => {
558            // A leaf (hole-free): defer to the kernel for its type (in the extended
559            // context, so bound variables resolve), then reconcile with the expected type
560            // via unification (which may solve metavariables on either side, including
561            // higher-order patterns over the local context).
562            let ty = infer_type(ctx, term)?;
563            if let Some(exp) = expected {
564                unify_in(ctx, mctx, lctx, &ty, exp);
565            }
566            Ok((term.clone(), ty))
567        }
568    }
569}
570
571/// Fill in inferred motives for `match` expressions written WITHOUT a `return` clause
572/// (the `Hole` motive the parser leaves). The motive is a CONSTANT `λ_:I. T` — covering
573/// non-dependent matches — where `T` is the EXPECTED type (a definition's declared
574/// result type, propagated through binders) or, lacking one, the type of a nullary first
575/// branch. The pass threads the kernel context through binders so the discriminant's type
576/// resolves; a `match` it cannot give a motive (a dependent case, no expected type) is
577/// reported so the user can add an explicit `return`.
578pub fn fill_match_motives(
579    ctx: &Context,
580    term: &Term,
581    expected: Option<&Term>,
582) -> KernelResult<Term> {
583    match term {
584        Term::Match { discriminant, motive, cases } => {
585            let disc = fill_match_motives(ctx, discriminant, None)?;
586            let cases = cases
587                .iter()
588                .map(|c| fill_match_motives(ctx, c, None))
589                .collect::<KernelResult<Vec<_>>>()?;
590            let motive = if matches!(motive.as_ref(), Term::Hole) {
591                infer_match_motive(ctx, &disc, &cases, expected)?
592            } else {
593                fill_match_motives(ctx, motive, None)?
594            };
595            Ok(Term::Match {
596                discriminant: Box::new(disc),
597                motive: Box::new(motive),
598                cases,
599            })
600        }
601        Term::Lambda { param, param_type, body } => {
602            let ext = ctx.extend(param, (**param_type).clone());
603            // The body's expected type is the codomain of the expected `Π`, α-renamed.
604            let body_expected = match expected.map(|e| normalize(ctx, e)) {
605                Some(Term::Pi { param: ep, body_type, .. }) => Some(if ep == *param {
606                    *body_type
607                } else {
608                    substitute(&body_type, &ep, &Term::Var(param.clone()))
609                }),
610                _ => None,
611            };
612            Ok(Term::Lambda {
613                param: param.clone(),
614                param_type: param_type.clone(),
615                body: Box::new(fill_match_motives(&ext, body, body_expected.as_ref())?),
616            })
617        }
618        Term::App(f, a) => Ok(Term::App(
619            Box::new(fill_match_motives(ctx, f, None)?),
620            Box::new(fill_match_motives(ctx, a, None)?),
621        )),
622        Term::Pi { param, param_type, body_type } => {
623            let ext = ctx.extend(param, (**param_type).clone());
624            Ok(Term::Pi {
625                param: param.clone(),
626                param_type: Box::new(fill_match_motives(ctx, param_type, None)?),
627                body_type: Box::new(fill_match_motives(&ext, body_type, None)?),
628            })
629        }
630        Term::Fix { name, body } => Ok(Term::Fix {
631            name: name.clone(),
632            body: Box::new(fill_match_motives(ctx, body, None)?),
633        }),
634        _ => Ok(term.clone()),
635    }
636}
637
638/// Build the motive `λx:I. T[disc := x]` for a `match` written without a `return` clause,
639/// by ABSTRACTING the discriminant out of the expected type — the Miller-pattern solution
640/// of `?P disc =?= T`. When `T` mentions the discriminant the motive is DEPENDENT (so a
641/// match whose result type varies per branch, like an eliminator `Π(n). P n`, elaborates);
642/// when it does not, this collapses to the constant motive `λ_:I. T`. A bare variable
643/// discriminant just captures the free variable; any other discriminant has its
644/// occurrences replaced.
645fn infer_match_motive(
646    ctx: &Context,
647    disc: &Term,
648    cases: &[Term],
649    expected: Option<&Term>,
650) -> KernelResult<Term> {
651    let disc_ty = normalize(ctx, &infer_type(ctx, disc)?);
652    let result_ty = match expected {
653        Some(t) => t.clone(),
654        None => {
655            // No expected type: infer it from a nullary first branch (a bare term, not a
656            // case lambda whose binder types are placeholders we cannot infer through).
657            match cases.first() {
658                Some(c) if !matches!(c, Term::Lambda { .. }) => infer_type(ctx, c)?,
659                _ => {
660                    return Err(KernelError::CertificationError(
661                        "cannot infer the motive of this `match`; add a `return` clause or a \
662                         type annotation"
663                            .to_string(),
664                    ))
665                }
666            }
667        }
668    };
669    let (param, body) = match disc {
670        // A variable discriminant: bind its name so its free occurrences in the result
671        // type are captured (`Π(n). P n` ⇒ motive `λn:I. P n`).
672        Term::Var(v) => (v.clone(), result_ty),
673        // Otherwise replace occurrences of the (compound) discriminant by a fresh binder.
674        other => {
675            let p = "__motive".to_string();
676            (p.clone(), replace_subterm(&result_ty, other, &Term::Var(p)))
677        }
678    };
679    Ok(Term::Lambda { param, param_type: Box::new(disc_ty), body: Box::new(body) })
680}
681
682/// Replace every subterm structurally equal to `target` by `repl`.
683fn replace_subterm(t: &Term, target: &Term, repl: &Term) -> Term {
684    if t == target {
685        return repl.clone();
686    }
687    match t {
688        Term::Var(_) | Term::Global(_) | Term::Sort(_) | Term::Lit(_) | Term::Hole
689        | Term::Const { .. } => t.clone(),
690        Term::Pi { param, param_type, body_type } => Term::Pi {
691            param: param.clone(),
692            param_type: Box::new(replace_subterm(param_type, target, repl)),
693            body_type: Box::new(replace_subterm(body_type, target, repl)),
694        },
695        Term::Lambda { param, param_type, body } => Term::Lambda {
696            param: param.clone(),
697            param_type: Box::new(replace_subterm(param_type, target, repl)),
698            body: Box::new(replace_subterm(body, target, repl)),
699        },
700        Term::App(f, a) => Term::App(
701            Box::new(replace_subterm(f, target, repl)),
702            Box::new(replace_subterm(a, target, repl)),
703        ),
704        Term::Match { discriminant, motive, cases } => Term::Match {
705            discriminant: Box::new(replace_subterm(discriminant, target, repl)),
706            motive: Box::new(replace_subterm(motive, target, repl)),
707            cases: cases.iter().map(|c| replace_subterm(c, target, repl)).collect(),
708        },
709        Term::Fix { name, body } => {
710            Term::Fix { name: name.clone(), body: Box::new(replace_subterm(body, target, repl)) }
711        }
712        Term::MutualFix { defs, index } => Term::MutualFix {
713            defs: defs.iter().map(|(n, b)| (n.clone(), replace_subterm(b, target, repl))).collect(),
714            index: *index,
715        },
716        Term::Let { name, ty, value, body } => Term::Let {
717            name: name.clone(),
718            ty: Box::new(replace_subterm(ty, target, repl)),
719            value: Box::new(replace_subterm(value, target, repl)),
720            body: Box::new(replace_subterm(body, target, repl)),
721        },
722    }
723}
724
725/// Auto-bind free type variables as leading implicit parameters. A definition written
726/// `id : A -> A := fun a : A => a` mentions `A` as a FREE, unregistered, single-uppercase
727/// global — the type-variable convention. This pass generalizes each such variable: it
728/// prepends `Π(A:Type)` to the type and `λ(A:Type)` to the body (converting `Global(A)`
729/// to the bound `Var(A)`), and returns the new implicit count, so `A` becomes an inferred
730/// implicit argument. Definitions that already bind their parameters reference them as
731/// `Var`s, not free `Global`s, so they are untouched — this only rescues what was
732/// previously an unbound-variable error.
733pub fn auto_bind_implicits(
734    ctx: &Context,
735    ty: &Term,
736    body: &Term,
737    existing_implicit: usize,
738) -> (Term, Term, usize) {
739    let mut candidates: Vec<String> = Vec::new();
740    collect_autobind(ctx, ty, &mut candidates);
741    collect_autobind(ctx, body, &mut candidates);
742    if candidates.is_empty() {
743        return (ty.clone(), body.clone(), existing_implicit);
744    }
745
746    let mut new_ty = ty.clone();
747    let mut new_body = body.clone();
748    for name in &candidates {
749        new_ty = global_to_var(&new_ty, name);
750        new_body = global_to_var(&new_body, name);
751    }
752    // First candidate becomes the OUTERMOST binder.
753    for name in candidates.iter().rev() {
754        new_ty = Term::Pi {
755            param: name.clone(),
756            param_type: Box::new(Term::Sort(crate::term::Universe::Type(0))),
757            body_type: Box::new(new_ty),
758        };
759        new_body = Term::Lambda {
760            param: name.clone(),
761            param_type: Box::new(Term::Sort(crate::term::Universe::Type(0))),
762            body: Box::new(new_body),
763        };
764    }
765    (new_ty, new_body, existing_implicit + candidates.len())
766}
767
768/// A free auto-bind candidate: a single uppercase letter that is not a registered global.
769fn is_autobind_name(n: &str) -> bool {
770    n.len() == 1 && n.chars().next().is_some_and(|c| c.is_ascii_uppercase())
771}
772
773/// Collect free auto-bind candidates from `term` in first-appearance order (deduped).
774fn collect_autobind(ctx: &Context, term: &Term, acc: &mut Vec<String>) {
775    match term {
776        Term::Global(n) => {
777            if is_autobind_name(n) && ctx.get_global(n).is_none() && !acc.contains(n) {
778                acc.push(n.clone());
779            }
780        }
781        Term::Pi { param_type, body_type, .. } => {
782            collect_autobind(ctx, param_type, acc);
783            collect_autobind(ctx, body_type, acc);
784        }
785        Term::Lambda { param_type, body, .. } => {
786            collect_autobind(ctx, param_type, acc);
787            collect_autobind(ctx, body, acc);
788        }
789        Term::App(f, a) => {
790            collect_autobind(ctx, f, acc);
791            collect_autobind(ctx, a, acc);
792        }
793        Term::Match { discriminant, motive, cases } => {
794            collect_autobind(ctx, discriminant, acc);
795            collect_autobind(ctx, motive, acc);
796            for c in cases {
797                collect_autobind(ctx, c, acc);
798            }
799        }
800        Term::Fix { body, .. } => collect_autobind(ctx, body, acc),
801        _ => {}
802    }
803}
804
805/// Replace every `Global(name)` by `Var(name)` (turning a free type-variable reference
806/// into a reference to the binder this pass prepends).
807/// Recursive-definition sugar: if `body` refers to the definition's own `name` as a free
808/// `Global`, bind that self-reference with a `fix` so the definition can call itself —
809/// `Definition f : T := … f … .` becomes `f := fix f. …`. The kernel's termination guard
810/// (run by `infer_type`) then certifies the recursion decreases structurally; a body with
811/// no self-reference (or one that already wrote an explicit `fix`, whose self-references are
812/// already bound `Var`s) is returned unchanged. A self-reference SHADOWS any same-named
813/// global, so a recursive `Definition add` over `Nat` overrides a built-in `add`.
814pub fn bind_self_recursion(name: &str, body: &Term) -> Term {
815    if references_global(body, name) {
816        Term::Fix { name: name.to_string(), body: Box::new(global_to_var(body, name)) }
817    } else {
818        body.clone()
819    }
820}
821
822/// Whether `term` mentions `Global(name)` anywhere — the self-reference test for
823/// [`bind_self_recursion`]. Bound occurrences (already `Var`) do not count.
824fn references_global(term: &Term, name: &str) -> bool {
825    match term {
826        Term::Global(n) => n == name,
827        Term::Var(_) | Term::Sort(_) | Term::Lit(_) | Term::Hole | Term::Const { .. } => false,
828        Term::Pi { param_type, body_type, .. } => {
829            references_global(param_type, name) || references_global(body_type, name)
830        }
831        Term::Lambda { param_type, body, .. } => {
832            references_global(param_type, name) || references_global(body, name)
833        }
834        Term::App(f, a) => references_global(f, name) || references_global(a, name),
835        Term::Match { discriminant, motive, cases } => {
836            references_global(discriminant, name)
837                || references_global(motive, name)
838                || cases.iter().any(|c| references_global(c, name))
839        }
840        Term::Fix { body, .. } => references_global(body, name),
841        Term::MutualFix { defs, .. } => defs.iter().any(|(_, b)| references_global(b, name)),
842        Term::Let { ty, value, body, .. } => {
843            references_global(ty, name)
844                || references_global(value, name)
845                || references_global(body, name)
846        }
847    }
848}
849
850fn global_to_var(term: &Term, name: &str) -> Term {
851    match term {
852        Term::Global(n) if n == name => Term::Var(n.clone()),
853        Term::Global(_) | Term::Var(_) | Term::Sort(_) | Term::Lit(_) | Term::Hole
854        | Term::Const { .. } => term.clone(),
855        Term::Pi { param, param_type, body_type } => Term::Pi {
856            param: param.clone(),
857            param_type: Box::new(global_to_var(param_type, name)),
858            body_type: Box::new(global_to_var(body_type, name)),
859        },
860        Term::Lambda { param, param_type, body } => Term::Lambda {
861            param: param.clone(),
862            param_type: Box::new(global_to_var(param_type, name)),
863            body: Box::new(global_to_var(body, name)),
864        },
865        Term::App(f, a) => {
866            Term::App(Box::new(global_to_var(f, name)), Box::new(global_to_var(a, name)))
867        }
868        Term::Match { discriminant, motive, cases } => Term::Match {
869            discriminant: Box::new(global_to_var(discriminant, name)),
870            motive: Box::new(global_to_var(motive, name)),
871            cases: cases.iter().map(|c| global_to_var(c, name)).collect(),
872        },
873        Term::Fix { name: fname, body } => {
874            Term::Fix { name: fname.clone(), body: Box::new(global_to_var(body, name)) }
875        }
876        Term::MutualFix { defs, index } => Term::MutualFix {
877            defs: defs.iter().map(|(fname, b)| (fname.clone(), global_to_var(b, name))).collect(),
878            index: *index,
879        },
880        Term::Let { name: lname, ty, value, body } => Term::Let {
881            name: lname.clone(),
882            ty: Box::new(global_to_var(ty, name)),
883            value: Box::new(global_to_var(value, name)),
884            body: Box::new(global_to_var(body, name)),
885        },
886    }
887}
888
889/// Elaborate a whole surface term: walk it, and at every application of a global with
890/// declared implicit parameters (`Context::implicit_args`), insert and infer those
891/// arguments — so `id 0` becomes `id Int 0`. Terms with no implicits are returned
892/// unchanged. The result is fully explicit and metavariable-free; the kernel certifies
893/// it as usual. This is the seam that wires the elaborator into the REPL.
894pub fn surface_elaborate(ctx: &Context, term: &Term) -> KernelResult<Term> {
895    surface_elaborate_against(ctx, term, None)
896}
897
898/// Like [`surface_elaborate`] but with an EXPECTED type (e.g. a definition's declared
899/// type). The expected type is propagated to the top-level application/global so an
900/// implicit with no value argument — `nil : {A} → List A` used where a `List Int` is
901/// wanted — is inferred from context.
902pub fn surface_elaborate_against(
903    ctx: &Context,
904    term: &Term,
905    expected: Option<&Term>,
906) -> KernelResult<Term> {
907    let mut mctx = MetaCtx::new();
908    let elaborated = elab_surface(ctx, &mut mctx, term, expected)?;
909    Ok(instantiate(&elaborated, &mctx))
910}
911
912fn elab_surface(
913    ctx: &Context,
914    mctx: &mut MetaCtx,
915    term: &Term,
916    expected: Option<&Term>,
917) -> KernelResult<Term> {
918    // A top-level surface-sugar term (`⟨…⟩` / `receiver.field`) is resolved through the typed
919    // core so its expected type propagates; its subterms are elaborated there.
920    if let Some(sugar) = as_surface_sugar(term) {
921        return elaborate_sugar(ctx, mctx, sugar, expected).map(|(t, _)| t);
922    }
923    match term {
924        Term::App(..) => {
925            // Decompose the application spine `head a0 a1 …`.
926            let mut args: Vec<&Term> = Vec::new();
927            let mut cur = term;
928            while let Term::App(f, a) = cur {
929                args.push(a);
930                cur = f;
931            }
932            args.reverse();
933            let head = elab_surface(ctx, mctx, cur, None)?;
934            let args: Vec<Term> = args
935                .iter()
936                .map(|a| elab_surface(ctx, mctx, a, None))
937                .collect::<KernelResult<_>>()?;
938
939            if let Term::Global(name) = &head {
940                if let Some(head_ty) = ctx.get_global(name).cloned() {
941                    // Route EVERY global-headed application through the typed elaboration
942                    // path — even one with no implicits — so argument type-checking and
943                    // COERCION insertion apply uniformly.
944                    //
945                    // The parameter kinds come from the recorded PER-BINDER info (E2) when it
946                    // exists and matches the number of explicit arguments — so implicit and
947                    // instance parameters may interleave with explicit ones. Otherwise the
948                    // legacy model applies: `implicit_args` leading implicits, the rest
949                    // explicit.
950                    let kinds = match ctx.binder_kinds(name) {
951                        Some(bk)
952                            if bk.iter().filter(|k| **k == ParamKind::Explicit).count()
953                                == args.len() =>
954                        {
955                            bk.to_vec()
956                        }
957                        _ => {
958                            let k = ctx.implicit_args(name);
959                            let mut kinds = vec![ParamKind::Implicit; k];
960                            kinds.extend(std::iter::repeat(ParamKind::Explicit).take(args.len()));
961                            kinds
962                        }
963                    };
964                    if let Ok((t, _)) =
965                        elaborate_app_against(ctx, mctx, &head, &head_ty, &kinds, &args, expected)
966                    {
967                        return Ok(t);
968                    }
969                    // Fall through to a plain application if typed elaboration did not apply
970                    // (e.g. the head is not a function of the expected arity) — preserving
971                    // the previous permissive behaviour for non-standard shapes.
972                }
973            }
974            Ok(args.into_iter().fold(head, |f, a| Term::App(Box::new(f), Box::new(a))))
975        }
976        // A bare implicit global (no value arguments) is elaborated only when an expected
977        // type is available to pin its implicits — otherwise it stays the polymorphic
978        // function value, not an unsolvable metavariable application.
979        Term::Global(name) if expected.is_some() && ctx.implicit_args(name) > 0 => {
980            let k = ctx.implicit_args(name);
981            if let Some(head_ty) = ctx.get_global(name).cloned() {
982                let kinds = vec![ParamKind::Implicit; k];
983                let (t, _) =
984                    elaborate_app_against(ctx, mctx, term, &head_ty, &kinds, &[], expected)?;
985                Ok(t)
986            } else {
987                Ok(term.clone())
988            }
989        }
990        Term::Lambda { param, param_type, body } => Ok(Term::Lambda {
991            param: param.clone(),
992            param_type: Box::new(elab_surface(ctx, mctx, param_type, None)?),
993            body: Box::new(elab_surface(ctx, mctx, body, None)?),
994        }),
995        Term::Pi { param, param_type, body_type } => Ok(Term::Pi {
996            param: param.clone(),
997            param_type: Box::new(elab_surface(ctx, mctx, param_type, None)?),
998            body_type: Box::new(elab_surface(ctx, mctx, body_type, None)?),
999        }),
1000        Term::Fix { name, body } => Ok(Term::Fix {
1001            name: name.clone(),
1002            body: Box::new(elab_surface(ctx, mctx, body, None)?),
1003        }),
1004        Term::Match { discriminant, motive, cases } => Ok(Term::Match {
1005            discriminant: Box::new(elab_surface(ctx, mctx, discriminant, None)?),
1006            motive: Box::new(elab_surface(ctx, mctx, motive, None)?),
1007            cases: cases
1008                .iter()
1009                .map(|c| elab_surface(ctx, mctx, c, None))
1010                .collect::<KernelResult<_>>()?,
1011        }),
1012        _ => Ok(term.clone()),
1013    }
1014}
1015
1016/// How a function parameter is supplied during elaboration.
1017#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1018pub enum ParamKind {
1019    /// The caller provides the argument.
1020    Explicit,
1021    /// Inferred by unification (a fresh metavariable is inserted).
1022    Implicit,
1023    /// A typeclass instance, resolved from the [`Context`]'s instance database — but only
1024    /// AFTER the explicit arguments have been processed, so any metavariable in the class
1025    /// type (e.g. the `A` in `Inhabited A`) is solved first.
1026    Instance,
1027}
1028
1029/// Elaborate an application of `head` (of type `head_ty`) where leading parameters may be
1030/// implicit or instance-implicit (`kinds[i]`). A fresh metavariable is inserted for each
1031/// implicit position; instance positions get a placeholder metavariable whose resolution
1032/// is DEFERRED until all explicit arguments (which may pin the class's type variables)
1033/// have been unified. Returns the fully explicit, metavariable-instantiated term and type.
1034pub fn elaborate_app(
1035    ctx: &Context,
1036    mctx: &mut MetaCtx,
1037    head: &Term,
1038    head_ty: &Term,
1039    kinds: &[ParamKind],
1040    explicit_args: &[Term],
1041) -> KernelResult<(Term, Term)> {
1042    elaborate_app_against(ctx, mctx, head, head_ty, kinds, explicit_args, None)
1043}
1044
1045/// Like [`elaborate_app`] but with an EXPECTED result type. After the explicit arguments
1046/// are processed, the result type is unified with `expected` — so an implicit that the
1047/// arguments alone do not pin (e.g. the `A` of `nil : {A} → List A`, which has no value
1048/// argument) is solved from the surrounding context. The expected-type pass runs BEFORE
1049/// instance resolution, so a class's type variable can be fixed by the context too.
1050#[allow(clippy::too_many_arguments)]
1051pub fn elaborate_app_against(
1052    ctx: &Context,
1053    mctx: &mut MetaCtx,
1054    head: &Term,
1055    head_ty: &Term,
1056    kinds: &[ParamKind],
1057    explicit_args: &[Term],
1058    expected: Option<&Term>,
1059) -> KernelResult<(Term, Term)> {
1060    let mut cur_term = head.clone();
1061    let mut cur_ty = head_ty.clone();
1062    let mut next_arg = 0usize;
1063    // (placeholder metavariable name, the class type to resolve it against).
1064    let mut obligations: Vec<(String, Term)> = Vec::new();
1065
1066    for kind in kinds {
1067        let (param, dom, body) = match resolve(ctx, mctx, &cur_ty) {
1068            Term::Pi { param, param_type, body_type } => (param, *param_type, *body_type),
1069            other => {
1070                return Err(KernelError::NotAFunction(format!(
1071                    "expected a Π to apply an argument, got {}",
1072                    other
1073                )))
1074            }
1075        };
1076
1077        let arg = match kind {
1078            ParamKind::Implicit => mctx.fresh(),
1079            ParamKind::Instance => {
1080                let m = mctx.fresh();
1081                if let Term::Var(name) = &m {
1082                    obligations.push((name.clone(), dom.clone()));
1083                }
1084                m
1085            }
1086            ParamKind::Explicit => {
1087                let provided = explicit_args.get(next_arg).ok_or_else(|| {
1088                    KernelError::CertificationError("too few explicit arguments".to_string())
1089                })?;
1090                next_arg += 1;
1091                let (a_elab, a_ty) = elaborate(ctx, mctx, provided, Some(&dom))?;
1092                if unify(ctx, mctx, &a_ty, &dom) {
1093                    a_elab
1094                } else {
1095                    // The argument type does not match — try to bridge it with a registered
1096                    // COERCION (`↑`). On success wrap the argument in the coercion function;
1097                    // otherwise it is a genuine type mismatch.
1098                    let a_ty_i = instantiate(&a_ty, mctx);
1099                    let dom_i = instantiate(&dom, mctx);
1100                    match resolve_coercion(ctx, mctx, &a_ty_i, &dom_i) {
1101                        Some(coe) => Term::App(Box::new(coe), Box::new(a_elab)),
1102                        None => {
1103                            return Err(KernelError::TypeMismatch {
1104                                expected: format!("{}", dom_i),
1105                                found: format!("{}", a_ty_i),
1106                            })
1107                        }
1108                    }
1109                }
1110            }
1111        };
1112
1113        cur_term = Term::App(Box::new(cur_term), Box::new(arg.clone()));
1114        cur_ty = substitute(&body, &param, &arg);
1115    }
1116
1117    if next_arg != explicit_args.len() {
1118        return Err(KernelError::CertificationError(format!(
1119            "too many explicit arguments: {} provided, {} consumed",
1120            explicit_args.len(),
1121            next_arg
1122        )));
1123    }
1124
1125    // Expected-type propagation: unify the result type with the context's expected type,
1126    // solving any implicit not already pinned by the explicit arguments.
1127    if let Some(exp) = expected {
1128        if !unify(ctx, mctx, &cur_ty, exp) {
1129            return Err(KernelError::TypeMismatch {
1130                expected: format!("{}", instantiate(exp, mctx)),
1131                found: format!("{}", instantiate(&cur_ty, mctx)),
1132            });
1133        }
1134    }
1135
1136    // Resolve the deferred instance obligations now that the metavariables their class
1137    // types mention have been solved by the explicit arguments (and the expected type).
1138    for (meta_name, class_ty) in &obligations {
1139        let required = instantiate(class_ty, mctx);
1140        match resolve_instance(ctx, mctx, &required) {
1141            Some(inst) => {
1142                // Bind directly (not via normalizing unification) so the instance stays
1143                // structured rather than being δ-unfolded into its body.
1144                mctx.solve(meta_name, inst);
1145            }
1146            None => {
1147                return Err(KernelError::CertificationError(format!(
1148                    "no typeclass instance found for {}",
1149                    required
1150                )))
1151            }
1152        }
1153    }
1154
1155    Ok((instantiate(&cur_term, mctx), instantiate(&cur_ty, mctx)))
1156}
1157
1158/// Depth bound on recursive instance resolution — a backstop against a pathological
1159/// instance set (`Inhabited A` from `Inhabited A`) looping forever.
1160const MAX_INSTANCE_DEPTH: usize = 64;
1161
1162/// The head `Global` of an application spine (`Inhabited (List A)` → `Inhabited`).
1163fn head_global(t: &Term) -> Option<&str> {
1164    let mut cur = t;
1165    while let Term::App(f, _) = cur {
1166        cur = f;
1167    }
1168    match cur {
1169        Term::Global(n) => Some(n),
1170        _ => None,
1171    }
1172}
1173
1174/// The set of typeclass "heads" — the head `Global` of every registered instance's
1175/// CONCLUSION (`Inhabited (List A)` and `Inhabited Nat` both contribute `Inhabited`).
1176/// A parameter whose type has one of these heads is an instance PREMISE, to be resolved
1177/// recursively, rather than a type parameter solved by unifying the conclusion.
1178fn class_heads(ctx: &Context) -> std::collections::HashSet<String> {
1179    ctx.instances()
1180        .iter()
1181        .filter_map(|(ty, _)| {
1182            let mut cur = ty;
1183            while let Term::Pi { body_type, .. } = cur {
1184                cur = body_type;
1185            }
1186            head_global(cur).map(|s| s.to_string())
1187        })
1188        .collect()
1189}
1190
1191/// Search the [`Context`]'s instance database for an instance proving `required`,
1192/// returning the (metavariable-instantiated) instance term. Handles POLYMORPHIC /
1193/// RECURSIVE instances (`instance {A} [Inhabited A] : Inhabited (List A)`): the instance's
1194/// parameter telescope is freshened to metavariables, its conclusion is unified against
1195/// `required` (solving the type parameters), and each PREMISE parameter is then resolved
1196/// RECURSIVELY. The first instance that fully resolves wins; failed trials never pollute
1197/// `mctx` (each runs on a clone, committed only on success).
1198pub fn resolve_instance(ctx: &Context, mctx: &mut MetaCtx, required: &Term) -> Option<Term> {
1199    resolve_instance_at(ctx, mctx, required, 0)
1200}
1201
1202/// Elaborate an ANONYMOUS CONSTRUCTOR `⟨f₀, …, fₙ⟩` (E3) against an EXPECTED inductive/
1203/// structure type `H a…`. It applies `H`'s (unique) constructor to the type parameters
1204/// `a…`, read off the expected type, then the field values — so `⟨Zero, true⟩` expected at
1205/// `Prod Nat Bool` becomes `Prod_mk Nat Bool Zero true`. Each field is elaborated against
1206/// its declared type (so coercions/implicits fire), and the whole is kernel-certified.
1207pub fn elaborate_anon_ctor(
1208    ctx: &Context,
1209    mctx: &mut MetaCtx,
1210    expected: &Term,
1211    fields: &[Term],
1212) -> KernelResult<Term> {
1213    let exp = crate::normalize(ctx, &instantiate(expected, mctx));
1214    let (head, args) = spine(&exp);
1215    let hname = match &head {
1216        Term::Global(n) => n.clone(),
1217        _ => {
1218            return Err(KernelError::CertificationError(format!(
1219                "anonymous constructor: expected type {exp} is not an inductive"
1220            )))
1221        }
1222    };
1223    let ctors = ctx.get_constructors(&hname);
1224    let ctor = match ctors.as_slice() {
1225        [(c, _)] => c.to_string(),
1226        _ => {
1227            return Err(KernelError::CertificationError(format!(
1228                "anonymous constructor: `{hname}` does not have exactly one constructor"
1229            )))
1230        }
1231    };
1232    // Apply the constructor to the type parameters, then each field elaborated against its
1233    // declared domain.
1234    let mut applied = Term::Global(ctor);
1235    for a in &args {
1236        applied = Term::App(Box::new(applied), Box::new(a.clone()));
1237    }
1238    for fv in fields {
1239        let dom = match resolve(ctx, mctx, &crate::infer_type(ctx, &applied)?) {
1240            Term::Pi { param_type, .. } => Some(*param_type),
1241            _ => None,
1242        };
1243        let (fe, fty) = elaborate(ctx, mctx, fv, dom.as_ref())?;
1244        let arg = if let Some(d) = &dom {
1245            if unify(ctx, mctx, &fty, d) {
1246                fe
1247            } else {
1248                match resolve_coercion(ctx, mctx, &instantiate(&fty, mctx), &instantiate(d, mctx)) {
1249                    Some(coe) => Term::App(Box::new(coe), Box::new(fe)),
1250                    None => fe,
1251                }
1252            }
1253        } else {
1254            fe
1255        };
1256        applied = Term::App(Box::new(applied), Box::new(arg));
1257    }
1258    crate::infer_type(ctx, &applied)?;
1259    Ok(instantiate(&applied, mctx))
1260}
1261
1262/// Elaborate DOT notation `receiver.field` (E4). The receiver's type head names an
1263/// inductive/structure `H`; the projection is `H_field` (K4's convention), applied to `H`'s
1264/// parameters — read off the receiver's type — and then the receiver itself. So
1265/// `p.fst` with `p : Prod A B` becomes `Prod_fst A B p`. Returns an error if no such
1266/// projection exists or the result does not type-check.
1267pub fn elaborate_dot(
1268    ctx: &Context,
1269    mctx: &mut MetaCtx,
1270    receiver: &Term,
1271    field: &str,
1272) -> KernelResult<Term> {
1273    let (r_elab, r_ty) = elaborate(ctx, mctx, receiver, None)?;
1274    let r_ty = crate::normalize(ctx, &instantiate(&r_ty, mctx));
1275    let (head, args) = spine(&r_ty);
1276    let hname = match &head {
1277        Term::Global(n) => n.clone(),
1278        _ => {
1279            return Err(KernelError::CertificationError(format!(
1280                "dot notation `.{field}`: the receiver's type {r_ty} is not headed by an inductive"
1281            )))
1282        }
1283    };
1284    let proj = format!("{hname}_{field}");
1285    if ctx.get_global(&proj).is_none() {
1286        return Err(KernelError::CertificationError(format!(
1287            "dot notation: no projection `{proj}` for field `{field}` of `{hname}`"
1288        )));
1289    }
1290    // `H_field params… receiver`.
1291    let mut applied = Term::Global(proj);
1292    for a in &args {
1293        applied = Term::App(Box::new(applied), Box::new(a.clone()));
1294    }
1295    applied = Term::App(Box::new(applied), Box::new(r_elab));
1296    // Certify it type-checks (the projection's arity/positions line up).
1297    crate::infer_type(ctx, &applied)?;
1298    Ok(applied)
1299}
1300
1301/// Find a registered coercion carrying `from` to `to` (up to unification), returning the
1302/// coercion FUNCTION to wrap the argument in — Lean's `↑`. The elaborator calls this when
1303/// an argument's type does not match the expected parameter type; a match commits the
1304/// unification (a polymorphic coercion's type variables get solved).
1305pub fn resolve_coercion(
1306    ctx: &Context,
1307    mctx: &mut MetaCtx,
1308    from: &Term,
1309    to: &Term,
1310) -> Option<Term> {
1311    for (c_from, c_to, c_fn) in ctx.coercions() {
1312        let mut trial = mctx.clone();
1313        if unify(ctx, &mut trial, c_from, from) && unify(ctx, &mut trial, c_to, to) {
1314            *mctx = trial;
1315            return Some(instantiate(c_fn, mctx));
1316        }
1317    }
1318    None
1319}
1320
1321fn resolve_instance_at(
1322    ctx: &Context,
1323    mctx: &mut MetaCtx,
1324    required: &Term,
1325    depth: usize,
1326) -> Option<Term> {
1327    if depth > MAX_INSTANCE_DEPTH {
1328        return None;
1329    }
1330    let heads = class_heads(ctx);
1331    for (inst_ty, inst_val) in ctx.instances() {
1332        let mut trial = mctx.clone();
1333        if let Some(result) =
1334            try_instance(ctx, &mut trial, inst_ty, inst_val, required, &heads, depth)
1335        {
1336            *mctx = trial;
1337            return Some(result);
1338        }
1339    }
1340    None
1341}
1342
1343/// Attempt one instance against `required`: freshen its parameters to metavariables,
1344/// unify its conclusion with `required`, and recursively resolve every premise parameter.
1345#[allow(clippy::too_many_arguments)]
1346fn try_instance(
1347    ctx: &Context,
1348    mctx: &mut MetaCtx,
1349    inst_ty: &Term,
1350    inst_val: &Term,
1351    required: &Term,
1352    heads: &std::collections::HashSet<String>,
1353    depth: usize,
1354) -> Option<Term> {
1355    // Freshen: peel the parameter telescope, replacing each parameter by a fresh
1356    // metavariable and recording which are instance premises. `applied` accumulates the
1357    // instance value applied to those metavariables.
1358    let mut applied = inst_val.clone();
1359    let mut premises: Vec<(Term, Term)> = Vec::new(); // (metavariable, premise type)
1360    let mut cur = inst_ty.clone();
1361    loop {
1362        match cur {
1363            Term::Pi { param, param_type, body_type } => {
1364                let mv = mctx.fresh();
1365                if head_global(&param_type).is_some_and(|h| heads.contains(h)) {
1366                    premises.push((mv.clone(), (*param_type).clone()));
1367                }
1368                applied = Term::App(Box::new(applied), Box::new(mv.clone()));
1369                cur = substitute(&body_type, &param, &mv);
1370            }
1371            conclusion => {
1372                // The conclusion must match the goal (this solves the type parameters).
1373                if !unify(ctx, mctx, &conclusion, required) {
1374                    return None;
1375                }
1376                // Each premise is now (after that unification) a ground class goal; resolve
1377                // it recursively and bind its metavariable to the result.
1378                for (pm, pty) in &premises {
1379                    let sub_goal = instantiate(pty, mctx);
1380                    let resolved = resolve_instance_at(ctx, mctx, &sub_goal, depth + 1)?;
1381                    // Bind the premise's metavariable directly, keeping the (possibly
1382                    // nested) resolved instance structured.
1383                    match pm {
1384                        Term::Var(name) => mctx.solve(name, resolved),
1385                        _ => return None,
1386                    }
1387                }
1388                return Some(instantiate(&applied, mctx));
1389            }
1390        }
1391    }
1392}