Skip to main content

logicaffeine_kernel/
tactics.rs

1//! Tactics over the kernel's own goals: a goal state of metavariables, outside the trusted base.
2//!
3//! A goal is a metavariable with the hypotheses in scope and the type it must inhabit. A tactic
4//! assigns the focused goal a term that may mention new metavariables; those are the new goals,
5//! front first, as Lean's tactic framework builds a proof. Nothing here is trusted: a tactic only
6//! builds terms, and [`GoalState::qed`] instantiates the statement's proof, requires it closed, and
7//! has the kernel check it against the statement exactly as a term written by hand. A tactic that
8//! cannot do what it is asked leaves the state as it was.
9//!
10//! Hypotheses are locals (`Term::Var`). A term handed to a tactic reads a global named like a
11//! hypothesis of the focused goal as that hypothesis ([`GoalState::read`]), the scoping a proof
12//! script expects.
13
14use std::collections::{BTreeSet, HashMap};
15use std::fmt;
16
17use crate::context::{Context, SimpRule};
18use crate::elaborate::{self, MetaCtx};
19use crate::error::KernelError;
20use crate::term::Term;
21use crate::type_checker::{
22    alpha_equivalent, def_eq, demanded_type_head, infer_sort, infer_type, is_subtype, substitute,
23    TypeHead,
24};
25
26/// One open goal as a reader sees it: the hypotheses in scope, outermost first, and the target,
27/// every solved metavariable substituted.
28#[derive(Debug, Clone, PartialEq)]
29pub struct Goal {
30    pub hyps: Vec<(String, Term)>,
31    pub target: Term,
32}
33
34impl fmt::Display for Goal {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        for (name, ty) in &self.hyps {
37            writeln!(f, "{name} : {}", Shown(ty))?;
38        }
39        write!(f, "⊢ {}", Shown(&self.target))
40    }
41}
42
43/// Why a tactic did nothing, or why there is no proof yet.
44#[derive(Debug)]
45pub enum TacticError {
46    /// Every goal is closed.
47    NoGoals,
48    /// The tactic does not apply to the focused goal.
49    DoesNotApply {
50        tactic: &'static str,
51        goal: Goal,
52        why: String,
53    },
54    /// `qed` with goals still open.
55    GoalsRemain(Vec<Goal>),
56    /// The kernel refused a term: the statement at [`GoalState::new`], the proof at
57    /// [`GoalState::qed`].
58    Kernel(KernelError),
59}
60
61impl fmt::Display for TacticError {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            TacticError::NoGoals => write!(f, "no goals: the proof is complete"),
65            TacticError::DoesNotApply { tactic, goal, why } => {
66                write!(f, "`{tactic}` does not apply: {why}\nin the goal\n{goal}")
67            }
68            TacticError::GoalsRemain(goals) => {
69                write!(f, "{} goal(s) remain:", goals.len())?;
70                for goal in goals {
71                    write!(f, "\n{goal}")?;
72                }
73                Ok(())
74            }
75            TacticError::Kernel(e) => write!(f, "{e}"),
76        }
77    }
78}
79
80impl std::error::Error for TacticError {}
81
82#[derive(Debug, Clone)]
83struct Decl {
84    hyps: Vec<(String, Term)>,
85    target: Term,
86}
87
88/// One step of a tactic script (`by intro A x; exact (refl A x)`).
89#[derive(Debug, Clone, PartialEq)]
90pub enum TacticStep {
91    Intro(Vec<String>),
92    Exact(Term),
93    Apply(Term),
94    Assumption,
95    Exfalso,
96    Constructor,
97    Left,
98    Right,
99    Exists(Term),
100    Have(String, Term),
101    Rfl,
102    Change(Term),
103    Rewrite(Term, bool),
104    Induction(String, Vec<Vec<String>>),
105    Cases(String, Vec<Vec<String>>),
106    Refine(Term),
107    Subst(String),
108    Injection(String, Vec<String>),
109    /// `calc [R₁ := p₁, R₂ := p₂, …]`: each step an equation and its proof.
110    Calc(Vec<(Term, Term)>),
111    Cc,
112    Decide,
113    /// `simp [l₁, …]`: rewrite by the lemmas (the context's `@[simp]` set when none is named).
114    Simp(Vec<Term>),
115}
116
117impl fmt::Display for TacticStep {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        let pattern = |cases: &[Vec<String>]| {
120            let cases: Vec<String> = cases.iter().map(|c| c.join(" ")).collect();
121            format!(" as [{}]", cases.join(" | "))
122        };
123        match self {
124            TacticStep::Intro(names) => write!(f, "intro {}", names.join(" ")),
125            TacticStep::Exact(t) => write!(f, "exact {}", Shown(t)),
126            TacticStep::Apply(t) => write!(f, "apply {}", Shown(t)),
127            TacticStep::Assumption => write!(f, "assumption"),
128            TacticStep::Exfalso => write!(f, "exfalso"),
129            TacticStep::Constructor => write!(f, "constructor"),
130            TacticStep::Left => write!(f, "left"),
131            TacticStep::Right => write!(f, "right"),
132            TacticStep::Exists(t) => write!(f, "exists {}", Shown(t)),
133            TacticStep::Have(n, t) => write!(f, "have {n} : {}", Shown(t)),
134            TacticStep::Rfl => write!(f, "rfl"),
135            TacticStep::Change(t) => write!(f, "change {}", Shown(t)),
136            TacticStep::Rewrite(t, reverse) => {
137                write!(f, "rw [{}{}]", if *reverse { "<- " } else { "" }, Shown(t))
138            }
139            TacticStep::Induction(x, cases) if cases.is_empty() => write!(f, "induction {x}"),
140            TacticStep::Induction(x, cases) => write!(f, "induction {x}{}", pattern(cases)),
141            TacticStep::Cases(x, cases) if cases.is_empty() => write!(f, "cases {x}"),
142            TacticStep::Cases(x, cases) => write!(f, "cases {x}{}", pattern(cases)),
143            TacticStep::Refine(t) => write!(f, "refine {}", Shown(t)),
144            TacticStep::Subst(h) => write!(f, "subst {h}"),
145            TacticStep::Injection(h, names) if names.is_empty() => write!(f, "injection {h}"),
146            TacticStep::Injection(h, names) => write!(f, "injection {h} as [{}]", names.join(" ")),
147            TacticStep::Calc(steps) => {
148                let steps: Vec<String> = steps
149                    .iter()
150                    .map(|(r, p)| format!("{} := {}", Shown(r), Shown(p)))
151                    .collect();
152                write!(f, "calc [{}]", steps.join(", "))
153            }
154            TacticStep::Cc => write!(f, "cc"),
155            TacticStep::Decide => write!(f, "decide"),
156            TacticStep::Simp(lemmas) if lemmas.is_empty() => write!(f, "simp"),
157            TacticStep::Simp(lemmas) => {
158                let lemmas: Vec<String> = lemmas.iter().map(|l| Shown(l).to_string()).collect();
159                write!(f, "simp [{}]", lemmas.join(", "))
160            }
161        }
162    }
163}
164
165/// A script's failure: the step, by position and as written, and why; no step when the script ran
166/// to its end and the proof is not done.
167#[derive(Debug)]
168pub struct ScriptError {
169    pub step: usize,
170    pub tactic: Option<TacticStep>,
171    pub error: TacticError,
172}
173
174impl fmt::Display for ScriptError {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        match &self.tactic {
177            Some(tactic) => write!(f, "step {} (`{tactic}`): {}", self.step + 1, self.error),
178            None => write!(f, "after the last step: {}", self.error),
179        }
180    }
181}
182
183impl std::error::Error for ScriptError {}
184
185/// A proof under construction: the statement, the metavariables standing for its unproved
186/// parts, and the goals still open.
187#[derive(Debug, Clone)]
188pub struct GoalState {
189    ctx: Context,
190    statement: Term,
191    mctx: MetaCtx,
192    decls: HashMap<String, Decl>,
193    open: Vec<String>,
194    root: String,
195}
196
197impl GoalState {
198    /// A proof of `statement` in `ctx`, one goal open. `statement` must be a type.
199    pub fn new(ctx: &Context, statement: &Term) -> Result<GoalState, TacticError> {
200        infer_sort(ctx, statement).map_err(TacticError::Kernel)?;
201        let mut st = GoalState {
202            ctx: ctx.clone(),
203            statement: statement.clone(),
204            mctx: MetaCtx::new(),
205            decls: HashMap::new(),
206            open: Vec::new(),
207            root: String::new(),
208        };
209        let root = st.declare(Vec::new(), statement.clone());
210        st.root = root.clone();
211        st.open.push(root);
212        Ok(st)
213    }
214
215    /// The open goals, the focused one first.
216    pub fn goals(&self) -> Vec<Goal> {
217        self.open.iter().map(|m| self.view(m)).collect()
218    }
219
220    /// `term` as the focused goal reads it: a global named like one of its hypotheses is that
221    /// hypothesis.
222    pub fn read(&self, term: &Term) -> Term {
223        match self.open.first() {
224            Some(m) => localize(term, &self.decls[m].hyps),
225            None => term.clone(),
226        }
227    }
228
229    /// The finished proof: the statement's proof term with every goal's solution in place, closed
230    /// and checked by the kernel against the statement.
231    pub fn qed(&self) -> Result<Term, TacticError> {
232        if !self.open.is_empty() {
233            return Err(TacticError::GoalsRemain(self.goals()));
234        }
235        let proof = elaborate::instantiate(&Term::Var(self.root.clone()), &self.mctx);
236        if elaborate::mentions_meta(&proof) {
237            return Err(TacticError::Kernel(KernelError::CertificationError(
238                format!(
239                    "the proof still holds an unsolved metavariable: {}",
240                    Shown(&proof)
241                ),
242            )));
243        }
244        let ty = infer_type(&self.ctx, &proof).map_err(TacticError::Kernel)?;
245        if !is_subtype(&self.ctx, &ty, &self.statement) {
246            return Err(TacticError::Kernel(KernelError::TypeMismatch {
247                expected: Shown(&self.statement).to_string(),
248                found: Shown(&ty).to_string(),
249            }));
250        }
251        Ok(proof)
252    }
253
254    /// Run one step; a step that does not apply leaves the state as it found it, a multi-name
255    /// `intro` included.
256    pub fn step(&mut self, step: &TacticStep) -> Result<(), TacticError> {
257        let saved = self.clone();
258        let result = self.step_unguarded(step);
259        if result.is_err() {
260            *self = saved;
261        }
262        result
263    }
264
265    fn step_unguarded(&mut self, step: &TacticStep) -> Result<(), TacticError> {
266        match step {
267            TacticStep::Intro(xs) => {
268                for x in xs {
269                    self.intro(x)?;
270                }
271                Ok(())
272            }
273            TacticStep::Exact(t) => self.exact(t),
274            TacticStep::Apply(t) => self.apply(t),
275            TacticStep::Assumption => self.assumption(),
276            TacticStep::Exfalso => self.exfalso(),
277            TacticStep::Constructor => self.constructor(),
278            TacticStep::Left => self.left(),
279            TacticStep::Right => self.right(),
280            TacticStep::Exists(t) => self.exists(t),
281            TacticStep::Have(n, t) => self.have(n, t),
282            TacticStep::Rfl => self.rfl(),
283            TacticStep::Change(t) => self.change(t),
284            TacticStep::Rewrite(t, reverse) => self.rewrite(t, *reverse),
285            TacticStep::Induction(x, cases) => {
286                let refs: Vec<Vec<&str>> = cases
287                    .iter()
288                    .map(|c| c.iter().map(String::as_str).collect())
289                    .collect();
290                let slices: Vec<&[&str]> = refs.iter().map(Vec::as_slice).collect();
291                self.induction(x, &slices)
292            }
293            TacticStep::Cases(x, cases) => {
294                let refs: Vec<Vec<&str>> = cases
295                    .iter()
296                    .map(|c| c.iter().map(String::as_str).collect())
297                    .collect();
298                let slices: Vec<&[&str]> = refs.iter().map(Vec::as_slice).collect();
299                self.cases(x, &slices)
300            }
301            TacticStep::Refine(t) => self.refine(t),
302            TacticStep::Subst(h) => self.subst(h),
303            TacticStep::Injection(h, names) => {
304                let names: Vec<&str> = names.iter().map(String::as_str).collect();
305                self.injection(h, &names)
306            }
307            TacticStep::Calc(steps) => self.calc(steps),
308            TacticStep::Cc => self.cc(),
309            TacticStep::Decide => self.decide(),
310            TacticStep::Simp(lemmas) => self.simp(lemmas),
311        }
312    }
313
314    /// Run a script, step by step; the first step that does not apply stops it, the state left
315    /// as that step found it.
316    pub fn run(&mut self, script: &[TacticStep]) -> Result<(), ScriptError> {
317        for (i, step) in script.iter().enumerate() {
318            self.step(step).map_err(|error| ScriptError {
319                step: i,
320                tactic: Some(step.clone()),
321                error,
322            })?;
323        }
324        Ok(())
325    }
326
327    /// `intro x`: a goal `Π(y : A). B` becomes `B[y := x]` with the hypothesis `x : A`.
328    pub fn intro(&mut self, name: &str) -> Result<(), TacticError> {
329        let (m, goal) = self.focus()?;
330        if goal.hyps.iter().any(|(n, _)| n == name) {
331            return Err(refuse(
332                "intro",
333                &goal,
334                format!("`{name}` already names a hypothesis"),
335            ));
336        }
337        let ctx = self.scope(&goal.hyps);
338        let Term::Pi {
339            param,
340            param_type,
341            body_type,
342        } = demanded_type_head(&ctx, goal.target.clone(), TypeHead::Function)
343        else {
344            return Err(refuse(
345                "intro",
346                &goal,
347                "the goal is not a function type".to_string(),
348            ));
349        };
350        let body = if param == name {
351            *body_type
352        } else {
353            substitute(&body_type, &param, &Term::Var(name.to_string()))
354        };
355        let mut hyps = goal.hyps.clone();
356        hyps.push((name.to_string(), (*param_type).clone()));
357        let inner = self.declare(hyps, body);
358        let proof = Term::Lambda {
359            param: name.to_string(),
360            param_type,
361            body: Box::new(Term::Var(inner.clone())),
362        };
363        self.close(&m, proof, vec![inner]);
364        Ok(())
365    }
366
367    /// `exact e`: close the goal with `e`, whose holes and implicit arguments unification fills.
368    pub fn exact(&mut self, term: &Term) -> Result<(), TacticError> {
369        let (m, goal) = self.focus()?;
370        let ctx = self.scope(&goal.hyps);
371        let term = localize(term, &goal.hyps);
372        let mut mctx = self.mctx.clone();
373        let (value, ty) =
374            typed(&ctx, &mut mctx, &goal.hyps, &term, Some(&goal.target)).map_err(|e| {
375                refuse(
376                    "exact",
377                    &goal,
378                    format!("{} does not type: {e}", Shown(&term)),
379                )
380            })?;
381        elaborate::unify_in(&ctx, &mut mctx, &goal.hyps, &ty, &goal.target);
382        let (value, ty, target) = (
383            elaborate::instantiate(&value, &mctx),
384            elaborate::instantiate(&ty, &mctx),
385            elaborate::instantiate(&goal.target, &mctx),
386        );
387        if elaborate::mentions_meta(&value) {
388            return Err(refuse(
389                "exact",
390                &goal,
391                format!("{} leaves a hole unsolved", Shown(&value)),
392            ));
393        }
394        if !is_subtype(&ctx, &ty, &target) {
395            return Err(refuse(
396                "exact",
397                &goal,
398                format!(
399                    "the goal is {}, and {} has type {}",
400                    Shown(&target),
401                    Shown(&value),
402                    Shown(&ty)
403                ),
404            ));
405        }
406        self.mctx = mctx;
407        self.close(&m, value, Vec::new());
408        Ok(())
409    }
410
411    /// `apply f`: unify `f`'s conclusion with the goal; each premise of `f` unification does not
412    /// determine becomes a goal, in order.
413    pub fn apply(&mut self, term: &Term) -> Result<(), TacticError> {
414        let term = self.read(term);
415        self.apply_term("apply", term)
416    }
417
418    /// `assumption`: close the goal with a hypothesis whose type it is.
419    pub fn assumption(&mut self) -> Result<(), TacticError> {
420        let (m, goal) = self.focus()?;
421        let ctx = self.scope(&goal.hyps);
422        let Some((name, _)) = goal
423            .hyps
424            .iter()
425            .rev()
426            .find(|(_, ty)| is_subtype(&ctx, ty, &goal.target))
427        else {
428            return Err(refuse(
429                "assumption",
430                &goal,
431                "no hypothesis states the goal".to_string(),
432            ));
433        };
434        let proof = Term::Var(name.clone());
435        self.close(&m, proof, Vec::new());
436        Ok(())
437    }
438
439    /// `exfalso`: prove the goal from `False`, by `False`'s elimination with no case.
440    pub fn exfalso(&mut self) -> Result<(), TacticError> {
441        let (m, goal) = self.focus()?;
442        let falsity = Term::Global("False".into());
443        let inner = self.declare(goal.hyps.clone(), falsity.clone());
444        let proof = Term::Match {
445            discriminant: Box::new(Term::Var(inner.clone())),
446            motive: Box::new(Term::Lambda {
447                param: "_".into(),
448                param_type: Box::new(falsity),
449                body: Box::new(goal.target.clone()),
450            }),
451            cases: Vec::new(),
452        };
453        self.close(&m, proof, vec![inner]);
454        Ok(())
455    }
456
457    /// `constructor`: apply the first constructor of the goal's family that applies.
458    pub fn constructor(&mut self) -> Result<(), TacticError> {
459        self.constructor_among("constructor", None)
460    }
461
462    /// `left`: apply the first of the two constructors of the goal's family.
463    pub fn left(&mut self) -> Result<(), TacticError> {
464        self.constructor_among("left", Some(0))
465    }
466
467    /// `right`: apply the second of the two constructors of the goal's family.
468    pub fn right(&mut self) -> Result<(), TacticError> {
469        self.constructor_among("right", Some(1))
470    }
471
472    /// `exists w`: the goal's family has one constructor, applied to the family's parameters and
473    /// the witness `w`; its remaining premises are the goals (`Ex A P` leaves `P w`).
474    pub fn exists(&mut self, witness: &Term) -> Result<(), TacticError> {
475        let (_, goal) = self.focus()?;
476        let ctx = self.scope(&goal.hyps);
477        let w = localize(witness, &goal.hyps);
478        let (ind, args) = self.goal_family("exists", &ctx, &goal)?;
479        let constructors = ctx.get_constructors(&ind);
480        let [(constructor, _)] = constructors.as_slice() else {
481            return Err(refuse(
482                "exists",
483                &goal,
484                format!("`{ind}` has {} constructors, not one", constructors.len()),
485            ));
486        };
487        let np = ctx.inductive_num_params(&ind).min(args.len());
488        let mut parts = args[..np].to_vec();
489        parts.push(w);
490        let term = apps(Term::Global(constructor.to_string()), &parts);
491        self.apply_term("exists", term)
492    }
493
494    /// `have h : A`: prove `A` first, then the goal with the hypothesis `h : A`.
495    pub fn have(&mut self, name: &str, fact: &Term) -> Result<(), TacticError> {
496        let (m, goal) = self.focus()?;
497        if goal.hyps.iter().any(|(n, _)| n == name) {
498            return Err(refuse(
499                "have",
500                &goal,
501                format!("`{name}` already names a hypothesis"),
502            ));
503        }
504        let ctx = self.scope(&goal.hyps);
505        let fact = localize(fact, &goal.hyps);
506        infer_sort(&ctx, &fact).map_err(|e| {
507            refuse(
508                "have",
509                &goal,
510                format!("{} is not a type: {e}", Shown(&fact)),
511            )
512        })?;
513        let first = self.declare(goal.hyps.clone(), fact.clone());
514        let mut hyps = goal.hyps.clone();
515        hyps.push((name.to_string(), fact.clone()));
516        let then = self.declare(hyps, goal.target.clone());
517        let proof = Term::App(
518            Box::new(Term::Lambda {
519                param: name.to_string(),
520                param_type: Box::new(fact),
521                body: Box::new(Term::Var(then.clone())),
522            }),
523            Box::new(Term::Var(first.clone())),
524        );
525        self.close(&m, proof, vec![first, then]);
526        Ok(())
527    }
528
529    fn constructor_among(
530        &mut self,
531        tactic: &'static str,
532        only: Option<usize>,
533    ) -> Result<(), TacticError> {
534        let (_, goal) = self.focus()?;
535        let ctx = self.scope(&goal.hyps);
536        let (ind, _) = self.goal_family(tactic, &ctx, &goal)?;
537        let constructors: Vec<String> = ctx
538            .get_constructors(&ind)
539            .iter()
540            .map(|(n, _)| n.to_string())
541            .collect();
542        let candidates = match only {
543            Some(i) if constructors.len() == 2 => vec![constructors[i].clone()],
544            Some(_) => {
545                return Err(refuse(
546                    tactic,
547                    &goal,
548                    format!("`{ind}` has {} constructors, not two", constructors.len()),
549                ))
550            }
551            None => constructors,
552        };
553        let mut last = refuse(tactic, &goal, format!("`{ind}` has no constructor"));
554        for c in candidates {
555            match self.apply_term(tactic, Term::Global(c)) {
556                Ok(()) => return Ok(()),
557                Err(e) => last = e,
558            }
559        }
560        Err(last)
561    }
562
563    /// The inductive family the goal is an instance of, and its arguments.
564    fn goal_family(
565        &self,
566        tactic: &'static str,
567        ctx: &Context,
568        goal: &Goal,
569    ) -> Result<(String, Vec<Term>), TacticError> {
570        let family = demanded_type_head(ctx, goal.target.clone(), TypeHead::Inductive);
571        let (head, args) = spine(&family);
572        match head {
573            Term::Global(n) | Term::Const { name: n, .. } if ctx.is_inductive(n) => {
574                Ok((n.clone(), args))
575            }
576            _ => Err(refuse(
577                tactic,
578                goal,
579                "the goal is not an instance of an inductive family".to_string(),
580            )),
581        }
582    }
583
584    fn apply_term(&mut self, tactic: &'static str, term: Term) -> Result<(), TacticError> {
585        let (m, goal) = self.focus()?;
586        let ctx = self.scope(&goal.hyps);
587        let mut mctx = self.mctx.clone();
588        let (f, f_ty) = typed(&ctx, &mut mctx, &goal.hyps, &term, None).map_err(|e| {
589            refuse(
590                tactic,
591                &goal,
592                format!("{} does not type: {e}", Shown(&term)),
593            )
594        })?;
595        let f_ty = elaborate::instantiate(&f_ty, &mctx);
596        let (f_arity, target_arity) = (pi_arity(&ctx, &f_ty), pi_arity(&ctx, &goal.target));
597        let Some(n) = f_arity.checked_sub(target_arity) else {
598            return Err(refuse(
599                tactic,
600                &goal,
601                format!(
602                    "{} : {} has fewer premises than the goal",
603                    Shown(&f),
604                    Shown(&f_ty)
605                ),
606            ));
607        };
608        let mut ty = f_ty;
609        let mut args = Vec::with_capacity(n);
610        let mut premises = Vec::with_capacity(n);
611        for _ in 0..n {
612            let Term::Pi {
613                param,
614                param_type,
615                body_type,
616            } = demanded_type_head(&ctx, ty, TypeHead::Function)
617            else {
618                unreachable!("`pi_arity` counted these premises");
619            };
620            let a = mctx.fresh();
621            let Term::Var(name) = &a else {
622                unreachable!("a metavariable is a `Var`")
623            };
624            premises.push((name.clone(), *param_type));
625            ty = substitute(&body_type, &param, &a);
626            args.push(a);
627        }
628        if !elaborate::unify_in(&ctx, &mut mctx, &goal.hyps, &ty, &goal.target) {
629            let ty = elaborate::instantiate(&ty, &mctx);
630            return Err(refuse(
631                tactic,
632                &goal,
633                format!(
634                    "{}'s conclusion {} does not unify with the goal",
635                    Shown(&f),
636                    Shown(&ty)
637                ),
638            ));
639        }
640        self.mctx = mctx;
641        let mut subgoals = Vec::with_capacity(premises.len());
642        for (name, target) in premises {
643            self.decls.insert(
644                name.clone(),
645                Decl {
646                    hyps: goal.hyps.clone(),
647                    target,
648                },
649            );
650            subgoals.push(name);
651        }
652        let proof = args
653            .into_iter()
654            .fold(f, |acc, a| Term::App(Box::new(acc), Box::new(a)));
655        self.close(&m, proof, subgoals);
656        Ok(())
657    }
658
659    /// `rfl`: close an equation whose two sides convert.
660    pub fn rfl(&mut self) -> Result<(), TacticError> {
661        let (m, goal) = self.focus()?;
662        let ctx = self.scope(&goal.hyps);
663        let Some(eq) = equation(&ctx, &goal.target) else {
664            return Err(refuse(
665                "rfl",
666                &goal,
667                "the goal is not an equation".to_string(),
668            ));
669        };
670        let proof = apps(eq.refl(), &[eq.carrier.clone(), eq.lhs.clone()]);
671        match infer_type(&ctx, &proof) {
672            Ok(ty) if is_subtype(&ctx, &ty, &goal.target) => {}
673            _ => {
674                return Err(refuse(
675                    "rfl",
676                    &goal,
677                    format!("{} and {} do not convert", Shown(&eq.lhs), Shown(&eq.rhs)),
678                ))
679            }
680        }
681        self.close(&m, proof, Vec::new());
682        Ok(())
683    }
684
685    /// `change T`: replace the goal by a target it converts with.
686    pub fn change(&mut self, target: &Term) -> Result<(), TacticError> {
687        let (m, goal) = self.focus()?;
688        let ctx = self.scope(&goal.hyps);
689        let new = localize(target, &goal.hyps);
690        infer_sort(&ctx, &new).map_err(|e| {
691            refuse(
692                "change",
693                &goal,
694                format!("{} is not a type: {e}", Shown(&new)),
695            )
696        })?;
697        if !def_eq(&ctx, &new, &goal.target) {
698            return Err(refuse(
699                "change",
700                &goal,
701                format!("{} does not convert with the goal", Shown(&new)),
702            ));
703        }
704        let inner = self.declare(goal.hyps.clone(), new);
705        self.close(&m, Term::Var(inner.clone()), vec![inner]);
706        Ok(())
707    }
708
709    /// `rw [h]` (`reverse`: `rw [← h]`): with `h : Eq A l r`, replace every occurrence of `l` (of
710    /// `r`) in the goal by `r` (by `l`), occurrences under binders included, and close the goal by
711    /// transport along `h`.
712    pub fn rewrite(&mut self, eq_proof: &Term, reverse: bool) -> Result<(), TacticError> {
713        let (m, goal) = self.focus()?;
714        let ctx = self.scope(&goal.hyps);
715        let h = localize(eq_proof, &goal.hyps);
716        let h_ty = infer_type(&ctx, &h).map_err(|e| {
717            refuse(
718                "rewrite",
719                &goal,
720                format!("{} does not type: {e}", Shown(&h)),
721            )
722        })?;
723        let Some(eq) = equation(&ctx, &h_ty) else {
724            return Err(refuse(
725                "rewrite",
726                &goal,
727                format!("{} : {} is not an equation", Shown(&h), Shown(&h_ty)),
728            ));
729        };
730        let (from, to) = if reverse {
731            (&eq.rhs, &eq.lhs)
732        } else {
733            (&eq.lhs, &eq.rhs)
734        };
735        let x = fresh_name("x", &ctx, &goal.hyps, &[&goal.target, from, to]);
736        let (body, found) = abstract_occurrences(&goal.target, from, &x);
737        if !found {
738            return Err(refuse(
739                "rewrite",
740                &goal,
741                format!("{} does not occur in the goal", Shown(from)),
742            ));
743        }
744        let new_target = substitute(&body, &x, to);
745        let motive = Term::Lambda {
746            param: x,
747            param_type: Box::new(eq.carrier.clone()),
748            body: Box::new(body),
749        };
750        // `Eq_rec A to motive (_ : motive to) from (e : Eq A to from) : motive from`.
751        let back = if reverse {
752            h
753        } else {
754            apps(
755                Term::Global("Eq_sym".into()),
756                &[eq.carrier.clone(), eq.lhs.clone(), eq.rhs.clone(), h],
757            )
758        };
759        let inner = self.declare(goal.hyps.clone(), new_target.clone());
760        let proof = apps(
761            Term::Global("Eq_rec".into()),
762            &[
763                eq.carrier.clone(),
764                to.clone(),
765                motive,
766                Term::Var(inner.clone()),
767                from.clone(),
768                back,
769            ],
770        );
771        // Checked with the new goal standing in as a local of its type, before anything commits.
772        let probe = ctx.extend(&inner, new_target);
773        match infer_type(&probe, &proof) {
774            Ok(ty) if is_subtype(&probe, &ty, &goal.target) => {}
775            Ok(ty) => {
776                self.decls.remove(&inner);
777                return Err(refuse(
778                    "rewrite",
779                    &goal,
780                    format!("the transport proves {}, not the goal", Shown(&ty)),
781                ));
782            }
783            Err(e) => {
784                self.decls.remove(&inner);
785                return Err(refuse(
786                    "rewrite",
787                    &goal,
788                    format!("the transport does not type: {e}"),
789                ));
790            }
791        }
792        self.close(&m, proof, vec![inner]);
793        Ok(())
794    }
795
796    /// `induction x with | c₁ a… | c₂ b… …`: `x : I p̄ ī`, a hypothesis whose indices are distinct
797    /// hypotheses, is eliminated by `I_rec`. The motive abstracts `x`, its indices and every
798    /// hypothesis that depends on them (reintroduced in each case under its own name); each
799    /// constructor's case is a goal whose fields and induction hypotheses are named by `names[i]`,
800    /// in order, the rest named fresh.
801    pub fn induction(&mut self, var: &str, names: &[&[&str]]) -> Result<(), TacticError> {
802        let (m, goal) = self.focus()?;
803        let ctx = self.scope(&goal.hyps);
804        let Some((_, var_ty)) = goal.hyps.iter().find(|(n, _)| n == var).cloned() else {
805            return Err(refuse(
806                "induction",
807                &goal,
808                format!("`{var}` is not a hypothesis"),
809            ));
810        };
811        let family = demanded_type_head(&ctx, var_ty, TypeHead::Inductive);
812        let (head, args) = spine(&family);
813        let ind = match head {
814            Term::Global(n) | Term::Const { name: n, .. } if ctx.is_inductive(n) => n.clone(),
815            _ => {
816                return Err(refuse(
817                    "induction",
818                    &goal,
819                    format!("`{var}` : {} is not of an inductive family", Shown(&family)),
820                ))
821            }
822        };
823        let np = ctx.inductive_num_params(&ind);
824        let (params, indices) = args.split_at(np.min(args.len()));
825        let mut index_vars: Vec<String> = Vec::new();
826        for ix in indices {
827            match ix {
828                Term::Var(n)
829                    if goal.hyps.iter().any(|(h, _)| h == n)
830                        && !index_vars.contains(n)
831                        && !params
832                            .iter()
833                            .any(|p| crate::type_checker::occurs_free(p, n)) =>
834                {
835                    index_vars.push(n.clone())
836                }
837                other => {
838                    return Err(refuse(
839                        "induction",
840                        &goal,
841                        format!(
842                            "the index {} of `{var}`'s type is not a hypothesis of its own",
843                            Shown(other)
844                        ),
845                    ))
846                }
847            }
848        }
849        let rec_name = format!("{ind}_rec");
850        if ctx.get_global(&rec_name).is_none() && ctx.get_universe_poly(&rec_name).is_none() {
851            return Err(refuse(
852                "induction",
853                &goal,
854                format!("`{ind}` has no recursor `{rec_name}`"),
855            ));
856        }
857        // What the motive abstracts: `x`, its indices, and every hypothesis depending on them.
858        let mut abstracted: BTreeSet<String> = index_vars.iter().cloned().collect();
859        abstracted.insert(var.to_string());
860        let mut dependents: Vec<(String, Term)> = Vec::new();
861        for (n, ty) in &goal.hyps {
862            if abstracted.contains(n) {
863                continue;
864            }
865            if abstracted
866                .iter()
867                .any(|a| crate::type_checker::occurs_free(ty, a))
868            {
869                abstracted.insert(n.clone());
870                dependents.push((n.clone(), ty.clone()));
871            }
872        }
873        let kept: Vec<(String, Term)> = goal
874            .hyps
875            .iter()
876            .filter(|(n, _)| !abstracted.contains(n))
877            .cloned()
878            .collect();
879        let generalized = dependents
880            .iter()
881            .rev()
882            .fold(goal.target.clone(), |t, (n, ty)| Term::Pi {
883                param: n.clone(),
884                param_type: Box::new(ty.clone()),
885                body_type: Box::new(t),
886            });
887        // The motive's telescope, read off the recursor's motive binder at these parameters.
888        let rec = apps(Term::Global(rec_name), params);
889        let rec_ty = infer_type(&ctx, &rec).map_err(|e| {
890            refuse(
891                "induction",
892                &goal,
893                format!("the recursor does not type at these parameters: {e}"),
894            )
895        })?;
896        let Term::Pi {
897            param: motive_param,
898            param_type: motive_ty,
899            body_type: after_motive,
900        } = demanded_type_head(&ctx, rec_ty, TypeHead::Function)
901        else {
902            return Err(refuse(
903                "induction",
904                &goal,
905                "the recursor takes no motive".to_string(),
906            ));
907        };
908        let telescope = pi_telescope(&ctx, &motive_ty, index_vars.len() + 1);
909        if telescope.len() != index_vars.len() + 1 {
910            return Err(refuse(
911                "induction",
912                &goal,
913                format!(
914                    "the motive {} does not range over the family",
915                    Shown(&motive_ty)
916                ),
917            ));
918        }
919        let mut avoid_terms: Vec<&Term> = vec![&goal.target];
920        avoid_terms.extend(goal.hyps.iter().map(|(_, t)| t));
921        let mut motive_names: Vec<String> = Vec::new();
922        let mut motive_types: Vec<Term> = Vec::new();
923        for (i, (bname, bty)) in telescope.iter().enumerate() {
924            let mut bty = bty.clone();
925            for (j, prev) in motive_names.iter().enumerate() {
926                bty = substitute(&bty, &telescope[j].0, &Term::Var(prev.clone()));
927            }
928            let base = if i == index_vars.len() {
929                "y"
930            } else {
931                bname.as_str()
932            };
933            let fresh = fresh_name_avoiding(base, &ctx, &goal.hyps, &avoid_terms, &motive_names);
934            motive_names.push(fresh);
935            motive_types.push(bty);
936        }
937        let mut body = generalized;
938        for (ix, fresh) in index_vars.iter().zip(&motive_names) {
939            body = substitute(&body, ix, &Term::Var(fresh.clone()));
940        }
941        body = substitute(
942            &body,
943            var,
944            &Term::Var(motive_names[index_vars.len()].clone()),
945        );
946        let motive = motive_names
947            .iter()
948            .zip(&motive_types)
949            .rev()
950            .fold(body, |b, (n, t)| Term::Lambda {
951                param: n.clone(),
952                param_type: Box::new(t.clone()),
953                body: Box::new(b),
954            });
955        let mut ty = substitute(&after_motive, &motive_param, &motive);
956        // One goal per constructor: its minor premise's binders introduced under their names,
957        // then the dependents reintroduced.
958        let constructors = ctx.get_constructors(&ind).len();
959        let mut minors = Vec::with_capacity(constructors);
960        let mut cases = Vec::with_capacity(constructors);
961        let mut new_decls: Vec<(String, Decl)> = Vec::new();
962        let mut taken: Vec<String> = kept.iter().map(|(n, _)| n.clone()).collect();
963        for i in 0..constructors {
964            let Term::Pi {
965                param,
966                param_type,
967                body_type,
968            } = demanded_type_head(&ctx, ty, TypeHead::Function)
969            else {
970                return Err(refuse(
971                    "induction",
972                    &goal,
973                    "the recursor has fewer minor premises than constructors".to_string(),
974                ));
975            };
976            let minor_ty = beta_normal(&param_type);
977            let given: &[&str] = names.get(i).copied().unwrap_or(&[]);
978            let mut case_hyps = kept.clone();
979            let mut binders: Vec<(String, Term)> = Vec::new();
980            let mut t = minor_ty;
981            let mut j = 0;
982            let case_ctx_base = self.scope(&kept);
983            loop {
984                let case_ctx = binders
985                    .iter()
986                    .fold(case_ctx_base.clone(), |c, (n, ty)| c.extend(n, ty.clone()));
987                let Term::Pi {
988                    param: b,
989                    param_type: bt,
990                    body_type: rest,
991                } = &t
992                else {
993                    break;
994                };
995                let _ = &case_ctx;
996                let name = match given.get(j) {
997                    Some(n) => {
998                        if taken.iter().any(|t| t == n) {
999                            return Err(refuse(
1000                                "induction",
1001                                &goal,
1002                                format!("`{n}` already names a hypothesis"),
1003                            ));
1004                        }
1005                        n.to_string()
1006                    }
1007                    None => fresh_name_avoiding(b, &ctx, &case_hyps, &[&goal.target], &taken),
1008                };
1009                taken.push(name.clone());
1010                let rest = if *b == name {
1011                    (**rest).clone()
1012                } else {
1013                    substitute(rest, b, &Term::Var(name.clone()))
1014                };
1015                binders.push((name.clone(), (**bt).clone()));
1016                case_hyps.push((name, (**bt).clone()));
1017                t = rest;
1018                j += 1;
1019            }
1020            if given.len() > binders.len() {
1021                return Err(refuse(
1022                    "induction",
1023                    &goal,
1024                    format!(
1025                        "case {} names {} hypotheses and has {}",
1026                        i + 1,
1027                        given.len(),
1028                        binders.len()
1029                    ),
1030                ));
1031            }
1032            // Reintroduce the dependents under their own names.
1033            for (dn, _) in &dependents {
1034                let Term::Pi {
1035                    param: b,
1036                    param_type: bt,
1037                    body_type: rest,
1038                } = &t
1039                else {
1040                    unreachable!("the motive's body is a Π over the dependents");
1041                };
1042                let rest = if b == dn {
1043                    (**rest).clone()
1044                } else {
1045                    substitute(rest, b, &Term::Var(dn.clone()))
1046                };
1047                binders.push((dn.clone(), (**bt).clone()));
1048                case_hyps.push((dn.clone(), (**bt).clone()));
1049                t = rest;
1050            }
1051            let case = self.fresh_meta_name();
1052            new_decls.push((
1053                case.clone(),
1054                Decl {
1055                    hyps: case_hyps,
1056                    target: t,
1057                },
1058            ));
1059            let minor = binders
1060                .iter()
1061                .rev()
1062                .fold(Term::Var(case.clone()), |b, (n, ty)| Term::Lambda {
1063                    param: n.clone(),
1064                    param_type: Box::new(ty.clone()),
1065                    body: Box::new(b),
1066                });
1067            ty = substitute(&body_type, &param, &minor);
1068            minors.push(minor);
1069            cases.push(case);
1070        }
1071        let mut proof = apps(apps(rec, &[motive]), &minors);
1072        for ix in &index_vars {
1073            proof = Term::App(Box::new(proof), Box::new(Term::Var(ix.clone())));
1074        }
1075        proof = Term::App(Box::new(proof), Box::new(Term::Var(var.to_string())));
1076        for (dn, _) in &dependents {
1077            proof = Term::App(Box::new(proof), Box::new(Term::Var(dn.clone())));
1078        }
1079        for (name, decl) in new_decls {
1080            self.decls.insert(name, decl);
1081        }
1082        self.close(&m, proof, cases);
1083        Ok(())
1084    }
1085
1086    /// `cases x with | c₁ a… | …`: `x : I p̄ ī` is eliminated by an indexed `match`, which owes a
1087    /// case only for the constructors `ī` allows (`type_checker::constructors_the_indices_allow`):
1088    /// `cases h` with `h : Eq Nat (Succ a) Zero` closes the goal with no case at all, as Lean's
1089    /// `noConfusion` does. With indices that are hypotheses of their own, the motive abstracts them,
1090    /// `x`, and every hypothesis depending on them, as [`GoalState::induction`] does, without
1091    /// induction hypotheses. An index that is not a hypothesis, at a family a constructor can still
1092    /// produce it in, needs equations between indices and is refused.
1093    pub fn cases(&mut self, var: &str, names: &[&[&str]]) -> Result<(), TacticError> {
1094        let (m, goal) = self.focus()?;
1095        let ctx = self.scope(&goal.hyps);
1096        let Some((_, var_ty)) = goal.hyps.iter().find(|(n, _)| n == var).cloned() else {
1097            return Err(refuse(
1098                "cases",
1099                &goal,
1100                format!("`{var}` is not a hypothesis"),
1101            ));
1102        };
1103        let family = demanded_type_head(&ctx, var_ty, TypeHead::Inductive);
1104        let (head, args) = spine(&family);
1105        let ind = match head {
1106            Term::Global(n) | Term::Const { name: n, .. } if ctx.is_inductive(n) => n.clone(),
1107            _ => {
1108                return Err(refuse(
1109                    "cases",
1110                    &goal,
1111                    format!("`{var}` : {} is not of an inductive family", Shown(&family)),
1112                ))
1113            }
1114        };
1115        let np = ctx.inductive_num_params(&ind).min(args.len());
1116        let (params, indices) = args.split_at(np);
1117        let allowed = crate::type_checker::constructors_the_indices_allow(&ctx, &ind, np, &args);
1118        let index_vars: Option<Vec<String>> = {
1119            let mut vars: Vec<String> = Vec::new();
1120            let mut ok = true;
1121            for ix in indices {
1122                match ix {
1123                    Term::Var(n)
1124                        if goal.hyps.iter().any(|(h, _)| h == n)
1125                            && !vars.contains(n)
1126                            && !params
1127                                .iter()
1128                                .any(|p| crate::type_checker::occurs_free(p, n)) =>
1129                    {
1130                        vars.push(n.clone())
1131                    }
1132                    _ => ok = false,
1133                }
1134            }
1135            ok.then_some(vars)
1136        };
1137        if index_vars.is_none() && !allowed.is_empty() {
1138            let head = head.clone();
1139            return self.cases_by_equations(
1140                var, names, &m, &goal, &ctx, &head, params, indices, &allowed,
1141            );
1142        }
1143        let index_vars = index_vars.unwrap_or_default();
1144        let mut abstracted: BTreeSet<String> = index_vars.iter().cloned().collect();
1145        abstracted.insert(var.to_string());
1146        let mut dependents: Vec<(String, Term)> = Vec::new();
1147        for (n, ty) in &goal.hyps {
1148            if abstracted.contains(n) {
1149                continue;
1150            }
1151            if abstracted
1152                .iter()
1153                .any(|a| crate::type_checker::occurs_free(ty, a))
1154            {
1155                abstracted.insert(n.clone());
1156                dependents.push((n.clone(), ty.clone()));
1157            }
1158        }
1159        let kept: Vec<(String, Term)> = goal
1160            .hyps
1161            .iter()
1162            .filter(|(n, _)| !abstracted.contains(n))
1163            .cloned()
1164            .collect();
1165        let generalized = dependents
1166            .iter()
1167            .rev()
1168            .fold(goal.target.clone(), |t, (n, ty)| Term::Pi {
1169                param: n.clone(),
1170                param_type: Box::new(ty.clone()),
1171                body_type: Box::new(t),
1172            });
1173        // The motive ranges over the family's indices at these parameters and the scrutinee.
1174        let arity = infer_type(&ctx, &apps(head.clone(), params)).map_err(|e| {
1175            refuse(
1176                "cases",
1177                &goal,
1178                format!("the family does not type at these parameters: {e}"),
1179            )
1180        })?;
1181        let index_telescope = pi_telescope(&ctx, &arity, usize::MAX);
1182        let mut avoid: Vec<&Term> = vec![&goal.target];
1183        avoid.extend(goal.hyps.iter().map(|(_, t)| t));
1184        let mut motive_names: Vec<String> = Vec::new();
1185        let mut motive_types: Vec<Term> = Vec::new();
1186        for (bname, bty) in &index_telescope {
1187            let mut bty = bty.clone();
1188            for (j, prev) in motive_names.iter().enumerate() {
1189                bty = substitute(&bty, &index_telescope[j].0, &Term::Var(prev.clone()));
1190            }
1191            let fresh = fresh_name_avoiding(bname, &ctx, &goal.hyps, &avoid, &motive_names);
1192            motive_names.push(fresh);
1193            motive_types.push(bty);
1194        }
1195        let scrutinee_name = fresh_name_avoiding("y", &ctx, &goal.hyps, &avoid, &motive_names);
1196        let scrutinee_ty = apps(
1197            apps(head.clone(), params),
1198            &motive_names
1199                .iter()
1200                .map(|n| Term::Var(n.clone()))
1201                .collect::<Vec<_>>(),
1202        );
1203        let mut body = generalized;
1204        for (ix, fresh) in index_vars.iter().zip(&motive_names) {
1205            body = substitute(&body, ix, &Term::Var(fresh.clone()));
1206        }
1207        body = substitute(&body, var, &Term::Var(scrutinee_name.clone()));
1208        let motive = motive_names.iter().zip(&motive_types).rev().fold(
1209            Term::Lambda {
1210                param: scrutinee_name,
1211                param_type: Box::new(scrutinee_ty),
1212                body: Box::new(body),
1213            },
1214            |b, (n, t)| Term::Lambda {
1215                param: n.clone(),
1216                param_type: Box::new(t.clone()),
1217                body: Box::new(b),
1218            },
1219        );
1220        // A case per allowed constructor: its fields, then the dependents, introduced by name.
1221        let constructors = ctx.get_constructors(&ind);
1222        let mut case_terms = Vec::with_capacity(allowed.len());
1223        let mut case_goals = Vec::with_capacity(allowed.len());
1224        let mut new_decls: Vec<(String, Decl)> = Vec::new();
1225        let mut taken: Vec<String> = kept.iter().map(|(n, _)| n.clone()).collect();
1226        for (slot, &ci) in allowed.iter().enumerate() {
1227            let cname = constructors[ci].0.to_string();
1228            let ctor = apps(
1229                match head {
1230                    Term::Const { levels, .. } if ctx.poly_declaration(&cname).is_some() => {
1231                        Term::Const {
1232                            name: cname.clone(),
1233                            levels: levels.clone(),
1234                        }
1235                    }
1236                    _ => Term::Global(cname.clone()),
1237                },
1238                params,
1239            );
1240            let ctor_ty = infer_type(&ctx, &ctor).map_err(|e| {
1241                refuse(
1242                    "cases",
1243                    &goal,
1244                    format!("`{cname}` does not type at these parameters: {e}"),
1245                )
1246            })?;
1247            let given: &[&str] = names.get(slot).copied().unwrap_or(&[]);
1248            let mut case_hyps = kept.clone();
1249            let mut fields: Vec<(String, Term)> = Vec::new();
1250            let mut t = ctor_ty;
1251            let mut j = 0;
1252            loop {
1253                let scope = fields
1254                    .iter()
1255                    .fold(self.scope(&kept), |c, (n, ty)| c.extend(n, ty.clone()));
1256                let Term::Pi {
1257                    param: b,
1258                    param_type: bt,
1259                    body_type: rest,
1260                } = demanded_type_head(&scope, t.clone(), TypeHead::Function)
1261                else {
1262                    break;
1263                };
1264                let name = match given.get(j) {
1265                    Some(n) => {
1266                        if taken.iter().any(|t| t == n) {
1267                            return Err(refuse(
1268                                "cases",
1269                                &goal,
1270                                format!("`{n}` already names a hypothesis"),
1271                            ));
1272                        }
1273                        n.to_string()
1274                    }
1275                    None => fresh_name_avoiding(&b, &ctx, &case_hyps, &[&goal.target], &taken),
1276                };
1277                taken.push(name.clone());
1278                t = if b == name {
1279                    *rest
1280                } else {
1281                    substitute(&rest, &b, &Term::Var(name.clone()))
1282                };
1283                fields.push((name.clone(), (*bt).clone()));
1284                case_hyps.push((name, *bt));
1285                j += 1;
1286            }
1287            if given.len() > fields.len() {
1288                return Err(refuse(
1289                    "cases",
1290                    &goal,
1291                    format!(
1292                        "case `{cname}` names {} hypotheses and has {}",
1293                        given.len(),
1294                        fields.len()
1295                    ),
1296                ));
1297            }
1298            // `t` is the family at the constructor's result indices.
1299            let (_, result) = spine(&t);
1300            let result_indices = &result[np.min(result.len())..];
1301            let value = apps(
1302                ctor.clone(),
1303                &fields
1304                    .iter()
1305                    .map(|(n, _)| Term::Var(n.clone()))
1306                    .collect::<Vec<_>>(),
1307            );
1308            let mut target = beta_normal(&apps(apps(motive.clone(), result_indices), &[value]));
1309            let mut binders = fields.clone();
1310            for (dn, _) in &dependents {
1311                let Term::Pi {
1312                    param: b,
1313                    param_type: bt,
1314                    body_type: rest,
1315                } = &target
1316                else {
1317                    unreachable!("the motive's body is a Π over the dependents");
1318                };
1319                let rest = if b == dn {
1320                    (**rest).clone()
1321                } else {
1322                    substitute(rest, b, &Term::Var(dn.clone()))
1323                };
1324                binders.push((dn.clone(), (**bt).clone()));
1325                case_hyps.push((dn.clone(), (**bt).clone()));
1326                target = rest;
1327            }
1328            let case = self.fresh_meta_name();
1329            new_decls.push((
1330                case.clone(),
1331                Decl {
1332                    hyps: case_hyps,
1333                    target,
1334                },
1335            ));
1336            case_terms.push(
1337                binders
1338                    .iter()
1339                    .rev()
1340                    .fold(Term::Var(case.clone()), |b, (n, ty)| Term::Lambda {
1341                        param: n.clone(),
1342                        param_type: Box::new(ty.clone()),
1343                        body: Box::new(b),
1344                    }),
1345            );
1346            case_goals.push(case);
1347        }
1348        let mut proof = Term::Match {
1349            discriminant: Box::new(Term::Var(var.to_string())),
1350            motive: Box::new(motive),
1351            cases: case_terms,
1352        };
1353        for (dn, _) in &dependents {
1354            proof = Term::App(Box::new(proof), Box::new(Term::Var(dn.clone())));
1355        }
1356        for (name, decl) in new_decls {
1357            self.decls.insert(name, decl);
1358        }
1359        self.close(&m, proof, case_goals);
1360        Ok(())
1361    }
1362
1363    /// `cases x` where `x : I p̄ ī` and the indices `ī` are not hypotheses of their own. Each index
1364    /// is generalized with an equation `eₖ : Eq Uₖ īₖ jₖ`, and the goal is stated of `x` moved back
1365    /// to `I p̄ ī` along them, so it means what it meant. Each constructor's case then solves its
1366    /// equations: a hypothesis on either side is substituted, two applications of one constructor
1367    /// are injected, two different constructors close the case, and an equation whose sides convert
1368    /// is dropped; one none of these settles stays a hypothesis. Lean's `cases` unifies indices the
1369    /// same way. When an index's type depends on an earlier index, the equations would relate values
1370    /// of different types (heterogeneous equality), and the tactic says so.
1371    #[allow(clippy::too_many_arguments)]
1372    fn cases_by_equations(
1373        &mut self,
1374        var: &str,
1375        names: &[&[&str]],
1376        m: &str,
1377        goal: &Goal,
1378        ctx: &Context,
1379        head: &Term,
1380        params: &[Term],
1381        indices: &[Term],
1382        allowed: &[usize],
1383    ) -> Result<(), TacticError> {
1384        let family_at = |ix: &[Term]| apps(apps(head.clone(), params), ix);
1385        let arity = infer_type(ctx, &apps(head.clone(), params)).map_err(|e| {
1386            refuse(
1387                "cases",
1388                goal,
1389                format!("the family does not type at these parameters: {e}"),
1390            )
1391        })?;
1392        let telescope = pi_telescope(ctx, &arity, indices.len());
1393        for (k, (_, ty)) in telescope.iter().enumerate() {
1394            if let Some((earlier, _)) = telescope[..k]
1395                .iter()
1396                .find(|(n, _)| crate::type_checker::occurs_free(ty, n))
1397            {
1398                return Err(refuse(
1399                    "cases",
1400                    goal,
1401                    format!(
1402                        "the type {} of index {} depends on the index `{earlier}`: its equation would be heterogeneous",
1403                        Shown(ty),
1404                        k + 1
1405                    ),
1406                ));
1407            }
1408        }
1409        let index_types: Vec<Term> = telescope.into_iter().map(|(_, t)| t).collect();
1410        let n = indices.len();
1411        let mut avoid: Vec<&Term> = vec![&goal.target];
1412        avoid.extend(goal.hyps.iter().map(|(_, t)| t));
1413        avoid.extend(indices.iter());
1414        let mut taken: Vec<String> = goal.hyps.iter().map(|(h, _)| h.clone()).collect();
1415        let fresh = |base: &str, taken: &mut Vec<String>| {
1416            let name = fresh_name_avoiding(base, ctx, &goal.hyps, &avoid, taken);
1417            taken.push(name.clone());
1418            name
1419        };
1420        let js: Vec<String> = (0..n).map(|_| fresh("j", &mut taken)).collect();
1421        let es: Vec<String> = (0..n).map(|_| fresh("e", &mut taken)).collect();
1422        let zs: Vec<String> = (0..n).map(|_| fresh("z", &mut taken)).collect();
1423        let ws: Vec<String> = (0..n).map(|_| fresh("w", &mut taken)).collect();
1424        let y = fresh("y", &mut taken);
1425        let mut abstracted: BTreeSet<String> = BTreeSet::from([var.to_string()]);
1426        let mut dependents: Vec<(String, Term)> = Vec::new();
1427        for (h, ty) in &goal.hyps {
1428            if !abstracted.contains(h)
1429                && abstracted
1430                    .iter()
1431                    .any(|a| crate::type_checker::occurs_free(ty, a))
1432            {
1433                abstracted.insert(h.clone());
1434                dependents.push((h.clone(), ty.clone()));
1435            }
1436        }
1437        let kept: Vec<(String, Term)> = goal
1438            .hyps
1439            .iter()
1440            .filter(|(h, _)| !abstracted.contains(h))
1441            .cloned()
1442            .collect();
1443        // `y : I p̄ j̄` moved back to `I p̄ ī`, one index at a time, along `eₖ`.
1444        let cast = |value: Term| {
1445            (0..n).fold(value, |value, k| {
1446                let at = |z: Term| {
1447                    let mut ix: Vec<Term> = indices[..k].to_vec();
1448                    ix.push(z);
1449                    ix.extend(js[k + 1..].iter().map(|j| Term::Var(j.clone())));
1450                    family_at(&ix)
1451                };
1452                let landed = at(indices[k].clone());
1453                let motive = lam(
1454                    &zs[k],
1455                    index_types[k].clone(),
1456                    lam(
1457                        "_",
1458                        eq_term(&index_types[k], &indices[k], &Term::Var(zs[k].clone())),
1459                        pi("_", at(Term::Var(zs[k].clone())), landed.clone()),
1460                    ),
1461                );
1462                let transport = Term::Match {
1463                    discriminant: Box::new(Term::Var(es[k].clone())),
1464                    motive: Box::new(motive),
1465                    cases: vec![lam(&ws[k], landed, Term::Var(ws[k].clone()))],
1466                };
1467                Term::App(Box::new(transport), Box::new(value))
1468            })
1469        };
1470        let mentioned =
1471            crate::type_checker::occurs_free(&goal.target, var) || !dependents.is_empty();
1472        let moved = if mentioned {
1473            cast(Term::Var(y.clone()))
1474        } else {
1475            Term::Var(y.clone())
1476        };
1477        let mut body = dependents
1478            .iter()
1479            .rev()
1480            .fold(goal.target.clone(), |t, (h, ty)| pi(h, ty.clone(), t));
1481        body = substitute(&body, var, &moved);
1482        for k in (0..n).rev() {
1483            body = pi(
1484                &es[k],
1485                eq_term(&index_types[k], &indices[k], &Term::Var(js[k].clone())),
1486                body,
1487            );
1488        }
1489        let js_vars: Vec<Term> = js.iter().map(|j| Term::Var(j.clone())).collect();
1490        let mut motive = lam(&y, family_at(&js_vars), body);
1491        for k in (0..n).rev() {
1492            motive = lam(&js[k], index_types[k].clone(), motive);
1493        }
1494        // A case per allowed constructor: its fields introduced by name, its equations still to solve.
1495        // A given name may not be one a case's hypotheses already have (the kept hypotheses, the
1496        // equations, the reintroduced dependents); the motive's own binder names are no hypotheses.
1497        let constructors = ctx.get_constructors(&self.family_name(head));
1498        let mut case_terms = Vec::with_capacity(allowed.len());
1499        let mut case_goals = Vec::with_capacity(allowed.len());
1500        taken.extend(dependents.iter().map(|(h, _)| h.clone()));
1501        let hypotheses: Vec<String> = kept
1502            .iter()
1503            .map(|(h, _)| h.clone())
1504            .chain(es.iter().cloned())
1505            .chain(dependents.iter().map(|(h, _)| h.clone()))
1506            .collect();
1507        for (slot, &ci) in allowed.iter().enumerate() {
1508            let cname = constructors[ci].0.to_string();
1509            let ctor = apps(constructor_at(ctx, head, &cname), params);
1510            let ctor_ty = infer_type(ctx, &ctor).map_err(|e| {
1511                refuse(
1512                    "cases",
1513                    goal,
1514                    format!("`{cname}` does not type at these parameters: {e}"),
1515                )
1516            })?;
1517            let given: &[&str] = names.get(slot).copied().unwrap_or(&[]);
1518            let mut fields: Vec<(String, Term)> = Vec::new();
1519            let mut t = ctor_ty;
1520            loop {
1521                let scope = fields
1522                    .iter()
1523                    .fold(self.scope(&kept), |c, (h, ty)| c.extend(h, ty.clone()));
1524                let Term::Pi {
1525                    param: b,
1526                    param_type: bt,
1527                    body_type: rest,
1528                } = demanded_type_head(&scope, t.clone(), TypeHead::Function)
1529                else {
1530                    break;
1531                };
1532                let name = match given.get(fields.len()) {
1533                    Some(h)
1534                        if hypotheses
1535                            .iter()
1536                            .chain(fields.iter().map(|(f, _)| f))
1537                            .any(|t| t == h) =>
1538                    {
1539                        return Err(refuse(
1540                            "cases",
1541                            goal,
1542                            format!("`{h}` already names a hypothesis"),
1543                        ))
1544                    }
1545                    Some(h) => h.to_string(),
1546                    None => fresh_name_avoiding(&b, ctx, &kept, &[&goal.target], &taken),
1547                };
1548                taken.push(name.clone());
1549                t = if b == name {
1550                    *rest
1551                } else {
1552                    substitute(&rest, &b, &Term::Var(name.clone()))
1553                };
1554                fields.push((name, *bt));
1555            }
1556            if given.len() > fields.len() {
1557                return Err(refuse(
1558                    "cases",
1559                    goal,
1560                    format!(
1561                        "case `{cname}` names {} hypotheses and has {}",
1562                        given.len(),
1563                        fields.len()
1564                    ),
1565                ));
1566            }
1567            let (_, result) = spine(&t);
1568            let result_indices = &result[params.len().min(result.len())..];
1569            let value = apps(
1570                ctor.clone(),
1571                &fields
1572                    .iter()
1573                    .map(|(h, _)| Term::Var(h.clone()))
1574                    .collect::<Vec<_>>(),
1575            );
1576            let target = beta_normal(&apps(apps(motive.clone(), result_indices), &[value]));
1577            let mut case_hyps = kept.clone();
1578            case_hyps.extend(fields.iter().cloned());
1579            let case = self.declare(case_hyps, target);
1580            case_terms.push(
1581                fields
1582                    .iter()
1583                    .rev()
1584                    .fold(Term::Var(case.clone()), |b, (h, ty)| lam(h, ty.clone(), b)),
1585            );
1586            case_goals.push(case);
1587        }
1588        let mut proof = Term::Match {
1589            discriminant: Box::new(Term::Var(var.to_string())),
1590            motive: Box::new(motive),
1591            cases: case_terms,
1592        };
1593        for k in 0..n {
1594            proof = Term::App(
1595                Box::new(proof),
1596                Box::new(apps(
1597                    Term::Global("refl".into()),
1598                    &[index_types[k].clone(), indices[k].clone()],
1599                )),
1600            );
1601        }
1602        for (h, _) in &dependents {
1603            proof = Term::App(Box::new(proof), Box::new(Term::Var(h.clone())));
1604        }
1605        self.close(m, proof, case_goals.clone());
1606        for case in case_goals {
1607            let foreign: BTreeSet<String> =
1608                self.open.iter().filter(|g| **g != case).cloned().collect();
1609            self.on_goal(&case, |st| {
1610                for e in &es {
1611                    st.intro(e)?;
1612                }
1613                for (h, _) in &dependents {
1614                    st.intro(h)?;
1615                }
1616                st.solve_index_equations(es.clone(), &foreign)
1617            })?;
1618        }
1619        Ok(())
1620    }
1621
1622    /// The inductive family a family head names.
1623    fn family_name(&self, head: &Term) -> String {
1624        match head {
1625            Term::Global(n) | Term::Const { name: n, .. } => n.clone(),
1626            other => unreachable!("a family head is a constant, not {other:?}"),
1627        }
1628    }
1629
1630    /// Settle the index equations `equations` of the focused goal, one at a time, until the goal is
1631    /// closed (a goal in `foreign` is focused) or none is left that a step settles.
1632    fn solve_index_equations(
1633        &mut self,
1634        equations: Vec<String>,
1635        foreign: &BTreeSet<String>,
1636    ) -> Result<(), TacticError> {
1637        let mut queue: std::collections::VecDeque<String> = equations.into();
1638        let mut injected: BTreeSet<String> = BTreeSet::new();
1639        while let Some(e) = queue.pop_front() {
1640            match self.open.first() {
1641                Some(g) if !foreign.contains(g) => {}
1642                _ => return Ok(()),
1643            }
1644            let (_, goal) = self.focus()?;
1645            let Some((_, ty)) = goal.hyps.iter().find(|(h, _)| *h == e).cloned() else {
1646                continue;
1647            };
1648            let ctx = self.scope(&goal.hyps);
1649            let Some(eq) = equation(&ctx, &ty) else {
1650                continue;
1651            };
1652            if def_eq(&ctx, &eq.lhs, &eq.rhs) {
1653                let _ = self.clear(&e);
1654                continue;
1655            }
1656            if substitutable(&goal, &eq).is_some() {
1657                self.subst(&e)?;
1658                continue;
1659            }
1660            if injected.contains(&e) {
1661                continue;
1662            }
1663            let heads = (
1664                crate::type_checker::demanded_index_constructor_head(&ctx, &eq.lhs),
1665                crate::type_checker::demanded_index_constructor_head(&ctx, &eq.rhs),
1666            );
1667            if let (Some((cl, _)), Some((cr, _))) = heads {
1668                if ctx.constructor_inductive(&cl).is_some()
1669                    && ctx.constructor_inductive(&cl) == ctx.constructor_inductive(&cr)
1670                {
1671                    let produced = self.inject(&e, &[])?;
1672                    injected.insert(e.clone());
1673                    for h in produced.into_iter().rev() {
1674                        queue.push_front(h);
1675                    }
1676                    queue.push_back(e);
1677                }
1678            }
1679        }
1680        Ok(())
1681    }
1682
1683    /// Run `f` with the open goal `m` focused; the goals it leaves in `m`'s place stay there.
1684    fn on_goal(
1685        &mut self,
1686        m: &str,
1687        f: impl FnOnce(&mut Self) -> Result<(), TacticError>,
1688    ) -> Result<(), TacticError> {
1689        let Some(pos) = self.open.iter().position(|g| g == m) else {
1690            return Ok(());
1691        };
1692        let before: Vec<String> = self.open.clone();
1693        let goal = self.open.remove(pos);
1694        self.open.insert(0, goal);
1695        let result = f(self);
1696        let produced: Vec<String> = self
1697            .open
1698            .iter()
1699            .filter(|g| !before.contains(g) || *g == m)
1700            .cloned()
1701            .collect();
1702        let mut order: Vec<String> = before
1703            .iter()
1704            .filter(|g| *g != m && self.open.contains(g))
1705            .cloned()
1706            .collect();
1707        let at = pos.min(order.len());
1708        order.splice(at..at, produced);
1709        self.open = order;
1710        result
1711    }
1712
1713    /// `subst h`: `h : Eq A a b` where `b` (or else `a`) is a hypothesis that the other side and `A`
1714    /// do not mention; it is replaced by the other side everywhere and leaves the context with `h`.
1715    pub fn subst(&mut self, h: &str) -> Result<(), TacticError> {
1716        let (_, goal) = self.focus()?;
1717        let ctx = self.scope(&goal.hyps);
1718        let Some((_, h_ty)) = goal.hyps.iter().find(|(n, _)| n == h).cloned() else {
1719            return Err(refuse("subst", &goal, format!("`{h}` is not a hypothesis")));
1720        };
1721        let Some(eq) = equation(&ctx, &h_ty) else {
1722            return Err(refuse(
1723                "subst",
1724                &goal,
1725                format!("`{h}` : {} is not an equation", Shown(&h_ty)),
1726            ));
1727        };
1728        match substitutable(&goal, &eq) {
1729            Some(Side::Right) => self.cases(h, &[&[]]),
1730            Some(Side::Left) => {
1731                let flipped = fresh_name(h, &ctx, &goal.hyps, &[&goal.target]);
1732                let z = fresh_name("z", &ctx, &goal.hyps, &[&goal.target, &h_ty]);
1733                let symmetric = Term::Match {
1734                    discriminant: Box::new(Term::Var(h.to_string())),
1735                    motive: Box::new(lam(
1736                        &z,
1737                        eq.carrier.clone(),
1738                        lam(
1739                            "_",
1740                            eq_term(&eq.carrier, &eq.lhs, &Term::Var(z.clone())),
1741                            eq_term(&eq.carrier, &Term::Var(z.clone()), &eq.lhs),
1742                        ),
1743                    )),
1744                    cases: vec![apps(eq.refl(), &[eq.carrier.clone(), eq.lhs.clone()])],
1745                };
1746                self.assert_fact(&flipped, eq_term(&eq.carrier, &eq.rhs, &eq.lhs), symmetric)?;
1747                self.cases(&flipped, &[&[]])?;
1748                let _ = self.clear(h);
1749                Ok(())
1750            }
1751            None => Err(refuse(
1752                "subst",
1753                &goal,
1754                format!("neither side of `{h}` is a hypothesis the other side does not mention"),
1755            )),
1756        }
1757    }
1758
1759    /// `injection h as e₁ …`: `h : Eq T (C ā) (C b̄)` gives `eᵢ : Eq Tᵢ aᵢ bᵢ` for each field of `C`
1760    /// whose type no earlier field decides; `h : Eq T (C ā) (D b̄)` with `C ≠ D` closes the goal.
1761    /// Each fact is proved by transport along `h` into a predicate that matches on the value, the
1762    /// construction Lean's `noConfusion` packages.
1763    pub fn injection(&mut self, h: &str, names: &[&str]) -> Result<(), TacticError> {
1764        self.inject(h, names).map(|_| ())
1765    }
1766
1767    /// [`GoalState::injection`], with the names of the equations it introduced (none when it closed
1768    /// the goal).
1769    fn inject(&mut self, h: &str, names: &[&str]) -> Result<Vec<String>, TacticError> {
1770        let (m, goal) = self.focus()?;
1771        let ctx = self.scope(&goal.hyps);
1772        let Some((_, h_ty)) = goal.hyps.iter().find(|(n, _)| n == h).cloned() else {
1773            return Err(refuse(
1774                "injection",
1775                &goal,
1776                format!("`{h}` is not a hypothesis"),
1777            ));
1778        };
1779        let Some(eq) = equation(&ctx, &h_ty) else {
1780            return Err(refuse(
1781                "injection",
1782                &goal,
1783                format!("`{h}` : {} is not an equation", Shown(&h_ty)),
1784            ));
1785        };
1786        let family = demanded_type_head(&ctx, eq.carrier.clone(), TypeHead::Inductive);
1787        let (fhead, fargs) = spine(&family);
1788        let ind = match fhead {
1789            Term::Global(n) | Term::Const { name: n, .. } if ctx.is_inductive(n) => n.clone(),
1790            _ => {
1791                return Err(refuse(
1792                    "injection",
1793                    &goal,
1794                    format!("{} is not an inductive family", Shown(&eq.carrier)),
1795                ))
1796            }
1797        };
1798        let np = ctx.inductive_num_params(&ind).min(fargs.len());
1799        let read = |t: &Term| crate::type_checker::demanded_index_constructor_head(&ctx, t);
1800        let (Some((cl, al)), Some((cr, ar))) = (read(&eq.lhs), read(&eq.rhs)) else {
1801            return Err(refuse(
1802                "injection",
1803                &goal,
1804                format!(
1805                    "{} and {} are not both constructor applications",
1806                    Shown(&eq.lhs),
1807                    Shown(&eq.rhs)
1808                ),
1809            ));
1810        };
1811        let allowed = crate::type_checker::constructors_the_indices_allow(&ctx, &ind, np, &fargs);
1812        let constructors: Vec<String> = ctx
1813            .get_constructors(&ind)
1814            .iter()
1815            .map(|(c, _)| c.to_string())
1816            .collect();
1817        let mut avoid: Vec<&Term> = vec![&goal.target, &h_ty];
1818        avoid.extend(goal.hyps.iter().map(|(_, t)| t));
1819        // `P z := match z with | C x̄ => body C x̄ | … end`, a proposition about a value of the carrier.
1820        let index_types = {
1821            let arity = infer_type(&ctx, &apps(fhead.clone(), &fargs[..np])).map_err(|e| {
1822                refuse(
1823                    "injection",
1824                    &goal,
1825                    format!("the family does not type at these parameters: {e}"),
1826                )
1827            })?;
1828            pi_telescope(&ctx, &arity, fargs.len() - np)
1829        };
1830        let mut taken: Vec<String> = Vec::new();
1831        let fresh = |base: &str, taken: &mut Vec<String>| {
1832            let name = fresh_name_avoiding(base, &ctx, &goal.hyps, &avoid, taken);
1833            taken.push(name.clone());
1834            name
1835        };
1836        let jvars: Vec<String> = index_types.iter().map(|_| fresh("j", &mut taken)).collect();
1837        let zp = fresh("z", &mut taken);
1838        let z = fresh("z", &mut taken);
1839        let mut fields_of: Vec<Vec<(String, Term)>> = Vec::new();
1840        for &ci in &allowed {
1841            let ctor_ty = infer_type(
1842                &ctx,
1843                &apps(constructor_at(&ctx, fhead, &constructors[ci]), &fargs[..np]),
1844            )
1845            .map_err(|e| {
1846                refuse(
1847                    "injection",
1848                    &goal,
1849                    format!("`{}` does not type: {e}", constructors[ci]),
1850                )
1851            })?;
1852            // Each field renamed apart from everything in scope and from the telescope's own names,
1853            // later fields' types following the renaming.
1854            let telescope = pi_telescope(&ctx, &ctor_ty, usize::MAX);
1855            taken.extend(telescope.iter().map(|(b, _)| b.clone()));
1856            let mut fields: Vec<(String, Term)> = Vec::new();
1857            let mut renames: Vec<(String, String)> = Vec::new();
1858            for (b, bt) in telescope {
1859                let bt = renames.iter().fold(bt, |t, (from, to)| {
1860                    substitute(&t, from, &Term::Var(to.clone()))
1861                });
1862                let renamed = fresh(&b, &mut taken);
1863                renames.push((b, renamed.clone()));
1864                fields.push((renamed, bt));
1865            }
1866            fields_of.push(fields);
1867        }
1868        let predicate = |on: &dyn Fn(&str, &[(String, Term)]) -> Term, of: Term| -> Term {
1869            let mut motive = lam(
1870                &zp,
1871                apps(
1872                    apps(fhead.clone(), &fargs[..np]),
1873                    &jvars
1874                        .iter()
1875                        .map(|j| Term::Var(j.clone()))
1876                        .collect::<Vec<_>>(),
1877                ),
1878                Term::Sort(crate::term::Universe::Prop),
1879            );
1880            for (k, (_, t)) in index_types.iter().enumerate().rev() {
1881                motive = lam(&jvars[k], t.clone(), motive);
1882            }
1883            let cases = allowed
1884                .iter()
1885                .zip(&fields_of)
1886                .map(|(&ci, fields)| {
1887                    fields
1888                        .iter()
1889                        .rev()
1890                        .fold(on(&constructors[ci], fields), |b, (x, t)| {
1891                            lam(x, t.clone(), b)
1892                        })
1893                })
1894                .collect();
1895            Term::Match {
1896                discriminant: Box::new(of),
1897                motive: Box::new(motive),
1898                cases,
1899            }
1900        };
1901        let transport = |on: &dyn Fn(&str, &[(String, Term)]) -> Term, start: Term| -> Term {
1902            Term::Match {
1903                discriminant: Box::new(Term::Var(h.to_string())),
1904                motive: Box::new(lam(
1905                    &z,
1906                    eq.carrier.clone(),
1907                    lam(
1908                        "_",
1909                        eq_term(&eq.carrier, &eq.lhs, &Term::Var(z.clone())),
1910                        predicate(on, Term::Var(z.clone())),
1911                    ),
1912                )),
1913                cases: vec![start],
1914            }
1915        };
1916        if cl != cr {
1917            let falsity = transport(
1918                &|c: &str, _: &[(String, Term)]| {
1919                    Term::Global(if c == cl { "True" } else { "False" }.into())
1920                },
1921                Term::Global("I".into()),
1922            );
1923            let proof = Term::Match {
1924                discriminant: Box::new(falsity),
1925                motive: Box::new(lam("_", Term::Global("False".into()), goal.target.clone())),
1926                cases: Vec::new(),
1927            };
1928            match infer_type(&ctx, &proof) {
1929                Ok(ty) if is_subtype(&ctx, &ty, &goal.target) => {}
1930                Ok(ty) => {
1931                    return Err(refuse(
1932                        "injection",
1933                        &goal,
1934                        format!("the refutation proves {}", Shown(&ty)),
1935                    ))
1936                }
1937                Err(e) => {
1938                    return Err(refuse(
1939                        "injection",
1940                        &goal,
1941                        format!("the refutation does not type: {e}"),
1942                    ))
1943                }
1944            }
1945            self.close(&m, proof, Vec::new());
1946            return Ok(Vec::new());
1947        }
1948        let position = allowed
1949            .iter()
1950            .position(|&ci| constructors[ci] == cl)
1951            .ok_or_else(|| {
1952                refuse(
1953                    "injection",
1954                    &goal,
1955                    format!("`{cl}` cannot produce a value of {}", Shown(&eq.carrier)),
1956                )
1957            })?;
1958        let fields = &fields_of[position];
1959        let (lhs_fields, rhs_fields) = (&al[np.min(al.len())..], &ar[np.min(ar.len())..]);
1960        let mut facts: Vec<(String, Term, Term)> = Vec::new();
1961        for (k, (_, field_ty)) in fields.iter().enumerate() {
1962            if fields[..k]
1963                .iter()
1964                .any(|(x, _)| crate::type_checker::occurs_free(field_ty, x))
1965            {
1966                continue;
1967            }
1968            let (Some(a), Some(b)) = (lhs_fields.get(k), rhs_fields.get(k)) else {
1969                continue;
1970            };
1971            let on = |c: &str, xs: &[(String, Term)]| {
1972                if c == cl {
1973                    eq_term(field_ty, a, &Term::Var(xs[k].0.clone()))
1974                } else {
1975                    Term::Global("True".into())
1976                }
1977            };
1978            let proof = transport(
1979                &on,
1980                apps(Term::Global("refl".into()), &[field_ty.clone(), a.clone()]),
1981            );
1982            let fact = eq_term(field_ty, a, b);
1983            match infer_type(&ctx, &proof) {
1984                Ok(ty) if is_subtype(&ctx, &ty, &fact) => {}
1985                Ok(ty) => {
1986                    return Err(refuse(
1987                        "injection",
1988                        &goal,
1989                        format!("the field's transport proves {}", Shown(&ty)),
1990                    ))
1991                }
1992                Err(e) => {
1993                    return Err(refuse(
1994                        "injection",
1995                        &goal,
1996                        format!("the field's transport does not type: {e}"),
1997                    ))
1998                }
1999            }
2000            let name = match names.get(facts.len()) {
2001                Some(n) if goal.hyps.iter().any(|(h, _)| h == n) => {
2002                    return Err(refuse(
2003                        "injection",
2004                        &goal,
2005                        format!("`{n}` already names a hypothesis"),
2006                    ))
2007                }
2008                Some(n) => n.to_string(),
2009                None => fresh_name_avoiding(
2010                    "e",
2011                    &ctx,
2012                    &goal.hyps,
2013                    &avoid,
2014                    &facts.iter().map(|(n, _, _)| n.clone()).collect::<Vec<_>>(),
2015                ),
2016            };
2017            facts.push((name, fact, proof));
2018        }
2019        if facts.is_empty() {
2020            return Err(refuse(
2021                "injection",
2022                &goal,
2023                format!("`{cl}` has no field whose equation can be stated"),
2024            ));
2025        }
2026        if names.len() > facts.len() {
2027            return Err(refuse(
2028                "injection",
2029                &goal,
2030                format!("{} names for {} equations", names.len(), facts.len()),
2031            ));
2032        }
2033        let mut hyps = goal.hyps.clone();
2034        hyps.extend(facts.iter().map(|(n, t, _)| (n.clone(), t.clone())));
2035        let inner = self.declare(hyps, goal.target.clone());
2036        let lambda = facts
2037            .iter()
2038            .rev()
2039            .fold(Term::Var(inner.clone()), |b, (n, t, _)| {
2040                lam(n, t.clone(), b)
2041            });
2042        let proof = apps(
2043            lambda,
2044            &facts.iter().map(|(_, _, p)| p.clone()).collect::<Vec<_>>(),
2045        );
2046        self.close(&m, proof, vec![inner]);
2047        Ok(facts.into_iter().map(|(n, _, _)| n).collect())
2048    }
2049
2050    /// `calc [R₁ := p₁, R₂ := p₂, …]`: a chain of equations `Eq A l r`, a step's left side `_` read as
2051    /// the previous step's right side, joined by transitivity into a proof that the first left side
2052    /// equals the last right side, which closes the goal. A step proved by `_` is left as a goal.
2053    pub fn calc(&mut self, steps: &[(Term, Term)]) -> Result<(), TacticError> {
2054        let (m, goal) = self.focus()?;
2055        let ctx = self.scope(&goal.hyps);
2056        if steps.is_empty() {
2057            return Err(refuse(
2058                "calc",
2059                &goal,
2060                "a calculation has at least one step".to_string(),
2061            ));
2062        }
2063        let mut mctx = self.mctx.clone();
2064        let mut carrier: Option<Term> = None;
2065        let mut first: Option<Term> = None;
2066        let mut last: Option<Term> = None;
2067        let mut proofs: Vec<(Term, Term)> = Vec::with_capacity(steps.len());
2068        let mut holes: Vec<(String, Term)> = Vec::new();
2069        for (i, (relation, proof)) in steps.iter().enumerate() {
2070            let step = i + 1;
2071            let relation = localize(relation, &goal.hyps);
2072            let (head, args) = spine(&relation);
2073            let (Term::Global(eq) | Term::Const { name: eq, .. }) = head else {
2074                return Err(refuse(
2075                    "calc",
2076                    &goal,
2077                    format!("step {step}, {}, is not an equation", Shown(&relation)),
2078                ));
2079            };
2080            let [a, l, r] = args.as_slice() else {
2081                return Err(refuse(
2082                    "calc",
2083                    &goal,
2084                    format!("step {step}, {}, is not an equation", Shown(&relation)),
2085                ));
2086            };
2087            if eq != "Eq" {
2088                return Err(refuse(
2089                    "calc",
2090                    &goal,
2091                    format!("step {step}, {}, is not an equation", Shown(&relation)),
2092                ));
2093            }
2094            let l = match (l, &last) {
2095                (Term::Hole, Some(previous)) => previous.clone(),
2096                (Term::Hole, None) => {
2097                    return Err(refuse(
2098                        "calc",
2099                        &goal,
2100                        "the first step's left side has no previous step to read".to_string(),
2101                    ))
2102                }
2103                (l, Some(previous)) if !def_eq(&ctx, previous, l) => {
2104                    return Err(refuse(
2105                        "calc",
2106                        &goal,
2107                        format!(
2108                            "step {step} starts at {}, and step {i} ends at {}",
2109                            Shown(l),
2110                            Shown(previous)
2111                        ),
2112                    ))
2113                }
2114                (l, _) => l.clone(),
2115            };
2116            if let Some(c) = &carrier {
2117                if !def_eq(&ctx, c, a) {
2118                    return Err(refuse(
2119                        "calc",
2120                        &goal,
2121                        format!(
2122                            "step {step} relates values of {}, not of {}",
2123                            Shown(a),
2124                            Shown(c)
2125                        ),
2126                    ));
2127                }
2128            }
2129            let stated = eq_term(a, &l, r);
2130            infer_sort(&ctx, &stated).map_err(|e| {
2131                refuse(
2132                    "calc",
2133                    &goal,
2134                    format!("step {step}, {}, is not a type: {e}", Shown(&stated)),
2135                )
2136            })?;
2137            let proof = localize(proof, &goal.hyps);
2138            if matches!(proof, Term::Hole) {
2139                let name = fresh_meta(&mut mctx);
2140                holes.push((name.clone(), stated.clone()));
2141                proofs.push((Term::Var(name), stated));
2142            } else {
2143                let (value, ty) = typed(&ctx, &mut mctx, &goal.hyps, &proof, Some(&stated))
2144                    .map_err(|e| {
2145                        refuse(
2146                            "calc",
2147                            &goal,
2148                            format!("step {step}'s proof {} does not type: {e}", Shown(&proof)),
2149                        )
2150                    })?;
2151                elaborate::unify_in(&ctx, &mut mctx, &goal.hyps, &ty, &stated);
2152                let (value, ty) = (
2153                    elaborate::instantiate(&value, &mctx),
2154                    elaborate::instantiate(&ty, &mctx),
2155                );
2156                if elaborate::mentions_meta(&value) {
2157                    return Err(refuse(
2158                        "calc",
2159                        &goal,
2160                        format!("step {step}'s proof leaves a hole; write `_` for the whole proof to leave it as a goal"),
2161                    ));
2162                }
2163                if !is_subtype(&ctx, &ty, &stated) {
2164                    return Err(refuse(
2165                        "calc",
2166                        &goal,
2167                        format!(
2168                            "step {step} states {}, and its proof proves {}",
2169                            Shown(&stated),
2170                            Shown(&ty)
2171                        ),
2172                    ));
2173                }
2174                proofs.push((value, stated));
2175            }
2176            carrier.get_or_insert_with(|| a.clone());
2177            first.get_or_insert_with(|| l.clone());
2178            last = Some(r.clone());
2179        }
2180        let (carrier, first, last) = (
2181            carrier.expect("a step"),
2182            first.expect("a step"),
2183            last.expect("a step"),
2184        );
2185        let conclusion = eq_term(&carrier, &first, &last);
2186        if !is_subtype(&ctx, &conclusion, &goal.target) {
2187            return Err(refuse(
2188                "calc",
2189                &goal,
2190                format!(
2191                    "the calculation proves {}, and the goal is {}",
2192                    Shown(&conclusion),
2193                    Shown(&goal.target)
2194                ),
2195            ));
2196        }
2197        // `trans p q := match q return (λ w _. Eq A x w) with p end`, folded over the steps.
2198        let mut avoid: Vec<&Term> = vec![&goal.target];
2199        avoid.extend(proofs.iter().map(|(p, _)| p));
2200        let w = fresh_name("w", &ctx, &goal.hyps, &avoid);
2201        let mut chained = proofs[0].0.clone();
2202        for (q, stated) in &proofs[1..] {
2203            let (_, args) = spine(stated);
2204            let y = args[1].clone();
2205            chained = Term::Match {
2206                discriminant: Box::new(q.clone()),
2207                motive: Box::new(lam(
2208                    &w,
2209                    carrier.clone(),
2210                    lam(
2211                        "_",
2212                        eq_term(&carrier, &y, &Term::Var(w.clone())),
2213                        eq_term(&carrier, &first, &Term::Var(w.clone())),
2214                    ),
2215                )),
2216                cases: vec![chained],
2217            };
2218        }
2219        // Checked with each step left open standing in as a local of its statement.
2220        let mut probe_ctx = ctx.clone();
2221        let mut probe = MetaCtx::new();
2222        let mut locals: Vec<String> = Vec::new();
2223        for (name, stated) in &holes {
2224            let local =
2225                fresh_name_avoiding("step", &ctx, &goal.hyps, &[&chained, &goal.target], &locals);
2226            locals.push(local.clone());
2227            probe_ctx = probe_ctx.extend(&local, stated.clone());
2228            probe.solve(name, Term::Var(local));
2229        }
2230        let checked = elaborate::instantiate(&chained, &probe);
2231        match infer_type(&probe_ctx, &checked) {
2232            Ok(t) if is_subtype(&probe_ctx, &t, &goal.target) => {}
2233            Ok(t) => {
2234                return Err(refuse(
2235                    "calc",
2236                    &goal,
2237                    format!("the chain proves {}, not the goal", Shown(&t)),
2238                ))
2239            }
2240            Err(e) => {
2241                return Err(refuse(
2242                    "calc",
2243                    &goal,
2244                    format!("the chain does not type: {e}"),
2245                ))
2246            }
2247        }
2248        self.mctx = mctx;
2249        let mut subgoals = Vec::with_capacity(holes.len());
2250        for (name, stated) in holes {
2251            self.decls.insert(
2252                name.clone(),
2253                Decl {
2254                    hyps: goal.hyps.clone(),
2255                    target: stated,
2256                },
2257            );
2258            subgoals.push(name);
2259        }
2260        self.close(&m, chained, subgoals);
2261        Ok(())
2262    }
2263
2264    /// `cc`: close an equation the hypotheses entail by congruence: their equations, read in either
2265    /// direction and chained, and the rule that equal functions applied to equal arguments are equal.
2266    /// The proof is built from the hypotheses by symmetry, transitivity and congruence, each a `match`
2267    /// on an equation's proof, so nothing is trusted: `qed` checks the chain.
2268    pub fn cc(&mut self) -> Result<(), TacticError> {
2269        let (m, goal) = self.focus()?;
2270        let ctx = self.scope(&goal.hyps);
2271        let Some(target) = equation(&ctx, &goal.target) else {
2272            return Err(refuse(
2273                "cc",
2274                &goal,
2275                "the goal is not an equation".to_string(),
2276            ));
2277        };
2278        let mut closure = congruence::Closure::new(&ctx, &goal);
2279        for (h, ty) in &goal.hyps {
2280            if let Some(eq) = equation(&ctx, ty) {
2281                closure.assert_hypothesis(h, &eq.lhs, &eq.rhs);
2282            }
2283        }
2284        let (a, b) = (closure.term(&target.lhs), closure.term(&target.rhs));
2285        closure.saturate();
2286        let Some(proof) = closure.prove(a, b) else {
2287            return Err(refuse(
2288                "cc",
2289                &goal,
2290                format!(
2291                    "the hypotheses do not entail {} = {}",
2292                    Shown(&target.lhs),
2293                    Shown(&target.rhs)
2294                ),
2295            ));
2296        };
2297        match infer_type(&ctx, &proof) {
2298            Ok(ty) if is_subtype(&ctx, &ty, &goal.target) => {}
2299            Ok(ty) => {
2300                return Err(refuse(
2301                    "cc",
2302                    &goal,
2303                    format!("the chain proves {}, not the goal", Shown(&ty)),
2304                ))
2305            }
2306            Err(e) => return Err(refuse("cc", &goal, format!("the chain does not type: {e}"))),
2307        }
2308        self.close(&m, proof, Vec::new());
2309        Ok(())
2310    }
2311
2312    /// `decide`: a goal `P` with a `Decidable P` instance the kernel evaluates to `isTrue`. The proof is
2313    /// `of_decide_eq_true P inst (refl Bool true)`, which the kernel accepts by computing
2314    /// `decide P inst` to `true`: a proved bridge and conversion, nothing assumed.
2315    pub fn decide(&mut self) -> Result<(), TacticError> {
2316        let (m, goal) = self.focus()?;
2317        let ctx = self.scope(&goal.hyps);
2318        let required = apps(Term::Global("Decidable".into()), &[goal.target.clone()]);
2319        let mut mctx = self.mctx.clone();
2320        let Some(instance) = elaborate::resolve_instance(&ctx, &mut mctx, &required) else {
2321            return Err(refuse(
2322                "decide",
2323                &goal,
2324                format!("no `Decidable` instance decides {}", Shown(&goal.target)),
2325            ));
2326        };
2327        let instance = elaborate::instantiate(&instance, &mctx);
2328        let verdict = crate::reduction::normalize(
2329            &ctx,
2330            &apps(
2331                Term::Global("decide".into()),
2332                &[goal.target.clone(), instance.clone()],
2333            ),
2334        );
2335        match &verdict {
2336            Term::Global(b) if b == "true" => {}
2337            Term::Global(b) if b == "false" => {
2338                return Err(refuse(
2339                    "decide",
2340                    &goal,
2341                    format!(
2342                        "{} is false: its instance decides it so",
2343                        Shown(&goal.target)
2344                    ),
2345                ))
2346            }
2347            other => {
2348                return Err(refuse(
2349                    "decide",
2350                    &goal,
2351                    format!(
2352                        "the instance does not compute to a verdict: `decide` reduces to {}",
2353                        Shown(other)
2354                    ),
2355                ))
2356            }
2357        }
2358        let proof = apps(
2359            Term::Global("of_decide_eq_true".into()),
2360            &[
2361                goal.target.clone(),
2362                instance,
2363                apps(
2364                    Term::Global("refl".into()),
2365                    &[Term::Global("Bool".into()), Term::Global("true".into())],
2366                ),
2367            ],
2368        );
2369        match infer_type(&ctx, &proof) {
2370            Ok(ty) if is_subtype(&ctx, &ty, &goal.target) => {}
2371            Ok(ty) => {
2372                return Err(refuse(
2373                    "decide",
2374                    &goal,
2375                    format!("the bridge proves {}, not the goal", Shown(&ty)),
2376                ))
2377            }
2378            Err(e) => {
2379                return Err(refuse(
2380                    "decide",
2381                    &goal,
2382                    format!("the bridge does not type: {e}"),
2383                ))
2384            }
2385        }
2386        self.close(&m, proof, Vec::new());
2387        Ok(())
2388    }
2389
2390    /// `simp [l₁ …]`: rewrite the goal by each lemma (`Π x̄. Eq A l r`: a hypothesis, a constant or a
2391    /// term), left to right, wherever an instance of `l` stands outside every binder of the goal,
2392    /// until none applies, then close the goal by `rfl` when its sides agree. With no lemma it
2393    /// rewrites by the context's `@[simp]` theorems. Each rewrite is a `rewrite` along the lemma at
2394    /// the matched instance, so the proof is the chain of their transports with each lemma cited
2395    /// by name, and nothing is trusted. A run that rewrites nothing is refused, as is one still
2396    /// rewriting after [`SIMP_STEPS`] rewrites; either leaves the goal as it found it.
2397    pub fn simp(&mut self, lemmas: &[Term]) -> Result<(), TacticError> {
2398        let saved = self.clone();
2399        let result = self.simp_rewrites(lemmas);
2400        if result.is_err() {
2401            *self = saved;
2402        }
2403        result
2404    }
2405
2406    fn simp_rewrites(&mut self, lemmas: &[Term]) -> Result<(), TacticError> {
2407        let (_, goal) = self.focus()?;
2408        let ctx = self.scope(&goal.hyps);
2409        let rules: Vec<(Term, SimpRule)> = if lemmas.is_empty() {
2410            ctx.simp_rules()
2411                .iter()
2412                .map(|r| (Term::Global(r.name.clone()), r.clone()))
2413                .collect()
2414        } else {
2415            lemmas
2416                .iter()
2417                .map(|lemma| {
2418                    let proof = localize(lemma, &goal.hyps);
2419                    let ty = infer_type(&ctx, &proof).map_err(|e| {
2420                        refuse(
2421                            "simp",
2422                            &goal,
2423                            format!("{} does not type: {e}", Shown(&proof)),
2424                        )
2425                    })?;
2426                    let rule = SimpRule::compile(&Shown(&proof).to_string(), &ty).map_err(|e| {
2427                        refuse(
2428                            "simp",
2429                            &goal,
2430                            format!("{} : {} is not a rewrite: {e}", Shown(&proof), Shown(&ty)),
2431                        )
2432                    })?;
2433                    Ok((proof, rule))
2434                })
2435                .collect::<Result<_, TacticError>>()?
2436        };
2437        if rules.is_empty() {
2438            return Err(refuse(
2439                "simp",
2440                &goal,
2441                "there is no lemma to rewrite by".to_string(),
2442            ));
2443        }
2444        let mut fired: Vec<String> = Vec::new();
2445        loop {
2446            let (_, goal) = self.focus()?;
2447            let Some((proof, name)) = simp_instance(&rules, &goal.target, &mut Vec::new()) else {
2448                break;
2449            };
2450            if fired.len() == SIMP_STEPS {
2451                let last: Vec<&str> = fired.iter().rev().take(4).map(String::as_str).collect();
2452                return Err(refuse(
2453                    "simp",
2454                    &goal,
2455                    format!(
2456                        "still rewriting after {SIMP_STEPS} rewrites (the last by {})",
2457                        last.join(", ")
2458                    ),
2459                ));
2460            }
2461            self.rewrite(&proof, false)?;
2462            fired.push(name);
2463        }
2464        if fired.is_empty() {
2465            let (_, goal) = self.focus()?;
2466            return Err(refuse(
2467                "simp",
2468                &goal,
2469                "no lemma rewrites the goal".to_string(),
2470            ));
2471        }
2472        if self
2473            .focus()
2474            .is_ok_and(|(_, goal)| equation(&self.scope(&goal.hyps), &goal.target).is_some())
2475        {
2476            let _ = self.rfl();
2477        }
2478        Ok(())
2479    }
2480
2481    /// Add the hypothesis `name : fact`, proved by `proof` here and now.
2482    fn assert_fact(&mut self, name: &str, fact: Term, proof: Term) -> Result<(), TacticError> {
2483        let (m, goal) = self.focus()?;
2484        let ctx = self.scope(&goal.hyps);
2485        match infer_type(&ctx, &proof) {
2486            Ok(ty) if is_subtype(&ctx, &ty, &fact) => {}
2487            Ok(ty) => {
2488                return Err(refuse(
2489                    "have",
2490                    &goal,
2491                    format!("the proof proves {}, not {}", Shown(&ty), Shown(&fact)),
2492                ))
2493            }
2494            Err(e) => {
2495                return Err(refuse(
2496                    "have",
2497                    &goal,
2498                    format!("the proof of {} does not type: {e}", Shown(&fact)),
2499                ))
2500            }
2501        }
2502        let mut hyps = goal.hyps.clone();
2503        hyps.push((name.to_string(), fact.clone()));
2504        let inner = self.declare(hyps, goal.target.clone());
2505        self.close(
2506            &m,
2507            Term::App(
2508                Box::new(lam(name, fact, Term::Var(inner.clone()))),
2509                Box::new(proof),
2510            ),
2511            vec![inner],
2512        );
2513        Ok(())
2514    }
2515
2516    /// Drop the hypothesis `h`, which nothing in the goal mentions.
2517    fn clear(&mut self, h: &str) -> Result<(), TacticError> {
2518        let (m, goal) = self.focus()?;
2519        if !goal.hyps.iter().any(|(n, _)| n == h) {
2520            return Err(refuse("clear", &goal, format!("`{h}` is not a hypothesis")));
2521        }
2522        if crate::type_checker::occurs_free(&goal.target, h)
2523            || goal
2524                .hyps
2525                .iter()
2526                .any(|(n, t)| n != h && crate::type_checker::occurs_free(t, h))
2527        {
2528            return Err(refuse(
2529                "clear",
2530                &goal,
2531                format!("the goal still mentions `{h}`"),
2532            ));
2533        }
2534        let hyps: Vec<(String, Term)> = goal.hyps.iter().filter(|(n, _)| n != h).cloned().collect();
2535        let inner = self.declare(hyps, goal.target.clone());
2536        self.close(&m, Term::Var(inner.clone()), vec![inner]);
2537        Ok(())
2538    }
2539
2540    /// `refine e`: `e` with holes (`_`), elaborated against the goal; every hole unification leaves
2541    /// open becomes a goal, in the order the holes appear, with the binders around it as hypotheses.
2542    pub fn refine(&mut self, term: &Term) -> Result<(), TacticError> {
2543        let (m, goal) = self.focus()?;
2544        let ctx = self.scope(&goal.hyps);
2545        let term = localize(term, &goal.hyps);
2546        let mut mctx = self.mctx.clone();
2547        let first = mctx.holes().len();
2548        let (value, ty) =
2549            elaborate::elaborate_in(&ctx, &mut mctx, &goal.hyps, &term, Some(&goal.target))
2550                .map_err(|e| {
2551                    refuse(
2552                        "refine",
2553                        &goal,
2554                        format!(
2555                            "{} does not elaborate against the goal {}: {e}",
2556                            Shown(&term),
2557                            Shown(&goal.target)
2558                        ),
2559                    )
2560                })?;
2561        if !elaborate::unify_in(&ctx, &mut mctx, &goal.hyps, &ty, &goal.target) {
2562            let ty = elaborate::instantiate(&ty, &mctx);
2563            return Err(refuse(
2564                "refine",
2565                &goal,
2566                format!(
2567                    "the goal is {}, and {} has type {}",
2568                    Shown(&goal.target),
2569                    Shown(&term),
2570                    Shown(&ty)
2571                ),
2572            ));
2573        }
2574        let value = elaborate::instantiate(&value, &mctx);
2575        let holes: Vec<(String, Vec<(String, Term)>, Term)> = mctx.holes()[first..]
2576            .iter()
2577            .filter(|h| mctx.solution(&h.name).is_none())
2578            .map(|h| {
2579                let scope = h
2580                    .scope
2581                    .iter()
2582                    .map(|(n, t)| (n.clone(), elaborate::instantiate(t, &mctx)))
2583                    .collect();
2584                (h.name.clone(), scope, elaborate::instantiate(&h.ty, &mctx))
2585            })
2586            .collect();
2587        for (_, scope, ty) in &holes {
2588            if elaborate::mentions_meta(ty) {
2589                return Err(refuse(
2590                    "refine",
2591                    &goal,
2592                    format!("the type {} of a hole is not determined", Shown(ty)),
2593                ));
2594            }
2595            if let Some((n, _)) = scope
2596                .iter()
2597                .enumerate()
2598                .find(|(i, (n, _))| scope[..*i].iter().any(|(o, _)| o == n))
2599                .map(|(_, b)| b)
2600            {
2601                return Err(refuse(
2602                    "refine",
2603                    &goal,
2604                    format!("a binder around a hole shadows the hypothesis `{n}`; name it apart"),
2605                ));
2606            }
2607        }
2608        let open: BTreeSet<&str> = holes.iter().map(|(n, _, _)| n.as_str()).collect();
2609        let mut stray = BTreeSet::new();
2610        metas(&value, &mut stray);
2611        if let Some(meta) = stray.iter().find(|n| !open.contains(n.as_str())) {
2612            return Err(refuse(
2613                "refine",
2614                &goal,
2615                format!(
2616                    "{} leaves {meta}, an argument unification cannot determine",
2617                    Shown(&term)
2618                ),
2619            ));
2620        }
2621        // Checked with each hole standing in as a local function of the binders around it.
2622        let mut probe_ctx = ctx.clone();
2623        let mut probe = MetaCtx::new();
2624        let mut locals: Vec<String> = Vec::new();
2625        for (name, scope, ty) in &holes {
2626            let extra = &scope[goal.hyps.len().min(scope.len())..];
2627            let local =
2628                fresh_name_avoiding("hole", &ctx, &goal.hyps, &[&value, &goal.target], &locals);
2629            locals.push(local.clone());
2630            let local_ty = extra
2631                .iter()
2632                .rev()
2633                .fold(ty.clone(), |b, (n, t)| pi(n, t.clone(), b));
2634            probe_ctx = probe_ctx.extend(&local, local_ty);
2635            probe.solve(
2636                name,
2637                apps(
2638                    Term::Var(local),
2639                    &extra
2640                        .iter()
2641                        .map(|(n, _)| Term::Var(n.clone()))
2642                        .collect::<Vec<_>>(),
2643                ),
2644            );
2645        }
2646        let checked = elaborate::instantiate(&value, &probe);
2647        match infer_type(&probe_ctx, &checked) {
2648            Ok(t) if is_subtype(&probe_ctx, &t, &goal.target) => {}
2649            Ok(t) => {
2650                return Err(refuse(
2651                    "refine",
2652                    &goal,
2653                    format!("{} proves {}, not the goal", Shown(&term), Shown(&t)),
2654                ))
2655            }
2656            Err(e) => {
2657                return Err(refuse(
2658                    "refine",
2659                    &goal,
2660                    format!("{} does not type: {e}", Shown(&term)),
2661                ))
2662            }
2663        }
2664        self.mctx = mctx;
2665        let mut subgoals = Vec::with_capacity(holes.len());
2666        for (name, scope, ty) in holes {
2667            self.decls.insert(
2668                name.clone(),
2669                Decl {
2670                    hyps: scope,
2671                    target: ty,
2672                },
2673            );
2674            subgoals.push(name);
2675        }
2676        self.close(&m, value, subgoals);
2677        Ok(())
2678    }
2679
2680    fn declare(&mut self, hyps: Vec<(String, Term)>, target: Term) -> String {
2681        let name = self.fresh_meta_name();
2682        self.decls.insert(name.clone(), Decl { hyps, target });
2683        name
2684    }
2685
2686    fn fresh_meta_name(&mut self) -> String {
2687        match self.mctx.fresh() {
2688            Term::Var(name) => name,
2689            other => unreachable!("a metavariable is a `Var`, not {other:?}"),
2690        }
2691    }
2692
2693    fn view(&self, m: &str) -> Goal {
2694        let decl = &self.decls[m];
2695        let mut hyps: Vec<(String, Term)> = Vec::with_capacity(decl.hyps.len());
2696        for (n, t) in &decl.hyps {
2697            let t = simplify(
2698                &self.scope(&hyps),
2699                &hyps,
2700                &elaborate::instantiate(t, &self.mctx),
2701            );
2702            hyps.push((n.clone(), t));
2703        }
2704        let target = simplify(
2705            &self.scope(&hyps),
2706            &hyps,
2707            &elaborate::instantiate(&decl.target, &self.mctx),
2708        );
2709        Goal { hyps, target }
2710    }
2711
2712    fn focus(&self) -> Result<(String, Goal), TacticError> {
2713        let m = self.open.first().ok_or(TacticError::NoGoals)?;
2714        Ok((m.clone(), self.view(m)))
2715    }
2716
2717    fn scope(&self, hyps: &[(String, Term)]) -> Context {
2718        hyps.iter()
2719            .fold(self.ctx.clone(), |c, (n, t)| c.extend(n, t.clone()))
2720    }
2721
2722    /// Solve the focused goal `m` with `proof`; the goals among `subgoals` it leaves unsolved
2723    /// take its place, in order.
2724    fn close(&mut self, m: &str, proof: Term, subgoals: Vec<String>) {
2725        self.mctx.solve(m, proof);
2726        self.open.retain(|g| g != m);
2727        let fresh: Vec<String> = subgoals
2728            .into_iter()
2729            .filter(|g| self.mctx.solution(g).is_none())
2730            .collect();
2731        for (i, g) in fresh.into_iter().enumerate() {
2732            self.open.insert(i, g);
2733        }
2734        let solved: Vec<String> = self
2735            .open
2736            .iter()
2737            .filter(|g| self.mctx.solution(g).is_some())
2738            .cloned()
2739            .collect();
2740        self.open.retain(|g| !solved.contains(g));
2741    }
2742}
2743
2744/// How many rewrites one `simp` makes before it is refused as not terminating.
2745pub const SIMP_STEPS: usize = 256;
2746
2747/// The first instance of a rule in `t`, leftmost outermost and outside every binder of `t` (an
2748/// instance mentioning a bound variable is not a term of the goal's context): the rule's proof at
2749/// the matched instance, and the rule's name.
2750fn simp_instance(
2751    rules: &[(Term, SimpRule)],
2752    t: &Term,
2753    bound: &mut Vec<String>,
2754) -> Option<(Term, String)> {
2755    if !bound.iter().any(|b| crate::type_checker::occurs_free(t, b)) {
2756        for (proof, rule) in rules {
2757            if let Some(instance) = rule.matching(t) {
2758                return Some((apps(proof.clone(), &instance), rule.name.clone()));
2759            }
2760        }
2761    }
2762    let mut under = |name: &str, body: &Term, bound: &mut Vec<String>| {
2763        bound.push(name.to_string());
2764        let found = simp_instance(rules, body, bound);
2765        bound.pop();
2766        found
2767    };
2768    match t {
2769        Term::App(f, a) => {
2770            simp_instance(rules, f, bound).or_else(|| simp_instance(rules, a, bound))
2771        }
2772        Term::Pi {
2773            param,
2774            param_type,
2775            body_type,
2776        } => simp_instance(rules, param_type, bound).or_else(|| under(param, body_type, bound)),
2777        Term::Lambda {
2778            param,
2779            param_type,
2780            body,
2781        } => simp_instance(rules, param_type, bound).or_else(|| under(param, body, bound)),
2782        _ => None,
2783    }
2784}
2785
2786fn fresh_meta(mctx: &mut MetaCtx) -> String {
2787    match mctx.fresh() {
2788        Term::Var(name) => name,
2789        other => unreachable!("a metavariable is a `Var`, not {other:?}"),
2790    }
2791}
2792
2793fn lam(param: &str, ty: Term, body: Term) -> Term {
2794    Term::Lambda {
2795        param: param.to_string(),
2796        param_type: Box::new(ty),
2797        body: Box::new(body),
2798    }
2799}
2800
2801fn pi(param: &str, ty: Term, body: Term) -> Term {
2802    Term::Pi {
2803        param: param.to_string(),
2804        param_type: Box::new(ty),
2805        body_type: Box::new(body),
2806    }
2807}
2808
2809fn eq_term(carrier: &Term, lhs: &Term, rhs: &Term) -> Term {
2810    apps(
2811        Term::Global("Eq".into()),
2812        &[carrier.clone(), lhs.clone(), rhs.clone()],
2813    )
2814}
2815
2816/// The constructor `name` at the levels its family's head carries.
2817fn constructor_at(ctx: &Context, family_head: &Term, name: &str) -> Term {
2818    match family_head {
2819        Term::Const { levels, .. } if ctx.poly_declaration(name).is_some() => Term::Const {
2820            name: name.to_string(),
2821            levels: levels.clone(),
2822        },
2823        _ => Term::Global(name.to_string()),
2824    }
2825}
2826
2827/// Which side of an equation is a hypothesis of `goal` that neither the other side nor the carrier
2828/// mentions, the right preferred.
2829#[derive(Clone, Copy)]
2830enum Side {
2831    Left,
2832    Right,
2833}
2834
2835fn substitutable(goal: &Goal, eq: &Equation) -> Option<Side> {
2836    let free = |t: &Term, other: &Term| match t {
2837        Term::Var(v) => {
2838            goal.hyps.iter().any(|(n, _)| n == v)
2839                && !crate::type_checker::occurs_free(other, v)
2840                && !crate::type_checker::occurs_free(&eq.carrier, v)
2841        }
2842        _ => false,
2843    };
2844    if free(&eq.rhs, &eq.lhs) {
2845        Some(Side::Right)
2846    } else if free(&eq.lhs, &eq.rhs) {
2847        Some(Side::Left)
2848    } else {
2849        None
2850    }
2851}
2852
2853/// The metavariables `t` mentions.
2854fn metas(t: &Term, out: &mut BTreeSet<String>) {
2855    match t {
2856        Term::Var(v) if elaborate::is_meta(v) => {
2857            out.insert(v.clone());
2858        }
2859        Term::Var(_)
2860        | Term::Global(_)
2861        | Term::Const { .. }
2862        | Term::Sort(_)
2863        | Term::Lit(_)
2864        | Term::Hole => {}
2865        Term::App(a, b) => {
2866            metas(a, out);
2867            metas(b, out);
2868        }
2869        Term::Pi {
2870            param_type: a,
2871            body_type: b,
2872            ..
2873        }
2874        | Term::Lambda {
2875            param_type: a,
2876            body: b,
2877            ..
2878        } => {
2879            metas(a, out);
2880            metas(b, out);
2881        }
2882        Term::Let {
2883            ty, value, body, ..
2884        } => {
2885            metas(ty, out);
2886            metas(value, out);
2887            metas(body, out);
2888        }
2889        Term::Match {
2890            discriminant,
2891            motive,
2892            cases,
2893        } => {
2894            metas(discriminant, out);
2895            metas(motive, out);
2896            cases.iter().for_each(|c| metas(c, out));
2897        }
2898        Term::Fix { body, .. } => metas(body, out),
2899        Term::MutualFix { defs, .. } => defs.iter().for_each(|(_, b)| metas(b, out)),
2900        Term::Annotated { term, .. } => metas(term, out),
2901    }
2902}
2903
2904/// `t` as a goal reads it: β-redexes contracted, a `match` on a constructor application reduced to
2905/// its case (ι), and a `match` on a hypothesis proving an equation whose sides convert reduced as if
2906/// on `refl` (the kernel's K-like rule). Each step is a conversion, nothing is unfolded.
2907fn simplify(ctx: &Context, hyps: &[(String, Term)], t: &Term) -> Term {
2908    let go = |t: &Term| simplify(ctx, hyps, t);
2909    match t {
2910        Term::App(f, a) => match (go(f), go(a)) {
2911            (Term::Lambda { param, body, .. }, a) => go(&substitute(&body, &param, &a)),
2912            (f, a) => Term::App(Box::new(f), Box::new(a)),
2913        },
2914        Term::Pi {
2915            param,
2916            param_type,
2917            body_type,
2918        } => pi(param, go(param_type), go(body_type)),
2919        Term::Lambda {
2920            param,
2921            param_type,
2922            body,
2923        } => lam(param, go(param_type), go(body)),
2924        Term::Match {
2925            discriminant,
2926            motive,
2927            cases,
2928        } => {
2929            let discriminant = go(discriminant);
2930            let cases: Vec<Term> = cases.iter().map(go).collect();
2931            if let Some(reduced) = iota(ctx, hyps, &discriminant, &cases) {
2932                return go(&reduced);
2933            }
2934            Term::Match {
2935                discriminant: Box::new(discriminant),
2936                motive: Box::new(go(motive)),
2937                cases,
2938            }
2939        }
2940        Term::Annotated { term, .. } => go(term),
2941        _ => t.clone(),
2942    }
2943}
2944
2945fn iota(
2946    ctx: &Context,
2947    hyps: &[(String, Term)],
2948    discriminant: &Term,
2949    cases: &[Term],
2950) -> Option<Term> {
2951    let (head, args) = spine(discriminant);
2952    match head {
2953        Term::Global(c) | Term::Const { name: c, .. } if ctx.is_constructor(c) => {
2954            let ind = ctx.constructor_inductive(c)?.to_string();
2955            let constructors = ctx.get_constructors(&ind);
2956            if cases.len() != constructors.len() {
2957                return None;
2958            }
2959            let position = constructors.iter().position(|(n, _)| n == c)?;
2960            let np = ctx.inductive_num_params(&ind);
2961            Some(apps(cases[position].clone(), args.get(np..)?))
2962        }
2963        Term::Var(h) if cases.len() == 1 => {
2964            let (_, ty) = hyps.iter().rev().find(|(n, _)| n == h)?;
2965            let eq = equation(ctx, ty)?;
2966            def_eq(ctx, &eq.lhs, &eq.rhs).then(|| cases[0].clone())
2967        }
2968        _ => None,
2969    }
2970}
2971
2972/// A term handed to a tactic, with its type. A term with no hole is complete: the kernel types it,
2973/// its application rule solving the levels of a bare polymorphic constant (`refl A x`), which the
2974/// elaborator's unifier cannot (it has no level metavariables). A term with holes, or one the kernel
2975/// cannot type as written (an omitted implicit argument), is elaborated against `expected`.
2976fn typed(
2977    ctx: &Context,
2978    mctx: &mut MetaCtx,
2979    hyps: &[(String, Term)],
2980    term: &Term,
2981    expected: Option<&Term>,
2982) -> Result<(Term, Term), KernelError> {
2983    if !crate::type_checker::mentions_hole(term) {
2984        if let Ok(ty) = infer_type(ctx, term) {
2985            return Ok((term.clone(), ty));
2986        }
2987    }
2988    elaborate::elaborate_in(ctx, mctx, hyps, term, expected)
2989}
2990
2991/// A term as the vernacular spells it (`Eq Nat Zero Zero`), its free variables (a goal's hypotheses)
2992/// by name, or the kernel's own rendering where the surface has no spelling (an unsolved
2993/// metavariable).
2994struct Shown<'a>(&'a Term);
2995
2996impl fmt::Display for Shown<'_> {
2997    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2998        let locals = crate::type_checker::free_vars(self.0);
2999        let locals: Vec<&str> = locals.iter().map(String::as_str).collect();
3000        match crate::interface::print_term_in(self.0, &locals) {
3001            Ok(text) => f.write_str(&text),
3002            Err(_) => write!(f, "{}", self.0),
3003        }
3004    }
3005}
3006
3007fn refuse(tactic: &'static str, goal: &Goal, why: String) -> TacticError {
3008    TacticError::DoesNotApply {
3009        tactic,
3010        goal: goal.clone(),
3011        why,
3012    }
3013}
3014
3015fn apps(f: Term, args: &[Term]) -> Term {
3016    args.iter()
3017        .fold(f, |acc, a| Term::App(Box::new(acc), Box::new(a.clone())))
3018}
3019
3020fn spine(t: &Term) -> (&Term, Vec<Term>) {
3021    let mut args = Vec::new();
3022    let mut head = t;
3023    while let Term::App(f, a) = head {
3024        args.push((**a).clone());
3025        head = f;
3026    }
3027    args.reverse();
3028    (head, args)
3029}
3030
3031/// A global named like a hypothesis is that hypothesis.
3032fn localize(term: &Term, hyps: &[(String, Term)]) -> Term {
3033    if hyps.is_empty() {
3034        return term.clone();
3035    }
3036    let names: BTreeSet<&str> = hyps.iter().map(|(n, _)| n.as_str()).collect();
3037    map_globals(term, &|n| {
3038        names.contains(n).then(|| Term::Var(n.to_string()))
3039    })
3040}
3041
3042fn map_globals(t: &Term, f: &dyn Fn(&str) -> Option<Term>) -> Term {
3043    let go = |t: &Term| Box::new(map_globals(t, f));
3044    match t {
3045        Term::Global(n) => f(n).unwrap_or_else(|| t.clone()),
3046        Term::Var(_) | Term::Const { .. } | Term::Sort(_) | Term::Lit(_) | Term::Hole => t.clone(),
3047        Term::App(a, b) => Term::App(go(a), go(b)),
3048        Term::Pi {
3049            param,
3050            param_type,
3051            body_type,
3052        } => Term::Pi {
3053            param: param.clone(),
3054            param_type: go(param_type),
3055            body_type: go(body_type),
3056        },
3057        Term::Lambda {
3058            param,
3059            param_type,
3060            body,
3061        } => Term::Lambda {
3062            param: param.clone(),
3063            param_type: go(param_type),
3064            body: go(body),
3065        },
3066        Term::Match {
3067            discriminant,
3068            motive,
3069            cases,
3070        } => Term::Match {
3071            discriminant: go(discriminant),
3072            motive: go(motive),
3073            cases: cases.iter().map(|c| map_globals(c, f)).collect(),
3074        },
3075        Term::Fix { name, body } => Term::Fix {
3076            name: name.clone(),
3077            body: go(body),
3078        },
3079        Term::MutualFix { defs, index } => Term::MutualFix {
3080            defs: defs
3081                .iter()
3082                .map(|(n, b)| (n.clone(), map_globals(b, f)))
3083                .collect(),
3084            index: *index,
3085        },
3086        Term::Let {
3087            name,
3088            ty,
3089            value,
3090            body,
3091        } => Term::Let {
3092            name: name.clone(),
3093            ty: go(ty),
3094            value: go(value),
3095            body: go(body),
3096        },
3097        Term::Annotated { annotation, term } => Term::Annotated {
3098            annotation: annotation.clone(),
3099            term: go(term),
3100        },
3101    }
3102}
3103
3104/// How many premises `ty` has, reading through definitions to each `Π`.
3105fn pi_arity(ctx: &Context, ty: &Term) -> usize {
3106    pi_telescope(ctx, ty, usize::MAX).len()
3107}
3108
3109/// The first `limit` binders of `ty`'s `Π` telescope, each type in the scope of those before it.
3110fn pi_telescope(ctx: &Context, ty: &Term, limit: usize) -> Vec<(String, Term)> {
3111    let mut out = Vec::new();
3112    let mut scope = ctx.clone();
3113    let mut t = ty.clone();
3114    while out.len() < limit {
3115        match demanded_type_head(&scope, t, TypeHead::Function) {
3116            Term::Pi {
3117                param,
3118                param_type,
3119                body_type,
3120            } => {
3121                scope = scope.extend(&param, (*param_type).clone());
3122                out.push((param, *param_type));
3123                t = *body_type;
3124            }
3125            _ => break,
3126        }
3127    }
3128    out
3129}
3130
3131/// An equation `Eq A l r`, read through definitions to the `Eq` head.
3132struct Equation {
3133    head: Term,
3134    carrier: Term,
3135    lhs: Term,
3136    rhs: Term,
3137}
3138
3139impl Equation {
3140    /// `refl` at the levels the equation's `Eq` carries.
3141    fn refl(&self) -> Term {
3142        match &self.head {
3143            Term::Const { levels, .. } => Term::Const {
3144                name: "refl".into(),
3145                levels: levels.clone(),
3146            },
3147            _ => Term::Global("refl".into()),
3148        }
3149    }
3150}
3151
3152fn equation(ctx: &Context, ty: &Term) -> Option<Equation> {
3153    let read = |t: &Term| {
3154        let (head, args) = spine(t);
3155        match head {
3156            Term::Global(n) | Term::Const { name: n, .. } if n == "Eq" && args.len() == 3 => {
3157                Some(Equation {
3158                    head: head.clone(),
3159                    carrier: args[0].clone(),
3160                    lhs: args[1].clone(),
3161                    rhs: args[2].clone(),
3162                })
3163            }
3164            _ => None,
3165        }
3166    };
3167    read(ty).or_else(|| read(&demanded_type_head(ctx, ty.clone(), TypeHead::Inductive)))
3168}
3169
3170/// `t` with every occurrence of `from` replaced by the variable `x`, and whether there was one.
3171/// An occurrence under a binder of one of `from`'s free variables is a different term and is
3172/// left alone.
3173fn abstract_occurrences(t: &Term, from: &Term, x: &str) -> (Term, bool) {
3174    if alpha_equivalent(t, from) {
3175        return (Term::Var(x.to_string()), true);
3176    }
3177    let captured = |name: &str| crate::type_checker::occurs_free(from, name);
3178    let mut found = false;
3179    let mut go = |t: &Term| {
3180        let (r, f) = abstract_occurrences(t, from, x);
3181        found |= f;
3182        Box::new(r)
3183    };
3184    let r = match t {
3185        Term::App(a, b) => Term::App(go(a), go(b)),
3186        Term::Pi {
3187            param,
3188            param_type,
3189            body_type,
3190        } => {
3191            let pt = go(param_type);
3192            let bt = if captured(param) {
3193                body_type.clone()
3194            } else {
3195                go(body_type)
3196            };
3197            Term::Pi {
3198                param: param.clone(),
3199                param_type: pt,
3200                body_type: bt,
3201            }
3202        }
3203        Term::Lambda {
3204            param,
3205            param_type,
3206            body,
3207        } => {
3208            let pt = go(param_type);
3209            let b = if captured(param) {
3210                body.clone()
3211            } else {
3212                go(body)
3213            };
3214            Term::Lambda {
3215                param: param.clone(),
3216                param_type: pt,
3217                body: b,
3218            }
3219        }
3220        Term::Let {
3221            name,
3222            ty,
3223            value,
3224            body,
3225        } => {
3226            let (ty2, v2) = (go(ty), go(value));
3227            let b = if captured(name) {
3228                body.clone()
3229            } else {
3230                go(body)
3231            };
3232            Term::Let {
3233                name: name.clone(),
3234                ty: ty2,
3235                value: v2,
3236                body: b,
3237            }
3238        }
3239        Term::Match {
3240            discriminant,
3241            motive,
3242            cases,
3243        } => {
3244            let d = go(discriminant);
3245            let mo = go(motive);
3246            let cs = cases.iter().map(|c| *go(c)).collect();
3247            Term::Match {
3248                discriminant: d,
3249                motive: mo,
3250                cases: cs,
3251            }
3252        }
3253        Term::Fix { name, body } => {
3254            let b = if captured(name) {
3255                body.clone()
3256            } else {
3257                go(body)
3258            };
3259            Term::Fix {
3260                name: name.clone(),
3261                body: b,
3262            }
3263        }
3264        Term::Annotated { term, .. } => *go(term),
3265        _ => t.clone(),
3266    };
3267    (r, found)
3268}
3269
3270/// Every redex `(λx. b) a` contracted, nothing unfolded.
3271fn beta_normal(t: &Term) -> Term {
3272    match t {
3273        Term::App(f, a) => {
3274            let (f, a) = (beta_normal(f), beta_normal(a));
3275            match f {
3276                Term::Lambda { param, body, .. } => beta_normal(&substitute(&body, &param, &a)),
3277                f => Term::App(Box::new(f), Box::new(a)),
3278            }
3279        }
3280        Term::Pi {
3281            param,
3282            param_type,
3283            body_type,
3284        } => Term::Pi {
3285            param: param.clone(),
3286            param_type: Box::new(beta_normal(param_type)),
3287            body_type: Box::new(beta_normal(body_type)),
3288        },
3289        Term::Lambda {
3290            param,
3291            param_type,
3292            body,
3293        } => Term::Lambda {
3294            param: param.clone(),
3295            param_type: Box::new(beta_normal(param_type)),
3296            body: Box::new(beta_normal(body)),
3297        },
3298        Term::Annotated { term, .. } => beta_normal(term),
3299        _ => t.clone(),
3300    }
3301}
3302
3303/// A name for a new local: `base`, or `base` with a numeral, mentioned nowhere in `terms`, naming
3304/// no hypothesis and no global.
3305fn fresh_name(base: &str, ctx: &Context, hyps: &[(String, Term)], terms: &[&Term]) -> String {
3306    fresh_name_avoiding(base, ctx, hyps, terms, &[])
3307}
3308
3309fn fresh_name_avoiding(
3310    base: &str,
3311    ctx: &Context,
3312    hyps: &[(String, Term)],
3313    terms: &[&Term],
3314    also: &[String],
3315) -> String {
3316    let base = if base.is_empty() || base == "_" {
3317        "x"
3318    } else {
3319        base
3320    };
3321    let clashes = |n: &str| {
3322        hyps.iter().any(|(h, _)| h == n)
3323            || also.iter().any(|a| a == n)
3324            || ctx.get_global(n).is_some()
3325            || terms.iter().any(|t| mentions_name(t, n))
3326    };
3327    if !clashes(base) {
3328        return base.to_string();
3329    }
3330    (1..)
3331        .map(|i| format!("{base}{i}"))
3332        .find(|n| !clashes(n))
3333        .expect("a fresh name exists")
3334}
3335
3336fn mentions_name(t: &Term, n: &str) -> bool {
3337    match t {
3338        Term::Var(v) | Term::Global(v) => v == n,
3339        Term::Const { name, .. } => name == n,
3340        Term::Sort(_) | Term::Lit(_) | Term::Hole => false,
3341        Term::App(a, b) => mentions_name(a, n) || mentions_name(b, n),
3342        Term::Pi {
3343            param,
3344            param_type,
3345            body_type,
3346        } => param == n || mentions_name(param_type, n) || mentions_name(body_type, n),
3347        Term::Lambda {
3348            param,
3349            param_type,
3350            body,
3351        } => param == n || mentions_name(param_type, n) || mentions_name(body, n),
3352        Term::Let {
3353            name,
3354            ty,
3355            value,
3356            body,
3357        } => name == n || mentions_name(ty, n) || mentions_name(value, n) || mentions_name(body, n),
3358        Term::Match {
3359            discriminant,
3360            motive,
3361            cases,
3362        } => {
3363            mentions_name(discriminant, n)
3364                || mentions_name(motive, n)
3365                || cases.iter().any(|c| mentions_name(c, n))
3366        }
3367        Term::Fix { name, body } => name == n || mentions_name(body, n),
3368        Term::MutualFix { defs, .. } => defs.iter().any(|(m, b)| m == n || mentions_name(b, n)),
3369        Term::Annotated { term, .. } => mentions_name(term, n),
3370    }
3371}
3372
3373/// Congruence closure with proof production (Nieuwenhuis and Oliveras): a union-find over the goal's
3374/// terms, every application read curried (`f a` is the pair `f`, `a`), and a proof forest recording
3375/// why each merge happened, from which an equation's proof is read back.
3376mod congruence {
3377    use super::{apps, eq_term, lam, Goal};
3378    use crate::context::Context;
3379    use crate::term::Term;
3380    use crate::type_checker::{demanded_type_head, infer_type, occurs_free, TypeHead};
3381    use std::collections::HashMap;
3382
3383    /// Why two terms were merged.
3384    #[derive(Clone)]
3385    enum Reason {
3386        /// The hypothesis `name : Eq T lhs rhs`, the merged pair being its two sides.
3387        Hypothesis { name: String, lhs: usize },
3388        /// Two applications whose functions and arguments are already equal.
3389        Congruence { left: usize, right: usize },
3390    }
3391
3392    pub(super) struct Closure<'a> {
3393        ctx: &'a Context,
3394        avoid: Vec<String>,
3395        terms: Vec<Term>,
3396        types: Vec<Option<Term>>,
3397        index: HashMap<String, usize>,
3398        /// For an application `f a` whose function is not dependent: `f` and `a`.
3399        app: Vec<Option<(usize, usize)>>,
3400        parent: Vec<usize>,
3401        /// The proof forest: each term's edge toward its tree's root.
3402        edge: Vec<Option<(usize, Reason)>>,
3403        pending: Vec<(usize, usize, Reason)>,
3404    }
3405
3406    impl<'a> Closure<'a> {
3407        pub(super) fn new(ctx: &'a Context, goal: &Goal) -> Closure<'a> {
3408            let mut avoid: Vec<String> = goal.hyps.iter().map(|(h, _)| h.clone()).collect();
3409            avoid.push("w".to_string());
3410            Closure {
3411                ctx,
3412                avoid,
3413                terms: Vec::new(),
3414                types: Vec::new(),
3415                index: HashMap::new(),
3416                app: Vec::new(),
3417                parent: Vec::new(),
3418                edge: Vec::new(),
3419                pending: Vec::new(),
3420            }
3421        }
3422
3423        /// The id of `t`, interned with its subterms.
3424        pub(super) fn term(&mut self, t: &Term) -> usize {
3425            let key = format!("{t:?}");
3426            if let Some(&id) = self.index.get(&key) {
3427                return id;
3428            }
3429            let app = match t {
3430                Term::App(f, a) if !self.dependent(f) => {
3431                    let (f, a) = (self.term(f), self.term(a));
3432                    Some((f, a))
3433                }
3434                _ => None,
3435            };
3436            let id = self.terms.len();
3437            self.terms.push(t.clone());
3438            self.types.push(infer_type(self.ctx, t).ok());
3439            self.index.insert(key, id);
3440            self.app.push(app);
3441            self.parent.push(id);
3442            self.edge.push(None);
3443            id
3444        }
3445
3446        /// Whether `f`'s result type depends on its argument (then `f a` and `f b` have different
3447        /// types, and no equation relates them).
3448        fn dependent(&self, f: &Term) -> bool {
3449            let Ok(ty) = infer_type(self.ctx, f) else {
3450                return true;
3451            };
3452            match demanded_type_head(self.ctx, ty, TypeHead::Function) {
3453                Term::Pi {
3454                    param, body_type, ..
3455                } => occurs_free(&body_type, &param),
3456                _ => true,
3457            }
3458        }
3459
3460        pub(super) fn assert_hypothesis(&mut self, name: &str, lhs: &Term, rhs: &Term) {
3461            let (l, r) = (self.term(lhs), self.term(rhs));
3462            self.pending.push((
3463                l,
3464                r,
3465                Reason::Hypothesis {
3466                    name: name.to_string(),
3467                    lhs: l,
3468                },
3469            ));
3470        }
3471
3472        fn find(&mut self, mut x: usize) -> usize {
3473            while self.parent[x] != x {
3474                self.parent[x] = self.parent[self.parent[x]];
3475                x = self.parent[x];
3476            }
3477            x
3478        }
3479
3480        /// Merge every pending pair, and every pair of applications the merges make congruent,
3481        /// until nothing changes.
3482        pub(super) fn saturate(&mut self) {
3483            loop {
3484                while let Some((a, b, why)) = self.pending.pop() {
3485                    let (ra, rb) = (self.find(a), self.find(b));
3486                    if ra == rb {
3487                        continue;
3488                    }
3489                    self.parent[ra] = rb;
3490                    self.reroot(a);
3491                    self.edge[a] = Some((b, why));
3492                }
3493                // One pass of the signature table: applications whose function and argument classes
3494                // agree are congruent.
3495                let mut signatures: HashMap<(usize, usize), usize> = HashMap::new();
3496                for x in 0..self.terms.len() {
3497                    let Some((f, a)) = self.app[x] else { continue };
3498                    let key = (self.find(f), self.find(a));
3499                    match signatures.get(&key) {
3500                        Some(&y) if self.find(x) != self.find(y) => {
3501                            self.pending
3502                                .push((y, x, Reason::Congruence { left: y, right: x }));
3503                        }
3504                        Some(_) => {}
3505                        None => {
3506                            signatures.insert(key, x);
3507                        }
3508                    }
3509                }
3510                if self.pending.is_empty() {
3511                    return;
3512                }
3513            }
3514        }
3515
3516        /// Make `x` the root of its proof tree by reversing the edges on its path to the root.
3517        fn reroot(&mut self, x: usize) {
3518            let mut path = Vec::new();
3519            let mut at = x;
3520            while let Some((next, why)) = self.edge[at].clone() {
3521                path.push((at, next, why));
3522                at = next;
3523            }
3524            self.edge[x] = None;
3525            for (from, to, why) in path {
3526                self.edge[to] = Some((from, why));
3527            }
3528        }
3529
3530        /// A proof that `a` equals `b`, when they are in one class.
3531        pub(super) fn prove(&mut self, a: usize, b: usize) -> Option<Term> {
3532            if self.find(a) != self.find(b) {
3533                return None;
3534            }
3535            self.explain(a, b)
3536        }
3537
3538        /// The path between `a` and `b` in the proof forest, each step's proof chained.
3539        fn explain(&self, a: usize, b: usize) -> Option<Term> {
3540            let up = |mut x: usize| {
3541                let mut path = vec![x];
3542                while let Some((next, _)) = &self.edge[x] {
3543                    x = *next;
3544                    path.push(x);
3545                }
3546                path
3547            };
3548            let (pa, pb) = (up(a), up(b));
3549            let meet = *pa.iter().find(|x| pb.contains(x))?;
3550            let mut proof = self.refl(a)?;
3551            let mut at = a;
3552            while at != meet {
3553                let (next, why) = self.edge[at].clone()?;
3554                let step = self.step(at, next, &why)?;
3555                proof = self.trans(at, a, proof, step)?;
3556                at = next;
3557            }
3558            let back: Vec<usize> = pb.iter().take_while(|x| **x != meet).copied().collect();
3559            for &x in back.iter().rev() {
3560                let (next, why) = self.edge[x].clone()?;
3561                let step = self.step(next, x, &why)?;
3562                proof = self.trans(next, a, proof, step)?;
3563            }
3564            Some(proof)
3565        }
3566
3567        /// A proof that `from` equals `to`, the two ends of one forest edge.
3568        fn step(&self, from: usize, to: usize, why: &Reason) -> Option<Term> {
3569            match why {
3570                Reason::Hypothesis { name, lhs } => {
3571                    let h = Term::Var(name.clone());
3572                    if *lhs == from {
3573                        Some(h)
3574                    } else {
3575                        self.symm(to, h)
3576                    }
3577                }
3578                Reason::Congruence { left, right } => {
3579                    let (l, r) = if *left == from {
3580                        (*left, *right)
3581                    } else {
3582                        (*right, *left)
3583                    };
3584                    let ((fl, al), (fr, ar)) = (self.app[l]?, self.app[r]?);
3585                    // `f a = g b` as `f a = f b` (the argument) then `f b = g b` (the function).
3586                    let args = self.explain(al, ar)?;
3587                    let by_arg = self.congr_arg(fl, al, args)?;
3588                    if fl == fr {
3589                        return Some(by_arg);
3590                    }
3591                    let funs = self.explain(fl, fr)?;
3592                    let by_fun = self.congr_fun(fl, ar, funs)?;
3593                    let mid = Term::App(
3594                        Box::new(self.terms[fl].clone()),
3595                        Box::new(self.terms[ar].clone()),
3596                    );
3597                    Some(self.trans_terms(
3598                        &self.types[l].clone()?,
3599                        &self.terms[l],
3600                        &mid,
3601                        by_arg,
3602                        by_fun,
3603                    ))
3604                }
3605            }
3606        }
3607
3608        fn refl(&self, x: usize) -> Option<Term> {
3609            Some(apps(
3610                Term::Global("refl".into()),
3611                &[self.types[x].clone()?, self.terms[x].clone()],
3612            ))
3613        }
3614
3615        fn fresh(&self, base: &str) -> String {
3616            let taken = |n: &str| {
3617                self.avoid.iter().any(|a| a == n)
3618                    || self.terms.iter().any(|t| super::mentions_name(t, n))
3619            };
3620            if !taken(base) {
3621                return base.to_string();
3622            }
3623            (1..)
3624                .map(|i| format!("{base}{i}"))
3625                .find(|n| !taken(n))
3626                .expect("a fresh name")
3627        }
3628
3629        /// From `p : Eq T x _`, the same equation the other way round.
3630        fn symm(&self, x: usize, p: Term) -> Option<Term> {
3631            let (t, xs) = (self.types[x].clone()?, self.terms[x].clone());
3632            let z = self.fresh("z");
3633            Some(Term::Match {
3634                discriminant: Box::new(p),
3635                motive: Box::new(lam(
3636                    &z,
3637                    t.clone(),
3638                    lam(
3639                        "_",
3640                        eq_term(&t, &xs, &Term::Var(z.clone())),
3641                        eq_term(&t, &Term::Var(z.clone()), &xs),
3642                    ),
3643                )),
3644                cases: vec![apps(Term::Global("refl".into()), &[t, xs])],
3645            })
3646        }
3647
3648        /// From `p : Eq T a y` and `q : Eq T y _` (`a` and `y` by id), `Eq T a _`.
3649        fn trans(&self, y: usize, a: usize, p: Term, q: Term) -> Option<Term> {
3650            let t = self.types[a].clone()?;
3651            Some(self.trans_terms(&t, &self.terms[a], &self.terms[y], p, q))
3652        }
3653
3654        /// From `p : Eq t a y` and `q : Eq t y _`, `Eq t a _`: transport `p` along `q`.
3655        fn trans_terms(&self, t: &Term, a: &Term, y: &Term, p: Term, q: Term) -> Term {
3656            let w = self.fresh("w");
3657            Term::Match {
3658                discriminant: Box::new(q),
3659                motive: Box::new(lam(
3660                    &w,
3661                    t.clone(),
3662                    lam(
3663                        "_",
3664                        eq_term(t, y, &Term::Var(w.clone())),
3665                        eq_term(t, a, &Term::Var(w.clone())),
3666                    ),
3667                )),
3668                cases: vec![p],
3669            }
3670        }
3671
3672        /// From `p : Eq A a _`, `Eq B (f a) (f _)`.
3673        fn congr_arg(&self, f: usize, a: usize, p: Term) -> Option<Term> {
3674            let fa = Term::App(
3675                Box::new(self.terms[f].clone()),
3676                Box::new(self.terms[a].clone()),
3677            );
3678            let ta = self.types[a].clone()?;
3679            let tb = infer_type(self.ctx, &fa).ok()?;
3680            let w = self.fresh("w");
3681            let at_w = Term::App(
3682                Box::new(self.terms[f].clone()),
3683                Box::new(Term::Var(w.clone())),
3684            );
3685            Some(Term::Match {
3686                discriminant: Box::new(p),
3687                motive: Box::new(lam(
3688                    &w,
3689                    ta.clone(),
3690                    lam(
3691                        "_",
3692                        eq_term(&ta, &self.terms[a], &Term::Var(w.clone())),
3693                        eq_term(&tb, &fa, &at_w),
3694                    ),
3695                )),
3696                cases: vec![apps(Term::Global("refl".into()), &[tb, fa.clone()])],
3697            })
3698        }
3699
3700        /// From `p : Eq (A → B) f _`, `Eq B (f b) (_ b)`.
3701        fn congr_fun(&self, f: usize, b: usize, p: Term) -> Option<Term> {
3702            let tf = self.types[f].clone()?;
3703            let fb = Term::App(
3704                Box::new(self.terms[f].clone()),
3705                Box::new(self.terms[b].clone()),
3706            );
3707            let tb = infer_type(self.ctx, &fb).ok()?;
3708            let w = self.fresh("w");
3709            let w_b = Term::App(
3710                Box::new(Term::Var(w.clone())),
3711                Box::new(self.terms[b].clone()),
3712            );
3713            Some(Term::Match {
3714                discriminant: Box::new(p),
3715                motive: Box::new(lam(
3716                    &w,
3717                    tf.clone(),
3718                    lam(
3719                        "_",
3720                        eq_term(&tf, &self.terms[f], &Term::Var(w.clone())),
3721                        eq_term(&tb, &fb, &w_b),
3722                    ),
3723                )),
3724                cases: vec![apps(Term::Global("refl".into()), &[tb, fb.clone()])],
3725            })
3726        }
3727    }
3728}