|
| 1 | +/- |
| 2 | +Copyright (c) 2020 Scott Morrison. All rights reserved. |
| 3 | +Released under Apache 2.0 license as described in the file LICENSE. |
| 4 | +Authors: Scott Morrison |
| 5 | +-/ |
| 6 | +import tactic.chain |
| 7 | +import tactic.doc_commands |
| 8 | +import data.quot |
| 9 | + |
| 10 | +namespace tactic |
| 11 | + |
| 12 | +/-- Auxiliary tactic for `trunc_cases`. -/ |
| 13 | +private meta def trunc_cases_subsingleton (e : expr) (ids : list name) : tactic expr := |
| 14 | +do |
| 15 | + -- When the target is a subsingleton, |
| 16 | + -- we can just use induction along `trunc.rec_on_subsingleton`, |
| 17 | + -- generating just a single goal. |
| 18 | + [(_, [e], _)] ← tactic.induction e ids `trunc.rec_on_subsingleton, |
| 19 | + return e |
| 20 | + |
| 21 | +/-- Auxiliary tactic for `trunc_cases`. -/ |
| 22 | +private meta def trunc_cases_nondependent (e : expr) (ids : list name) : tactic expr := |
| 23 | +do |
| 24 | + -- We may as well just use `trunc.lift_on`. |
| 25 | + -- (It would be nice if we could use the `induction` tactic with non-dependent recursors, too?) |
| 26 | + -- (In fact, the general strategy works just as well here, |
| 27 | + -- except that it leaves a beta redex in the invariance goal.) |
| 28 | + to_expr ``(trunc.lift_on %%e) >>= tactic.fapply, |
| 29 | + -- Replace the hypothesis `e` with the unboxed version. |
| 30 | + tactic.clear e, |
| 31 | + e ← tactic.intro e.local_pp_name, |
| 32 | + -- In the invariance goal, introduce the two arguments using the specified identifiers |
| 33 | + tactic.swap, |
| 34 | + match ids.nth 1 with |
| 35 | + | some n := tactic.intro n |
| 36 | + | none := tactic.intro1 |
| 37 | + end, |
| 38 | + match ids.nth 2 with |
| 39 | + | some n := tactic.intro n |
| 40 | + | none := tactic.intro1 |
| 41 | + end, |
| 42 | + tactic.swap, |
| 43 | + return e |
| 44 | + |
| 45 | +/-- Auxiliary tactic for `trunc_cases`. -/ |
| 46 | +private meta def trunc_cases_dependent (e : expr) (ids : list name) : tactic expr := |
| 47 | +do |
| 48 | + -- If all else fails, just use the general induction principle. |
| 49 | + [(_, [e], _), (_, [e_a, e_b, e_p], _)] ← tactic.induction e ids, |
| 50 | + -- However even now we can do something useful: |
| 51 | + -- the invariance goal has a useless `e_p : true` hypothesis, |
| 52 | + -- and after casing on that we may be able to simplify away |
| 53 | + -- the `eq.rec`. |
| 54 | + swap, (tactic.cases e_p >> `[try { simp only [eq_rec_constant] }]), swap, |
| 55 | + return e |
| 56 | + |
| 57 | +namespace interactive |
| 58 | + |
| 59 | +open interactive |
| 60 | +open interactive.types |
| 61 | +open tactic |
| 62 | + |
| 63 | +/-- |
| 64 | +`trunc_cases e` performs case analysis on a `trunc` expression `e`, |
| 65 | +attempting the following strategies: |
| 66 | +1. when the goal is a subsingleton, calling `induction e using trunc.rec_on_subsingleton`, |
| 67 | +2. when the goal does not depend on `e`, calling `fapply trunc.lift_on e`, |
| 68 | + and using `intro` and `clear` afterwards to make the goals look like we used `induction`, |
| 69 | +3. otherwise, falling through to `trunc.rec_on`, and in the new invariance goal |
| 70 | + calling `cases h_p` on the useless `h_p : true` hypothesis, |
| 71 | + and then attempting to simplify the `eq.rec`. |
| 72 | +
|
| 73 | +`trunc_cases e with h` names the new hypothesis `h`. |
| 74 | +If `e` is a local hypothesis already, |
| 75 | +`trunc_cases` defaults to reusing the same name. |
| 76 | +
|
| 77 | +`trunc_cases e with h h_a h_b` will use the names `h_a` and `h_b` for the new hypothesis |
| 78 | +in the invariance goal if `trunc_cases` uses `trunc.lift_on` or `trunc.rec_on`. |
| 79 | +
|
| 80 | +Finally, if the new hypothesis from inside the `trunc` is a type class, |
| 81 | +`trunc_cases` resets the instance cache so that it is immediately available. |
| 82 | +-/ |
| 83 | +meta def trunc_cases (e : parse texpr) (ids : parse with_ident_list) : tactic unit := |
| 84 | +do |
| 85 | + e ← to_expr e, |
| 86 | + -- If `ids = []` and `e` is a local constant, we'll want to give |
| 87 | + -- the new unboxed hypothesis the same name. |
| 88 | + let ids := if ids = [] ∧ e.is_local_constant then [e.local_pp_name] else ids, |
| 89 | + -- Make a note of the expr `e`, or reuse `e` if it is already a local constant. |
| 90 | + e ← if e.is_local_constant then |
| 91 | + return e |
| 92 | + else |
| 93 | + (do n ← match ids.nth 0 with | some n := pure n | none := mk_fresh_name end, note n none e), |
| 94 | + -- Now check if the target is a subsingleton. |
| 95 | + tgt ← target, |
| 96 | + ss ← succeeds (mk_app `subsingleton [tgt] >>= mk_instance), |
| 97 | + -- In each branch here, we're going to capture the name of the new unboxed hypothesis |
| 98 | + -- so that we can later check if it's a typeclass and if so unfreeze local instances. |
| 99 | + e ← if ss then trunc_cases_subsingleton e ids |
| 100 | + else if e.occurs tgt then trunc_cases_dependent e ids |
| 101 | + else trunc_cases_nondependent e ids, |
| 102 | + c ← infer_type e >>= is_class, |
| 103 | + when c unfreeze_local_instances |
| 104 | + |
| 105 | +end interactive |
| 106 | +end tactic |
| 107 | + |
| 108 | +add_tactic_doc |
| 109 | +{ name := "trunc_cases", |
| 110 | + category := doc_category.tactic, |
| 111 | + decl_names := [`tactic.interactive.trunc_cases], |
| 112 | + tags := ["case bashing"] } |
0 commit comments