-
Notifications
You must be signed in to change notification settings - Fork 80
/
interactive.lean
1823 lines (1542 loc) · 72.4 KB
/
interactive.lean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Jannis Limperg
-/
prelude
import init.meta.tactic init.meta.type_context init.meta.rewrite_tactic init.meta.simp_tactic
import init.meta.smt.congruence_closure init.control.combinators
import init.meta.interactive_base init.meta.derive init.meta.match_tactic
import init.meta.congr_tactic init.meta.case_tag
open lean
open lean.parser
open native
precedence `?` : max
local postfix `?`:9001 := optional
local postfix *:9001 := many
namespace tactic
/-- allows metavars -/
meta def i_to_expr (q : pexpr) : tactic expr :=
to_expr q tt
/-- allow metavars and no subgoals -/
meta def i_to_expr_no_subgoals (q : pexpr) : tactic expr :=
to_expr q tt ff
/-- doesn't allows metavars -/
meta def i_to_expr_strict (q : pexpr) : tactic expr :=
to_expr q ff
/-- Auxiliary version of i_to_expr for apply-like tactics.
This is a workaround for comment
https://github.com/leanprover/lean/issues/1342#issuecomment-307912291
at issue #1342.
In interactive mode, given a tactic
apply f
we want the apply tactic to create all metavariables. The following
definition will return `@f` for `f`. That is, it will **not** create
metavariables for implicit arguments.
Before we added `i_to_expr_for_apply`, the tactic
apply le_antisymm
would first elaborate `le_antisymm`, and create
@le_antisymm ?m_1 ?m_2 ?m_3 ?m_4
The type class resolution problem
?m_2 : weak_order ?m_1
by the elaborator since ?m_1 is not assigned yet, and the problem is
discarded.
Then, we would invoke `apply_core`, which would create two
new metavariables for the explicit arguments, and try to unify the resulting
type with the current target. After the unification,
the metavariables ?m_1, ?m_3 and ?m_4 are assigned, but we lost
the information about the pending type class resolution problem.
With `i_to_expr_for_apply`, `le_antisymm` is elaborate into `@le_antisymm`,
the apply_core tactic creates all metavariables, and solves the ones that
can be solved by type class resolution.
Another possible fix: we modify the elaborator to return pending
type class resolution problems, and store them in the tactic_state.
-/
meta def i_to_expr_for_apply (q : pexpr) : tactic expr :=
let aux (n : name) : tactic expr := do
p ← resolve_name n,
match p with
| (expr.const c []) := do r ← mk_const c, save_type_info r q, return r
| _ := i_to_expr p
end
in match q with
| (expr.const c []) := aux c
| (expr.local_const c _ _ _) := aux c
| _ := i_to_expr q
end
namespace interactive
open _root_.interactive interactive.types expr
/--
itactic: parse a nested "interactive" tactic. That is, parse
`{` tactic `}`
-/
meta def itactic : Type :=
tactic unit
meta def propagate_tags (tac : itactic) : tactic unit :=
do tag ← get_main_tag,
if tag = [] then tac
else focus1 $ do
tac,
gs ← get_goals,
when (bnot gs.empty) $ do
new_tag ← get_main_tag,
when new_tag.empty $ with_enable_tags (set_main_tag tag)
meta def concat_tags (tac : tactic (list (name × expr))) : tactic unit :=
mcond tags_enabled
(do in_tag ← get_main_tag,
r ← tac,
/- remove assigned metavars -/
r ← r.mfilter $ λ ⟨n, m⟩, bnot <$> is_assigned m,
match r with
| [(_, m)] := set_tag m in_tag /- if there is only new subgoal, we just propagate `in_tag` -/
| _ := r.mmap' (λ ⟨n, m⟩, set_tag m (n::in_tag))
end)
(tac >> skip)
/--
If the current goal is a Pi/forall `∀ x : t, u` (resp. `let x := t in u`) then `intro` puts `x : t` (resp. `x := t`) in the local context. The new subgoal target is `u`.
If the goal is an arrow `t → u`, then it puts `h : t` in the local context and the new goal target is `u`.
If the goal is neither a Pi/forall nor begins with a let binder, the tactic `intro` applies the tactic `whnf` until an introduction can be applied or the goal is not head reducible. In the latter case, the tactic fails.
-/
meta def intro : parse ident_? → tactic unit
| none := propagate_tags (intro1 >> skip)
| (some h) := propagate_tags (tactic.intro h >> skip)
/--
Similar to `intro` tactic. The tactic `intros` will keep introducing new hypotheses until the goal target is not a Pi/forall or let binder.
The variant `intros h₁ ... hₙ` introduces `n` new hypotheses using the given identifiers to name them.
-/
meta def intros : parse ident_* → tactic unit
| [] := propagate_tags (tactic.intros >> skip)
| hs := propagate_tags (intro_lst hs >> skip)
/--
The tactic `introv` allows the user to automatically introduce the variables of a theorem and explicitly name the hypotheses involved. The given names are used to name non-dependent hypotheses.
Examples:
```
example : ∀ a b : nat, a = b → b = a :=
begin
introv h,
exact h.symm
end
```
The state after `introv h` is
```
a b : ℕ,
h : a = b
⊢ b = a
```
```
example : ∀ a b : nat, a = b → ∀ c, b = c → a = c :=
begin
introv h₁ h₂,
exact h₁.trans h₂
end
```
The state after `introv h₁ h₂` is
```
a b : ℕ,
h₁ : a = b,
c : ℕ,
h₂ : b = c
⊢ a = c
```
-/
meta def introv (ns : parse ident_*) : tactic unit :=
propagate_tags (tactic.introv ns >> return ())
/-- Parse a current name and new name for `rename`. -/
private meta def rename_arg_parser : parser (name × name) :=
prod.mk <$> ident <*> (optional (tk "->") *> ident)
/-- Parse the arguments of `rename`. -/
private meta def rename_args_parser : parser (list (name × name)) :=
(functor.map (λ x, [x]) rename_arg_parser)
<|>
(tk "[" *> sep_by (tk ",") rename_arg_parser <* tk "]")
/--
Rename one or more local hypotheses. The renamings are given as follows:
```
rename x y -- rename x to y
rename x → y -- ditto
rename [x y, a b] -- rename x to y and a to b
rename [x → y, a → b] -- ditto
```
Note that if there are multiple hypotheses called `x` in the context, then
`rename x y` will rename *all* of them. If you want to rename only one, use
`dedup` first.
-/
meta def rename (renames : parse rename_args_parser) : tactic unit :=
propagate_tags $ tactic.rename_many $ native.rb_map.of_list renames
/--
The `apply` tactic tries to match the current goal against the conclusion of the type of term. The argument term should be a term well-formed in the local context of the main goal. If it succeeds, then the tactic returns as many subgoals as the number of premises that have not been fixed by type inference or type class resolution. Non-dependent premises are added before dependent ones.
The `apply` tactic uses higher-order pattern matching, type class resolution, and first-order unification with dependent types.
-/
meta def apply (q : parse texpr) : tactic unit :=
concat_tags (do h ← i_to_expr_for_apply q, tactic.apply h)
/--
Similar to the `apply` tactic, but does not reorder goals.
-/
meta def fapply (q : parse texpr) : tactic unit :=
concat_tags (i_to_expr_for_apply q >>= tactic.fapply)
/--
Similar to the `apply` tactic, but only creates subgoals for non-dependent premises that have not been fixed by type inference or type class resolution.
-/
meta def eapply (q : parse texpr) : tactic unit :=
concat_tags (i_to_expr_for_apply q >>= tactic.eapply)
/--
Similar to the `apply` tactic, but allows the user to provide a `apply_cfg` configuration object.
-/
meta def apply_with (q : parse parser.pexpr) (cfg : apply_cfg) : tactic unit :=
concat_tags (do e ← i_to_expr_for_apply q, tactic.apply e cfg)
/--
Similar to the `apply` tactic, but uses matching instead of unification.
`apply_match t` is equivalent to `apply_with t {unify := ff}`
-/
meta def mapply (q : parse texpr) : tactic unit :=
concat_tags (do e ← i_to_expr_for_apply q, tactic.apply e {unify := ff})
/--
This tactic tries to close the main goal `... ⊢ t` by generating a term of type `t` using type class resolution.
-/
meta def apply_instance : tactic unit :=
tactic.apply_instance
/--
This tactic behaves like `exact`, but with a big difference: the user can put underscores `_` in the expression as placeholders for holes that need to be filled, and `refine` will generate as many subgoals as there are holes.
Note that some holes may be implicit. The type of each hole must either be synthesized by the system or declared by an explicit type ascription like `(_ : nat → Prop)`.
-/
meta def refine (q : parse texpr) : tactic unit :=
tactic.refine q
/--
This tactic looks in the local context for a hypothesis whose type is equal to the goal target. If it finds one, it uses it to prove the goal, and otherwise it fails.
-/
meta def assumption : tactic unit :=
tactic.assumption
/-- Try to apply `assumption` to all goals. -/
meta def assumption' : tactic unit :=
tactic.any_goals' tactic.assumption
private meta def change_core (e : expr) : option expr → tactic unit
| none := tactic.change e
| (some h) :=
do num_reverted : ℕ ← revert h,
expr.pi n bi d b ← target,
tactic.change $ expr.pi n bi e b,
intron num_reverted
/--
`change u` replaces the target `t` of the main goal to `u` provided that `t` is well formed with respect to the local context of the main goal and `t` and `u` are definitionally equal.
`change u at h` will change a local hypothesis to `u`.
`change t with u at h1 h2 ...` will replace `t` with `u` in all the supplied hypotheses (or `*`), or in the goal if no `at` clause is specified, provided that `t` and `u` are definitionally equal.
-/
meta def change (q : parse texpr) : parse (tk "with" *> texpr)? → parse location → tactic unit
| none (loc.ns [none]) := do e ← i_to_expr q, change_core e none
| none (loc.ns [some h]) := do eq ← i_to_expr q, eh ← get_local h, change_core eq (some eh)
| none _ := fail "change-at does not support multiple locations"
| (some w) l :=
do u ← mk_meta_univ,
ty ← mk_meta_var (sort u),
eq ← i_to_expr ``(%%q : %%ty),
ew ← i_to_expr ``(%%w : %%ty),
let repl := λe : expr, e.replace (λ a n, if a = eq then some ew else none),
l.try_apply
(λh, do e ← infer_type h, change_core (repl e) (some h))
(do g ← target, change_core (repl g) none)
/--
This tactic provides an exact proof term to solve the main goal. If `t` is the goal and `p` is a term of type `u` then `exact p` succeeds if and only if `t` and `u` can be unified.
-/
meta def exact (q : parse texpr) : tactic unit :=
do tgt : expr ← target,
i_to_expr_strict ``(%%q : %%tgt) >>= tactic.exact
/--
Like `exact`, but takes a list of terms and checks that all goals are discharged after the tactic.
-/
meta def exacts : parse pexpr_list_or_texpr → tactic unit
| [] := done
| (t :: ts) := exact t >> exacts ts
/--
A synonym for `exact` that allows writing `have/suffices/show ..., from ...` in tactic mode.
-/
meta def «from» := exact
/--
`revert h₁ ... hₙ` applies to any goal with hypotheses `h₁` ... `hₙ`. It moves the hypotheses and their dependencies to the target of the goal. This tactic is the inverse of `intro`.
-/
meta def revert (ids : parse ident*) : tactic unit :=
propagate_tags (do hs ← mmap tactic.get_local ids, revert_lst hs, skip)
private meta def resolve_name' (n : name) : tactic expr :=
do {
p ← resolve_name n,
match p with
| expr.const n _ := mk_const n -- create metavars for universe levels
| _ := i_to_expr p
end
}
/-- Version of to_expr that tries to bypass the elaborator if `p` is just a constant or local constant.
This is not an optimization, by skipping the elaborator we make sure that no unwanted resolution is used.
Example: the elaborator will force any unassigned ?A that must have be an instance of (has_one ?A) to nat.
Remark: another benefit is that auxiliary temporary metavariables do not appear in error messages. -/
meta def to_expr' (p : pexpr) : tactic expr :=
match p with
| (const c []) := do new_e ← resolve_name' c, save_type_info new_e p, return new_e
| (local_const c _ _ _) := do new_e ← resolve_name' c, save_type_info new_e p, return new_e
| _ := i_to_expr p
end
@[derive has_reflect]
meta structure rw_rule :=
(pos : pos)
(symm : bool)
(rule : pexpr)
meta def get_rule_eqn_lemmas (r : rw_rule) : tactic (list name) :=
let aux (n : name) : tactic (list name) := do {
p ← resolve_name n,
-- unpack local refs
let e := p.erase_annotations.get_app_fn.erase_annotations,
match e with
| const n _ := get_eqn_lemmas_for tt n
| _ := return []
end } <|> return [] in
match r.rule with
| const n _ := aux n
| local_const n _ _ _ := aux n
| _ := return []
end
private meta def rw_goal (cfg : rewrite_cfg) (rs : list rw_rule) : tactic unit :=
rs.mmap' $ λ r, do
save_info r.pos,
eq_lemmas ← get_rule_eqn_lemmas r,
orelse'
(do e ← to_expr' r.rule, rewrite_target e {symm := r.symm, ..cfg})
(eq_lemmas.mfirst $ λ n, do e ← mk_const n, rewrite_target e {symm := r.symm, ..cfg})
(eq_lemmas.empty)
private meta def uses_hyp (e : expr) (h : expr) : bool :=
e.fold ff $ λ t _ r, r || to_bool (t = h)
private meta def rw_hyp (cfg : rewrite_cfg) : list rw_rule → expr → tactic unit
| [] hyp := skip
| (r::rs) hyp := do
save_info r.pos,
eq_lemmas ← get_rule_eqn_lemmas r,
orelse'
(do e ← to_expr' r.rule,
(if uses_hyp e hyp then pure e else rewrite_hyp e hyp {symm := r.symm, ..cfg}) >>= rw_hyp rs)
(eq_lemmas.mfirst $ λ n, do e ← mk_const n, rewrite_hyp e hyp {symm := r.symm, ..cfg} >>= rw_hyp rs)
(eq_lemmas.empty)
meta def rw_rule_p (ep : parser pexpr) : parser rw_rule :=
rw_rule.mk <$> cur_pos <*> (option.is_some <$> (with_desc "←" (tk "←" <|> tk "<-"))?) <*> ep
@[derive has_reflect]
meta structure rw_rules_t :=
(rules : list rw_rule)
(end_pos : option pos)
-- accepts the same content as `pexpr_list_or_texpr`, but with correct goal info pos annotations
meta def rw_rules : parser rw_rules_t :=
(tk "[" *>
rw_rules_t.mk <$> sep_by (skip_info (tk ",")) (set_goal_info_pos $ rw_rule_p (parser.pexpr 0))
<*> (some <$> cur_pos <* set_goal_info_pos (tk "]")))
<|> rw_rules_t.mk <$> (list.ret <$> rw_rule_p texpr) <*> return none
private meta def rw_core (rs : parse rw_rules) (loca : parse location) (cfg : rewrite_cfg) : tactic unit :=
match loca with
| loc.wildcard := loca.try_apply (rw_hyp cfg rs.rules) (rw_goal cfg rs.rules)
| _ := loca.apply (rw_hyp cfg rs.rules) (rw_goal cfg rs.rules)
end >> try (reflexivity reducible)
>> (returnopt rs.end_pos >>= save_info <|> skip)
/--
`rewrite e` applies identity `e` as a rewrite rule to the target of the main goal. If `e` is preceded by left arrow (`←` or `<-`), the rewrite is applied in the reverse direction. If `e` is a defined constant, then the equational lemmas associated with `e` are used. This provides a convenient way to unfold `e`.
`rewrite [e₁, ..., eₙ]` applies the given rules sequentially.
`rewrite e at l` rewrites `e` at location(s) `l`, where `l` is either `*` or a list of hypotheses in the local context. In the latter case, a turnstile `⊢` or `|-` can also be used, to signify the target of the goal.
-/
meta def rewrite (q : parse rw_rules) (l : parse location) (cfg : rewrite_cfg := {}) : tactic unit :=
propagate_tags (rw_core q l cfg)
/--
An abbreviation for `rewrite`.
-/
meta def rw (q : parse rw_rules) (l : parse location) (cfg : rewrite_cfg := {}) : tactic unit :=
propagate_tags (rw_core q l cfg)
/--
`rewrite` followed by `assumption`.
-/
meta def rwa (q : parse rw_rules) (l : parse location) (cfg : rewrite_cfg := {}) : tactic unit :=
rewrite q l cfg >> try assumption
/--
A variant of `rewrite` that uses the unifier more aggressively, unfolding semireducible definitions.
-/
meta def erewrite (q : parse rw_rules) (l : parse location) (cfg : rewrite_cfg := {md := semireducible}) : tactic unit :=
propagate_tags (rw_core q l cfg)
/--
An abbreviation for `erewrite`.
-/
meta def erw (q : parse rw_rules) (l : parse location) (cfg : rewrite_cfg := {md := semireducible}) : tactic unit :=
propagate_tags (rw_core q l cfg)
/--
Returns the unique names of all hypotheses (local constants) in the context.
-/
private meta def hyp_unique_names : tactic name_set :=
do ctx ← local_context,
pure $ ctx.foldl (λ r h, r.insert h.local_uniq_name) mk_name_set
/--
Returns all hypotheses (local constants) from the context except those whose
unique names are in `hyp_uids`.
-/
private meta def hyps_except (hyp_uids : name_set) : tactic (list expr) :=
do ctx ← local_context,
pure $ ctx.filter (λ (h : expr), ¬ hyp_uids.contains h.local_uniq_name)
/--
Apply `t` to the main goal and revert any new hypothesis in the generated goals.
If `t` is a supported tactic or chain of supported tactics (e.g. `induction`,
`cases`, `apply`, `constructor`), the generated goals are also tagged with case
tags. You can then use `case` to focus such tagged goals.
Two typical uses of `with_cases`:
1. Applying a custom eliminator:
```
lemma my_nat_rec :
∀ n {P : ℕ → Prop} (zero : P 0) (succ : ∀ n, P n → P (n + 1)), P n := ...
example (n : ℕ) : n = n :=
begin
with_cases { apply my_nat_rec n },
case zero { refl },
case succ : m ih { refl }
end
```
2. Enabling the use of `case` after a chain of case-splitting tactics:
```
example (n m : ℕ) : unit :=
begin
with_cases { cases n; induction m },
case nat.zero nat.zero { exact () },
case nat.zero nat.succ : k { exact () },
case nat.succ nat.zero : i { exact () },
case nat.succ nat.succ : k i ih_i { exact () }
end
```
-/
meta def with_cases (t : itactic) : tactic unit :=
with_enable_tags $ focus1 $ do
input_hyp_uids ← hyp_unique_names,
t,
all_goals' $ do
in_tag ← get_main_tag,
new_hyps ← hyps_except input_hyp_uids,
n ← revert_lst new_hyps,
set_main_tag (case_tag.from_tag_pi in_tag n).render
private meta def generalize_arg_p_aux : pexpr → parser (pexpr × name)
| (app (app (macro _ [const `eq _ ]) h) (local_const x _ _ _)) := pure (h, x)
| _ := fail "parse error"
private meta def generalize_arg_p : parser (pexpr × name) :=
with_desc "expr = id" $ parser.pexpr 0 >>= generalize_arg_p_aux
/--
`generalize : e = x` replaces all occurrences of `e` in the target with a new hypothesis `x` of the same type.
`generalize h : e = x` in addition registers the hypothesis `h : e = x`.
-/
meta def generalize (h : parse ident?) (_ : parse $ tk ":") (p : parse generalize_arg_p) : tactic unit :=
propagate_tags $
do let (p, x) := p,
e ← i_to_expr p,
some h ← pure h | tactic.generalize e x >> intro1 >> skip,
tgt ← target,
-- if generalizing fails, fall back to not replacing anything
tgt' ← do {
⟨tgt', _⟩ ← solve_aux tgt (tactic.generalize e x >> target),
to_expr ``(Π x, %%e = x → %%(tgt'.binding_body.lift_vars 0 1))
} <|> to_expr ``(Π x, %%e = x → %%tgt),
t ← assert h tgt',
swap,
exact ``(%%t %%e rfl),
intro x,
intro h
meta def cases_arg_p : parser (option name × pexpr) :=
with_desc "(id :)? expr" $ do
t ← texpr,
match t with
| (local_const x _ _ _) :=
(tk ":" *> do t ← texpr, pure (some x, t)) <|> pure (none, t)
| _ := pure (none, t)
end
/--
Updates the tags of new subgoals produced by `cases` or `induction`. `in_tag`
is the initial tag, i.e. the tag of the goal on which `cases`/`induction` was
applied. `rs` should contain, for each subgoal, the constructor name
associated with that goal and the hypotheses that were introduced.
-/
private meta def set_cases_tags (in_tag : tag) (rs : list (name × list expr)) : tactic unit :=
do gs ← get_goals,
match gs with
-- if only one goal was produced, we should not make the tag longer
| [g] := set_tag g in_tag
| _ :=
let tgs : list (name × list expr × expr) :=
rs.map₂ (λ ⟨n, new_hyps⟩ g, ⟨n, new_hyps, g⟩) gs in
tgs.mmap' $ λ ⟨n, new_hyps, g⟩, with_enable_tags $
set_tag g $
(case_tag.from_tag_hyps (n :: in_tag) (new_hyps.map expr.local_uniq_name)).render
end
precedence `generalizing` : 0
/--
Assuming `x` is a variable in the local context with an inductive type, `induction x` applies induction on `x` to the main goal, producing one goal for each constructor of the inductive type, in which the target is replaced by a general instance of that constructor and an inductive hypothesis is added for each recursive argument to the constructor. If the type of an element in the local context depends on `x`, that element is reverted and reintroduced afterward, so that the inductive hypothesis incorporates that hypothesis as well.
For example, given `n : nat` and a goal with a hypothesis `h : P n` and target `Q n`, `induction n` produces one goal with hypothesis `h : P 0` and target `Q 0`, and one goal with hypotheses `h : P (nat.succ a)` and `ih₁ : P a → Q a` and target `Q (nat.succ a)`. Here the names `a` and `ih₁` ire chosen automatically.
`induction e`, where `e` is an expression instead of a variable, generalizes `e` in the goal, and then performs induction on the resulting variable.
`induction e with y₁ ... yₙ`, where `e` is a variable or an expression, specifies that the sequence of names `y₁ ... yₙ` should be used for the arguments to the constructors and inductive hypotheses, including implicit arguments. If the list does not include enough names for all of the arguments, additional names are generated automatically. If too many names are given, the extra ones are ignored. Underscores can be used in the list, in which case the corresponding names are generated automatically. Note that for long sequences of names, the `case` tactic provides a more convenient naming mechanism.
`induction e using r` allows the user to specify the principle of induction that should be used. Here `r` should be a theorem whose result type must be of the form `C t`, where `C` is a bound variable and `t` is a (possibly empty) sequence of bound variables
`induction e generalizing z₁ ... zₙ`, where `z₁ ... zₙ` are variables in the local context, generalizes over `z₁ ... zₙ` before applying the induction but then introduces them in each goal. In other words, the net effect is that each inductive hypothesis is generalized.
`induction h : t` will introduce an equality of the form `h : t = C x y`, asserting that the input term is equal to the current constructor case, to the context.
-/
meta def induction (hp : parse cases_arg_p) (rec_name : parse using_ident) (ids : parse with_ident_list)
(revert : parse $ (tk "generalizing" *> ident*)?) : tactic unit :=
do in_tag ← get_main_tag,
focus1 $ do {
-- process `h : t` case
e ← match hp with
| (some h, p) := do
x ← get_unused_name,
generalize h () (p, x),
get_local x
| (none, p) := i_to_expr p
end,
-- generalize major premise
e ← if e.is_local_constant then pure e
else tactic.generalize e >> intro1,
-- generalize major premise args
(e, newvars, locals) ← do {
none ← pure rec_name | pure (e, [], []),
t ← infer_type e,
t ← whnf_ginductive t,
const n _ ← pure t.get_app_fn | pure (e, [], []),
env ← get_env,
tt ← pure $ env.is_inductive n | pure (e, [], []),
let (locals, nonlocals) := (t.get_app_args.drop $ env.inductive_num_params n).partition
(λ arg : expr, arg.is_local_constant),
_ :: _ ← pure nonlocals | pure (e, [], []),
n ← tactic.revert e,
newvars ← nonlocals.mmap $ λ arg, do {
n ← revert_kdeps arg,
tactic.generalize arg,
h ← intro1,
intron n,
-- now try to clear hypotheses that may have been abstracted away
let locals := arg.fold [] (λ e _ acc, if e.is_local_constant then e::acc else acc),
locals.mmap' (try ∘ clear),
pure h
},
intron (n-1),
e ← intro1,
pure (e, newvars, locals)
},
-- revert `generalizing` params (and their dependencies, if any)
to_generalize ← (revert.get_or_else []).mmap tactic.get_local,
num_generalized ← revert_lst to_generalize,
-- perform the induction
rs ← tactic.induction e ids rec_name,
-- re-introduce the generalized hypotheses
gen_hyps ← all_goals $ do {
new_hyps ← intron' num_generalized,
clear_lst (newvars.map local_pp_name),
(e::locals).mmap' (try ∘ clear),
pure new_hyps
},
set_cases_tags in_tag $
@list.map₂ (name × list expr × list (name × expr)) _ (name × list expr)
(λ ⟨n, hyps, _⟩ gen_hyps, ⟨n, hyps ++ gen_hyps⟩) rs gen_hyps
}
open case_tag.match_result
private meta def goals_with_matching_tag (ns : list name) :
tactic (list (expr × case_tag) × list (expr × case_tag)) :=
do gs ← get_goals,
(gs : list (expr × tag)) ← gs.mmap (λ g, do t ← get_tag g, pure (g, t)),
pure $ gs.foldr
(λ ⟨g, t⟩ ⟨exact_matches, suffix_matches⟩,
match case_tag.parse t with
| none := ⟨exact_matches, suffix_matches⟩
| some t :=
match case_tag.match_tag ns t with
| exact_match := ⟨⟨g, t⟩ :: exact_matches, suffix_matches⟩
| fuzzy_match := ⟨exact_matches, ⟨g, t⟩ :: suffix_matches⟩
| no_match := ⟨exact_matches, suffix_matches⟩
end
end)
([], [])
private meta def goal_with_matching_tag (ns : list name) : tactic (expr × case_tag) :=
do ⟨exact_matches, suffix_matches⟩ ← goals_with_matching_tag ns,
match exact_matches, suffix_matches with
| [] , [] := fail format!
"Invalid `case`: there is no goal tagged with suffix {ns}."
| [] , [g] := pure g
| [] , _ :=
let tags : list (list name) := suffix_matches.map (λ ⟨_, t⟩, t.case_names.reverse) in
fail format!
"Invalid `case`: there is more than one goal tagged with suffix {ns}.\nMatching tags: {tags}"
| [g], _ := pure g
| _ , _ := fail format!
"Invalid `case`: there is more than one goal tagged with tag {ns}."
end
meta def case_arg_parser : lean.parser (list name × option (list name)) :=
prod.mk <$> ident_* <*> (tk ":" *> ident_*)?
meta def case_parser : lean.parser (list (list name × option (list name))) :=
(list_of case_arg_parser)
<|>
(functor.map (λ x, [x]) case_arg_parser)
/--
Focuses on a goal ('case') generated by `induction`, `cases` or `with_cases`.
The goal is selected by giving one or more names which must match exactly one
goal. A goal is matched if the given names are a suffix of its goal tag.
Additionally, each name in the sequence can be abbreviated to a suffix of the
corresponding name in the goal tag. Thus, a goal with tag
```
nat.zero, list.nil
```
can be selected with any of these invocations (among others):
```
case nat.zero list.nil {...}
case nat.zero nil {...}
case zero nil {...}
case nil {...}
```
Additionally, the form
```
case C : N₀ ... Nₙ {...}
```
can be used to rename hypotheses introduced by the preceding
`cases`/`induction`/`with_cases`, using the names `Nᵢ`. For example:
```
example (xs : list ℕ) : xs = xs :=
begin
induction xs,
case nil { reflexivity },
case cons : x xs ih {
-- x : ℕ, xs : list ℕ, ih : xs = xs
reflexivity }
end
```
Note that this renaming functionality only work reliably *directly after* an
`induction`/`cases`/`with_cases`. If you need to perform additional work after
an `induction` or `cases` (e.g. introduce hypotheses in all goals), use
`with_cases`.
Multiple cases can be handled by the same tactic block with
```
case [A : N₀ ... Nₙ, B : M₀ ... Mₙ] {...}
```
-/
/-
TODO `case` could be generalised to work with zero names as well. The form
case : x y z { ... }
would select the first goal (or the first goal with a case tag), renaming
hypotheses to `x, y, z`. The renaming functionality would be available only if
the goal has a case tag.
-/
meta def case (args : parse case_parser) (tac : itactic) : tactic unit :=
do
target_goals ← args.mmap (λ ⟨ns, ids⟩, do
⟨goal, tag⟩ ← goal_with_matching_tag ns,
let ids := ids.get_or_else [],
let num_ids := ids.length,
goals ← get_goals,
let other_goals := goals.filter (≠ goal),
set_goals [goal],
match tag with
| (case_tag.pi _ num_args) := do
intro_lst ids,
when (num_ids < num_args) $ intron (num_args - num_ids)
| (case_tag.hyps _ new_hyp_names) := do
let num_new_hyps := new_hyp_names.length,
when (num_ids > num_new_hyps) $ fail format!
("Invalid `case`: You gave {num_ids} names, but the case introduces " ++
"{num_new_hyps} new hypotheses."),
let renamings := native.rb_map.of_list (new_hyp_names.zip ids),
propagate_tags $ tactic.rename_many renamings tt tt
end,
goals ← get_goals,
set_goals other_goals,
match goals with
| [g] := return g
| _ := fail "Unexpected goals introduced by renaming"
end),
remaining_goals ← get_goals,
set_goals target_goals,
tac,
unsolved_goals ← get_goals,
match unsolved_goals with
| [] := set_goals remaining_goals
| _ := fail "case tactic failed, focused goals have not been solved"
end
/--
Assuming `x` is a variable in the local context with an inductive type, `destruct x` splits the main goal, producing one goal for each constructor of the inductive type, in which `x` is assumed to be a general instance of that constructor. In contrast to `cases`, the local context is unchanged, i.e. no elements are reverted or introduced.
For example, given `n : nat` and a goal with a hypothesis `h : P n` and target `Q n`, `destruct n` produces one goal with target `n = 0 → Q n`, and one goal with target `∀ (a : ℕ), (λ (w : ℕ), n = w → Q n) (nat.succ a)`. Here the name `a` is chosen automatically.
-/
meta def destruct (p : parse texpr) : tactic unit :=
i_to_expr p >>= tactic.destruct
meta def cases_core (e : expr) (ids : list name := []) : tactic unit :=
do in_tag ← get_main_tag,
focus1 $ do
rs ← tactic.cases e ids,
set_cases_tags in_tag rs
/--
Assuming `x` is a variable in the local context with an inductive type, `cases x` splits the main goal, producing one goal for each constructor of the inductive type, in which the target is replaced by a general instance of that constructor. If the type of an element in the local context depends on `x`, that element is reverted and reintroduced afterward, so that the case split affects that hypothesis as well.
For example, given `n : nat` and a goal with a hypothesis `h : P n` and target `Q n`, `cases n` produces one goal with hypothesis `h : P 0` and target `Q 0`, and one goal with hypothesis `h : P (nat.succ a)` and target `Q (nat.succ a)`. Here the name `a` is chosen automatically.
`cases e`, where `e` is an expression instead of a variable, generalizes `e` in the goal, and then cases on the resulting variable.
`cases e with y₁ ... yₙ`, where `e` is a variable or an expression, specifies that the sequence of names `y₁ ... yₙ` should be used for the arguments to the constructors, including implicit arguments. If the list does not include enough names for all of the arguments, additional names are generated automatically. If too many names are given, the extra ones are ignored. Underscores can be used in the list, in which case the corresponding names are generated automatically.
`cases h : e`, where `e` is a variable or an expression, performs cases on `e` as above, but also adds a hypothesis `h : e = ...` to each hypothesis, where `...` is the constructor instance for that particular case.
-/
meta def cases : parse cases_arg_p → parse with_ident_list → tactic unit
| (none, p) ids := do
e ← i_to_expr p,
cases_core e ids
| (some h, p) ids := do
x ← get_unused_name,
generalize h () (p, x),
hx ← get_local x,
cases_core hx ids
private meta def find_matching_hyp (ps : list pattern) : tactic expr :=
any_hyp $ λ h, do
type ← infer_type h,
ps.mfirst $ λ p, do
match_pattern p type,
return h
/--
`cases_matching p` applies the `cases` tactic to a hypothesis `h : type` if `type` matches the pattern `p`.
`cases_matching [p_1, ..., p_n]` applies the `cases` tactic to a hypothesis `h : type` if `type` matches one of the given patterns.
`cases_matching* p` more efficient and compact version of `focus1 { repeat { cases_matching p } }`. It is more efficient because the pattern is compiled once.
Example: The following tactic destructs all conjunctions and disjunctions in the current goal.
```
cases_matching* [_ ∨ _, _ ∧ _]
```
-/
meta def cases_matching (rec : parse $ (tk "*")?) (ps : parse pexpr_list_or_texpr) : tactic unit :=
do ps ← ps.mmap pexpr_to_pattern,
if rec.is_none
then find_matching_hyp ps >>= cases_core
else tactic.focus1 $ tactic.repeat $ find_matching_hyp ps >>= cases_core
/-- Shorthand for `cases_matching` -/
meta def casesm (rec : parse $ (tk "*")?) (ps : parse pexpr_list_or_texpr) : tactic unit :=
cases_matching rec ps
private meta def try_cases_for_types (type_names : list name) (at_most_one : bool) : tactic unit :=
any_hyp $ λ h, do
I ← expr.get_app_fn <$> (infer_type h >>= head_beta),
guard I.is_constant,
guard (I.const_name ∈ type_names),
tactic.focus1 (cases_core h >> if at_most_one then do n ← num_goals, guard (n <= 1) else skip)
/--
`cases_type I` applies the `cases` tactic to a hypothesis `h : (I ...)`
`cases_type I_1 ... I_n` applies the `cases` tactic to a hypothesis `h : (I_1 ...)` or ... or `h : (I_n ...)`
`cases_type* I` is shorthand for `focus1 { repeat { cases_type I } }`
`cases_type! I` only applies `cases` if the number of resulting subgoals is <= 1.
Example: The following tactic destructs all conjunctions and disjunctions in the current goal.
```
cases_type* or and
```
-/
meta def cases_type (one : parse $ (tk "!")?) (rec : parse $ (tk "*")?) (type_names : parse ident*) : tactic unit :=
do type_names ← type_names.mmap resolve_constant,
if rec.is_none
then try_cases_for_types type_names (bnot one.is_none)
else tactic.focus1 $ tactic.repeat $ try_cases_for_types type_names (bnot one.is_none)
/--
Tries to solve the current goal using a canonical proof of `true`, or the `reflexivity` tactic, or the `contradiction` tactic.
-/
meta def trivial : tactic unit :=
tactic.triv <|> tactic.reflexivity <|> tactic.contradiction <|> fail "trivial tactic failed"
/--
Closes the main goal using `sorry`. Takes an optional ignored tactic block.
The ignored tactic block is useful for "commenting out" part of a proof during development:
```lean
begin
split,
admit { expensive_tactic },
end
```
-/
meta def admit (t : parse (with_desc "{...}" parser.itactic)?) : tactic unit := tactic.admit
/--
Closes the main goal using `sorry`. Takes an optional ignored tactic block.
The ignored tactic block is useful for "commenting out" part of a proof during development:
```lean
begin
split,
sorry { expensive_tactic },
end
```
-/
meta def «sorry» (t : parse (with_desc "{...}" parser.itactic)?) : tactic unit := tactic.admit
/--
The contradiction tactic attempts to find in the current local context a hypothesis that is equivalent to an empty inductive type (e.g. `false`), a hypothesis of the form `c_1 ... = c_2 ...` where `c_1` and `c_2` are distinct constructors, or two contradictory hypotheses.
-/
meta def contradiction : tactic unit :=
tactic.contradiction
/--
`iterate { t }` repeatedly applies tactic `t` until `t` fails. `iterate { t }` always succeeds.
`iterate n { t }` applies `t` `n` times.
-/
meta def iterate (n : parse small_nat?) (t : itactic) : tactic unit :=
match n with
| none := tactic.iterate' t
| some n := iterate_exactly' n t
end
/--
`repeat { t }` applies `t` to each goal. If the application succeeds,
the tactic is applied recursively to all the generated subgoals until it eventually fails.
The recursion stops in a subgoal when the tactic has failed to make progress.
The tactic `repeat { t }` never fails.
-/
meta def repeat : itactic → tactic unit :=
tactic.repeat
/--
`try { t }` tries to apply tactic `t`, but succeeds whether or not `t` succeeds.
-/
meta def try : itactic → tactic unit :=
tactic.try
/--
A do-nothing tactic that always succeeds.
-/
meta def skip : tactic unit :=
tactic.skip
/--
`solve1 { t }` applies the tactic `t` to the main goal and fails if it is not solved.
-/
meta def solve1 : itactic → tactic unit :=
tactic.solve1
/--
`abstract id { t }` tries to use tactic `t` to solve the main goal. If it succeeds, it abstracts the goal as an independent definition or theorem with name `id`. If `id` is omitted, a name is generated automatically.
-/
meta def abstract (id : parse ident?) (tac : itactic) : tactic unit :=
tactic.abstract tac id
/--
`all_goals { t }` applies the tactic `t` to every goal, and succeeds if each application succeeds.
-/
meta def all_goals : itactic → tactic unit :=
tactic.all_goals'
/--
`any_goals { t }` applies the tactic `t` to every goal, and succeeds if at least one application succeeds.
-/
meta def any_goals : itactic → tactic unit :=
tactic.any_goals'
/--
`focus { t }` temporarily hides all goals other than the first, applies `t`, and then restores the other goals. It fails if there are no goals.
-/
meta def focus (tac : itactic) : tactic unit :=
tactic.focus1 tac
private meta def assume_core (n : name) (ty : pexpr) :=
do t ← target,
when (not $ t.is_pi ∨ t.is_let) whnf_target,
t ← target,
when (not $ t.is_pi ∨ t.is_let) $
fail "assume tactic failed, Pi/let expression expected",
ty ← i_to_expr ``(%%ty : Sort*),
unify ty t.binding_domain,
intro_core n >> skip
/--
Assuming the target of the goal is a Pi or a let, `assume h : t` unifies the type of the binder with `t` and introduces it with name `h`, just like `intro h`. If `h` is absent, the tactic uses the name `this`. If `t` is omitted, it will be inferred.
`assume (h₁ : t₁) ... (hₙ : tₙ)` introduces multiple hypotheses. Any of the types may be omitted, but the names must be present.
-/
meta def «assume» : parse (sum.inl <$> (tk ":" *> texpr) <|> sum.inr <$> parse_binders tac_rbp) → tactic unit
| (sum.inl ty) := assume_core `this ty
| (sum.inr binders) :=
binders.mmap' $ λ b, assume_core b.local_pp_name b.local_type
/--
`have h : t := p` adds the hypothesis `h : t` to the current goal if `p` a term of type `t`. If `t` is omitted, it will be inferred.
`have h : t` adds the hypothesis `h : t` to the current goal and opens a new subgoal with target `t`. The new subgoal becomes the main goal. If `t` is omitted, it will be replaced by a fresh metavariable.
If `h` is omitted, the name `this` is used.
-/
meta def «have» (h : parse ident?) (q₁ : parse (tk ":" *> texpr)?) (q₂ : parse $ (tk ":=" *> texpr)?) : tactic unit :=
let h := h.get_or_else `this in
match q₁, q₂ with
| some e, some p := do
t ← i_to_expr ``(%%e : Sort*),
v ← i_to_expr ``(%%p : %%t),
tactic.assertv h t v
| none, some p := do
p ← i_to_expr p,
tactic.note h none p
| some e, none := i_to_expr ``(%%e : Sort*) >>= tactic.assert h
| none, none := do
u ← mk_meta_univ,
e ← mk_meta_var (sort u),
tactic.assert h e
end >> skip
/--
`let h : t := p` adds the hypothesis `h : t := p` to the current goal if `p` a term of type `t`. If `t` is omitted, it will be inferred.
`let h : t` adds the hypothesis `h : t := ?M` to the current goal and opens a new subgoal `?M : t`. The new subgoal becomes the main goal. If `t` is omitted, it will be replaced by a fresh metavariable.