/-
Copyright (c) 2018 Sean Leather. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sean Leather, Mario Carneiro

! This file was ported from Lean 3 source module data.list.alist
! leanprover-community/mathlib commit f808feb6c18afddb25e66a71d317643cf7fb5fbb
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.List.Sigma

/-!
# Association Lists

This file defines association lists. An association list is a list where every element consists of
a key and a value, and no two entries have the same key. The type of the value is allowed to be
dependent on the type of the key.

This type dependence is implemented using `Sigma`: The elements of the list are of type `Sigma β`,
for some type index `β`.

## Main definitions

Association lists are represented by the `AList` structure. This file defines this structure and
provides ways to access, modify, and combine `AList`s.

* `AList.keys` returns a list of keys of the alist.
* `AList.membership` returns membership in the set of keys.
* `AList.erase` removes a certain key.
* `AList.insert` adds a key-value mapping to the list.
* `AList.union` combines two association lists.

## References

* <https://en.wikipedia.org/wiki/Association_list>

-/

universe u v w

open List

variable {α : Type u} {β : α → Type v}

/-- `AList β` is a key-value map stored as a `List` (i.e. a linked list).
  It is a wrapper around certain `List` functions with the added constraint
  that the list have unique keys. -/
structure AList (β : α → Type v) : Type max u v where
  /-- The underlying `List` of an `AList` -/
  entries : List (Sigma β)
  /-- There are no duplicate keys in `entries` -/
  nodupKeys : entries.NodupKeys
#align alist AList

/-- Given `l : List (sigma β)`, create a term of type `alist β` by removing
entries with duplicate keys. -/
def List.toAList [DecidableEq α] {β : α → Type v} (l : List (Sigma β)) : AList β where
  entries := _
  nodupKeys := nodupKeys_dedupKeys l
#align list.to_alist List.toAList

namespace AList

@[ext]
theorem ext : ∀ {s t : AList β}, s.entries = t.entries → s = t
  | ⟨l₁, h₁⟩, ⟨l₂, _⟩, H => by congr
#align alist.ext AList.ext

theorem ext_iff {s t : AList β} : s = t ↔ s.entries = t.entries :=
  ⟨congr_arg _, ext⟩
#align alist.ext_iff AList.ext_iff

instance [DecidableEq α] [∀ a, DecidableEq (β a)] : DecidableEq (AList β) := fun xs ys => by
  rw [ext_iff]; infer_instance

/-! ### keys -/

/-- The list of keys of an association list. -/
def keys (s : AList β) : List α :=
  s.entries.keys
#align alist.keys AList.keys

theorem keys_nodup (s : AList β) : s.keys.Nodup :=
  s.nodupKeys
#align alist.keys_nodup AList.keys_nodup

/-! ### mem -/

/-- The predicate `a ∈ s` means that `s` has a value associated to the key `a`. -/
instance : Membership α (AList β) :=
  ⟨fun a s => a ∈ s.keys⟩

theorem mem_keys {a : α} {s : AList β} : a ∈ s ↔ a ∈ s.keys :=
  Iff.rfl
#align alist.mem_keys AList.mem_keys

theorem mem_of_perm {a : α} {s₁ s₂ : AList β} (p : s₁.entries ~ s₂.entries) : a ∈ s₁ ↔ a ∈ s₂ :=
  (p.map Sigma.fst).mem_iff
#align alist.mem_of_perm AList.mem_of_perm

/-! ### empty -/

/-- The empty association list. -/
instance : EmptyCollection (AList β) :=
  ⟨⟨[], nodupKeys_nil⟩⟩

instance : Inhabited (AList β) :=
  ⟨∅⟩

@[simp]
theorem not_mem_empty (a : α) : a ∉ (∅ : AList β) :=
  not_mem_nil a
#align alist.not_mem_empty AList.not_mem_empty

@[simp]
theorem empty_entries : (∅ : AList β).entries = [] :=
  rfl
#align alist.empty_entries AList.empty_entries

@[simp]
theorem keys_empty : (∅ : AList β).keys = [] :=
  rfl
#align alist.keys_empty AList.keys_empty

/-! ### singleton -/

/-- The singleton association list. -/
def singleton (a : α) (b : β a) : AList β :=
  ⟨[⟨a, b⟩], nodupKeys_singleton _⟩
#align alist.singleton AList.singleton

@[simp]
theorem singleton_entries (a : α) (b : β a) : (singleton a b).entries = [Sigma.mk a b] :=
  rfl
#align alist.singleton_entries AList.singleton_entries

@[simp]
theorem keys_singleton (a : α) (b : β a) : (singleton a b).keys = [a] :=
  rfl
#align alist.keys_singleton AList.keys_singleton

/-! ### lookup -/

section

variable [DecidableEq α]

/-- Look up the value associated to a key in an association list. -/
def lookup (a : α) (s : AList β) : Option (β a) :=
  s.entries.dlookup a
#align alist.lookup AList.lookup

@[simp]
theorem lookup_empty (a) : lookup a (∅ : AList β) = none :=
  rfl
#align alist.lookup_empty AList.lookup_empty

theorem lookup_isSome {a : α} {s : AList β} : (s.lookup a).isSome ↔ a ∈ s :=
  dlookup_isSome
#align alist.lookup_is_some AList.lookup_isSome

theorem lookup_eq_none {a : α} {s : AList β} : lookup a s = none ↔ a ∉ s :=
  dlookup_eq_none
#align alist.lookup_eq_none AList.lookup_eq_none

theorem mem_lookup_iff {a : α} {b : β a} {s : AList β} :
    b ∈ lookup a s ↔ Sigma.mk a b ∈ s.entries :=
  mem_dlookup_iff s.nodupKeys
#align alist.mem_lookup_iff AList.mem_lookup_iff

theorem perm_lookup {a : α} {s₁ s₂ : AList β} (p : s₁.entries ~ s₂.entries) :
    s₁.lookup a = s₂.lookup a :=
  perm_dlookup _ s₁.nodupKeys s₂.nodupKeys p
#align alist.perm_lookup AList.perm_lookup

instance (a : α) (s : AList β) : Decidable (a ∈ s) :=
  decidable_of_iff _ lookup_isSome

/-! ### replace -/

/-- Replace a key with a given value in an association list.
  If the key is not present it does nothing. -/
def replace (a : α) (b : β a) (s : AList β) : AList β :=
  ⟨kreplace a b s.entries, (kreplace_nodupKeys a b).2 s.nodupKeys⟩
#align alist.replace AList.replace

@[simp]
theorem keys_replace (a : α) (b : β a) (s : AList β) : (replace a b s).keys = s.keys :=
  keys_kreplace _ _ _
#align alist.keys_replace AList.keys_replace

@[simp]
theorem mem_replace {a a' : α} {b : β a} {s : AList β} : a' ∈ replace a b s ↔ a' ∈ s := by
  rw [mem_keys, keys_replace, ← mem_keys]
#align alist.mem_replace AList.mem_replace

theorem perm_replace {a : α} {b : β a} {s₁ s₂ : AList β} :
    s₁.entries ~ s₂.entries → (replace a b s₁).entries ~ (replace a b s₂).entries :=
  Perm.kreplace s₁.nodupKeys
#align alist.perm_replace AList.perm_replace

end

/-- Fold a function over the key-value pairs in the map. -/
def foldl {δ : Type w} (f : δ → ∀ a, β a → δ) (d : δ) (m : AList β) : δ :=
  m.entries.foldl (fun r a => f r a.1 a.2) d
#align alist.foldl AList.foldl

/-! ### erase -/

section

variable [DecidableEq α]

/-- Erase a key from the map. If the key is not present, do nothing. -/
def erase (a : α) (s : AList β) : AList β :=
  ⟨s.entries.kerase a, s.nodupKeys.kerase a⟩
#align alist.erase AList.erase

@[simp]

@[simp]
theorem mem_erase {a a' : α} {s : AList β} : a' ∈ erase a s ↔ a' ≠ a ∧ a' ∈ s := by
  rw [mem_keys, keys_erase, s.keys_nodup.mem_erase_iff, ← mem_keys]
#align alist.mem_erase AList.mem_erase

theorem perm_erase {a : α} {s₁ s₂ : AList β} :
    s₁.entries ~ s₂.entries → (erase a s₁).entries ~ (erase a s₂).entries :=
  Perm.kerase s₁.nodupKeys
#align alist.perm_erase AList.perm_erase

@[simp]
theorem lookup_erase (a) (s : AList β) : lookup a (erase a s) = none :=
  dlookup_kerase a s.nodupKeys
#align alist.lookup_erase AList.lookup_erase

@[simp]
theorem lookup_erase_ne {a a'} {s : AList β} (h : a ≠ a') : lookup a (erase a' s) = lookup a s :=
  dlookup_kerase_ne h
#align alist.lookup_erase_ne AList.lookup_erase_ne

theorem erase_erase (a a' : α) (s : AList β) : (s.erase a).erase a' = (s.erase a').erase a :=
  ext <| kerase_kerase
#align alist.erase_erase AList.erase_erase

/-! ### insert -/

/-- Insert a key-value pair into an association list and erase any existing pair
  with the same key. -/
def insert (a : α) (b : β a) (s : AList β) : AList β :=
  ⟨kinsert a b s.entries, kinsert_nodupKeys a b s.nodupKeys⟩
#align alist.insert AList.insert

@[simp]
theorem insert_entries {a} {b : β a} {s : AList β} :
    (insert a b s).entries = Sigma.mk a b :: kerase a s.entries :=
  rfl
#align alist.insert_entries AList.insert_entries

theorem insert_entries_of_neg {a} {b : β a} {s : AList β} (h : a ∉ s) :
    (insert a b s).entries = ⟨a, b⟩ :: s.entries := by rw [insert_entries, kerase_of_not_mem_keys h]
#align alist.insert_entries_of_neg AList.insert_entries_of_neg

-- Todo: rename to `insert_of_not_mem`.
theorem insert_of_neg {a} {b : β a} {s : AList β} (h : a ∉ s) :
    insert a b s = ⟨⟨a, b⟩ :: s.entries, nodupKeys_cons.2 ⟨h, s.2⟩⟩ :=
  ext <| insert_entries_of_neg h
#align alist.insert_of_neg AList.insert_of_neg

@[simp]
theorem insert_empty (a) (b : β a) : insert a b ∅ = singleton a b :=
  rfl
#align alist.insert_empty AList.insert_empty

@[simp]
theorem mem_insert {a a'} {b' : β a'} (s : AList β) : a ∈ insert a' b' s ↔ a = a' ∨ a ∈ s :=
  mem_keys_kinsert
#align alist.mem_insert AList.mem_insert

@[simp]
theorem keys_insert {a} {b : β a} (s : AList β) : (insert a b s).keys = a :: s.keys.erase a := by
  simp [insert, keys, keys_kerase]
#align alist.keys_insert AList.keys_insert

theorem perm_insert {a} {b : β a} {s₁ s₂ : AList β} (p : s₁.entries ~ s₂.entries) :
    (insert a b s₁).entries ~ (insert a b s₂).entries := by
  simp only [insert_entries]; exact p.kinsert s₁.nodupKeys
#align alist.perm_insert AList.perm_insert

@[simp]
theorem lookup_insert {a} {b : β a} (s : AList β) : lookup a (insert a b s) = some b := by
  simp only [lookup, insert, dlookup_kinsert]
#align alist.lookup_insert AList.lookup_insert

@[simp]
theorem lookup_insert_ne {a a'} {b' : β a'} {s : AList β} (h : a ≠ a') :
    lookup a (insert a' b' s) = lookup a s :=
  dlookup_kinsert_ne h
#align alist.lookup_insert_ne AList.lookup_insert_ne

@[simp]
theorem lookup_to_alist {a} (s : List (Sigma β)) : lookup a s.toAList = s.dlookup a := by
  rw [List.toAList, lookup, dlookup_dedupKeys]
#align alist.lookup_to_alist AList.lookup_to_alist

@[simp]
theorem insert_insert {a} {b b' : β a} (s : AList β) : (s.insert a b).insert a b' = s.insert a b' :=
  by
  ext : 1 ; simp only [AList.insert_entries, List.kerase_cons_eq]
#align alist.insert_insert AList.insert_insert

theorem insert_insert_of_ne {a a'} {b : β a} {b' : β a'} (s : AList β) (h : a ≠ a') :
    ((s.insert a b).insert a' b').entries ~ ((s.insert a' b').insert a b).entries := by
  simp only [insert_entries]; rw [kerase_cons_ne, kerase_cons_ne, kerase_comm] <;>
    [apply Perm.swap, exact h, exact h.symm]
#align alist.insert_insert_of_ne AList.insert_insert_of_ne

@[simp]
theorem insert_singleton_eq {a : α} {b b' : β a} : insert a b (singleton a b') = singleton a b :=
  ext <| by
    simp only [AList.insert_entries, List.kerase_cons_eq, and_self_iff, AList.singleton_entries,
      heq_iff_eq, eq_self_iff_true]
#align alist.insert_singleton_eq AList.insert_singleton_eq

@[simp]
theorem entries_toAList (xs : List (Sigma β)) : (List.toAList xs).entries = dedupKeys xs :=
  rfl
#align alist.entries_to_alist AList.entries_toAList

theorem toAList_cons (a : α) (b : β a) (xs : List (Sigma β)) :
    List.toAList (⟨a, b⟩ :: xs) = insert a b xs.toAList :=
  rfl
#align alist.to_alist_cons AList.toAList_cons

theorem mk_cons_eq_insert (c : Sigma β) (l : List (Sigma β)) (h : (c :: l).NodupKeys) :
    (⟨c :: l, h⟩ : AList β) = insert c.1 c.2 ⟨l, nodupKeys_of_nodupKeys_cons h⟩ := by
  simpa [insert] using (kerase_of_not_mem_keys <| not_mem_keys_of_nodupKeys_cons h).symm
#align alist.mk_cons_eq_insert AList.mk_cons_eq_insert

/-- Recursion on an `alist`, using `insert`. Use as `induction l using alist.insert_rec`. -/
@[elab_as_elim]
def insertRec {C : AList β → Sort _} (H0 : C ∅)
    (IH : ∀ (a : α) (b : β a) (l : AList β), a ∉ l → C l → C (l.insert a b)) :
    ∀ l : AList β, C l
  | ⟨[], _⟩ => H0
  | ⟨c :: l, h⟩ => by
    rw [mk_cons_eq_insert]
    refine' IH _ _ _ _ (insertRec H0 IH _)
    exact not_mem_keys_of_nodupKeys_cons h
#align alist.insert_rec AList.insertRec

-- Test that the `induction` tactic works on `insert_rec`.
example (l : AList β) : True := by induction l using AList.insertRec <;> trivial

@[simp]
theorem insertRec_empty {C : AList β → Sort _} (H0 : C ∅)
    (IH : ∀ (a : α) (b : β a) (l : AList β), a ∉ l → C l → C (l.insert a b)) :
    @insertRec α β _ C H0 IH ∅ = H0 := by
  change @insertRec α β _ C H0 IH ⟨[], _⟩ = H0
  rw [insertRec]
#align alist.insert_rec_empty AList.insertRec_empty

theorem insertRec_insert {C : AList β → Sort _} (H0 : C ∅)
    (IH : ∀ (a : α) (b : β a) (l : AList β), a ∉ l → C l → C (l.insert a b)) {c : Sigma β}
    {l : AList β} (h : c.1 ∉ l) :
    @insertRec α β _ C H0 IH (l.insert c.1 c.2) = IH c.1 c.2 l h (@insertRec α β _ C H0 IH l) := by
  cases' l with l hl
  suffices
    HEq (@insertRec α β _ C H0 IH ⟨c :: l, nodupKeys_cons.2 ⟨h, hl⟩⟩)
      (IH c.1 c.2 ⟨l, hl⟩ h (@insertRec α β _ C H0 IH ⟨l, hl⟩))
    by
    cases c
    apply eq_of_heq
    convert this <;> rw [insert_of_neg h]
  rw [insertRec]
  apply cast_heq
#align alist.insert_rec_insert AList.insertRec_insert

theorem insertRec_insert_mk {C : AList β → Sort _} (H0 : C ∅)
    (IH : ∀ (a : α) (b : β a) (l : AList β), a ∉ l → C l → C (l.insert a b)) {a : α} (b : β a)
    {l : AList β} (h : a ∉ l) :
    @insertRec α β _ C H0 IH (l.insert a b) = IH a b l h (@insertRec α β _ C H0 IH l) :=
  @insertRec_insert α β _ C H0 IH ⟨a, b⟩ l h
#align alist.recursion_insert_mk AList.insertRec_insert_mk

/-! ### extract -/

/-- Erase a key from the map, and return the corresponding value, if found. -/
def extract (a : α) (s : AList β) : Option (β a) × AList β :=
  have : (kextract a s.entries).2.NodupKeys := by
    rw [kextract_eq_dlookup_kerase]; exact s.nodupKeys.kerase _
  match kextract a s.entries, this with
  | (b, l), h => (b, ⟨l, h⟩)
#align alist.extract AList.extract

@[simp]
theorem extract_eq_lookup_erase (a : α) (s : AList β) : extract a s = (lookup a s, erase a s) := by
  simp [extract]; constructor <;> rfl
#align alist.extract_eq_lookup_erase AList.extract_eq_lookup_erase

/-! ### union -/

/-- `s₁ ∪ s₂` is the key-based union of two association lists. It is
left-biased: if there exists an `a ∈ s₁`, `lookup a (s₁ ∪ s₂) = lookup a s₁`.
-/
def union (s₁ s₂ : AList β) : AList β :=
  ⟨s₁.entries.kunion s₂.entries, s₁.nodupKeys.kunion s₂.nodupKeys⟩
#align alist.union AList.union

instance : Union (AList β) :=
  ⟨union⟩

@[simp]
theorem union_entries {s₁ s₂ : AList β} : (s₁ ∪ s₂).entries = kunion s₁.entries s₂.entries :=
  rfl
#align alist.union_entries AList.union_entries

@[simp]
theorem empty_union {s : AList β} : (∅ : AList β) ∪ s = s :=
  ext rfl
#align alist.empty_union AList.empty_union

@[simp]
theorem union_empty {s : AList β} : s ∪ (∅ : AList β) = s :=
  ext <| by simp
#align alist.union_empty AList.union_empty

@[simp]
theorem mem_union {a} {s₁ s₂ : AList β} : a ∈ s₁ ∪ s₂ ↔ a ∈ s₁ ∨ a ∈ s₂ :=
  mem_keys_kunion
#align alist.mem_union AList.mem_union

theorem perm_union {s₁ s₂ s₃ s₄ : AList β} (p₁₂ : s₁.entries ~ s₂.entries)
    (p₃₄ : s₃.entries ~ s₄.entries) : (s₁ ∪ s₃).entries ~ (s₂ ∪ s₄).entries := by
  simp [p₁₂.kunion s₃.nodupKeys p₃₄]
#align alist.perm_union AList.perm_union

theorem union_erase (a : α) (s₁ s₂ : AList β) : erase a (s₁ ∪ s₂) = erase a s₁ ∪ erase a s₂ :=
  ext kunion_kerase.symm
#align alist.union_erase AList.union_erase

@[simp]
theorem lookup_union_left {a} {s₁ s₂ : AList β} : a ∈ s₁ → lookup a (s₁ ∪ s₂) = lookup a s₁ :=
  dlookup_kunion_left
#align alist.lookup_union_left AList.lookup_union_left

@[simp]
theorem lookup_union_right {a} {s₁ s₂ : AList β} : a ∉ s₁ → lookup a (s₁ ∪ s₂) = lookup a s₂ :=
  dlookup_kunion_right
#align alist.lookup_union_right AList.lookup_union_right

--Porting note: removing simp, LHS not in SNF, new theorem added instead.
theorem mem_lookup_union {a} {b : β a} {s₁ s₂ : AList β} :
    b ∈ lookup a (s₁ ∪ s₂) ↔ b ∈ lookup a s₁ ∨ a ∉ s₁ ∧ b ∈ lookup a s₂ :=
  mem_dlookup_kunion
#align alist.mem_lookup_union AList.mem_lookup_union

--Porting note: new theorem, version of `mem_lookup_union` with LHS in simp-normal form
@[simp]
theorem lookup_union_eq_some {a} {b : β a} {s₁ s₂ : AList β} :
    lookup a (s₁ ∪ s₂) = some b ↔ lookup a s₁ = some b ∨ a ∉ s₁ ∧ lookup a s₂ = some b :=
  mem_dlookup_kunion

theorem mem_lookup_union_middle {a} {b : β a} {s₁ s₂ s₃ : AList β} :
    b ∈ lookup a (s₁ ∪ s₃) → a ∉ s₂ → b ∈ lookup a (s₁ ∪ s₂ ∪ s₃) :=
  mem_dlookup_kunion_middle
#align alist.mem_lookup_union_middle AList.mem_lookup_union_middle

theorem insert_union {a} {b : β a} {s₁ s₂ : AList β} : insert a b (s₁ ∪ s₂) = insert a b s₁ ∪ s₂ :=
  by ext; simp
#align alist.insert_union AList.insert_union

theorem union_assoc {s₁ s₂ s₃ : AList β} : (s₁ ∪ s₂ ∪ s₃).entries ~ (s₁ ∪ (s₂ ∪ s₃)).entries :=
  lookup_ext (AList.nodupKeys _) (AList.nodupKeys _)
    (by simp [not_or, or_assoc, and_or_left, and_assoc])
#align alist.union_assoc AList.union_assoc

end

/-! ### disjoint -/

/-- Two associative lists are disjoint if they have no common keys. -/
def Disjoint (s₁ s₂ : AList β) : Prop :=
  ∀ k ∈ s₁.keys, ¬k ∈ s₂.keys
#align alist.disjoint AList.Disjoint

variable [DecidableEq α]

theorem union_comm_of_disjoint {s₁ s₂ : AList β} (h : Disjoint s₁ s₂) :
    (s₁ ∪ s₂).entries ~ (s₂ ∪ s₁).entries :=
  lookup_ext (AList.nodupKeys _) (AList.nodupKeys _)
    (by
      intros ; simp
      constructor <;> intro h'
      . cases' h' with h' h'
        · right
          refine' ⟨_, h'⟩
          apply h
          rw [keys, ← List.dlookup_isSome, h']
          exact rfl
        · left
          rw [h'.2]
      . cases' h' with h' h'
        · right
          refine' ⟨_, h'⟩
          intro h''
          apply h _ h''
          rw [keys, ← List.dlookup_isSome, h']
          exact rfl
        · left
          rw [h'.2])
#align alist.union_comm_of_disjoint AList.union_comm_of_disjoint

end AList


/-
Copyright (c) 2020 Jannis Limperg. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jannis Limperg

! This file was ported from Lean 3 source module tactic.dependencies
! leanprover-community/mathlib commit d6814c584384ddf2825ff038e868451a7c956f31
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Meta.RbMap
import Mathbin.Tactic.Core

/-!
# Tactics About Dependencies

This module provides tactics to compute dependencies and reverse dependencies of
hypotheses. An expression `e` depends on a hypothesis `h` if `e` would not be
valid if `h` were removed from the context. For example, the expression
`e := x > 0` depends on `x`. We say that `x` is a dependency of `e` and that `e`
is a reverse dependency of `x`.

It is sometimes useful to consider *inclusive* dependency: `e` inclusively
depends on `h` iff `e` depends on `h` or `e = h` (so inclusive dependency is the
reflexive closure of regular dependency).

Note that the standard library does not use quite the same terminology:

* `kdependencies`/`kdeps` from the standard library compute reverse
  dependencies, not dependencies.
* `kdepends_on` and functions derived from it ignore local definitions and
  therefore compute a weaker dependency relation (see next section).

## Local Definitions

Determining dependencies of hypotheses is usually straightforward: a hypothesis
`r : R` depends on another hypothesis `d : D` if `d` occurs in `R`. The
implementation is more involved, however, in the presence of local definitions.
Consider this context:

```lean
n m : ℕ
k : ℕ := m
o : ℕ := k
h : o > 0
```

`h` depends on `o`, `k` and `m`, but only the dependency on `o` is syntactically
obvious. `kdepends_on` ignores this complication and claims that `h` does not
depend on `k` or `m`. We do not follow this example but process local
definitions properly. This means that if the context contains a local
definition, we need to compute the syntactic dependencies of `h`, then their
dependencies, and so on.

## Direct Dependencies

If you want to ignore local definitions while computing dependencies, this
module also provides tactics to find the *direct* dependencies of a hypothesis.
These are the hypotheses that syntactically appear in the hypothesis's type (or
value, if the hypothesis is a local definition).
-/

open Native

open ExprSet (local_set_to_name_set)

open NameSet (local_list_to_name_set)

namespace Tactic

/-! ### Direct Dependencies -/

/-! #### Checking whether hypotheses directly depend on each other -/

/-- `type_has_local_in_name_set h ns` returns true iff the type of `h` contains a
local constant whose unique name appears in `ns`.
-/
unsafe def type_has_local_in_name_set (h : expr) (ns : name_set) : tactic Bool := do
  let h_type ← infer_type h
  pure <| h_type ns
#align tactic.type_has_local_in_name_set tactic.type_has_local_in_name_set

/-- `type_has_local_in_set h hs` returns true iff the type of `h` contains any of
the local constants `hs`.
-/
unsafe def type_has_local_in_set (h : expr) (hs : expr_set) : tactic Bool :=
  type_has_local_in_name_set h <| local_set_to_name_set hs
#align tactic.type_has_local_in_set tactic.type_has_local_in_set

/-- `type_has_local_in h hs` returns true iff the type of `h` contains any of the
local constants `hs`.
-/
unsafe def type_has_local_in (h : expr) (hs : List expr) : tactic Bool :=
  type_has_local_in_name_set h <| local_list_to_name_set hs
#align tactic.type_has_local_in tactic.type_has_local_in

/-- `local_def_value_has_local_in_name_set h ns` returns true iff `h` is a local
definition whose value contains a local constant whose unique name appears in
`ns`.
-/
unsafe def local_def_value_has_local_in_name_set (h : expr) (ns : name_set) : tactic Bool := do
  let some h_val ← try_core <| local_def_value h |
    pure false
  pure <| h_val ns
#align tactic.local_def_value_has_local_in_name_set tactic.local_def_value_has_local_in_name_set

/-- `local_def_value_has_local_in_set h hs` returns true iff `h` is a local
definition whose value contains any of the local constants `hs`.
-/
unsafe def local_def_value_has_local_in_set (h : expr) (hs : expr_set) : tactic Bool :=
  local_def_value_has_local_in_name_set h <| local_set_to_name_set hs
#align tactic.local_def_value_has_local_in_set tactic.local_def_value_has_local_in_set

/-- `local_def_value_has_local_in h hs` returns true iff `h` is a local definition
whose value contains any of the local constants `hs`.
-/
unsafe def local_def_value_has_local_in (h : expr) (hs : List expr) : tactic Bool :=
  local_def_value_has_local_in_name_set h <| local_list_to_name_set hs
#align tactic.local_def_value_has_local_in tactic.local_def_value_has_local_in

/-- `hyp_directly_depends_on_local_name_set h ns` is true iff the hypothesis `h`
directly depends on a hypothesis whose unique name appears in `ns`.
-/
unsafe def hyp_directly_depends_on_local_name_set (h : expr) (ns : name_set) : tactic Bool :=
  List.orM [type_has_local_in_name_set h ns, local_def_value_has_local_in_name_set h ns]
#align tactic.hyp_directly_depends_on_local_name_set tactic.hyp_directly_depends_on_local_name_set

/-- `hyp_directly_depends_on_local_set h hs` is true iff the hypothesis `h` directly
depends on any of the hypotheses `hs`.
-/
unsafe def hyp_directly_depends_on_local_set (h : expr) (hs : expr_set) : tactic Bool :=
  hyp_directly_depends_on_local_name_set h <| local_set_to_name_set hs
#align tactic.hyp_directly_depends_on_local_set tactic.hyp_directly_depends_on_local_set

/-- `hyp_directly_depends_on_locals h hs` is true iff the hypothesis `h` directly
depends on any of the hypotheses `hs`.
-/
unsafe def hyp_directly_depends_on_locals (h : expr) (hs : List expr) : tactic Bool :=
  hyp_directly_depends_on_local_name_set h <| local_list_to_name_set hs
#align tactic.hyp_directly_depends_on_locals tactic.hyp_directly_depends_on_locals

/-- `hyp_directly_depends_on_local_name_set_inclusive h ns` is true iff the
hypothesis `h` directly depends on a hypothesis whose unique name appears in
`ns` or `h`'s name appears in `ns`.
-/
unsafe def hyp_directly_depends_on_local_name_set_inclusive (h : expr) (ns : name_set) :
    tactic Bool :=
  List.orM [pure <| ns.contains h.local_uniq_name, hyp_directly_depends_on_local_name_set h ns]
#align tactic.hyp_directly_depends_on_local_name_set_inclusive tactic.hyp_directly_depends_on_local_name_set_inclusive

/-- `hyp_directly_depends_on_local_set_inclusive h ns` is true iff the hypothesis `h`
directly depends on any of the hypotheses `hs` or `h` appears in `hs`.
-/
unsafe def hyp_directly_depends_on_local_set_inclusive (h : expr) (hs : expr_set) : tactic Bool :=
  hyp_directly_depends_on_local_name_set_inclusive h <| local_set_to_name_set hs
#align tactic.hyp_directly_depends_on_local_set_inclusive tactic.hyp_directly_depends_on_local_set_inclusive

/-- `hyp_directly_depends_on_locals_inclusive h ns` is true iff the hypothesis `h`
directly depends on any of the hypotheses `hs` or `h` appears in `hs`.
-/
unsafe def hyp_directly_depends_on_locals_inclusive (h : expr) (hs : List expr) : tactic Bool :=
  hyp_directly_depends_on_local_name_set_inclusive h <| local_list_to_name_set hs
#align tactic.hyp_directly_depends_on_locals_inclusive tactic.hyp_directly_depends_on_locals_inclusive

/-! #### Computing the direct dependencies of a hypothesis -/

/-- `direct_dependency_set_of_hyp h` is the set of hypotheses that the hypothesis
`h` directly depends on. These are the hypotheses that appear in `h`'s type or
value (if `h` is a local definition).
-/
unsafe def direct_dependency_set_of_hyp (h : expr) : tactic expr_set := do
  let t ← infer_type h
  let deps := t.list_local_consts'
  let some val ← try_core <| local_def_value h |
    pure deps
  let deps := deps.union val.list_local_consts'
  pure deps
#align tactic.direct_dependency_set_of_hyp tactic.direct_dependency_set_of_hyp

/-- `direct_dependency_name_set_of_hyp h` is the set of unique names of hypotheses
that the hypothesis `h` directly depends on. These are the hypotheses that
appear in `h`'s type or value (if `h` is a local definition).
-/
unsafe def direct_dependency_name_set_of_hyp (h : expr) : tactic name_set :=
  local_set_to_name_set <$> direct_dependency_set_of_hyp h
#align tactic.direct_dependency_name_set_of_hyp tactic.direct_dependency_name_set_of_hyp

/-- `direct_dependencies_of_hyp h` is the list of hypotheses that the hypothesis `h`
directly depends on. These are the hypotheses that appear in `h`'s type or value
(if `h` is a local definition). The dependencies are returned in no particular
order.
-/
unsafe def direct_dependencies_of_hyp (h : expr) : tactic (List expr) :=
  rb_set.to_list <$> direct_dependency_set_of_hyp h
#align tactic.direct_dependencies_of_hyp tactic.direct_dependencies_of_hyp

/-- `direct_dependency_set_of_hyp_inclusive h` is the set of hypotheses that the
hypothesis `h` directly depends on, plus `h` itself.
-/
unsafe def direct_dependency_set_of_hyp_inclusive (h : expr) : tactic expr_set := do
  let deps ← direct_dependency_set_of_hyp h
  pure <| deps h
#align tactic.direct_dependency_set_of_hyp_inclusive tactic.direct_dependency_set_of_hyp_inclusive

/-- `direct_dependency_name_set_of_hyp_inclusive h` is the set of unique names of
hypotheses that the hypothesis `h` directly depends on, plus `h` itself.
-/
unsafe def direct_dependency_name_set_of_hyp_inclusive (h : expr) : tactic name_set :=
  local_set_to_name_set <$> direct_dependency_set_of_hyp_inclusive h
#align tactic.direct_dependency_name_set_of_hyp_inclusive tactic.direct_dependency_name_set_of_hyp_inclusive

/-- `direct_dependencies_of_hyp_inclusive h` is the list of hypotheses that the
hypothesis `h` directly depends on, plus `h` itself. The dependencies are
returned in no particular order.
-/
unsafe def direct_dependencies_of_hyp_inclusive (h : expr) : tactic (List expr) :=
  rb_set.to_list <$> direct_dependency_set_of_hyp_inclusive h
#align tactic.direct_dependencies_of_hyp_inclusive tactic.direct_dependencies_of_hyp_inclusive

/-! ### Indirect/Transitive Dependencies -/

/-! #### Checking whether hypotheses depend on each other -/

/-- `hyp_depends_on_local_name_set' cache h ns` is true iff `h` depends on any of
the hypotheses whose unique names appear in `ns`. `cache` must be a set of
hypotheses known *not* to depend (even indirectly) on any of the `ns`. This is
a performance optimisation, so you can give an empty cache. The tactic also
returns an expanded cache with hypotheses which the tactic has encountered.

You probably want to use `tactic.hyp_depends_on_local_name_set` or
`tactic.hyps_depend_on_local_name_set` instead of this tactic.
-/
unsafe def hyp_depends_on_local_name_set' : expr_set → expr → name_set → tactic (Bool × expr_set) :=
  fun cache h ns => do
  let ff ← pure <| cache.contains h |
    pure (false, cache)
  let direct_deps ← direct_dependency_set_of_hyp h
  let has_dep := direct_deps.fold false fun d b => b || ns.contains d.local_uniq_name
  let ff ← pure has_dep |
    pure (true, cache)
  let (has_dep, cache) ←
    direct_deps.mfold (false, cache) fun d ⟨b, cache⟩ =>
        if b then pure (true, cache) else hyp_depends_on_local_name_set' cache d ns
  if has_dep then pure (tt, cache) else pure (ff, cache h)
#align tactic.hyp_depends_on_local_name_set' tactic.hyp_depends_on_local_name_set'

/-- `hyp_depends_on_local_name_set h ns` is true iff the hypothesis `h` depends on
any of the hypotheses whose unique names appear in `ns`. If you need to check
dependencies of multiple hypotheses, use `tactic.hyps_depend_on_local_name_set`.
-/
unsafe def hyp_depends_on_local_name_set (h : expr) (ns : name_set) : tactic Bool := do
  let ctx_has_local_def ← context_upto_hyp_has_local_def h
  if ctx_has_local_def then Prod.fst <$> hyp_depends_on_local_name_set' mk_expr_set h ns
    else hyp_directly_depends_on_local_name_set h ns
#align tactic.hyp_depends_on_local_name_set tactic.hyp_depends_on_local_name_set

/-- `hyp_depends_on_local_set h hs` is true iff the hypothesis `h` depends on
any of the hypotheses `hs`. If you need to check dependencies of multiple
hypotheses, use `tactic.hyps_depend_on_local_set`.
-/
unsafe def hyp_depends_on_local_set (h : expr) (hs : expr_set) : tactic Bool :=
  hyp_depends_on_local_name_set h <| local_set_to_name_set hs
#align tactic.hyp_depends_on_local_set tactic.hyp_depends_on_local_set

/-- `hyp_depends_on_locals h hs` is true iff the hypothesis `h` depends on any of
the hypotheses `hs`. If you need to check dependencies of multiple hypotheses,
use `tactic.hyps_depend_on_locals`.
-/
unsafe def hyp_depends_on_locals (h : expr) (hs : List expr) : tactic Bool :=
  hyp_depends_on_local_name_set h <| local_list_to_name_set hs
#align tactic.hyp_depends_on_locals tactic.hyp_depends_on_locals

/-- `hyps_depend_on_local_name_set hs ns` returns, for each `h ∈ hs`, whether `h`
depends on a hypothesis whose unique name appears in `ns`. This is the same as
(but more efficient than) calling `tactic.hyp_depends_on_local_name_set` for
every `h ∈ hs`.
-/
unsafe def hyps_depend_on_local_name_set (hs : List expr) (ns : name_set) : tactic (List Bool) := do
  let ctx_has_local ← context_has_local_def
  if ctx_has_local then
      let go : expr → List Bool × expr_set → tactic (List Bool × expr_set) := fun h ⟨deps, cache⟩ =>
        do
        let (h_dep, cache) ← hyp_depends_on_local_name_set' cache h ns
        pure (h_dep :: deps, cache)
      Prod.fst <$> hs go ([], mk_expr_map)
    else hs fun h => hyp_directly_depends_on_local_name_set h ns
#align tactic.hyps_depend_on_local_name_set tactic.hyps_depend_on_local_name_set

/-- `hyps_depend_on_local_set hs is` returns, for each `h ∈ hs`, whether `h` depends
on any of the hypotheses `is`. This is the same as (but more efficient than)
calling `tactic.hyp_depends_on_local_set` for every `h ∈ hs`.
-/
unsafe def hyps_depend_on_local_set (hs : List expr) (is : expr_set) : tactic (List Bool) :=
  hyps_depend_on_local_name_set hs <| local_set_to_name_set is
#align tactic.hyps_depend_on_local_set tactic.hyps_depend_on_local_set

/-- `hyps_depend_on_locals hs is` returns, for each `h ∈ hs`, whether `h` depends
on any of the hypotheses `is`. This is the same as (but more efficient than)
calling `tactic.hyp_depends_on_locals` for every `h ∈ hs`.
-/
unsafe def hyps_depend_on_locals (hs is : List expr) : tactic (List Bool) :=
  hyps_depend_on_local_name_set hs <| local_list_to_name_set is
#align tactic.hyps_depend_on_locals tactic.hyps_depend_on_locals

/-- `hyp_depends_on_local_name_set_inclusive' cache h ns` is true iff the hypothesis
`h` inclusively depends on a hypothesis whose unique name appears in `ns`.
`cache` must be a set of hypotheses known *not* to depend (even indirectly) on
any of the `ns`. This is a performance optimisation, so you can give an empty
cache. The tactic also returns an expanded cache with hypotheses which the
tactic has encountered. Note that the cache records exclusive, not inclusive
dependencies.

You probably want to use `tactic.hyp_depends_on_local_name_set_inclusive` or
`tactic.hyps_depend_on_local_name_set_inclusive` instead of this tactic.
-/
unsafe def hyp_depends_on_local_name_set_inclusive' (cache : expr_set) (h : expr) (ns : name_set) :
    tactic (Bool × expr_set) :=
  if ns.contains h.local_uniq_name then pure (true, cache)
  else hyp_depends_on_local_name_set' cache h ns
#align tactic.hyp_depends_on_local_name_set_inclusive' tactic.hyp_depends_on_local_name_set_inclusive'

/-- `hyp_depends_on_local_name_set_inclusive h ns` is true iff the hypothesis `h`
inclusively depends on any of the hypotheses whose unique names appear in `ns`.
If you need to check the dependencies of multiple hypotheses, use
`tactic.hyps_depend_on_local_name_set_inclusive`.
-/
unsafe def hyp_depends_on_local_name_set_inclusive (h : expr) (ns : name_set) : tactic Bool :=
  List.orM [pure <| ns.contains h.local_uniq_name, hyp_depends_on_local_name_set h ns]
#align tactic.hyp_depends_on_local_name_set_inclusive tactic.hyp_depends_on_local_name_set_inclusive

/-- `hyp_depends_on_local_set_inclusive h hs` is true iff the hypothesis `h`
inclusively depends on any of the hypotheses `hs`. If you need to check
dependencies of multiple hypotheses, use
`tactic.hyps_depend_on_local_set_inclusive`.
-/
unsafe def hyp_depends_on_local_set_inclusive (h : expr) (hs : expr_set) : tactic Bool :=
  hyp_depends_on_local_name_set_inclusive h <| local_set_to_name_set hs
#align tactic.hyp_depends_on_local_set_inclusive tactic.hyp_depends_on_local_set_inclusive

/-- `hyp_depends_on_locals_inclusive h hs` is true iff the hypothesis `h`
inclusively depends on any of the hypotheses `hs`. If you need to check
dependencies of multiple hypotheses, use
`tactic.hyps_depend_on_locals_inclusive`.
-/
unsafe def hyp_depends_on_locals_inclusive (h : expr) (hs : List expr) : tactic Bool :=
  hyp_depends_on_local_name_set_inclusive h <| local_list_to_name_set hs
#align tactic.hyp_depends_on_locals_inclusive tactic.hyp_depends_on_locals_inclusive

/-- `hyps_depend_on_local_name_set_inclusive hs ns` returns, for each `h ∈ hs`,
whether `h` inclusively depends on a hypothesis whose unique name appears in
`ns`. This is the same as (but more efficient than) calling
`tactic.hyp_depends_on_local_name_set_inclusive` for every `h ∈ hs`.
-/
unsafe def hyps_depend_on_local_name_set_inclusive (hs : List expr) (ns : name_set) :
    tactic (List Bool) := do
  let ctx_has_local ← context_has_local_def
  if ctx_has_local then
      let go : expr → List Bool × expr_set → tactic (List Bool × expr_set) := fun h ⟨deps, cache⟩ =>
        do
        let (h_dep, cache) ← hyp_depends_on_local_name_set_inclusive' cache h ns
        pure (h_dep :: deps, cache)
      Prod.fst <$> hs go ([], mk_expr_map)
    else hs fun h => hyp_directly_depends_on_local_name_set_inclusive h ns
#align tactic.hyps_depend_on_local_name_set_inclusive tactic.hyps_depend_on_local_name_set_inclusive

/-- `hyps_depend_on_local_set_inclusive hs is` returns, for each `h ∈ hs`, whether
`h` depends inclusively on any of the hypotheses `is`. This is the same as
(but more efficient than) calling `tactic.hyp_depends_on_local_set_inclusive`
for every `h ∈ hs`.
-/
unsafe def hyps_depend_on_local_set_inclusive (hs : List expr) (is : expr_set) :
    tactic (List Bool) :=
  hyps_depend_on_local_name_set_inclusive hs <| local_set_to_name_set is
#align tactic.hyps_depend_on_local_set_inclusive tactic.hyps_depend_on_local_set_inclusive

/-- `hyps_depend_on_locals_inclusive hs is` returns, for each `h ∈ hs`, whether `h`
depends inclusively on any of the hypotheses `is`. This is the same as (but more
efficient than) calling `tactic.hyp_depends_on_locals_inclusive` for every
`h ∈ hs`.
-/
unsafe def hyps_depend_on_locals_inclusive (hs is : List expr) : tactic (List Bool) :=
  hyps_depend_on_local_name_set_inclusive hs <| local_list_to_name_set is
#align tactic.hyps_depend_on_locals_inclusive tactic.hyps_depend_on_locals_inclusive

/-! #### Computing the dependencies of a hypothesis -/

/-- `dependency_set_of_hyp' cache h` is the set of dependencies of the hypothesis
`h`. `cache` is a map from hypotheses to all their dependencies (including
indirect dependencies). This is a performance optimisation, so you can give an
empty cache. The tactic also returns an expanded cache with hypotheses which
the tactic has encountered.

You probably want to use `tactic.dependency_set_of_hyp` or
`tactic.dependency_sets_of_hyps` instead of this tactic.
-/
unsafe def dependency_set_of_hyp' :
    expr_map expr_set → expr → tactic (expr_set × expr_map expr_set) := fun cache h => do
  match cache h with
    | some deps => pure (deps, cache)
    | none => do
      let direct_deps ← direct_dependency_set_of_hyp h
      let (deps, cache) ←
        direct_deps (direct_deps, cache) fun h' ⟨deps, cache⟩ => do
            let (deps', cache) ← dependency_set_of_hyp' cache h'
            pure (deps deps', cache)
      pure (deps, cache h deps)
#align tactic.dependency_set_of_hyp' tactic.dependency_set_of_hyp'

/-- `dependency_set_of_hyp h` is the set of dependencies of the hypothesis `h`. If
you need the dependencies of multiple hypotheses, use
`tactic.dependency_sets_of_hyps`.
-/
unsafe def dependency_set_of_hyp (h : expr) : tactic expr_set := do
  let ctx_has_local ← context_upto_hyp_has_local_def h
  if ctx_has_local then Prod.fst <$> dependency_set_of_hyp' mk_expr_map h
    else direct_dependency_set_of_hyp h
#align tactic.dependency_set_of_hyp tactic.dependency_set_of_hyp

/-- `dependency_name_set_of_hyp h` is the set of unique names of the dependencies of
the hypothesis `h`. If you need the dependencies of multiple hypotheses, use
`tactic.dependency_name_sets_of_hyps`.
-/
unsafe def dependency_name_set_of_hyp (h : expr) : tactic name_set :=
  local_set_to_name_set <$> dependency_set_of_hyp h
#align tactic.dependency_name_set_of_hyp tactic.dependency_name_set_of_hyp

/-- `dependencies_of_hyp h` is the list of dependencies of the hypothesis `h`.
The dependencies are returned in no particular order. If you need the
dependencies of multiple hypotheses, use `tactic.dependencies_of_hyps`.
-/
unsafe def dependencies_of_hyp (h : expr) : tactic (List expr) :=
  rb_set.to_list <$> dependency_set_of_hyp h
#align tactic.dependencies_of_hyp tactic.dependencies_of_hyp

/-- `dependency_sets_of_hyps hs` returns, for each `h ∈ hs`, the set of dependencies
of `h`. This is the same as (but more performant than) using
`tactic.dependency_set_of_hyp` on every `h ∈ hs`.
-/
unsafe def dependency_sets_of_hyps (hs : List expr) : tactic (List expr_set) := do
  let ctx_has_def ← context_has_local_def
  if ctx_has_def then
      let go :
        expr → List expr_set × expr_map expr_set → tactic (List expr_set × expr_map expr_set) := do
        fun h ⟨deps, cache⟩ => do
          let (h_deps, cache) ← dependency_set_of_hyp' cache h
          pure (h_deps :: deps, cache)
      Prod.fst <$> hs go ([], mk_expr_map)
    else hs direct_dependency_set_of_hyp
#align tactic.dependency_sets_of_hyps tactic.dependency_sets_of_hyps

/-- `dependency_name_sets_of_hyps hs` returns, for each `h ∈ hs`, the set of unique
names of the dependencies of `h`. This is the same as (but more performant than)
using `tactic.dependency_name_set_of_hyp` on every `h ∈ hs`.
-/
unsafe def dependency_name_sets_of_hyps (hs : List expr) : tactic (List name_set) :=
  List.map local_set_to_name_set <$> dependency_sets_of_hyps hs
#align tactic.dependency_name_sets_of_hyps tactic.dependency_name_sets_of_hyps

/-- `dependencies_of_hyps hs` returns, for each `h ∈ hs`, the dependencies of `h`.
The dependencies appear in no particular order in the returned lists. This is
the same as (but more performant than) using `tactic.dependencies_of_hyp` on
every `h ∈ hs`.
-/
unsafe def dependencies_of_hyps (hs : List expr) : tactic (List (List expr)) :=
  List.map rb_set.to_list <$> dependency_sets_of_hyps hs
#align tactic.dependencies_of_hyps tactic.dependencies_of_hyps

/-- `dependency_set_of_hyp_inclusive' cache h` is the set of dependencies of the
hypothesis `h`, plus `h` itself. `cache` is a map from hypotheses to all their
dependencies (including indirect dependencies). This is a performance
optimisation, so you can give an empty cache. The tactic also returns an
expanded cache with hypotheses which the tactic has encountered. Note that the
cache records exclusive, not inclusive dependencies.

You probably want to use `tactic.dependency_set_of_hyp_inclusive` or
`tactic.dependency_sets_of_hyps_inclusive` instead of this tactic.
-/
unsafe def dependency_set_of_hyp_inclusive' (cache : expr_map expr_set) (h : expr) :
    tactic (expr_set × expr_map expr_set) := do
  let (deps, cache) ← dependency_set_of_hyp' cache h
  pure (deps h, cache)
#align tactic.dependency_set_of_hyp_inclusive' tactic.dependency_set_of_hyp_inclusive'

/-- `dependency_set_of_hyp_inclusive h` is the set of dependencies of the hypothesis
`h`, plus `h` itself. If you need the dependencies of multiple hypotheses, use
`tactic.dependency_sets_of_hyps_inclusive`.
-/
unsafe def dependency_set_of_hyp_inclusive (h : expr) : tactic expr_set := do
  let deps ← dependency_set_of_hyp h
  pure <| deps h
#align tactic.dependency_set_of_hyp_inclusive tactic.dependency_set_of_hyp_inclusive

/-- `dependency_name_set_of_hyp_inclusive h` is the set of unique names of the
dependencies of the hypothesis `h`, plus the unique name of `h` itself. If you
need the dependencies of multiple hypotheses, use
`tactic.dependency_name_sets_of_hyps_inclusive`.
-/
unsafe def dependency_name_set_of_hyp_inclusive (h : expr) : tactic name_set :=
  local_set_to_name_set <$> dependency_set_of_hyp_inclusive h
#align tactic.dependency_name_set_of_hyp_inclusive tactic.dependency_name_set_of_hyp_inclusive

/-- `dependencies_of_hyp_inclusive h` is the list of dependencies of the hypothesis
`h`, plus `h` itself. The dependencies are returned in no particular order. If
you need the dependencies of multiple hypotheses, use
`tactic.dependencies_of_hyps_inclusive`.
-/
unsafe def dependencies_of_hyp_inclusive (h : expr) : tactic (List expr) :=
  rb_set.to_list <$> dependency_set_of_hyp_inclusive h
#align tactic.dependencies_of_hyp_inclusive tactic.dependencies_of_hyp_inclusive

/-- `dependency_sets_of_hyps_inclusive hs` returns, for each `h ∈ hs`, the
dependencies of `h`, plus `h` itself. This is the same as (but more performant
than) using `tactic.dependency_set_of_hyp_inclusive` on every `h ∈ hs`.
-/
unsafe def dependency_sets_of_hyps_inclusive (hs : List expr) : tactic (List expr_set) := do
  let ctx_has_def ← context_has_local_def
  if ctx_has_def then
      let go :
        expr → List expr_set × expr_map expr_set → tactic (List expr_set × expr_map expr_set) :=
        fun h ⟨deps, cache⟩ => do
        let (h_deps, cache) ← dependency_set_of_hyp_inclusive' cache h
        pure (h_deps :: deps, cache)
      Prod.fst <$> hs go ([], mk_expr_map)
    else hs direct_dependency_set_of_hyp_inclusive
#align tactic.dependency_sets_of_hyps_inclusive tactic.dependency_sets_of_hyps_inclusive

/-- `dependency_name_sets_of_hyps_inclusive hs` returns, for each `h ∈ hs`, the
unique names of the dependencies of `h`, plus the unique name of `h` itself.
This is the same as (but more performant than) using
`tactic.dependency_name_set_of_hyp_inclusive` on every `h ∈ hs`.
-/
unsafe def dependency_name_sets_of_hyps_inclusive (hs : List expr) : tactic (List name_set) :=
  List.map local_set_to_name_set <$> dependency_sets_of_hyps_inclusive hs
#align tactic.dependency_name_sets_of_hyps_inclusive tactic.dependency_name_sets_of_hyps_inclusive

/-- `dependencies_of_hyps_inclusive hs` returns, for each `h ∈ hs`, the dependencies
of `h`, plus `h` itself. The dependencies appear in no particular order in the
returned lists. This is the same as (but more performant than) using
`tactic.dependencies_of_hyp_inclusive` on every `h ∈ hs`.
-/
unsafe def dependencies_of_hyps_inclusive (hs : List expr) : tactic (List (List expr)) :=
  List.map rb_set.to_list <$> dependency_sets_of_hyps_inclusive hs
#align tactic.dependencies_of_hyps_inclusive tactic.dependencies_of_hyps_inclusive

/-! #### Computing the reverse dependencies of a hypothesis -/

private unsafe def reverse_dependencies_of_hyp_name_set_aux (hs : name_set) :
    List expr → List expr → name_set → tactic (List expr)
  | [], revdeps, _ => pure revdeps.reverse
  | H :: Hs, revdeps, ns => do
    let H_uname := H.local_uniq_name
    let H_is_revdep ←
      List.andM [pure <| ¬hs.contains H_uname, hyp_directly_depends_on_local_name_set H ns]
    if H_is_revdep then reverse_dependencies_of_hyp_name_set_aux Hs (H :: revdeps) (ns H_uname)
      else reverse_dependencies_of_hyp_name_set_aux Hs revdeps ns
#align tactic.reverse_dependencies_of_hyp_name_set_aux tactic.reverse_dependencies_of_hyp_name_set_aux

/-- `reverse_dependencies_of_hyp_name_set hs` is the list of reverse dependencies of
the hypotheses whose unique names appear in `hs`, excluding the `hs` themselves.
The reverse dependencies are returned in the order in which they appear in the
context.
-/
unsafe def reverse_dependencies_of_hyp_name_set (hs : name_set) : tactic (List expr) := do
  let ctx ← local_context
  let ctx := ctx.afterₓ fun h => hs.contains h.local_uniq_name
  reverse_dependencies_of_hyp_name_set_aux hs ctx [] hs
#align tactic.reverse_dependencies_of_hyp_name_set tactic.reverse_dependencies_of_hyp_name_set

/-- `reverse_dependencies_of_hyp_set hs` is the list of reverse dependencies of the
hypotheses `hs`, excluding the `hs` themselves. The reverse dependencies are
returned in the order in which they appear in the context.
-/
unsafe def reverse_dependencies_of_hyp_set (hs : expr_set) : tactic (List expr) :=
  reverse_dependencies_of_hyp_name_set <| local_set_to_name_set hs
#align tactic.reverse_dependencies_of_hyp_set tactic.reverse_dependencies_of_hyp_set

/-- `reverse_dependencies_of_hyps hs` is the list of reverse dependencies of the
hypotheses `hs`, excluding the `hs` themselves. The reverse dependencies are
returned in the order in which they appear in the context.
-/
unsafe def reverse_dependencies_of_hyps (hs : List expr) : tactic (List expr) :=
  reverse_dependencies_of_hyp_name_set <| local_list_to_name_set hs
#align tactic.reverse_dependencies_of_hyps tactic.reverse_dependencies_of_hyps

private unsafe def reverse_dependencies_of_hyp_name_set_inclusive_aux :
    List expr → List expr → name_set → tactic (List expr)
  | [], revdeps, _ => pure revdeps.reverse
  | H :: Hs, revdeps, ns => do
    let H_uname := H.local_uniq_name
    let H_is_revdep ←
      List.orM [pure <| ns.contains H.local_uniq_name, hyp_directly_depends_on_local_name_set H ns]
    if H_is_revdep then
        reverse_dependencies_of_hyp_name_set_inclusive_aux Hs (H :: revdeps) (ns H_uname)
      else reverse_dependencies_of_hyp_name_set_inclusive_aux Hs revdeps ns
#align tactic.reverse_dependencies_of_hyp_name_set_inclusive_aux tactic.reverse_dependencies_of_hyp_name_set_inclusive_aux

/-- `reverse_dependencies_of_hyp_name_set_inclusive hs` is the list of reverse
dependencies of the hypotheses whose unique names appear in `hs`, including the
`hs` themselves. The reverse dependencies are returned in the order in which
they appear in the context.
-/
unsafe def reverse_dependencies_of_hyp_name_set_inclusive (hs : name_set) : tactic (List expr) := do
  let ctx ← local_context
  let ctx := ctx.dropWhileₓ fun h => ¬hs.contains h.local_uniq_name
  reverse_dependencies_of_hyp_name_set_inclusive_aux ctx [] hs
#align tactic.reverse_dependencies_of_hyp_name_set_inclusive tactic.reverse_dependencies_of_hyp_name_set_inclusive

/-- `reverse_dependencies_of_hyp_set_inclusive hs` is the list of reverse
dependencies of the hypotheses `hs`, including the `hs` themselves. The
inclusive reverse dependencies are returned in the order in which they appear in
the context.
-/
unsafe def reverse_dependencies_of_hyp_set_inclusive (hs : expr_set) : tactic (List expr) :=
  reverse_dependencies_of_hyp_name_set_inclusive <| local_set_to_name_set hs
#align tactic.reverse_dependencies_of_hyp_set_inclusive tactic.reverse_dependencies_of_hyp_set_inclusive

/-- `reverse_dependencies_of_hyps_inclusive hs` is the list of reverse dependencies
of the hypotheses `hs`, including the `hs` themselves. The reverse dependencies
are returned in the order in which they appear in the context.
-/
unsafe def reverse_dependencies_of_hyps_inclusive (hs : List expr) : tactic (List expr) :=
  reverse_dependencies_of_hyp_name_set_inclusive <| local_list_to_name_set hs
#align tactic.reverse_dependencies_of_hyps_inclusive tactic.reverse_dependencies_of_hyps_inclusive

/-! ### Reverting a hypothesis and its reverse dependencies -/

/-- `revert_name_set hs` reverts the hypotheses whose unique names appear in `hs`,
as well as any hypotheses that depend on them. Returns the number of reverted
hypotheses and a list containing these hypotheses. The reverted hypotheses are
returned in the order in which they used to appear in the context and are
guaranteed to store the correct type (see `tactic.update_type`).
-/
unsafe def revert_name_set (hs : name_set) : tactic (ℕ × List expr) := do
  let to_revert ← reverse_dependencies_of_hyp_name_set_inclusive hs
  let to_revert_with_types ← to_revert.mapM update_type
  let num_reverted ← revert_lst to_revert
  pure (num_reverted, to_revert_with_types)
#align tactic.revert_name_set tactic.revert_name_set

/-- `revert_set hs` reverts the hypotheses `hs`, as well as any hypotheses that
depend on them. Returns the number of reverted hypotheses and a list containing
these hypotheses. The reverted hypotheses are returned in the order in which
they used to appear in the context and are guaranteed to store the correct type
(see `tactic.update_type`).
-/
unsafe def revert_set (hs : expr_set) : tactic (ℕ × List expr) :=
  revert_name_set <| local_set_to_name_set hs
#align tactic.revert_set tactic.revert_set

/-- `revert_lst' hs` reverts the hypotheses `hs`, as well as any hypotheses that
depend on them. Returns the number of reverted hypotheses and a list containing
these hypotheses. The reverted hypotheses are returned in the order in which
they used to appear in the context and are guaranteed to store the correct type
(see `tactic.update_type`).

This is a more informative version of `tactic.revert_lst`.
-/
unsafe def revert_lst' (hs : List expr) : tactic (ℕ × List expr) :=
  revert_name_set <| local_list_to_name_set hs
#align tactic.revert_lst' tactic.revert_lst'

/- We cannot implement it as `revert e >> intro1` because that would change the
local constant in the context. -/
/-- `revert_reverse_dependencies_of_hyp h` reverts all the hypotheses that depend on
the hypothesis `h`, including the local definitions that have `h` in their
value. This fixes a bug in `tactic.revert_kdependencies` that does not revert
local definitions for which `h` only appears in the value. Returns the number
of reverted hypotheses.
-/
unsafe def revert_reverse_dependencies_of_hyp (h : expr) : tactic ℕ :=
  reverse_dependencies_of_hyp_name_set (mk_name_set.insert h.local_uniq_name) >>= revert_lst
#align tactic.revert_reverse_dependencies_of_hyp tactic.revert_reverse_dependencies_of_hyp

/-- `revert_reverse_dependencies_of_hyp_name_set hs` reverts all the hypotheses that
depend on a hypothesis whose unique name appears in `hs`. The `hs` themselves
are not reverted, unless they depend on each other. Returns the number of
reverted hypotheses.
-/
unsafe def revert_reverse_dependencies_of_hyp_name_set (hs : name_set) : tactic ℕ :=
  reverse_dependencies_of_hyp_name_set hs >>= revert_lst
#align tactic.revert_reverse_dependencies_of_hyp_name_set tactic.revert_reverse_dependencies_of_hyp_name_set

/-- `revert_reverse_dependencies_of_hyp_set hs` reverts all the hypotheses that
depend on a hypothesis in `hs`. The `hs` themselves are not reverted, unless
they depend on each other. Returns the number of reverted hypotheses.
-/
unsafe def revert_reverse_dependencies_of_hyp_set (hs : expr_set) : tactic ℕ :=
  reverse_dependencies_of_hyp_set hs >>= revert_lst
#align tactic.revert_reverse_dependencies_of_hyp_set tactic.revert_reverse_dependencies_of_hyp_set

/-- `revert_reverse_dependencies_of_hyp hs` reverts all the hypotheses that depend
on a hypothesis in `hs`. The `hs` themselves are not reverted, unless they
depend on each other. Returns the number of reverted hypotheses.
-/
unsafe def revert_reverse_dependencies_of_hyps (hs : List expr) : tactic ℕ :=
  reverse_dependencies_of_hyps hs >>= revert_lst
#align tactic.revert_reverse_dependencies_of_hyps tactic.revert_reverse_dependencies_of_hyps

end Tactic


/-
Copyright (c) 2018 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin, Reid Barton, Simon Hudon
-/
import data.list.tfae
import tactic.scc

/-!
# The Following Are Equivalent (TFAE)

This file provides the tactics `tfae_have` and `tfae_finish` for proving the pairwise equivalence of
propositions in a set using various implications between them.
-/

namespace tactic

export list (tfae)

namespace tfae

@[derive has_reflect, derive inhabited]
inductive arrow : Type
| right      : arrow
| left_right : arrow
| left       : arrow

meta def mk_implication : Π (re : arrow) (e₁ e₂ : expr), pexpr
| arrow.right      e₁ e₂ := ``(%%e₁ → %%e₂)
| arrow.left_right e₁ e₂ := ``(%%e₁ ↔ %%e₂)
| arrow.left       e₁ e₂ := ``(%%e₂ → %%e₁)

meta def mk_name : Π (re : arrow) (i₁ i₂ : nat), name
| arrow.right      i₁ i₂ := ("tfae_" ++ to_string i₁ ++ "_to_"  ++ to_string i₂ : string)
| arrow.left_right i₁ i₂ := ("tfae_" ++ to_string i₁ ++ "_iff_" ++ to_string i₂ : string)
| arrow.left       i₁ i₂ := ("tfae_" ++ to_string i₂ ++ "_to_"  ++ to_string i₁ : string)

end tfae

namespace interactive

setup_tactic_parser
open tactic.tfae list

meta def parse_list : expr → option (list expr)
| `([]) := pure []
| `(%%e :: %%es) := (::) e <$> parse_list es
| _ := none

/-- In a goal of the form `tfae [a₀, a₁, a₂]`,
`tfae_have : i → j` creates the assertion `aᵢ → aⱼ`. The other possible
notations are `tfae_have : i ← j` and `tfae_have : i ↔ j`. The user can
also provide a label for the assertion, as with `have`: `tfae_have h : i ↔ j`.
-/
meta def tfae_have
  (h : parse $ optional ident <* tk ":")
  (i₁ : parse (with_desc "i" small_nat))
  (re : parse (((tk "→" <|> tk "->")  *> return arrow.right)      <|>
               ((tk "↔" <|> tk "<->") *> return arrow.left_right) <|>
               ((tk "←" <|> tk "<-")  *> return arrow.left)))
  (i₂ : parse (with_desc "j" small_nat)) :
  tactic unit := do
    `(tfae %%l) <- target,
    l ← parse_list l,
    e₁ ← list.nth l (i₁ - 1) <|> fail format!"index {i₁} is not between 1 and {l.length}",
    e₂ ← list.nth l (i₂ - 1) <|> fail format!"index {i₂} is not between 1 and {l.length}",
    type ← to_expr (tfae.mk_implication re e₁ e₂),
    let h := h.get_or_else (mk_name re i₁ i₂),
    tactic.assert h type,
    return ()

/-- Finds all implications and equivalences in the context
to prove a goal of the form `tfae [...]`.
-/
meta def tfae_finish : tactic unit :=
applyc ``tfae_nil <|>
closure.with_new_closure (λ cl,
do impl_graph.mk_scc cl,
   `(tfae %%l) ← target,
   l ← parse_list l,
   (_,r,_) ← cl.root l.head,
   refine ``(tfae_of_forall %%r _ _),
   thm ← mk_const ``forall_mem_cons,
   l.mmap' (λ e,
     do rewrite_target thm, split,
        (_,r',p) ← cl.root e,
        tactic.exact p ),
   applyc ``forall_mem_nil,
   pure ())

end interactive
end tactic

/--
The `tfae` tactic suite is a set of tactics that help with proving that certain
propositions are equivalent.
In `data/list/basic.lean` there is a section devoted to propositions of the
form
```lean
tfae [p1, p2, ..., pn]
```
where `p1`, `p2`, through, `pn` are terms of type `Prop`.
This proposition asserts that all the `pi` are pairwise equivalent.
There are results that allow to extract the equivalence
of two propositions `pi` and `pj`.

To prove a goal of the form `tfae [p1, p2, ..., pn]`, there are two
tactics.  The first tactic is `tfae_have`.  As an argument it takes an
expression of the form `i arrow j`, where `i` and `j` are two positive
natural numbers, and `arrow` is an arrow such as `→`, `->`, `←`, `<-`,
`↔`, or `<->`.  The tactic `tfae_have : i arrow j` sets up a subgoal in
which the user has to prove the equivalence (or implication) of `pi` and `pj`.

The remaining tactic, `tfae_finish`, is a finishing tactic. It
collects all implications and equivalences from the local context and
computes their transitive closure to close the
main goal.

`tfae_have` and `tfae_finish` can be used together in a proof as
follows:

```lean
example (a b c d : Prop) : tfae [a,b,c,d] :=
begin
  tfae_have : 3 → 1,
  { /- prove c → a -/ },
  tfae_have : 2 → 3,
  { /- prove b → c -/ },
  tfae_have : 2 ← 1,
  { /- prove a → b -/ },
  tfae_have : 4 ↔ 2,
  { /- prove d ↔ b -/ },
    -- a b c d : Prop,
    -- tfae_3_to_1 : c → a,
    -- tfae_2_to_3 : b → c,
    -- tfae_1_to_2 : a → b,
    -- tfae_4_iff_2 : d ↔ b
    -- ⊢ tfae [a, b, c, d]
  tfae_finish,
end
```
-/
add_tactic_doc
{ name := "tfae",
  category                 := doc_category.tactic,
  decl_names               := [`tactic.interactive.tfae_have, `tactic.interactive.tfae_finish],
  tags                     := ["logic"],
  inherit_description_from := `tactic.interactive.tfae_finish }


/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura
-/
prelude
import Init.Data.Nat.Linear
import Init.Data.List.Basic
import Init.Util

universe u

namespace List
/-! The following functions can't be defined at `Init.Data.List.Basic`, because they depend on `Init.Util`,
   and `Init.Util` depends on `Init.Data.List.Basic`. -/

def get! [Inhabited α] : List α → Nat → α
  | a::_,  0   => a
  | _::as, n+1 => get! as n
  | _,     _   => panic! "invalid index"

def get? : List α → Nat → Option α
  | a::_,  0   => some a
  | _::as, n+1 => get? as n
  | _,     _   => none

def getD (as : List α) (idx : Nat) (a₀ : α) : α :=
  (as.get? idx).getD a₀

def head! [Inhabited α] : List α → α
  | []   => panic! "empty list"
  | a::_ => a

def head? : List α → Option α
  | []   => none
  | a::_ => some a

def headD : List α → α → α
  | [],   a₀ => a₀
  | a::_, _  => a

def head : (as : List α) → as ≠ [] → α
  | a::_, _ => a

def tail! : List α → List α
  | []    => panic! "empty list"
  | _::as => as

def tail? : List α → Option (List α)
  | []    => none
  | _::as => some as

def tailD : List α → List α → List α
  | [],   as₀ => as₀
  | _::as, _  => as

def getLast : ∀ (as : List α), as ≠ [] → α
  | [],       h => absurd rfl h
  | [a],      _ => a
  | _::b::as, _ => getLast (b::as) (fun h => List.noConfusion h)

def getLast! [Inhabited α] : List α → α
  | []    => panic! "empty list"
  | a::as => getLast (a::as) (fun h => List.noConfusion h)

def getLast? : List α → Option α
  | []    => none
  | a::as => some (getLast (a::as) (fun h => List.noConfusion h))

def getLastD : List α → α → α
  | [],   a₀ => a₀
  | a::as, _ => getLast (a::as) (fun h => List.noConfusion h)

def rotateLeft (xs : List α) (n : Nat := 1) : List α :=
  let len := xs.length
  if len ≤ 1 then
    xs
  else
    let n := n % len
    let b := xs.take n
    let e := xs.drop n
    e ++ b

def rotateRight (xs : List α) (n : Nat := 1) : List α :=
  let len := xs.length
  if len ≤ 1 then
    xs
  else
    let n := len - n % len
    let b := xs.take n
    let e := xs.drop n
    e ++ b

theorem get_append_left (as bs : List α) (h : i < as.length) {h'} : (as ++ bs).get ⟨i, h'⟩ = as.get ⟨i, h⟩ := by
  induction as generalizing i with
  | nil => trivial
  | cons a as ih =>
    cases i with
    | zero => rfl
    | succ i => apply ih

theorem get_append_right (as bs : List α) (h : ¬ i < as.length) {h' h''} : (as ++ bs).get ⟨i, h'⟩ = bs.get ⟨i - as.length, h''⟩ := by
  induction as generalizing i with
  | nil => trivial
  | cons a as ih =>
    cases i with simp [get, Nat.succ_sub_succ] <;> simp_arith [Nat.succ_sub_succ] at h
    | succ i => apply ih; simp_arith [h]

theorem get_last {as : List α} {i : Fin (length (as ++ [a]))} (h : ¬ i.1 < as.length) : (as ++ [a] : List _).get i = a := by
  cases i; rename_i i h'
  induction as generalizing i with
  | nil => cases i with
    | zero => simp [List.get]
    | succ => simp_arith at h'
  | cons a as ih =>
    cases i with simp_arith at h
    | succ i => apply ih; simp_arith [h]

theorem sizeOf_lt_of_mem [SizeOf α] {as : List α} (h : a ∈ as) : sizeOf a < sizeOf as := by
  induction h with
  | head => simp_arith
  | tail _ _ ih => exact Nat.lt_trans ih (by simp_arith)

/-- This tactic, added to the `decreasing_trivial` toolbox, proves that
`sizeOf a < sizeOf as` when `a ∈ as`, which is useful for well founded recursions
over a nested inductive like `inductive T | mk : List T → T`. -/
macro "sizeOf_list_dec" : tactic =>
  `(tactic| first
    | apply sizeOf_lt_of_mem; assumption; done
    | apply Nat.lt_trans (sizeOf_lt_of_mem ?h)
      case' h => assumption
      simp_arith)

macro_rules | `(tactic| decreasing_trivial) => `(tactic| sizeOf_list_dec)

theorem append_cancel_left {as bs cs : List α} (h : as ++ bs = as ++ cs) : bs = cs := by
  induction as with
  | nil => assumption
  | cons a as ih =>
    injection h with _ h
    exact ih h

theorem append_cancel_right {as bs cs : List α} (h : as ++ bs = cs ++ bs) : as = cs := by
  match as, cs with
  | [], []       => rfl
  | [], c::cs    => have aux := congrArg length h; simp_arith at aux
  | a::as, []    => have aux := congrArg length h; simp_arith at aux
  | a::as, c::cs => injection h with h₁ h₂; subst h₁; rw [append_cancel_right h₂]

@[simp] theorem append_cancel_left_eq (as bs cs : List α) : (as ++ bs = as ++ cs) = (bs = cs) := by
  apply propext; apply Iff.intro
  next => apply append_cancel_left
  next => intro h; simp [h]

@[simp] theorem append_cancel_right_eq (as bs cs : List α) : (as ++ bs = cs ++ bs) = (as = cs) := by
  apply propext; apply Iff.intro
  next => apply append_cancel_right
  next => intro h; simp [h]

@[simp] theorem sizeOf_get [SizeOf α] (as : List α) (i : Fin as.length) : sizeOf (as.get i) < sizeOf as := by
  match as, i with
  | a::as, ⟨0, _⟩  => simp_arith [get]
  | a::as, ⟨i+1, h⟩ =>
    have ih := sizeOf_get as ⟨i, Nat.le_of_succ_le_succ h⟩
    apply Nat.lt_trans ih
    simp_arith

theorem le_antisymm [LT α] [s : Antisymm (¬ · < · : α → α → Prop)] {as bs : List α} (h₁ : as ≤ bs) (h₂ : bs ≤ as) : as = bs :=
  match as, bs with
  | [],    []    => rfl
  | [],    b::bs => False.elim <| h₂ (List.lt.nil ..)
  | a::as, []    => False.elim <| h₁ (List.lt.nil ..)
  | a::as, b::bs => by
    by_cases hab : a < b
    · exact False.elim <| h₂ (List.lt.head _ _ hab)
    · by_cases hba : b < a
      · exact False.elim <| h₁ (List.lt.head _ _ hba)
      · have h₁ : as ≤ bs := fun h => h₁ (List.lt.tail hba hab h)
        have h₂ : bs ≤ as := fun h => h₂ (List.lt.tail hab hba h)
        have ih : as = bs := le_antisymm h₁ h₂
        have : a = b := s.antisymm hab hba
        simp [this, ih]

instance [LT α] [Antisymm (¬ · < · : α → α → Prop)] : Antisymm (· ≤ · : List α → List α → Prop) where
  antisymm h₁ h₂ := le_antisymm h₁ h₂

@[specialize] private unsafe def mapMonoMImp [Monad m] (as : List α) (f : α → m α) : m (List α) := do
  match as with
  | [] => return as
  | b :: bs =>
    let b'  ← f b
    let bs' ← mapMonoMImp bs f
    if ptrEq b' b && ptrEq bs' bs then
      return as
    else
      return b' :: bs'

/--
Monomorphic `List.mapM`. The internal implementation uses pointer equality, and does not allocate a new list
if the result of each `f a` is a pointer equal value `a`.
-/
@[implemented_by mapMonoMImp] def mapMonoM [Monad m] (as : List α) (f : α → m α) : m (List α) :=
  match as with
  | [] => return []
  | a :: as => return (← f a) :: (← mapMonoM as f)

def mapMono (as : List α) (f : α → α) : List α :=
  Id.run <| as.mapMonoM f

end List


/-
Copyright (c) 2020 Dany Fabian. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Dany Fabian

! This file was ported from Lean 3 source module tactic.unfold_cases
! leanprover-community/mathlib commit 0035e2dd30b71232b3bf7d0827eeaa0c002c1825
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Tactic.SplitIfs

/-!
  # Unfold cases tactic

  In Lean, pattern matching expressions are not atomic parts of the syntax, but
  rather they are compiled down into simpler terms that are later checked by the kernel.

  This allows Lean to have a minimalistic kernel but can occasionally lead an explosion
  of cases that need to be considered. What looks like one case in the `match` expression
  can in fact be compiled into many different cases that all need to proved by case analysis.

  This tactic automates the process by allowing us to write down an equation `f x = y`
  where we know that `f x = y` is provably true, but does not hold definitionally. In that
  case the `unfold_cases` tactic will continue unfolding `f` and introducing `cases` where
  necessary until the left hand side becomes definitionally equal to the right hand side.

  Consider a definition as follows:

  ```lean
  def myand : bool → bool → bool
  | ff _ := ff
  | _ ff := ff
  | _ _ := tt
  ```

  The equation compiler generates 4 equation lemmas for us:
  ```lean
  myand ff ff = ff
  myand ff tt = ff
  myand tt ff = ff
  myand tt tt = tt
  ```

  This is not in line with what one might expect looking at the definition.
  Whilst it is provably true, that `∀ x, myand ff x = ff` and `∀ x, myand x ff = ff`,
  we do not get these stronger lemmas from the compiler for free but must in fact
  prove them using `cases` or some other local reasoning.

  In other words, the following does not constitute a proof that lean accepts.
  ```lean
  example : ∀ x, myand ff x = ff :=
  begin
    intros, refl
  end
  ```

  However, you can use `unfold_cases { refl }` to prove `∀ x, myand ff x = ff` and
  `∀ x, myand x ff = ff`. For definitions with many cases, the savings can be very
  significant.

  The term that gets generated for the above definition looks like this:
  ```lean
  λ (a a_1 : bool),
  a.cases_on
    (a_1.cases_on (id_rhs bool ff) (id_rhs bool ff))
    (a_1.cases_on (id_rhs bool ff) (id_rhs bool tt))
  ```

  When the tactic tries to prove the goal `∀ x, myand ff x = ff`, it starts by `intros`,
  followed by unfolding the definition:
  ```lean
  ⊢ ff.cases_on
    (x.cases_on (id_rhs bool ff) (id_rhs bool ff))
    (x.cases_on (id_rhs bool ff) (id_rhs bool tt)) = ff
  ```

  At this point, it can make progress using `dsimp`. But then it gets stuck:
  ```lean
  ⊢ bool.rec (id_rhs bool ff) (id_rhs bool ff) x = ff
  ```

  Next, it can introduce a case split on `x`. At this point, it has to prove two
  goals:
  ```lean
  ⊢ bool.rec (id_rhs bool ff) (id_rhs bool ff) ff = ff
  ⊢ bool.rec (id_rhs bool ff) (id_rhs bool ff) tt = ff
  ```

  Now, however, both goals can be discharged using `refl`.
-/

namespace Tactic

open Expr

namespace UnfoldCases

-- failed to format: unknown constant 'term.pseudo.antiquot'
/--
      Given an equation `f x = y`, this tactic tries to infer an expression that can be
        used to do distinction by cases on to make progress.

        Pre-condition: assumes that the outer-most application cannot be beta-reduced
        (e.g. `whnf` or `dsimp`).
      -/
    unsafe
  def
    find_splitting_expr
    : expr → tactic expr
    |
        q( @ ite _ $ ( cond ) $ ( dec_inst ) _ _ = _ )
        =>
        pure q( @ Decidable.em $ ( cond ) $ ( dec_inst ) )
      | q( $ ( app x y ) = _ ) => pure y
      |
        e
        =>
        throwError
          "expected an expression of the form: f x = y. Got:
            { ← e }"
#align tactic.unfold_cases.find_splitting_expr tactic.unfold_cases.find_splitting_expr

/-- Tries to finish the current goal using the `inner` tactic. If the tactic
  fails, it tries to find an expression on which to do a distinction by
  cases and calls itself recursively.

  The order of operations is significant. Because the unfolding can potentially
  be infinite, it is important to apply the `inner` tactic at every step.

  Notice, that if the `inner` tactic succeeds, the recursive unfolding is stopped.
-/
unsafe def unfold_cases_core (inner : interactive.itactic) : tactic Unit :=
  inner <|>
    (do
        split_ifs []
        all_goals unfold_cases_core
        skip) <|>
      do
      let tgt ← target
      let e ← find_splitting_expr tgt
      focus1 do
          cases e
          all_goals <| dsimp_target >> unfold_cases_core <|> skip
          skip
#align tactic.unfold_cases.unfold_cases_core tactic.unfold_cases.unfold_cases_core

-- failed to format: unknown constant 'term.pseudo.antiquot'
/--
      Given a target of the form `⊢ f x₁ ... xₙ = y`, unfolds `f` using a delta reduction.
      -/
    unsafe
  def
    unfold_tgt
    : expr → tactic Unit
    |
        q( $ ( l @ app _ _ ) = $ ( r ) )
        =>
        match
          l . get_app_fn
          with
          | const n ls => delta_target [ n ]
            |
              e
              =>
              throwError
                "couldn't unfold:
                  { ← e }"
      |
        e
        =>
        throwError
          "expected an expression of the form: f x = y. Got:
            { ← e }"
#align tactic.unfold_cases.unfold_tgt tactic.unfold_cases.unfold_tgt

end UnfoldCases

namespace Interactive

open UnfoldCases

/-- This tactic unfolds the definition of a function or `match` expression.
  Then it recursively introduces a distinction by cases. The decision what expression
  to do the distinction on is driven by the pattern matching expression.

  A typical use case is using `unfold_cases { refl }` to collapse cases that need to be
  considered in a pattern matching.

  ```lean
  have h : foo x = y, by unfold_cases { refl },
  rw h,
  ```

  The tactic expects a goal in the form of an equation, possibly universally quantified.

  We can prove a theorem, even if the various case do not directly correspond to the
  function definition. Here is an example application of the tactic:

  ```lean
  def foo : ℕ → ℕ → ℕ
  | 0     0 := 17
  | (n+2) 17 := 17
  | 1     0 := 23
  | 0     (n+18) := 15
  | 0     17 := 17
  | 1     17 := 17
  | _     (n+18) := 27
  | _     _ := 15

  example : ∀ x, foo x 17 = 17 :=
  begin
    unfold_cases { refl },
  end
  ```

  The compiler generates 57 cases for `foo`. However, when we look at the definition, we see
  that whenever the function is applied to `17` in the second argument, it returns `17`.

  Proving this property consists of merely considering all the cases, eliminating invalid ones
  and applying `refl` on the ones which remain.

  Further examples can be found in `test/unfold_cases.lean`.
-/
unsafe def unfold_cases (inner : itactic) : tactic Unit :=
  focus1 do
    tactic.intros
    let tgt ← target
    unfold_tgt tgt
    try dsimp_target
    unfold_cases_core inner
#align tactic.interactive.unfold_cases tactic.interactive.unfold_cases

add_tactic_doc
  { Name := "unfold_cases"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.unfold_cases]
    tags := ["induction", "case bashing"] }

end Interactive

end Tactic


/-
Copyright (c) 2020 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Buzzard, Bryan Gin-ge Chen, Robert Y. Lewis, Scott Morrison

! This file was ported from Lean 3 source module tactic.lean_core_docs
! leanprover-community/mathlib commit 7e3fa4c114f6f12380cf3b181fd4bd03a2f05b79
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Tactic.DocCommands

/-!

# Core tactic documentation

This file adds the majority of the interactive tactics from core Lean (i.e. pre-mathlib) to
the API documentation.

## TODO

* Make a PR to core changing core docstrings to the docstrings below,
and also changing the docstrings of `cc`, `simp` and `conv` to the ones
already in the API docs.

* SMT tactics are currently not documented.

* `rsimp` and `constructor_matching` are currently not documented.

* `dsimp` deserves better documentation.
-/

add_tactic_doc
  { Name := "abstract"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.abstract]
    tags := ["core", "proof extraction"] }

/-- Proves a goal of the form `s = t` when `s` and `t` are expressions built up out of a binary
operation, and equality can be proved using associativity and commutativity of that operation. -/
add_tactic_doc
  { Name := "ac_refl"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.ac_refl, `tactic.interactive.ac_reflexivity]
    tags := ["core", "lemma application", "finishing"] }

add_tactic_doc
  { Name := "all_goals"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.all_goals]
    tags := ["core", "goal management"] }

add_tactic_doc
  { Name := "any_goals"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.any_goals]
    tags := ["core", "goal management"] }

add_tactic_doc
  { Name := "apply"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.apply]
    tags := ["core", "basic", "lemma application"] }

add_tactic_doc
  { Name := "apply_auto_param"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.apply_auto_param]
    tags := ["core", "lemma application"] }

add_tactic_doc
  { Name := "apply_instance"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.apply_instance]
    tags := ["core", "type class"] }

add_tactic_doc
  { Name := "apply_opt_param"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.apply_opt_param]
    tags := ["core", "lemma application"] }

add_tactic_doc
  { Name := "apply_with"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.apply_with]
    tags := ["core", "lemma application"] }

add_tactic_doc
  { Name := "assume"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.assume]
    tags := ["core", "logic"] }

add_tactic_doc
  { Name := "assumption"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.assumption]
    tags := ["core", "basic", "finishing"] }

add_tactic_doc
  { Name := "assumption'"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.assumption']
    tags := ["core", "goal management"] }

add_tactic_doc
  { Name := "async"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.async]
    tags := ["core", "goal management", "combinator", "proof extraction"] }

/-- `by_cases p` splits the main goal into two cases, assuming `h : p` in the first branch, and
`h : ¬ p` in the second branch. You can specify the name of the new hypothesis using the syntax
`by_cases h : p`.

If `p` is not already decidable, `by_cases` will use the instance `classical.prop_decidable p`.
-/
add_tactic_doc
  { Name := "by_cases"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.by_cases]
    tags := ["core", "basic", "logic", "case bashing"] }

/-- If the target of the main goal is a proposition `p`, `by_contra h` reduces the goal to proving
`false` using the additional hypothesis `h : ¬ p`. If `h` is omitted, a name is generated
automatically.

This tactic requires that `p` is decidable. To ensure that all propositions are decidable via
classical reasoning, use `open_locale classical`
(or `local attribute [instance, priority 10] classical.prop_decidable` if you are not using
mathlib).
-/
add_tactic_doc
  { Name := "by_contra / by_contradiction"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.by_contra, `tactic.interactive.by_contradiction]
    tags := ["core", "logic"] }

add_tactic_doc
  { Name := "case"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.case]
    tags := ["core", "goal management"] }

add_tactic_doc
  { Name := "cases"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.cases]
    tags := ["core", "basic", "induction"] }

/-- `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` is a more efficient and compact version
of `focus1 { repeat { cases_matching p } }`.
It is more efficient because the pattern is compiled once.

`casesm` is shorthand for `cases_matching`.

Example: The following tactic destructs all conjunctions and disjunctions in the current context.
```
cases_matching* [_ ∨ _, _ ∧ _]
```
-/
add_tactic_doc
  { Name := "cases_matching / casesm"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.cases_matching, `tactic.interactive.casesm]
    tags := ["core", "induction", "context management"] }

/-- * `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 context.
```
cases_type* or and
```
-/
add_tactic_doc
  { Name := "cases_type"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.cases_type]
    tags := ["core", "induction", "context management"] }

add_tactic_doc
  { Name := "change"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.change]
    tags := ["core", "basic", "renaming"] }

add_tactic_doc
  { Name := "clear"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.clear]
    tags := ["core", "context management"] }

/-- Close goals of the form `n ≠ m` when `n` and `m` have type `nat`, `char`, `string`, `int`
or `fin sz`, and they are literals. It also closes goals of the form `n < m`, `n > m`, `n ≤ m` and
`n ≥ m` for `nat`. If the goal is of the form `n = m`, then it tries to close it using reflexivity.

In mathlib, consider using `norm_num` instead for numeric types.
-/
add_tactic_doc
  { Name := "comp_val"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.comp_val]
    tags := ["core", "arithmetic"] }

/-- The `congr` tactic attempts to identify both sides of an equality goal `A = B`,
leaving as new goals the subterms of `A` and `B` which are not definitionally equal.
Example: suppose the goal is `x * f y = g w * f z`. Then `congr` will produce two goals:
`x = g w` and `y = z`.

If `x y : t`, and an instance `subsingleton t` is in scope, then any goals of the form
`x = y` are solved automatically.

Note that `congr` can be over-aggressive at times; the `congr'` tactic in mathlib
provides a more refined approach, by taking a parameter that limits the recursion depth.
-/
add_tactic_doc
  { Name := "congr"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.congr]
    tags := ["core", "congruence"] }

add_tactic_doc
  { Name := "constructor"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.constructor]
    tags := ["core", "logic"] }

add_tactic_doc
  { Name := "contradiction"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.contradiction]
    tags := ["core", "basic", "finishing"] }

add_tactic_doc
  { Name := "delta"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.delta]
    tags := ["core", "simplification"] }

add_tactic_doc
  { Name := "destruct"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.destruct]
    tags := ["core", "induction"] }

add_tactic_doc
  { Name := "done"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.done]
    tags := ["core", "goal management"] }

add_tactic_doc
  { Name := "dsimp"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.dsimp]
    tags := ["core", "simplification"] }

add_tactic_doc
  { Name := "dunfold"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.dunfold]
    tags := ["core", "simplification"] }

add_tactic_doc
  { Name := "eapply"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.eapply]
    tags := ["core", "lemma application"] }

add_tactic_doc
  { Name := "econstructor"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.econstructor]
    tags := ["core", "logic"] }

/-- A variant of `rw` that uses the unifier more aggressively, unfolding semireducible definitions.
-/
add_tactic_doc
  { Name := "erewrite / erw"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.erewrite, `tactic.interactive.erw]
    tags := ["core", "rewriting"] }

add_tactic_doc
  { Name := "exact"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.exact]
    tags := ["core", "basic", "finishing"] }

add_tactic_doc
  { Name := "exacts"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.exacts]
    tags := ["core", "finishing"] }

add_tactic_doc
  { Name := "exfalso"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.exfalso]
    tags := ["core", "basic", "logic"] }

/-- `existsi e` will instantiate an existential quantifier in the target with `e` and leave the
instantiated body as the new target. More generally, it applies to any inductive type with one
constructor and at least two arguments, applying the constructor with `e` as the first argument
and leaving the remaining arguments as goals.

`existsi [e₁, ..., eₙ]` iteratively does the same for each expression in the list.

Note: in mathlib, the `use` tactic is an equivalent tactic which sometimes is smarter with
unification.
-/
add_tactic_doc
  { Name := "existsi"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.existsi]
    tags := ["core", "logic"] }

add_tactic_doc
  { Name := "fail_if_success"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.fail_if_success]
    tags := ["core", "testing", "combinator"] }

add_tactic_doc
  { Name := "fapply"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.fapply]
    tags := ["core", "lemma application"] }

add_tactic_doc
  { Name := "focus"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.focus]
    tags := ["core", "goal management", "combinator"] }

add_tactic_doc
  { Name := "from"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.from]
    tags := ["core", "finishing"] }

/-- Apply function extensionality and introduce new hypotheses.
The tactic `funext` will keep applying new the `funext` lemma until the goal target is not reducible
to
```
  |-  ((fun x, ...) = (fun x, ...))
```
The variant `funext h₁ ... hₙ` applies `funext` `n` times, and uses the given identifiers to name
the new hypotheses.

Note also the mathlib tactic `ext`, which applies as many extensionality lemmas as possible.
-/
add_tactic_doc
  { Name := "funext"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.funext]
    tags := ["core", "logic"] }

add_tactic_doc
  { Name := "generalize"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.generalize]
    tags := ["core", "context management"] }

add_tactic_doc
  { Name := "guard_hyp"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.guard_hyp]
    tags := ["core", "testing", "context management"] }

add_tactic_doc
  { Name := "guard_target"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.guard_target]
    tags := ["core", "testing", "goal management"] }

add_tactic_doc
  { Name := "have"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.have]
    tags := ["core", "basic", "context management"] }

add_tactic_doc
  { Name := "induction"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.induction]
    tags := ["core", "basic", "induction"] }

add_tactic_doc
  { Name := "injection"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.injection]
    tags := ["core", "structures", "induction"] }

add_tactic_doc
  { Name := "injections"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.injections]
    tags := ["core", "structures", "induction"] }

/-- 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.

The variant `intro z` uses the identifier `z` to name the new hypothesis.

The variant `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.
-/
add_tactic_doc
  { Name := "intro / intros"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.intro, `tactic.interactive.intros]
    tags := ["core", "basic", "logic"] }

add_tactic_doc
  { Name := "introv"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.introv]
    tags := ["core", "logic"] }

add_tactic_doc
  { Name := "iterate"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.iterate]
    tags := ["core", "combinator"] }

/-- `left` applies the first constructor when the type of the target is an inductive data type with
two constructors.

Similarly, `right` applies the second constructor.
-/
add_tactic_doc
  { Name := "left / right"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.left, `tactic.interactive.right]
    tags := ["core", "basic", "logic"] }

/-- `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.

If `h` is omitted, the name `this` is used.

Note the related mathlib tactic `set a := t with h`, which adds the hypothesis `h : a = t` to
the local context and replaces `t` with `a` everywhere it can.
-/
add_tactic_doc
  { Name := "let"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.let]
    tags := ["core", "basic", "logic", "context management"] }

add_tactic_doc
  { Name := "mapply"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.mapply]
    tags := ["core", "lemma application"] }

add_tactic_doc
  { Name := "match_target"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.match_target]
    tags := ["core", "testing", "goal management"] }

add_tactic_doc
  { Name := "refine"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.refine]
    tags := ["core", "basic", "lemma application"] }

/--
This tactic applies to a goal whose target has the form `t ~ u` where `~` is a reflexive relation,
that is, a relation which has a reflexivity lemma tagged with the attribute `[refl]`.
The tactic checks whether `t` and `u` are definitionally equal and then solves the goal.
-/
add_tactic_doc
  { Name := "refl / reflexivity"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.refl, `tactic.interactive.reflexivity]
    tags := ["core", "basic", "finishing"] }

add_tactic_doc
  { Name := "rename"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.rename]
    tags := ["core", "renaming"] }

add_tactic_doc
  { Name := "repeat"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.repeat]
    tags := ["core", "combinator"] }

add_tactic_doc
  { Name := "revert"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.revert]
    tags := ["core", "context management", "goal management"] }

/-- `rw e` applies an equation or iff `e` as a rewrite rule to 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`.

`rw [e₁, ..., eₙ]` applies the given rules sequentially.

`rw 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.

`rewrite` is synonymous with `rw`.
-/
add_tactic_doc
  { Name := "rw / rewrite"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.rw, `tactic.interactive.rewrite]
    tags := ["core", "basic", "rewriting"] }

add_tactic_doc
  { Name := "rwa"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.rwa]
    tags := ["core", "rewriting"] }

add_tactic_doc
  { Name := "show"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.show]
    tags := ["core", "goal management", "renaming"] }

add_tactic_doc
  { Name := "simp_intros"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.simp_intros]
    tags := ["core", "simplification"] }

add_tactic_doc
  { Name := "skip"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.skip]
    tags := ["core", "combinator"] }

add_tactic_doc
  { Name := "solve1"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.solve1]
    tags := ["core", "combinator", "goal management"] }

add_tactic_doc
  { Name := "sorry / admit"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.sorry, `tactic.interactive.admit]
    inheritDescriptionFrom := `tactic.interactive.sorry
    tags := ["core", "testing", "debugging"] }

add_tactic_doc
  { Name := "specialize"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.specialize]
    tags := ["core", "context management", "lemma application"] }

add_tactic_doc
  { Name := "split"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.split]
    tags := ["core", "basic", "logic"] }

add_tactic_doc
  { Name := "subst"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.subst]
    tags := ["core", "rewriting"] }

add_tactic_doc
  { Name := "subst_vars"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.subst_vars]
    tags := ["core", "rewriting"] }

add_tactic_doc
  { Name := "success_if_fail"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.success_if_fail]
    tags := ["core", "testing", "combinator"] }

add_tactic_doc
  { Name := "suffices"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.suffices]
    tags := ["core", "basic", "goal management"] }

add_tactic_doc
  { Name := "symmetry"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.symmetry]
    tags := ["core", "basic", "lemma application"] }

add_tactic_doc
  { Name := "trace"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.trace]
    tags := ["core", "debugging", "testing"] }

add_tactic_doc
  { Name := "trace_simp_set"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.trace_simp_set]
    tags := ["core", "debugging", "testing"] }

add_tactic_doc
  { Name := "trace_state"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.trace_state]
    tags := ["core", "debugging", "testing"] }

add_tactic_doc
  { Name := "transitivity"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.transitivity]
    tags := ["core", "lemma application"] }

add_tactic_doc
  { Name := "trivial"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.trivial]
    tags := ["core", "finishing"] }

add_tactic_doc
  { Name := "try"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.try]
    tags := ["core", "combinator"] }

add_tactic_doc
  { Name := "type_check"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.type_check]
    tags := ["core", "debugging", "testing"] }

add_tactic_doc
  { Name := "unfold"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.unfold]
    tags := ["core", "basic", "rewriting"] }

add_tactic_doc
  { Name := "unfold1"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.unfold1]
    tags := ["core", "rewriting"] }

add_tactic_doc
  { Name := "unfold_projs"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.unfold_projs]
    tags := ["core", "rewriting"] }

add_tactic_doc
  { Name := "with_cases"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.with_cases]
    tags := ["core", "combinator"] }

-- conv mode tactics
/-- Navigate to the left-hand-side of a relation.
A goal of `| a = b` will turn into the goal `| a`.
-/
add_tactic_doc
  { Name := "conv: to_lhs"
    category := DocCategory.tactic
    declNames := [`conv.interactive.to_lhs]
    tags := ["conv"] }

/-- Navigate to the right-hand-side of a relation.
A goal of `| a = b` will turn into the goal `| b`.
-/
add_tactic_doc
  { Name := "conv: to_rhs"
    category := DocCategory.tactic
    declNames := [`conv.interactive.to_rhs]
    tags := ["conv"] }

/-- Navigate into every argument of the current head function.
A target of `| (a * b) * c` will turn into the two targets `| a * b` and `| c`.
-/
add_tactic_doc
  { Name := "conv: congr"
    category := DocCategory.tactic
    declNames := [`conv.interactive.congr]
    tags := ["conv"] }

/-- Navigate into the contents of top-level `λ` binders.
A target of `| λ a, a + b` will turn into the target `| a + b` and introduce `a` into the local
context.
If there are multiple binders, all of them will be entered, and if there are none, this tactic is a
no-op.
-/
add_tactic_doc
  { Name := "conv: funext"
    category := DocCategory.tactic
    declNames := [`conv.interactive.funext]
    tags := ["conv"] }

/-- Navigate into the first scope matching the expression.

For a target of `| ∀ c, a + (b + c) = 1`, `find (b + _) { ... }` will run the tactics within the
`{}` with a target of `| b + c`.
-/
add_tactic_doc
  { Name := "conv: find"
    category := DocCategory.tactic
    declNames := [`conv.interactive.find]
    tags := ["conv"] }

/-- Navigate into the numbered scopes matching the expression.

For a target of `| λ c, 10 * c + 20 * c + 30 * c`, `for (_ * _) [1, 3] { ... }` will run the
tactics within the `{}` with first a target of `| 10 * c`, then a target of `| 30 * c`.
-/
add_tactic_doc
  { Name := "conv: for"
    category := DocCategory.tactic
    declNames := [`conv.interactive.for]
    tags := ["conv"] }

/-- End conversion of the current goal. This is often what is needed when muscle memory would type
`sorry`.
-/
add_tactic_doc
  { Name := "conv: skip"
    category := DocCategory.tactic
    declNames := [`conv.interactive.skip]
    tags := ["conv"] }