/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Mario Carneiro, Jeremy Avigad
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.order.filter.ultrafilter
import Mathlib.order.filter.partial
import Mathlib.PostPort

universes u l w v u_1 u_2 u_3 u_5

namespace Mathlib

/-!
# Basic theory of topological spaces.

The main definition is the type class `topological space α` which endows a type `α` with a topology.
Then `set α` gets predicates `is_open`, `is_closed` and functions `interior`, `closure` and
`frontier`. Each point `x` of `α` gets a neighborhood filter `𝓝 x`. A filter `F` on `α` has
`x` as a cluster point if `cluster_pt x F : 𝓝 x ⊓ F ≠ ⊥`. A map `f : ι → α` clusters at `x`
along `F : filter ι` if `map_cluster_pt x F f : cluster_pt x (map f F)`. In particular
the notion of cluster point of a sequence `u` is `map_cluster_pt x at_top u`.

This file also defines locally finite families of subsets of `α`.

For topological spaces `α` and `β`, a function `f : α → β` and a point `a : α`,
`continuous_at f a` means `f` is continuous at `a`, and global continuity is
`continuous f`. There is also a version of continuity `pcontinuous` for
partially defined functions.

## Notation

* `𝓝 x`: the filter of neighborhoods of a point `x`;
* `𝓟 s`: the principal filter of a set `s`;

## Implementation notes

Topology in mathlib heavily uses filters (even more than in Bourbaki). See explanations in
<https://leanprover-community.github.io/theories/topology.html>.

## References

*  [N. Bourbaki, *General Topology*][bourbaki1966]
*  [I. M. James, *Topologies and Uniformities*][james1999]

## Tags

topological space, interior, closure, frontier, neighborhood, continuity, continuous function
-/

/-!
### Topological spaces
-/

/-- A topology on `α`. -/
class topological_space (α : Type u)
where
  is_open : set α → Prop
  is_open_univ : is_open set.univ
  is_open_inter : ∀ (s t : set α), is_open s → is_open t → is_open (s ∩ t)
  is_open_sUnion : ∀ (s : set (set α)), (∀ (t : set α), t ∈ s → is_open t) → is_open (⋃₀s)

/-- A constructor for topologies by specifying the closed sets,
and showing that they satisfy the appropriate conditions. -/
def topological_space.of_closed {α : Type u} (T : set (set α)) (empty_mem : ∅ ∈ T) (sInter_mem : ∀ (A : set (set α)), A ⊆ T → ⋂₀A ∈ T) (union_mem : ∀ (A B : set α), A ∈ T → B ∈ T → A ∪ B ∈ T) : topological_space α :=
  topological_space.mk (fun (X : set α) => Xᶜ ∈ T) sorry sorry sorry

theorem topological_space_eq {α : Type u} {f : topological_space α} {g : topological_space α} : topological_space.is_open f = topological_space.is_open g → f = g := sorry

/-- `is_open s` means that `s` is open in the ambient topological space on `α` -/
def is_open {α : Type u} [t : topological_space α] (s : set α) :=
  topological_space.is_open t s

@[simp] theorem is_open_univ {α : Type u} [t : topological_space α] : is_open set.univ :=
  topological_space.is_open_univ t

theorem is_open_inter {α : Type u} {s₁ : set α} {s₂ : set α} [t : topological_space α] (h₁ : is_open s₁) (h₂ : is_open s₂) : is_open (s₁ ∩ s₂) :=
  topological_space.is_open_inter t s₁ s₂ h₁ h₂

theorem is_open_sUnion {α : Type u} [t : topological_space α] {s : set (set α)} (h : ∀ (t_1 : set α), t_1 ∈ s → is_open t_1) : is_open (⋃₀s) :=
  topological_space.is_open_sUnion t s h

theorem topological_space_eq_iff {α : Type u} {t : topological_space α} {t' : topological_space α} : t = t' ↔ ∀ (s : set α), is_open s ↔ is_open s :=
  { mp := fun (h : t = t') (s : set α) => h ▸ iff.rfl,
    mpr :=
      fun (h : ∀ (s : set α), is_open s ↔ is_open s) => topological_space_eq (funext fun (x : set α) => propext (h x)) }

theorem is_open_fold {α : Type u} {s : set α} {t : topological_space α} : topological_space.is_open t s = is_open s :=
  rfl

theorem is_open_Union {α : Type u} {ι : Sort w} [topological_space α] {f : ι → set α} (h : ∀ (i : ι), is_open (f i)) : is_open (set.Union fun (i : ι) => f i) :=
  is_open_sUnion
    fun (t : set α) (H : t ∈ set.range fun (i : ι) => f i) =>
      Exists.dcases_on H fun (i : ι) (H_h : (fun (i : ι) => f i) i = t) => Eq._oldrec (h i) H_h

theorem is_open_bUnion {α : Type u} {β : Type v} [topological_space α] {s : set β} {f : β → set α} (h : ∀ (i : β), i ∈ s → is_open (f i)) : is_open (set.Union fun (i : β) => set.Union fun (H : i ∈ s) => f i) :=
  is_open_Union fun (i : β) => is_open_Union fun (hi : i ∈ s) => h i hi

theorem is_open_union {α : Type u} {s₁ : set α} {s₂ : set α} [topological_space α] (h₁ : is_open s₁) (h₂ : is_open s₂) : is_open (s₁ ∪ s₂) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_open (s₁ ∪ s₂))) set.union_eq_Union))
    (is_open_Union (iff.mpr bool.forall_bool { left := h₂, right := h₁ }))

@[simp] theorem is_open_empty {α : Type u} [topological_space α] : is_open ∅ :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_open ∅)) (Eq.symm set.sUnion_empty))) (is_open_sUnion fun (a : set α) => false.elim)

theorem is_open_sInter {α : Type u} [topological_space α] {s : set (set α)} (hs : set.finite s) : (∀ (t : set α), t ∈ s → is_open t) → is_open (⋂₀s) := sorry

theorem is_open_bInter {α : Type u} {β : Type v} [topological_space α] {s : set β} {f : β → set α} (hs : set.finite s) : (∀ (i : β), i ∈ s → is_open (f i)) → is_open (set.Inter fun (i : β) => set.Inter fun (H : i ∈ s) => f i) := sorry

theorem is_open_Inter {α : Type u} {β : Type v} [topological_space α] [fintype β] {s : β → set α} (h : ∀ (i : β), is_open (s i)) : is_open (set.Inter fun (i : β) => s i) := sorry

theorem is_open_Inter_prop {α : Type u} [topological_space α] {p : Prop} {s : p → set α} (h : ∀ (h : p), is_open (s h)) : is_open (set.Inter s) := sorry

theorem is_open_const {α : Type u} [topological_space α] {p : Prop} : is_open (set_of fun (a : α) => p) := sorry

theorem is_open_and {α : Type u} {p₁ : α → Prop} {p₂ : α → Prop} [topological_space α] : is_open (set_of fun (a : α) => p₁ a) →
  is_open (set_of fun (a : α) => p₂ a) → is_open (set_of fun (a : α) => p₁ a ∧ p₂ a) :=
  is_open_inter

/-- A set is closed if its complement is open -/
def is_closed {α : Type u} [topological_space α] (s : set α) :=
  is_open (sᶜ)

@[simp] theorem is_closed_empty {α : Type u} [topological_space α] : is_closed ∅ :=
  eq.mpr (id (is_closed.equations._eqn_1 ∅))
    (eq.mpr (id (Eq._oldrec (Eq.refl (is_open (∅ᶜ))) set.compl_empty)) is_open_univ)

@[simp] theorem is_closed_univ {α : Type u} [topological_space α] : is_closed set.univ :=
  eq.mpr (id (is_closed.equations._eqn_1 set.univ))
    (eq.mpr (id (Eq._oldrec (Eq.refl (is_open (set.univᶜ))) set.compl_univ)) is_open_empty)

theorem is_closed_union {α : Type u} {s₁ : set α} {s₂ : set α} [topological_space α] : is_closed s₁ → is_closed s₂ → is_closed (s₁ ∪ s₂) :=
  fun (h₁ : is_closed s₁) (h₂ : is_closed s₂) =>
    eq.mpr (id (is_closed.equations._eqn_1 (s₁ ∪ s₂)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (is_open (s₁ ∪ s₂ᶜ))) (set.compl_union s₁ s₂))) (is_open_inter h₁ h₂))

theorem is_closed_sInter {α : Type u} [topological_space α] {s : set (set α)} : (∀ (t : set α), t ∈ s → is_closed t) → is_closed (⋂₀s) := sorry

theorem is_closed_Inter {α : Type u} {ι : Sort w} [topological_space α] {f : ι → set α} (h : ∀ (i : ι), is_closed (f i)) : is_closed (set.Inter fun (i : ι) => f i) := sorry

@[simp] theorem is_open_compl_iff {α : Type u} [topological_space α] {s : set α} : is_open (sᶜ) ↔ is_closed s :=
  iff.rfl

@[simp] theorem is_closed_compl_iff {α : Type u} [topological_space α] {s : set α} : is_closed (sᶜ) ↔ is_open s :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_closed (sᶜ) ↔ is_open s)) (Eq.symm (propext is_open_compl_iff))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (is_open (sᶜᶜ) ↔ is_open s)) (compl_compl s))) (iff.refl (is_open s)))

theorem is_open_diff {α : Type u} [topological_space α] {s : set α} {t : set α} (h₁ : is_open s) (h₂ : is_closed t) : is_open (s \ t) :=
  is_open_inter h₁ (iff.mpr is_open_compl_iff h₂)

theorem is_closed_inter {α : Type u} {s₁ : set α} {s₂ : set α} [topological_space α] (h₁ : is_closed s₁) (h₂ : is_closed s₂) : is_closed (s₁ ∩ s₂) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_closed (s₁ ∩ s₂))) (is_closed.equations._eqn_1 (s₁ ∩ s₂))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (is_open (s₁ ∩ s₂ᶜ))) (set.compl_inter s₁ s₂))) (is_open_union h₁ h₂))

theorem is_closed_bUnion {α : Type u} {β : Type v} [topological_space α] {s : set β} {f : β → set α} (hs : set.finite s) : (∀ (i : β), i ∈ s → is_closed (f i)) → is_closed (set.Union fun (i : β) => set.Union fun (H : i ∈ s) => f i) := sorry

theorem is_closed_Union {α : Type u} {β : Type v} [topological_space α] [fintype β] {s : β → set α} (h : ∀ (i : β), is_closed (s i)) : is_closed (set.Union s) := sorry

theorem is_closed_Union_prop {α : Type u} [topological_space α] {p : Prop} {s : p → set α} (h : ∀ (h : p), is_closed (s h)) : is_closed (set.Union s) := sorry

theorem is_closed_imp {α : Type u} [topological_space α] {p : α → Prop} {q : α → Prop} (hp : is_open (set_of fun (x : α) => p x)) (hq : is_closed (set_of fun (x : α) => q x)) : is_closed (set_of fun (x : α) => p x → q x) := sorry

theorem is_open_neg {α : Type u} {p : α → Prop} [topological_space α] : is_closed (set_of fun (a : α) => p a) → is_open (set_of fun (a : α) => ¬p a) :=
  iff.mpr is_open_compl_iff

/-!
### Interior of a set
-/

/-- The interior of a set `s` is the largest open subset of `s`. -/
def interior {α : Type u} [topological_space α] (s : set α) : set α :=
  ⋃₀set_of fun (t : set α) => is_open t ∧ t ⊆ s

theorem mem_interior {α : Type u} [topological_space α] {s : set α} {x : α} : x ∈ interior s ↔ ∃ (t : set α), ∃ (H : t ⊆ s), is_open t ∧ x ∈ t := sorry

@[simp] theorem is_open_interior {α : Type u} [topological_space α] {s : set α} : is_open (interior s) := sorry

theorem interior_subset {α : Type u} [topological_space α] {s : set α} : interior s ⊆ s := sorry

theorem interior_maximal {α : Type u} [topological_space α] {s : set α} {t : set α} (h₁ : t ⊆ s) (h₂ : is_open t) : t ⊆ interior s :=
  set.subset_sUnion_of_mem { left := h₂, right := h₁ }

theorem is_open.interior_eq {α : Type u} [topological_space α] {s : set α} (h : is_open s) : interior s = s :=
  set.subset.antisymm interior_subset (interior_maximal (set.subset.refl s) h)

theorem interior_eq_iff_open {α : Type u} [topological_space α] {s : set α} : interior s = s ↔ is_open s :=
  { mp := fun (h : interior s = s) => h ▸ is_open_interior, mpr := is_open.interior_eq }

theorem subset_interior_iff_open {α : Type u} [topological_space α] {s : set α} : s ⊆ interior s ↔ is_open s := sorry

theorem subset_interior_iff_subset_of_open {α : Type u} [topological_space α] {s : set α} {t : set α} (h₁ : is_open s) : s ⊆ interior t ↔ s ⊆ t :=
  { mp := fun (h : s ⊆ interior t) => set.subset.trans h interior_subset,
    mpr := fun (h₂ : s ⊆ t) => interior_maximal h₂ h₁ }

theorem interior_mono {α : Type u} [topological_space α] {s : set α} {t : set α} (h : s ⊆ t) : interior s ⊆ interior t :=
  interior_maximal (set.subset.trans interior_subset h) is_open_interior

@[simp] theorem interior_empty {α : Type u} [topological_space α] : interior ∅ = ∅ :=
  is_open.interior_eq is_open_empty

@[simp] theorem interior_univ {α : Type u} [topological_space α] : interior set.univ = set.univ :=
  is_open.interior_eq is_open_univ

@[simp] theorem interior_interior {α : Type u} [topological_space α] {s : set α} : interior (interior s) = interior s :=
  is_open.interior_eq is_open_interior

@[simp] theorem interior_inter {α : Type u} [topological_space α] {s : set α} {t : set α} : interior (s ∩ t) = interior s ∩ interior t := sorry

theorem interior_union_is_closed_of_interior_empty {α : Type u} [topological_space α] {s : set α} {t : set α} (h₁ : is_closed s) (h₂ : interior t = ∅) : interior (s ∪ t) = interior s := sorry

theorem is_open_iff_forall_mem_open {α : Type u} {s : set α} [topological_space α] : is_open s ↔ ∀ (x : α) (H : x ∈ s), ∃ (t : set α), ∃ (H : t ⊆ s), is_open t ∧ x ∈ t := sorry

/-!
### Closure of a set
-/

/-- The closure of `s` is the smallest closed set containing `s`. -/
def closure {α : Type u} [topological_space α] (s : set α) : set α :=
  ⋂₀set_of fun (t : set α) => is_closed t ∧ s ⊆ t

@[simp] theorem is_closed_closure {α : Type u} [topological_space α] {s : set α} : is_closed (closure s) := sorry

theorem subset_closure {α : Type u} [topological_space α] {s : set α} : s ⊆ closure s := sorry

theorem closure_minimal {α : Type u} [topological_space α] {s : set α} {t : set α} (h₁ : s ⊆ t) (h₂ : is_closed t) : closure s ⊆ t :=
  set.sInter_subset_of_mem { left := h₂, right := h₁ }

theorem is_closed.closure_eq {α : Type u} [topological_space α] {s : set α} (h : is_closed s) : closure s = s :=
  set.subset.antisymm (closure_minimal (set.subset.refl s) h) subset_closure

theorem is_closed.closure_subset {α : Type u} [topological_space α] {s : set α} (hs : is_closed s) : closure s ⊆ s :=
  closure_minimal (set.subset.refl s) hs

theorem is_closed.closure_subset_iff {α : Type u} [topological_space α] {s : set α} {t : set α} (h₁ : is_closed t) : closure s ⊆ t ↔ s ⊆ t :=
  { mp := set.subset.trans subset_closure, mpr := fun (h : s ⊆ t) => closure_minimal h h₁ }

theorem closure_mono {α : Type u} [topological_space α] {s : set α} {t : set α} (h : s ⊆ t) : closure s ⊆ closure t :=
  closure_minimal (set.subset.trans h subset_closure) is_closed_closure

theorem monotone_closure (α : Type u_1) [topological_space α] : monotone closure :=
  fun (_x _x_1 : set α) => closure_mono

theorem closure_inter_subset_inter_closure {α : Type u} [topological_space α] (s : set α) (t : set α) : closure (s ∩ t) ⊆ closure s ∩ closure t :=
  monotone.map_inf_le (monotone_closure α) s t

theorem is_closed_of_closure_subset {α : Type u} [topological_space α] {s : set α} (h : closure s ⊆ s) : is_closed s :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_closed s)) (set.subset.antisymm subset_closure h))) is_closed_closure

theorem closure_eq_iff_is_closed {α : Type u} [topological_space α] {s : set α} : closure s = s ↔ is_closed s :=
  { mp := fun (h : closure s = s) => h ▸ is_closed_closure, mpr := is_closed.closure_eq }

theorem closure_subset_iff_is_closed {α : Type u} [topological_space α] {s : set α} : closure s ⊆ s ↔ is_closed s :=
  { mp := is_closed_of_closure_subset, mpr := is_closed.closure_subset }

@[simp] theorem closure_empty {α : Type u} [topological_space α] : closure ∅ = ∅ :=
  is_closed.closure_eq is_closed_empty

@[simp] theorem closure_empty_iff {α : Type u} [topological_space α] (s : set α) : closure s = ∅ ↔ s = ∅ :=
  { mp := set.subset_eq_empty subset_closure, mpr := fun (h : s = ∅) => Eq.symm h ▸ closure_empty }

theorem set.nonempty.closure {α : Type u} [topological_space α] {s : set α} (h : set.nonempty s) : set.nonempty (closure s) := sorry

@[simp] theorem closure_univ {α : Type u} [topological_space α] : closure set.univ = set.univ :=
  is_closed.closure_eq is_closed_univ

@[simp] theorem closure_closure {α : Type u} [topological_space α] {s : set α} : closure (closure s) = closure s :=
  is_closed.closure_eq is_closed_closure

@[simp] theorem closure_union {α : Type u} [topological_space α] {s : set α} {t : set α} : closure (s ∪ t) = closure s ∪ closure t := sorry

theorem interior_subset_closure {α : Type u} [topological_space α] {s : set α} : interior s ⊆ closure s :=
  set.subset.trans interior_subset subset_closure

theorem closure_eq_compl_interior_compl {α : Type u} [topological_space α] {s : set α} : closure s = (interior (sᶜ)ᶜ) := sorry

@[simp] theorem interior_compl {α : Type u} [topological_space α] {s : set α} : interior (sᶜ) = (closure sᶜ) := sorry

@[simp] theorem closure_compl {α : Type u} [topological_space α] {s : set α} : closure (sᶜ) = (interior sᶜ) := sorry

theorem mem_closure_iff {α : Type u} [topological_space α] {s : set α} {a : α} : a ∈ closure s ↔ ∀ (o : set α), is_open o → a ∈ o → set.nonempty (o ∩ s) := sorry

/-- A set is dense in a topological space if every point belongs to its closure. -/
def dense {α : Type u} [topological_space α] (s : set α) :=
  ∀ (x : α), x ∈ closure s

theorem dense_iff_closure_eq {α : Type u} [topological_space α] {s : set α} : dense s ↔ closure s = set.univ :=
  iff.symm set.eq_univ_iff_forall

theorem dense.closure_eq {α : Type u} [topological_space α] {s : set α} (h : dense s) : closure s = set.univ :=
  iff.mp dense_iff_closure_eq h

/-- The closure of a set `s` is dense if and only if `s` is dense. -/
@[simp] theorem dense_closure {α : Type u} [topological_space α] {s : set α} : dense (closure s) ↔ dense s := sorry

theorem dense.of_closure {α : Type u} [topological_space α] {s : set α} : dense (closure s) → dense s :=
  iff.mp dense_closure

@[simp] theorem dense_univ {α : Type u} [topological_space α] : dense set.univ :=
  fun (x : α) => subset_closure trivial

/-- A set is dense if and only if it has a nonempty intersection with each nonempty open set. -/
theorem dense_iff_inter_open {α : Type u} [topological_space α] {s : set α} : dense s ↔ ∀ (U : set α), is_open U → set.nonempty U → set.nonempty (U ∩ s) := sorry

theorem dense.inter_open_nonempty {α : Type u} [topological_space α] {s : set α} : dense s → ∀ (U : set α), is_open U → set.nonempty U → set.nonempty (U ∩ s) :=
  iff.mp dense_iff_inter_open

theorem dense.nonempty_iff {α : Type u} [topological_space α] {s : set α} (hs : dense s) : set.nonempty s ↔ Nonempty α := sorry

theorem dense.nonempty {α : Type u} [topological_space α] [h : Nonempty α] {s : set α} (hs : dense s) : set.nonempty s :=
  iff.mpr (dense.nonempty_iff hs) h

theorem dense.mono {α : Type u} [topological_space α] {s₁ : set α} {s₂ : set α} (h : s₁ ⊆ s₂) (hd : dense s₁) : dense s₂ :=
  fun (x : α) => closure_mono h (hd x)

/-!
### Frontier of a set
-/

/-- The frontier of a set is the set of points between the closure and interior. -/
def frontier {α : Type u} [topological_space α] (s : set α) : set α :=
  closure s \ interior s

theorem frontier_eq_closure_inter_closure {α : Type u} [topological_space α] {s : set α} : frontier s = closure s ∩ closure (sᶜ) := sorry

/-- The complement of a set has the same frontier as the original set. -/
@[simp] theorem frontier_compl {α : Type u} [topological_space α] (s : set α) : frontier (sᶜ) = frontier s := sorry

theorem frontier_inter_subset {α : Type u} [topological_space α] (s : set α) (t : set α) : frontier (s ∩ t) ⊆ frontier s ∩ closure t ∪ closure s ∩ frontier t := sorry

theorem frontier_union_subset {α : Type u} [topological_space α] (s : set α) (t : set α) : frontier (s ∪ t) ⊆ frontier s ∩ closure (tᶜ) ∪ closure (sᶜ) ∩ frontier t := sorry

theorem is_closed.frontier_eq {α : Type u} [topological_space α] {s : set α} (hs : is_closed s) : frontier s = s \ interior s :=
  eq.mpr (id (Eq._oldrec (Eq.refl (frontier s = s \ interior s)) (frontier.equations._eqn_1 s)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (closure s \ interior s = s \ interior s)) (is_closed.closure_eq hs)))
      (Eq.refl (s \ interior s)))

theorem is_open.frontier_eq {α : Type u} [topological_space α] {s : set α} (hs : is_open s) : frontier s = closure s \ s :=
  eq.mpr (id (Eq._oldrec (Eq.refl (frontier s = closure s \ s)) (frontier.equations._eqn_1 s)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (closure s \ interior s = closure s \ s)) (is_open.interior_eq hs)))
      (Eq.refl (closure s \ s)))

/-- The frontier of a set is closed. -/
theorem is_closed_frontier {α : Type u} [topological_space α] {s : set α} : is_closed (frontier s) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_closed (frontier s))) frontier_eq_closure_inter_closure))
    (is_closed_inter is_closed_closure is_closed_closure)

/-- The frontier of a closed set has no interior point. -/
theorem interior_frontier {α : Type u} [topological_space α] {s : set α} (h : is_closed s) : interior (frontier s) = ∅ := sorry

theorem closure_eq_interior_union_frontier {α : Type u} [topological_space α] (s : set α) : closure s = interior s ∪ frontier s :=
  Eq.symm (set.union_diff_cancel interior_subset_closure)

theorem closure_eq_self_union_frontier {α : Type u} [topological_space α] (s : set α) : closure s = s ∪ frontier s :=
  Eq.symm (set.union_diff_cancel' interior_subset subset_closure)

/-!
### Neighborhoods
-/

/-- A set is called a neighborhood of `a` if it contains an open set around `a`. The set of all
neighborhoods of `a` forms a filter, the neighborhood filter at `a`, is here defined as the
infimum over the principal filters of all open sets containing `a`. -/
def nhds {α : Type u} [topological_space α] (a : α) : filter α :=
  infi fun (s : set α) => infi fun (H : s ∈ set_of fun (s : set α) => a ∈ s ∧ is_open s) => filter.principal s

theorem nhds_def {α : Type u} [topological_space α] (a : α) : nhds a = infi fun (s : set α) => infi fun (H : s ∈ set_of fun (s : set α) => a ∈ s ∧ is_open s) => filter.principal s :=
  rfl

/-- The open sets containing `a` are a basis for the neighborhood filter. See `nhds_basis_opens'`
for a variant using open neighborhoods instead. -/
theorem nhds_basis_opens {α : Type u} [topological_space α] (a : α) : filter.has_basis (nhds a) (fun (s : set α) => a ∈ s ∧ is_open s) fun (x : set α) => x := sorry

/-- A filter lies below the neighborhood filter at `a` iff it contains every open set around `a`. -/
theorem le_nhds_iff {α : Type u} [topological_space α] {f : filter α} {a : α} : f ≤ nhds a ↔ ∀ (s : set α), a ∈ s → is_open s → s ∈ f := sorry

/-- To show a filter is above the neighborhood filter at `a`, it suffices to show that it is above
the principal filter of some open set `s` containing `a`. -/
theorem nhds_le_of_le {α : Type u} [topological_space α] {f : filter α} {a : α} {s : set α} (h : a ∈ s) (o : is_open s) (sf : filter.principal s ≤ f) : nhds a ≤ f :=
  eq.mpr (id (Eq._oldrec (Eq.refl (nhds a ≤ f)) (nhds_def a)))
    (infi_le_of_le s (infi_le_of_le { left := h, right := o } sf))

theorem mem_nhds_sets_iff {α : Type u} [topological_space α] {a : α} {s : set α} : s ∈ nhds a ↔ ∃ (t : set α), ∃ (H : t ⊆ s), is_open t ∧ a ∈ t := sorry

/-- A predicate is true in a neighborhood of `a` iff it is true for all the points in an open set
containing `a`. -/
theorem eventually_nhds_iff {α : Type u} [topological_space α] {a : α} {p : α → Prop} : filter.eventually (fun (x : α) => p x) (nhds a) ↔ ∃ (t : set α), (∀ (x : α), x ∈ t → p x) ∧ is_open t ∧ a ∈ t := sorry

theorem map_nhds {α : Type u} {β : Type v} [topological_space α] {a : α} {f : α → β} : filter.map f (nhds a) =
  infi fun (s : set α) => infi fun (H : s ∈ set_of fun (s : set α) => a ∈ s ∧ is_open s) => filter.principal (f '' s) :=
  filter.has_basis.eq_binfi (filter.has_basis.map f (nhds_basis_opens a))

theorem mem_of_nhds {α : Type u} [topological_space α] {a : α} {s : set α} : s ∈ nhds a → a ∈ s := sorry

/-- If a predicate is true in a neighborhood of `a`, then it is true for `a`. -/
theorem filter.eventually.self_of_nhds {α : Type u} [topological_space α] {p : α → Prop} {a : α} (h : filter.eventually (fun (y : α) => p y) (nhds a)) : p a :=
  mem_of_nhds h

theorem mem_nhds_sets {α : Type u} [topological_space α] {a : α} {s : set α} (hs : is_open s) (ha : a ∈ s) : s ∈ nhds a :=
  iff.mpr mem_nhds_sets_iff (Exists.intro s (Exists.intro (set.subset.refl s) { left := hs, right := ha }))

theorem is_open.eventually_mem {α : Type u} [topological_space α] {a : α} {s : set α} (hs : is_open s) (ha : a ∈ s) : filter.eventually (fun (x : α) => x ∈ s) (nhds a) :=
  mem_nhds_sets hs ha

/-- The open neighborhoods of `a` are a basis for the neighborhood filter. See `nhds_basis_opens`
for a variant using open sets around `a` instead. -/
theorem nhds_basis_opens' {α : Type u} [topological_space α] (a : α) : filter.has_basis (nhds a) (fun (s : set α) => s ∈ nhds a ∧ is_open s) fun (x : set α) => x := sorry

/-- If a predicate is true in a neighbourhood of `a`, then for `y` sufficiently close
to `a` this predicate is true in a neighbourhood of `y`. -/
theorem filter.eventually.eventually_nhds {α : Type u} [topological_space α] {p : α → Prop} {a : α} (h : filter.eventually (fun (y : α) => p y) (nhds a)) : filter.eventually (fun (y : α) => filter.eventually (fun (y : α) => p y) (nhds y)) (nhds a) := sorry

@[simp] theorem eventually_eventually_nhds {α : Type u} [topological_space α] {p : α → Prop} {a : α} : filter.eventually (fun (y : α) => filter.eventually (fun (x : α) => p x) (nhds y)) (nhds a) ↔
  filter.eventually (fun (x : α) => p x) (nhds a) := sorry

@[simp] theorem nhds_bind_nhds {α : Type u} {a : α} [topological_space α] : filter.bind (nhds a) nhds = nhds a :=
  filter.ext fun (s : set α) => eventually_eventually_nhds

@[simp] theorem eventually_eventually_eq_nhds {α : Type u} {β : Type v} [topological_space α] {f : α → β} {g : α → β} {a : α} : filter.eventually (fun (y : α) => filter.eventually_eq (nhds y) f g) (nhds a) ↔ filter.eventually_eq (nhds a) f g :=
  eventually_eventually_nhds

theorem filter.eventually_eq.eq_of_nhds {α : Type u} {β : Type v} [topological_space α] {f : α → β} {g : α → β} {a : α} (h : filter.eventually_eq (nhds a) f g) : f a = g a :=
  filter.eventually.self_of_nhds h

@[simp] theorem eventually_eventually_le_nhds {α : Type u} {β : Type v} [topological_space α] [HasLessEq β] {f : α → β} {g : α → β} {a : α} : filter.eventually (fun (y : α) => filter.eventually_le (nhds y) f g) (nhds a) ↔ filter.eventually_le (nhds a) f g :=
  eventually_eventually_nhds

/-- If two functions are equal in a neighbourhood of `a`, then for `y` sufficiently close
to `a` these functions are equal in a neighbourhood of `y`. -/
theorem filter.eventually_eq.eventually_eq_nhds {α : Type u} {β : Type v} [topological_space α] {f : α → β} {g : α → β} {a : α} (h : filter.eventually_eq (nhds a) f g) : filter.eventually (fun (y : α) => filter.eventually_eq (nhds y) f g) (nhds a) :=
  filter.eventually.eventually_nhds h

/-- If `f x ≤ g x` in a neighbourhood of `a`, then for `y` sufficiently close to `a` we have
`f x ≤ g x` in a neighbourhood of `y`. -/
theorem filter.eventually_le.eventually_le_nhds {α : Type u} {β : Type v} [topological_space α] [HasLessEq β] {f : α → β} {g : α → β} {a : α} (h : filter.eventually_le (nhds a) f g) : filter.eventually (fun (y : α) => filter.eventually_le (nhds y) f g) (nhds a) :=
  filter.eventually.eventually_nhds h

theorem all_mem_nhds {α : Type u} [topological_space α] (x : α) (P : set α → Prop) (hP : ∀ (s t : set α), s ⊆ t → P s → P t) : (∀ (s : set α), s ∈ nhds x → P s) ↔ ∀ (s : set α), is_open s → x ∈ s → P s := sorry

theorem all_mem_nhds_filter {α : Type u} {β : Type v} [topological_space α] (x : α) (f : set α → set β) (hf : ∀ (s t : set α), s ⊆ t → f s ⊆ f t) (l : filter β) : (∀ (s : set α), s ∈ nhds x → f s ∈ l) ↔ ∀ (s : set α), is_open s → x ∈ s → f s ∈ l :=
  all_mem_nhds x (fun (s : set α) => f s ∈ l)
    fun (s t : set α) (ssubt : s ⊆ t) (h : f s ∈ l) => filter.mem_sets_of_superset h (hf s t ssubt)

theorem rtendsto_nhds {α : Type u} {β : Type v} [topological_space α] {r : rel β α} {l : filter β} {a : α} : filter.rtendsto r l (nhds a) ↔ ∀ (s : set α), is_open s → a ∈ s → rel.core r s ∈ l :=
  all_mem_nhds_filter a (fun (U : set α) => U) (fun (s t : set α) => id) (filter.rmap r l)

theorem rtendsto'_nhds {α : Type u} {β : Type v} [topological_space α] {r : rel β α} {l : filter β} {a : α} : filter.rtendsto' r l (nhds a) ↔ ∀ (s : set α), is_open s → a ∈ s → rel.preimage r s ∈ l := sorry

theorem ptendsto_nhds {α : Type u} {β : Type v} [topological_space α] {f : β →. α} {l : filter β} {a : α} : filter.ptendsto f l (nhds a) ↔ ∀ (s : set α), is_open s → a ∈ s → pfun.core f s ∈ l :=
  rtendsto_nhds

theorem ptendsto'_nhds {α : Type u} {β : Type v} [topological_space α] {f : β →. α} {l : filter β} {a : α} : filter.ptendsto' f l (nhds a) ↔ ∀ (s : set α), is_open s → a ∈ s → pfun.preimage f s ∈ l :=
  rtendsto'_nhds

theorem tendsto_nhds {α : Type u} {β : Type v} [topological_space α] {f : β → α} {l : filter β} {a : α} : filter.tendsto f l (nhds a) ↔ ∀ (s : set α), is_open s → a ∈ s → f ⁻¹' s ∈ l :=
  all_mem_nhds_filter a (fun (U : set α) => U) (fun (s t : set α) (h : s ⊆ t) => set.preimage_mono h) (filter.map f l)

theorem tendsto_const_nhds {α : Type u} {β : Type v} [topological_space α] {a : α} {f : filter β} : filter.tendsto (fun (b : β) => a) f (nhds a) :=
  iff.mpr tendsto_nhds fun (s : set α) (hs : is_open s) (ha : a ∈ s) => filter.univ_mem_sets' fun (_x : β) => ha

theorem pure_le_nhds {α : Type u} [topological_space α] : pure ≤ nhds :=
  fun (a : α) (s : set α) (hs : s ∈ nhds a) => iff.mpr filter.mem_pure_sets (mem_of_nhds hs)

theorem tendsto_pure_nhds {β : Type v} {α : Type u_1} [topological_space β] (f : α → β) (a : α) : filter.tendsto f (pure a) (nhds (f a)) :=
  filter.tendsto.mono_right (filter.tendsto_pure_pure f a) (pure_le_nhds (f a))

theorem order_top.tendsto_at_top_nhds {β : Type v} {α : Type u_1} [order_top α] [topological_space β] (f : α → β) : filter.tendsto f filter.at_top (nhds (f ⊤)) :=
  filter.tendsto.mono_right (filter.tendsto_at_top_pure f) (pure_le_nhds (f ⊤))

@[simp] protected instance nhds_ne_bot {α : Type u} [topological_space α] {a : α} : filter.ne_bot (nhds a) :=
  filter.ne_bot_of_le (pure_le_nhds a)

/-!
### Cluster points

In this section we define [cluster points](https://en.wikipedia.org/wiki/Limit_point)
(also known as limit points and accumulation points) of a filter and of a sequence.
-/

/-- A point `x` is a cluster point of a filter `F` if 𝓝 x ⊓ F ≠ ⊥. Also known as
an accumulation point or a limit point. -/
def cluster_pt {α : Type u} [topological_space α] (x : α) (F : filter α) :=
  filter.ne_bot (nhds x ⊓ F)

theorem cluster_pt.ne_bot {α : Type u} [topological_space α] {x : α} {F : filter α} (h : cluster_pt x F) : filter.ne_bot (nhds x ⊓ F) :=
  h

theorem cluster_pt_iff {α : Type u} [topological_space α] {x : α} {F : filter α} : cluster_pt x F ↔ ∀ {U : set α}, U ∈ nhds x → ∀ {V : set α}, V ∈ F → set.nonempty (U ∩ V) :=
  filter.inf_ne_bot_iff

/-- `x` is a cluster point of a set `s` if every neighbourhood of `x` meets `s` on a nonempty
set. -/
theorem cluster_pt_principal_iff {α : Type u} [topological_space α] {x : α} {s : set α} : cluster_pt x (filter.principal s) ↔ ∀ (U : set α), U ∈ nhds x → set.nonempty (U ∩ s) :=
  filter.inf_principal_ne_bot_iff

theorem cluster_pt_principal_iff_frequently {α : Type u} [topological_space α] {x : α} {s : set α} : cluster_pt x (filter.principal s) ↔ filter.frequently (fun (y : α) => y ∈ s) (nhds x) := sorry

theorem cluster_pt.of_le_nhds {α : Type u} [topological_space α] {x : α} {f : filter α} (H : f ≤ nhds x) [filter.ne_bot f] : cluster_pt x f :=
  eq.mpr (id (Eq._oldrec (Eq.refl (cluster_pt x f)) (cluster_pt.equations._eqn_1 x f)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (filter.ne_bot (nhds x ⊓ f))) (iff.mpr inf_eq_right H))) _inst_2)

theorem cluster_pt.of_le_nhds' {α : Type u} [topological_space α] {x : α} {f : filter α} (H : f ≤ nhds x) (hf : filter.ne_bot f) : cluster_pt x f :=
  cluster_pt.of_le_nhds H

theorem cluster_pt.of_nhds_le {α : Type u} [topological_space α] {x : α} {f : filter α} (H : nhds x ≤ f) : cluster_pt x f := sorry

theorem cluster_pt.mono {α : Type u} [topological_space α] {x : α} {f : filter α} {g : filter α} (H : cluster_pt x f) (h : f ≤ g) : cluster_pt x g :=
  ne_bot_of_le_ne_bot H (inf_le_inf_left (nhds x) h)

theorem cluster_pt.of_inf_left {α : Type u} [topological_space α] {x : α} {f : filter α} {g : filter α} (H : cluster_pt x (f ⊓ g)) : cluster_pt x f :=
  cluster_pt.mono H inf_le_left

theorem cluster_pt.of_inf_right {α : Type u} [topological_space α] {x : α} {f : filter α} {g : filter α} (H : cluster_pt x (f ⊓ g)) : cluster_pt x g :=
  cluster_pt.mono H inf_le_right

theorem ultrafilter.cluster_pt_iff {α : Type u} [topological_space α] {x : α} {f : ultrafilter α} : cluster_pt x ↑f ↔ ↑f ≤ nhds x :=
  { mp := ultrafilter.le_of_inf_ne_bot' f, mpr := fun (h : ↑f ≤ nhds x) => cluster_pt.of_le_nhds h }

/-- A point `x` is a cluster point of a sequence `u` along a filter `F` if it is a cluster point
of `map u F`. -/
def map_cluster_pt {α : Type u} [topological_space α] {ι : Type u_1} (x : α) (F : filter ι) (u : ι → α) :=
  cluster_pt x (filter.map u F)

theorem map_cluster_pt_iff {α : Type u} [topological_space α] {ι : Type u_1} (x : α) (F : filter ι) (u : ι → α) : map_cluster_pt x F u ↔ ∀ (s : set α), s ∈ nhds x → filter.frequently (fun (a : ι) => u a ∈ s) F := sorry

theorem map_cluster_pt_of_comp {α : Type u} [topological_space α] {ι : Type u_1} {δ : Type u_2} {F : filter ι} {φ : δ → ι} {p : filter δ} {x : α} {u : ι → α} [filter.ne_bot p] (h : filter.tendsto φ p F) (H : filter.tendsto (u ∘ φ) p (nhds x)) : map_cluster_pt x F u :=
  filter.ne_bot_of_le (le_inf H (trans_rel_right LessEq filter.map_map (filter.map_mono h)))

/-!
### Interior, closure and frontier in terms of neighborhoods
-/

theorem interior_eq_nhds' {α : Type u} [topological_space α] {s : set α} : interior s = set_of fun (a : α) => s ∈ nhds a := sorry

theorem interior_eq_nhds {α : Type u} [topological_space α] {s : set α} : interior s = set_of fun (a : α) => nhds a ≤ filter.principal s := sorry

theorem mem_interior_iff_mem_nhds {α : Type u} [topological_space α] {s : set α} {a : α} : a ∈ interior s ↔ s ∈ nhds a :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a ∈ interior s ↔ s ∈ nhds a)) interior_eq_nhds'))
    (eq.mpr (id (Eq._oldrec (Eq.refl ((a ∈ set_of fun (a : α) => s ∈ nhds a) ↔ s ∈ nhds a)) set.mem_set_of_eq))
      (iff.refl (s ∈ nhds a)))

theorem interior_set_of_eq {α : Type u} [topological_space α] {p : α → Prop} : interior (set_of fun (x : α) => p x) = set_of fun (x : α) => filter.eventually (fun (x : α) => p x) (nhds x) :=
  interior_eq_nhds'

theorem is_open_set_of_eventually_nhds {α : Type u} [topological_space α] {p : α → Prop} : is_open (set_of fun (x : α) => filter.eventually (fun (y : α) => p y) (nhds x)) := sorry

theorem subset_interior_iff_nhds {α : Type u} [topological_space α] {s : set α} {V : set α} : s ⊆ interior V ↔ ∀ (x : α), x ∈ s → V ∈ nhds x := sorry

theorem is_open_iff_nhds {α : Type u} [topological_space α] {s : set α} : is_open s ↔ ∀ (a : α), a ∈ s → nhds a ≤ filter.principal s :=
  iff.trans (iff.symm subset_interior_iff_open)
    (eq.mpr (id (Eq._oldrec (Eq.refl (s ⊆ interior s ↔ ∀ (a : α), a ∈ s → nhds a ≤ filter.principal s)) interior_eq_nhds))
      (iff.refl (s ⊆ set_of fun (a : α) => nhds a ≤ filter.principal s)))

theorem is_open_iff_mem_nhds {α : Type u} [topological_space α] {s : set α} : is_open s ↔ ∀ (a : α), a ∈ s → s ∈ nhds a :=
  iff.trans is_open_iff_nhds (forall_congr fun (_x : α) => imp_congr_right fun (_x_1 : _x ∈ s) => filter.le_principal_iff)

theorem is_open_iff_ultrafilter {α : Type u} [topological_space α] {s : set α} : is_open s ↔ ∀ (x : α), x ∈ s → ∀ (l : ultrafilter α), ↑l ≤ nhds x → s ∈ l := sorry

theorem mem_closure_iff_frequently {α : Type u} [topological_space α] {s : set α} {a : α} : a ∈ closure s ↔ filter.frequently (fun (x : α) => x ∈ s) (nhds a) := sorry

theorem filter.frequently.mem_closure {α : Type u} [topological_space α] {s : set α} {a : α} : filter.frequently (fun (x : α) => x ∈ s) (nhds a) → a ∈ closure s :=
  iff.mpr mem_closure_iff_frequently

/-- The set of cluster points of a filter is closed. In particular, the set of limit points
of a sequence is closed. -/
theorem is_closed_set_of_cluster_pt {α : Type u} [topological_space α] {f : filter α} : is_closed (set_of fun (x : α) => cluster_pt x f) := sorry

theorem mem_closure_iff_cluster_pt {α : Type u} [topological_space α] {s : set α} {a : α} : a ∈ closure s ↔ cluster_pt a (filter.principal s) :=
  iff.trans mem_closure_iff_frequently (iff.symm cluster_pt_principal_iff_frequently)

theorem closure_eq_cluster_pts {α : Type u} [topological_space α] {s : set α} : closure s = set_of fun (a : α) => cluster_pt a (filter.principal s) :=
  set.ext fun (x : α) => mem_closure_iff_cluster_pt

theorem mem_closure_iff_nhds {α : Type u} [topological_space α] {s : set α} {a : α} : a ∈ closure s ↔ ∀ (t : set α), t ∈ nhds a → set.nonempty (t ∩ s) :=
  iff.trans mem_closure_iff_cluster_pt cluster_pt_principal_iff

theorem mem_closure_iff_nhds' {α : Type u} [topological_space α] {s : set α} {a : α} : a ∈ closure s ↔ ∀ (t : set α), t ∈ nhds a → ∃ (y : ↥s), ↑y ∈ t := sorry

theorem mem_closure_iff_comap_ne_bot {α : Type u} [topological_space α] {A : set α} {x : α} : x ∈ closure A ↔ filter.ne_bot (filter.comap coe (nhds x)) := sorry

theorem mem_closure_iff_nhds_basis {α : Type u} {β : Type v} [topological_space α] {a : α} {p : β → Prop} {s : β → set α} (h : filter.has_basis (nhds a) p s) {t : set α} : a ∈ closure t ↔ ∀ (i : β), p i → ∃ (y : α), ∃ (H : y ∈ t), y ∈ s i := sorry

/-- `x` belongs to the closure of `s` if and only if some ultrafilter
  supported on `s` converges to `x`. -/
theorem mem_closure_iff_ultrafilter {α : Type u} [topological_space α] {s : set α} {x : α} : x ∈ closure s ↔ ∃ (u : ultrafilter α), s ∈ u ∧ ↑u ≤ nhds x := sorry

theorem is_closed_iff_cluster_pt {α : Type u} [topological_space α] {s : set α} : is_closed s ↔ ∀ (a : α), cluster_pt a (filter.principal s) → a ∈ s := sorry

theorem is_closed_iff_nhds {α : Type u} [topological_space α] {s : set α} : is_closed s ↔ ∀ (x : α), (∀ (U : set α), U ∈ nhds x → set.nonempty (U ∩ s)) → x ∈ s := sorry

theorem closure_inter_open {α : Type u} [topological_space α] {s : set α} {t : set α} (h : is_open s) : s ∩ closure t ⊆ closure (s ∩ t) := sorry

/-- The intersection of an open dense set with a dense set is a dense set. -/
theorem dense.inter_of_open_left {α : Type u} [topological_space α] {s : set α} {t : set α} (hs : dense s) (ht : dense t) (hso : is_open s) : dense (s ∩ t) := sorry

/-- The intersection of a dense set with an open dense set is a dense set. -/
theorem dense.inter_of_open_right {α : Type u} [topological_space α] {s : set α} {t : set α} (hs : dense s) (ht : dense t) (hto : is_open t) : dense (s ∩ t) :=
  set.inter_comm t s ▸ dense.inter_of_open_left ht hs hto

theorem dense.inter_nhds_nonempty {α : Type u} [topological_space α] {s : set α} {t : set α} (hs : dense s) {x : α} (ht : t ∈ nhds x) : set.nonempty (s ∩ t) := sorry

theorem closure_diff {α : Type u} [topological_space α] {s : set α} {t : set α} : closure s \ closure t ⊆ closure (s \ t) := sorry

theorem filter.frequently.mem_of_closed {α : Type u} [topological_space α] {a : α} {s : set α} (h : filter.frequently (fun (x : α) => x ∈ s) (nhds a)) (hs : is_closed s) : a ∈ s :=
  is_closed.closure_subset hs (filter.frequently.mem_closure h)

theorem is_closed.mem_of_frequently_of_tendsto {α : Type u} {β : Type v} [topological_space α] {f : β → α} {b : filter β} {a : α} {s : set α} (hs : is_closed s) (h : filter.frequently (fun (x : β) => f x ∈ s) b) (hf : filter.tendsto f b (nhds a)) : a ∈ s := sorry

theorem is_closed.mem_of_tendsto {α : Type u} {β : Type v} [topological_space α] {f : β → α} {b : filter β} {a : α} {s : set α} [filter.ne_bot b] (hs : is_closed s) (hf : filter.tendsto f b (nhds a)) (h : filter.eventually (fun (x : β) => f x ∈ s) b) : a ∈ s :=
  is_closed.mem_of_frequently_of_tendsto hs (filter.eventually.frequently h) hf

theorem mem_closure_of_tendsto {α : Type u} {β : Type v} [topological_space α] {f : β → α} {b : filter β} {a : α} {s : set α} [filter.ne_bot b] (hf : filter.tendsto f b (nhds a)) (h : filter.eventually (fun (x : β) => f x ∈ s) b) : a ∈ closure s :=
  is_closed.mem_of_tendsto is_closed_closure hf (filter.eventually.mono h (set.preimage_mono subset_closure))

/-- Suppose that `f` sends the complement to `s` to a single point `a`, and `l` is some filter.
Then `f` tends to `a` along `l` restricted to `s` if and only if it tends to `a` along `l`. -/
theorem tendsto_inf_principal_nhds_iff_of_forall_eq {α : Type u} {β : Type v} [topological_space α] {f : β → α} {l : filter β} {s : set β} {a : α} (h : ∀ (x : β), ¬x ∈ s → f x = a) : filter.tendsto f (l ⊓ filter.principal s) (nhds a) ↔ filter.tendsto f l (nhds a) := sorry

/-!
### Limits of filters in topological spaces
-/

/-- If `f` is a filter, then `Lim f` is a limit of the filter, if it exists. -/
def Lim {α : Type u} [topological_space α] [Nonempty α] (f : filter α) : α :=
  classical.epsilon fun (a : α) => f ≤ nhds a

/--
If `f` is a filter satisfying `ne_bot f`, then `Lim' f` is a limit of the filter, if it exists.
-/
def Lim' {α : Type u} [topological_space α] (f : filter α) [filter.ne_bot f] : α :=
  Lim f

/--
If `F` is an ultrafilter, then `filter.ultrafilter.Lim F` is a limit of the filter, if it exists.
Note that dot notation `F.Lim` can be used for `F : ultrafilter α`.
-/
def ultrafilter.Lim {α : Type u} [topological_space α] : ultrafilter α → α :=
  fun (F : ultrafilter α) => Lim' ↑F

/-- If `f` is a filter in `β` and `g : β → α` is a function, then `lim f` is a limit of `g` at `f`,
if it exists. -/
def lim {α : Type u} {β : Type v} [topological_space α] [Nonempty α] (f : filter β) (g : β → α) : α :=
  Lim (filter.map g f)

/-- If a filter `f` is majorated by some `𝓝 a`, then it is majorated by `𝓝 (Lim f)`. We formulate
this lemma with a `[nonempty α]` argument of `Lim` derived from `h` to make it useful for types
without a `[nonempty α]` instance. Because of the built-in proof irrelevance, Lean will unify
this instance with any other instance. -/
theorem le_nhds_Lim {α : Type u} [topological_space α] {f : filter α} (h : ∃ (a : α), f ≤ nhds a) : f ≤ nhds (Lim f) :=
  classical.epsilon_spec h

/-- If `g` tends to some `𝓝 a` along `f`, then it tends to `𝓝 (lim f g)`. We formulate
this lemma with a `[nonempty α]` argument of `lim` derived from `h` to make it useful for types
without a `[nonempty α]` instance. Because of the built-in proof irrelevance, Lean will unify
this instance with any other instance. -/
theorem tendsto_nhds_lim {α : Type u} {β : Type v} [topological_space α] {f : filter β} {g : β → α} (h : ∃ (a : α), filter.tendsto g f (nhds a)) : filter.tendsto g f (nhds (lim f g)) :=
  le_nhds_Lim h

/-!
### Locally finite families
-/

/- locally finite family [General Topology (Bourbaki, 1995)] -/

/-- A family of sets in `set α` is locally finite if at every point `x:α`,
  there is a neighborhood of `x` which meets only finitely many sets in the family -/
def locally_finite {α : Type u} {β : Type v} [topological_space α] (f : β → set α) :=
  ∀ (x : α), ∃ (t : set α), ∃ (H : t ∈ nhds x), set.finite (set_of fun (i : β) => set.nonempty (f i ∩ t))

theorem locally_finite_of_finite {α : Type u} {β : Type v} [topological_space α] {f : β → set α} (h : set.finite set.univ) : locally_finite f := sorry

theorem locally_finite_subset {α : Type u} {β : Type v} [topological_space α] {f₁ : β → set α} {f₂ : β → set α} (hf₂ : locally_finite f₂) (hf : ∀ (b : β), f₁ b ⊆ f₂ b) : locally_finite f₁ := sorry

theorem is_closed_Union_of_locally_finite {α : Type u} {β : Type v} [topological_space α] {f : β → set α} (h₁ : locally_finite f) (h₂ : ∀ (i : β), is_closed (f i)) : is_closed (set.Union fun (i : β) => f i) := sorry

/-!
### Continuity
-/

/-- A function between topological spaces is continuous if the preimage
  of every open set is open. Registered as a structure to make sure it is not unfolded by Lean. -/
structure continuous {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (f : α → β)
where
  is_open_preimage : ∀ (s : set β), is_open s → is_open (f ⁻¹' s)

theorem continuous_def {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} : continuous f ↔ ∀ (s : set β), is_open s → is_open (f ⁻¹' s) :=
  { mp := fun (hf : continuous f) (s : set β) (hs : is_open s) => continuous.is_open_preimage hf s hs,
    mpr := fun (h : ∀ (s : set β), is_open s → is_open (f ⁻¹' s)) => continuous.mk h }

theorem is_open.preimage {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} (hf : continuous f) {s : set β} (h : is_open s) : is_open (f ⁻¹' s) :=
  continuous.is_open_preimage hf s h

/-- A function between topological spaces is continuous at a point `x₀`
if `f x` tends to `f x₀` when `x` tends to `x₀`. -/
def continuous_at {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (f : α → β) (x : α) :=
  filter.tendsto f (nhds x) (nhds (f x))

theorem continuous_at.tendsto {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} {x : α} (h : continuous_at f x) : filter.tendsto f (nhds x) (nhds (f x)) :=
  h

theorem continuous_at.preimage_mem_nhds {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} {x : α} {t : set β} (h : continuous_at f x) (ht : t ∈ nhds (f x)) : f ⁻¹' t ∈ nhds x :=
  h ht

theorem cluster_pt.map {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {x : α} {la : filter α} {lb : filter β} (H : cluster_pt x la) {f : α → β} (hfc : continuous_at f x) (hf : filter.tendsto f la lb) : cluster_pt (f x) lb :=
  ne_bot_of_le_ne_bot (iff.mpr (filter.map_ne_bot_iff f) H) (filter.tendsto.inf (continuous_at.tendsto hfc) hf)

theorem preimage_interior_subset_interior_preimage {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} {s : set β} (hf : continuous f) : f ⁻¹' interior s ⊆ interior (f ⁻¹' s) :=
  interior_maximal (set.preimage_mono interior_subset) (is_open.preimage hf is_open_interior)

theorem continuous_id {α : Type u_1} [topological_space α] : continuous id :=
  iff.mpr continuous_def fun (s : set α) (h : is_open s) => h

theorem continuous.comp {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] {g : β → γ} {f : α → β} (hg : continuous g) (hf : continuous f) : continuous (g ∘ f) :=
  iff.mpr continuous_def fun (s : set γ) (h : is_open s) => is_open.preimage hf (is_open.preimage hg h)

theorem continuous.iterate {α : Type u_1} [topological_space α] {f : α → α} (h : continuous f) (n : ℕ) : continuous (nat.iterate f n) :=
  nat.rec_on n continuous_id fun (n : ℕ) (ihn : continuous (nat.iterate f n)) => continuous.comp ihn h

theorem continuous_at.comp {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] {g : β → γ} {f : α → β} {x : α} (hg : continuous_at g (f x)) (hf : continuous_at f x) : continuous_at (g ∘ f) x :=
  filter.tendsto.comp hg hf

theorem continuous.tendsto {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} (hf : continuous f) (x : α) : filter.tendsto f (nhds x) (nhds (f x)) := sorry

/-- A version of `continuous.tendsto` that allows one to specify a simpler form of the limit.
E.g., one can write `continuous_exp.tendsto' 0 1 exp_zero`. -/
theorem continuous.tendsto' {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} (hf : continuous f) (x : α) (y : β) (h : f x = y) : filter.tendsto f (nhds x) (nhds y) :=
  h ▸ continuous.tendsto hf x

theorem continuous.continuous_at {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} {x : α} (h : continuous f) : continuous_at f x :=
  continuous.tendsto h x

theorem continuous_iff_continuous_at {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} : continuous f ↔ ∀ (x : α), continuous_at f x := sorry

theorem continuous_at_const {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {x : α} {b : β} : continuous_at (fun (a : α) => b) x :=
  tendsto_const_nhds

theorem continuous_const {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {b : β} : continuous fun (a : α) => b :=
  iff.mpr continuous_iff_continuous_at fun (a : α) => continuous_at_const

theorem continuous_at_id {α : Type u_1} [topological_space α] {x : α} : continuous_at id x :=
  continuous.continuous_at continuous_id

theorem continuous_at.iterate {α : Type u_1} [topological_space α] {f : α → α} {x : α} (hf : continuous_at f x) (hx : f x = x) (n : ℕ) : continuous_at (nat.iterate f n) x :=
  nat.rec_on n continuous_at_id
    fun (n : ℕ) (ihn : continuous_at (nat.iterate f n) x) =>
      (fun (this : continuous_at (nat.iterate f n ∘ f) x) => this) (continuous_at.comp (Eq.symm hx ▸ ihn) hf)

theorem continuous_iff_is_closed {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} : continuous f ↔ ∀ (s : set β), is_closed s → is_closed (f ⁻¹' s) := sorry

theorem is_closed.preimage {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} (hf : continuous f) {s : set β} (h : is_closed s) : is_closed (f ⁻¹' s) :=
  iff.mp continuous_iff_is_closed hf s h

theorem continuous_at_iff_ultrafilter {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} {x : α} : continuous_at f x ↔ ∀ (g : ultrafilter α), ↑g ≤ nhds x → filter.tendsto f (↑g) (nhds (f x)) :=
  filter.tendsto_iff_ultrafilter f (nhds x) (nhds (f x))

theorem continuous_iff_ultrafilter {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} : continuous f ↔ ∀ (x : α) (g : ultrafilter α), ↑g ≤ nhds x → filter.tendsto f (↑g) (nhds (f x)) := sorry

/-- A piecewise defined function `if p then f else g` is continuous, if both `f` and `g`
are continuous, and they coincide on the frontier (boundary) of the set `{a | p a}`. -/
theorem continuous_if {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {p : α → Prop} {f : α → β} {g : α → β} {h : (a : α) → Decidable (p a)} (hp : ∀ (a : α), a ∈ frontier (set_of fun (a : α) => p a) → f a = g a) (hf : continuous f) (hg : continuous g) : continuous fun (a : α) => ite (p a) (f a) (g a) := sorry

/-! ### Continuity and partial functions -/

/-- Continuity of a partial function -/
def pcontinuous {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (f : α →. β) :=
  ∀ (s : set β), is_open s → is_open (pfun.preimage f s)

theorem open_dom_of_pcontinuous {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α →. β} (h : pcontinuous f) : is_open (pfun.dom f) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_open (pfun.dom f))) (Eq.symm (pfun.preimage_univ f)))) (h set.univ is_open_univ)

theorem pcontinuous_iff' {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α →. β} : pcontinuous f ↔ ∀ {x : α} {y : β}, y ∈ f x → filter.ptendsto' f (nhds x) (nhds y) := sorry

/-- If a continuous map `f` maps `s` to `t`, then it maps `closure s` to `closure t`. -/
theorem set.maps_to.closure {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {s : set α} {t : set β} {f : α → β} (h : set.maps_to f s t) (hc : continuous f) : set.maps_to f (closure s) (closure t) := sorry

theorem image_closure_subset_closure_image {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} {s : set α} (h : continuous f) : f '' closure s ⊆ closure (f '' s) :=
  set.maps_to.image_subset (set.maps_to.closure (set.maps_to_image f s) h)

theorem map_mem_closure {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {s : set α} {t : set β} {f : α → β} {a : α} (hf : continuous f) (ha : a ∈ closure s) (ht : ∀ (a : α), a ∈ s → f a ∈ t) : f a ∈ closure t :=
  set.maps_to.closure ht hf ha

/-!
### Function with dense range
-/

/-- `f : ι → β` has dense range if its range (image) is a dense subset of β. -/
def dense_range {β : Type u_2} [topological_space β] {κ : Type u_5} (f : κ → β) :=
  dense (set.range f)

/-- A surjective map has dense range. -/
theorem function.surjective.dense_range {β : Type u_2} [topological_space β] {κ : Type u_5} {f : κ → β} (hf : function.surjective f) : dense_range f := sorry

theorem dense_range_iff_closure_range {β : Type u_2} [topological_space β] {κ : Type u_5} {f : κ → β} : dense_range f ↔ closure (set.range f) = set.univ :=
  dense_iff_closure_eq

theorem dense_range.closure_range {β : Type u_2} [topological_space β] {κ : Type u_5} {f : κ → β} (h : dense_range f) : closure (set.range f) = set.univ :=
  dense.closure_eq h

theorem continuous.range_subset_closure_image_dense {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} (hf : continuous f) {s : set α} (hs : dense s) : set.range f ⊆ closure (f '' s) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (set.range f ⊆ closure (f '' s))) (Eq.symm set.image_univ)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (f '' set.univ ⊆ closure (f '' s))) (Eq.symm (dense.closure_eq hs))))
      (image_closure_subset_closure_image hf))

/-- The image of a dense set under a continuous map with dense range is a dense set. -/
theorem dense_range.dense_image {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} (hf' : dense_range f) (hf : continuous f) {s : set α} (hs : dense s) : dense (f '' s) :=
  dense.of_closure (dense.mono (continuous.range_subset_closure_image_dense hf hs) hf')

/-- If a continuous map with dense range maps a dense set to a subset of `t`, then `t` is a dense
set. -/
theorem dense_range.dense_of_maps_to {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} (hf' : dense_range f) (hf : continuous f) {s : set α} (hs : dense s) {t : set β} (ht : set.maps_to f s t) : dense t :=
  dense.mono (set.maps_to.image_subset ht) (dense_range.dense_image hf' hf hs)

/-- Composition of a continuous map with dense range and a function with dense range has dense
range. -/
theorem dense_range.comp {β : Type u_2} {γ : Type u_3} [topological_space β] [topological_space γ] {κ : Type u_5} {g : β → γ} {f : κ → β} (hg : dense_range g) (hf : dense_range f) (cg : continuous g) : dense_range (g ∘ f) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (dense_range (g ∘ f))) (dense_range.equations._eqn_1 (g ∘ f))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (dense (set.range (g ∘ f)))) (set.range_comp g f)))
      (dense_range.dense_image hg cg hf))

theorem dense_range.nonempty_iff {β : Type u_2} [topological_space β] {κ : Type u_5} {f : κ → β} (hf : dense_range f) : Nonempty κ ↔ Nonempty β :=
  iff.trans (iff.symm set.range_nonempty_iff_nonempty) (dense.nonempty_iff hf)

theorem dense_range.nonempty {β : Type u_2} [topological_space β] {κ : Type u_5} {f : κ → β} [h : Nonempty β] (hf : dense_range f) : Nonempty κ :=
  iff.mpr (dense_range.nonempty_iff hf) h

/-- Given a function `f : α → β` with dense range and `b : β`, returns some `a : α`. -/
def dense_range.some {β : Type u_2} [topological_space β] {κ : Type u_5} {f : κ → β} (hf : dense_range f) (b : β) : κ :=
  Classical.choice sorry


/-
Copyright (c) 2018 Simon Hudon. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Simon Hudon

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

namespace Tactic

open Expr List

unsafe def match_fn (fn : expr) : expr → tactic (expr × expr)
  | app (app fn' e₀) e₁ => unify fn fn' $> (e₀, e₁)
  | _ => failed
#align tactic.match_fn tactic.match_fn

unsafe def fill_args : expr → tactic (expr × List expr)
  | pi n bi d b => do
    let v ← mk_meta_var d
    let (r, vs) ← fill_args (b.instantiate_var v)
    return (r, v :: vs)
  | e => return (e, [])
#align tactic.fill_args tactic.fill_args

unsafe def mk_assoc_pattern' (fn : expr) : expr → tactic (Dlist expr)
  | e =>
    (do
        let (e₀, e₁) ← match_fn fn e
        (· ++ ·) <$> mk_assoc_pattern' e₀ <*> mk_assoc_pattern' e₁) <|>
      pure (Dlist.singleton e)
#align tactic.mk_assoc_pattern' tactic.mk_assoc_pattern'

unsafe def mk_assoc_pattern (fn e : expr) : tactic (List expr) :=
  Dlist.toList <$> mk_assoc_pattern' fn e
#align tactic.mk_assoc_pattern tactic.mk_assoc_pattern

unsafe def mk_assoc (fn : expr) : List expr → tactic expr
  | [] => failed
  | [x] => pure x
  | x₀ :: x₁ :: xs => mk_assoc (fn x₀ x₁ :: xs)
#align tactic.mk_assoc tactic.mk_assoc

unsafe def chain_eq_trans : List expr → tactic expr
  | [] => to_expr ``(rfl)
  | [e] => pure e
  | e :: es => chain_eq_trans es >>= mk_eq_trans e
#align tactic.chain_eq_trans tactic.chain_eq_trans

unsafe def unify_prefix : List expr → List expr → tactic Unit
  | [], _ => pure ()
  | _, [] => failed
  | x :: xs, y :: ys => unify x y >> unify_prefix xs ys
#align tactic.unify_prefix tactic.unify_prefix

unsafe def match_assoc_pattern' (p : List expr) : List expr → tactic (List expr × List expr)
  | es =>
    unify_prefix p es $> ([], es.drop p.length) <|>
      match es with
      | [] => failed
      | x :: xs => Prod.map (cons x) id <$> match_assoc_pattern' xs
#align tactic.match_assoc_pattern' tactic.match_assoc_pattern'

unsafe def match_assoc_pattern (fn p e : expr) : tactic (List expr × List expr) := do
  let p' ← mk_assoc_pattern fn p
  let e' ← mk_assoc_pattern fn e
  match_assoc_pattern' p' e'
#align tactic.match_assoc_pattern tactic.match_assoc_pattern

/-- Tag for proofs generated by `assoc_rewrite`. -/
def IdTag.assocProof :=
  ()
#align tactic.id_tag.assoc_proof Tactic.IdTag.assocProof

unsafe def mk_eq_proof (fn : expr) (e₀ e₁ : List expr) (p : expr) : tactic (expr × expr × expr) :=
  do
  let (l, r) ← infer_type p >>= match_eq
  if e₀ ∧ e₁ then pure (l, r, p)
    else do
      let l' ← mk_assoc fn (e₀ ++ [l] ++ e₁)
      let r' ← mk_assoc fn (e₀ ++ [r] ++ e₁)
      let t ← infer_type l'
      let v ← mk_local_def `x t
      let e ← mk_assoc fn (e₀ ++ [v] ++ e₁)
      let p ← mk_congr_arg (e [v]) p
      let p' ← mk_app `` Eq [l', r']
      let p' := mk_tagged_proof p' p `` id_tag.assoc_proof
      return (l', r', p')
#align tactic.mk_eq_proof tactic.mk_eq_proof

unsafe def assoc_root (fn assoc : expr) : expr → tactic (expr × expr)
  | e =>
    (do
        let (e₀, e₁) ← match_fn fn e
        let (ea, eb) ← match_fn fn e₁
        let e' := fn (fn e₀ ea) eb
        let p' ← mk_eq_symm (assoc e₀ ea eb)
        let (e'', p'') ← assoc_root e'
        Prod.mk e'' <$> mk_eq_trans p' p'') <|>
      Prod.mk e <$> mk_eq_refl e
#align tactic.assoc_root tactic.assoc_root

unsafe def assoc_refl' (fn assoc : expr) : expr → expr → tactic expr
  | l, r =>
    is_def_eq l r >> mk_eq_refl l <|> do
      let (l', l_p) ← assoc_root fn assoc l <|> fail "A"
      let (el₀, el₁) ← match_fn fn l' <|> fail "B"
      let (r', r_p) ← assoc_root fn assoc r <|> fail "C"
      let (er₀, er₁) ← match_fn fn r' <|> fail "D"
      let p₀ ← assoc_refl' el₀ er₀
      let p₁ ← is_def_eq el₁ er₁ >> mk_eq_refl el₁
      let f_eq ← mk_congr_arg fn p₀ <|> fail "G"
      let p' ← mk_congr f_eq p₁ <|> fail "H"
      let r_p' ← mk_eq_symm r_p
      chain_eq_trans [l_p, p', r_p']
#align tactic.assoc_refl' tactic.assoc_refl'

unsafe def assoc_refl (fn : expr) : tactic Unit := do
  let (l, r) ← target >>= match_eq
  let assoc ← mk_mapp `` IsAssociative.assoc [none, fn, none] <|> fail f! "{fn} is not associative"
  assoc_refl' fn assoc l r >>= tactic.exact
#align tactic.assoc_refl tactic.assoc_refl

unsafe def flatten (fn assoc e : expr) : tactic (expr × expr) := do
  let ls ← mk_assoc_pattern fn e
  let e' ← mk_assoc fn ls
  let p ← assoc_refl' fn assoc e e'
  return (e', p)
#align tactic.flatten tactic.flatten

unsafe def assoc_rewrite_intl (assoc h e : expr) : tactic (expr × expr) := do
  let t ← infer_type h
  let (lhs, rhs) ← match_eq t
  let fn := lhs.app_fn.app_fn
  let (l, r) ← match_assoc_pattern fn lhs e
  let (lhs', rhs', h') ← mk_eq_proof fn l r h
  let e_p ← assoc_refl' fn assoc e lhs'
  let (rhs'', rhs_p) ← flatten fn assoc rhs'
  let final_p ← chain_eq_trans [e_p, h', rhs_p]
  return (rhs'', final_p)
#align tactic.assoc_rewrite_intl tactic.assoc_rewrite_intl

-- TODO(Simon): visit expressions built of `fn` nested inside other such expressions:
-- e.g.: x + f (a + b + c) + y should generate two rewrite candidates
unsafe def enum_assoc_subexpr' (fn : expr) : expr → tactic (Dlist expr)
  | e =>
    Dlist.singleton e <$ (match_fn fn e >> guard ¬e.has_var) <|>
      expr.mfoldl (fun es e' => (· ++ es) <$> enum_assoc_subexpr' e') Dlist.empty e
#align tactic.enum_assoc_subexpr' tactic.enum_assoc_subexpr'

unsafe def enum_assoc_subexpr (fn e : expr) : tactic (List expr) :=
  Dlist.toList <$> enum_assoc_subexpr' fn e
#align tactic.enum_assoc_subexpr tactic.enum_assoc_subexpr

unsafe def mk_assoc_instance (fn : expr) : tactic expr := do
  let t ← mk_mapp `` IsAssociative [none, fn]
  let inst ←
    Prod.snd <$> solve_aux t assumption <|>
        mk_instance t >>= assertv `_inst t <|> fail f! "{fn} is not associative"
  mk_mapp `` IsAssociative.assoc [none, fn, inst]
#align tactic.mk_assoc_instance tactic.mk_assoc_instance

unsafe def assoc_rewrite (h e : expr) (opt_assoc : Option expr := none) :
    tactic (expr × expr × List expr) := do
  let (t, vs) ← infer_type h >>= fill_args
  let (lhs, rhs) ← match_eq t
  let fn := lhs.app_fn.app_fn
  let es ← enum_assoc_subexpr fn e
  let assoc ←
    match opt_assoc with
      | none => mk_assoc_instance fn
      | some assoc => pure assoc
  let (_, p) ← firstM (assoc_rewrite_intl assoc <| h.mk_app vs) es
  let (e', p', _) ← tactic.rewrite p e
  pure (e', p', vs)
#align tactic.assoc_rewrite tactic.assoc_rewrite

unsafe def assoc_rewrite_target (h : expr) (opt_assoc : Option expr := none) : tactic Unit := do
  let tgt ← target
  let (tgt', p, _) ← assoc_rewrite h tgt opt_assoc
  replace_target tgt' p
#align tactic.assoc_rewrite_target tactic.assoc_rewrite_target

unsafe def assoc_rewrite_hyp (h hyp : expr) (opt_assoc : Option expr := none) : tactic expr := do
  let tgt ← infer_type hyp
  let (tgt', p, _) ← assoc_rewrite h tgt opt_assoc
  replace_hyp hyp tgt' p
#align tactic.assoc_rewrite_hyp tactic.assoc_rewrite_hyp

namespace Interactive

/- ./././Mathport/Syntax/Translate/Tactic/Mathlib/Core.lean:38:34: unsupported: setup_tactic_parser -/
/- ./././Mathport/Syntax/Translate/Expr.lean:207:4: warning: unsupported notation `eq_lemmas -/
private unsafe def assoc_rw_goal (rs : List rw_rule) : tactic Unit :=
  rs.mapM' fun r => do
    save_info r
    let eq_lemmas ← get_rule_eqn_lemmas r
    orelse'
        (do
          let e ← to_expr' r
          assoc_rewrite_target e)
        (eq_lemmas fun n => do
          let e ← mk_const n
          assoc_rewrite_target e)
        (eq_lemmas eq_lemmas.empty)
#align tactic.interactive.assoc_rw_goal tactic.interactive.assoc_rw_goal

private unsafe def uses_hyp (e : expr) (h : expr) : Bool :=
  e.fold false fun t _ r => r || t = h
#align tactic.interactive.uses_hyp tactic.interactive.uses_hyp

/- ./././Mathport/Syntax/Translate/Expr.lean:207:4: warning: unsupported notation `eq_lemmas -/
private unsafe def assoc_rw_hyp : List rw_rule → expr → tactic Unit
  | [], hyp => skip
  | r :: rs, hyp => do
    save_info r
    let eq_lemmas ← get_rule_eqn_lemmas r
    orelse'
        (do
          let e ← to_expr' r
          when ¬uses_hyp e hyp <| assoc_rewrite_hyp e hyp >>= assoc_rw_hyp rs)
        (eq_lemmas fun n => do
          let e ← mk_const n
          assoc_rewrite_hyp e hyp >>= assoc_rw_hyp rs)
        (eq_lemmas eq_lemmas.empty)
#align tactic.interactive.assoc_rw_hyp tactic.interactive.assoc_rw_hyp

private unsafe def assoc_rw_core (rs : parse rw_rules) (loca : parse location) : tactic Unit :=
  ((match loca with
      | loc.wildcard => loca.try_apply (assoc_rw_hyp rs.rules) (assoc_rw_goal rs.rules)
      | _ => loca.apply (assoc_rw_hyp rs.rules) (assoc_rw_goal rs.rules)) >>
      try reflexivity) >>
    try (returnopt rs.end_pos >>= save_info)
#align tactic.interactive.assoc_rw_core tactic.interactive.assoc_rw_core

/-- `assoc_rewrite [h₀,← h₁] at ⊢ h₂` behaves like `rewrite [h₀,← h₁] at ⊢ h₂`
with the exception that associativity is used implicitly to make rewriting
possible.

It works for any function `f` for which an `is_associative f` instance can be found.

```
example {α : Type*} (f : α → α → α) [is_associative α f] (a b c d x : α) :
  let infix ` ~ ` := f in
  b ~ c = x → (a ~ b ~ c ~ d) = (a ~ x ~ d) :=
begin
  intro h,
  assoc_rw h,
end
```
-/
unsafe def assoc_rewrite (q : parse rw_rules) (l : parse location) : tactic Unit :=
  propagate_tags (assoc_rw_core q l)
#align tactic.interactive.assoc_rewrite tactic.interactive.assoc_rewrite

/-- synonym for `assoc_rewrite` -/
unsafe def assoc_rw (q : parse rw_rules) (l : parse location) : tactic Unit :=
  assoc_rewrite q l
#align tactic.interactive.assoc_rw tactic.interactive.assoc_rw

add_tactic_doc
  { Name := "assoc_rewrite"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.assoc_rewrite, `tactic.interactive.assoc_rw]
    tags := ["rewriting"]
    inheritDescriptionFrom := `tactic.interactive.assoc_rewrite }

end Interactive

end Tactic


/-
Copyright (c) 2018 Simon Hudon. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Simon Hudon, Jesse Michael Han
-/
import tactic.rcases
import data.sum
import logic.function.basic

universes u₁ u₂

open interactive interactive.types
section ext
open lean.parser nat tactic

/--
`derive_struct_ext_lemma n` generates two extensionality lemmas based on
the equality of all non-propositional projections.

On the following:

```lean
@[ext]
structure foo (α : Type*) :=
(x y : ℕ)
(z : {z // z < x})
(k : α)
(h : x < y)
```

`derive_struct_lemma` generates:

```lean
lemma foo.ext : ∀ {α : Type u_1} (x y : foo α),
  x.x = y.x → x.y = y.y → x.z == y.z → x.k = y.k → x = y
lemma foo.ext_iff : ∀ {α : Type u_1} (x y : foo α),
  x = y ↔ x.x = y.x ∧ x.y = y.y ∧ x.z == y.z ∧ x.k = y.k
```

-/
meta def derive_struct_ext_lemma (n : name) : tactic name :=
do e ← get_env,
   fs ← e.structure_fields n,
   d ← get_decl n,
   n ← resolve_constant n,
   let r := @expr.const tt n $ d.univ_params.map level.param,
   (args,_) ← infer_type r >>= open_pis,
   let args := args.map expr.to_implicit_local_const,
   let t := r.mk_app args,
   x ← mk_local_def `x t,
   y ← mk_local_def `y t,
   let args_x := args ++ [x],
   let args_y := args ++ [y],
   bs ← fs.mmap $ λ f,
     do { d ← get_decl (n ++ f),
          let a := @expr.const tt (n ++ f) $ d.univ_params.map level.param,
          t ← infer_type a,
          s ← infer_type t,

          if s ≠ `(Prop)
            then do
              let x := a.mk_app args_x,
              let y := a.mk_app args_y,
              t ← infer_type x,
              t' ← infer_type y,
              some <$> if t = t'
                then mk_app `eq [x,y] >>= mk_local_def `h
                else mk_mapp `heq [none,x,none,y] >>= mk_local_def `h
            else pure none },
   let bs := bs.filter_map id,
   eq_t ← mk_app `eq [x,y],
   t ← pis (args ++ [x,y] ++ bs) eq_t,
   pr ← run_async $
     do { (_,pr) ← solve_aux t (do
          { args ← intron args.length,
            x ← intro1, y ← intro1,
            cases x, cases y,
            bs.mmap' (λ _,
              do e ← intro1,
                 cases e),
            reflexivity }),
          instantiate_mvars pr },
   let decl_n := n <.> "ext",
   add_decl (declaration.thm decl_n d.univ_params t pr),
   bs ← bs.mmap infer_type,
   let rhs := expr.mk_and_lst bs,
   iff_t ← mk_app `iff [eq_t,rhs],
   t ← pis (args ++ [x,y]) iff_t,
   pr ← run_async $
     do { (_,pr) ← solve_aux t $ do
          { args ← intron args.length,
            x ← intro1, y ← intro1,
            cases x, cases y,
            split,
            solve1 $ do
            { h ← intro1, hs ← injection h, subst_vars,
              repeat (refine ``( and.intro _ _ ) >> reflexivity ),
              done <|> reflexivity },
            solve1 $ do
            { repeat (do refine ``(and_imp.mpr _),
                         h ← intro1, cases h, skip ),
              h ← intro1, cases h,
              reflexivity }  },
          instantiate_mvars pr },
   add_decl (declaration.thm (n <.> "ext_iff") d.univ_params t pr),
   pure decl_n

meta def get_ext_subject : expr → tactic name
| (expr.pi n bi d b) :=
  do v  ← mk_local' n bi d,
     b' ← whnf $ b.instantiate_var v,
     get_ext_subject b'
| (expr.app _ e) :=
  do t ← infer_type e >>= instantiate_mvars >>= head_beta,
     if t.get_app_fn.is_constant then
       pure $ t.get_app_fn.const_name
     else if t.is_pi then
       pure $ name.mk_numeral 0 name.anonymous
     else if t.is_sort then
       pure $ name.mk_numeral 1 name.anonymous
     else do
       t ← pp t,
       fail format!"only constants and Pi types are supported: {t}"
| e := fail format!"Only expressions of the form `_ → _ → ... → R ... e are supported: {e}"

open native

@[reducible] def ext_param_type := option name ⊕ option name

meta def opt_minus : lean.parser (option name → ext_param_type) :=
sum.inl <$ tk "-" <|> pure sum.inr

meta def ext_param :=
opt_minus <*> ( name.mk_numeral 0 name.anonymous <$ brackets "(" ")" (tk "→" <|> tk "->") <|>
                none <$  tk "*" <|>
                some <$> ident )

meta def saturate_fun : name → tactic expr
| (name.mk_numeral 0 name.anonymous) :=
do v₀ ← mk_mvar,
   v₁ ← mk_mvar,
   return $ v₀.imp v₁
| (name.mk_numeral 1 name.anonymous) :=
do u ← mk_meta_univ,
   pure $ expr.sort u
| n :=
do e ← resolve_constant n >>= mk_const,
   a ← get_arity e,
   e.mk_app <$> (list.iota a).mmap (λ _, mk_mvar)

meta def equiv_type_constr (n n' : name) : tactic unit :=
do e  ← saturate_fun n,
   e' ← saturate_fun n',
   unify e e' <|> fail format!"{n} and {n'} are not definitionally equal types"

section performance_hack
/--
For performance reasons, it is inadvisable to use `user_attribute.get_param`.
The parameter is stored as a reflected expression.  When calling `get_param`,
the stored parameter is evaluated using `eval_expr`, which first compiles the
expression into VM bytecode. The unevaluated expression is available using
`user_attribute.get_param_untyped`.

In particular, `user_attribute.get_param` MUST NEVER BE USED in the
implementation of an attribute cache. This is because calling `eval_expr`
disables the attribute cache.

There are several possible workarounds:
 1. Set a different attribute depending on the parameter.
 2. Use your own evaluation function instead of `eval_expr`, such as e.g. `expr.to_nat`.
 3. Write your own `has_reflect Param` instance (using a more efficient serialization format).
   The `user_attribute` code unfortunately checks whether the expression has the correct type,
   but you can use `` `(id %%e : Param) `` to pretend that your expression `e` has type `Param`.
-/
library_note "user attribute parameters"

/-!
For performance reasons, the parameters of the `@[ext]` attribute are stored
in two auxiliary attributes:
```lean
attribute [ext [thunk]] funext

-- is turned into
attribute [_ext_core (@id name @funext)] thunk
attribute [_ext_lemma_core] funext
```

see Note [user attribute parameters]
-/

local attribute [semireducible] reflected

local attribute [instance, priority 9000]
private meta def hacky_name_reflect : has_reflect name :=
λ n, `(id %%(expr.const n []) : name)

@[user_attribute]
private meta def ext_attr_core : user_attribute (name_map name) name :=
{ name := `_ext_core,
  descr := "(internal attribute used by ext)",
  cache_cfg := {
    dependencies := [],
    mk_cache := λ ns, do
      attrs ← ns.mmap (λ n, do
        ext_l ← ext_attr_core.get_param_untyped n,
        pure (n, ext_l.app_arg.const_name)),
      pure $ rb_map.of_list attrs },
  parser := failure }

end performance_hack

/-- Private attribute used to tag extensionality lemmas. -/
@[user_attribute]
private meta def ext_lemma_attr_core : user_attribute :=
{ name := `_ext_lemma_core,
  descr := "(internal attribute used by ext)",
  parser := failure }

/--
Returns the extensionality lemmas in the environment, as a map from structure
name to lemma name.
-/
meta def get_ext_lemmas : tactic (name_map name) :=
ext_attr_core.get_cache

/--
Returns the extensionality lemmas in the environment, as a list of lemma names.
-/
meta def get_ext_lemma_names : tactic (list name) :=
attribute.get_instances ext_lemma_attr_core.name

/--
Tag lemmas of the form:

```lean
@[ext]
lemma my_collection.ext (a b : my_collection)
  (h : ∀ x, a.lookup x = b.lookup y) :
  a = b := ...
```

The attribute indexes extensionality lemma using the type of the
objects (i.e. `my_collection`) which it gets from the statement of
the lemma.  In some cases, the same lemma can be used to state the
extensionality of multiple types that are definitionally equivalent.

```lean
attribute [ext [(→),thunk,stream]] funext
```

Those parameters are cumulative. The following are equivalent:

```lean
attribute [ext [(→),thunk]] funext
attribute [ext [stream]] funext
```
and
```lean
attribute [ext [(→),thunk,stream]] funext
```

One removes type names from the list for one lemma with:
```lean
attribute [ext [-stream,-thunk]] funext
```

Also, the following:

```lean
@[ext]
lemma my_collection.ext (a b : my_collection)
  (h : ∀ x, a.lookup x = b.lookup y) :
  a = b := ...
```

is equivalent to

```lean
@[ext *]
lemma my_collection.ext (a b : my_collection)
  (h : ∀ x, a.lookup x = b.lookup y) :
  a = b := ...
```

This allows us specify type synonyms along with the type
that is referred to in the lemma statement.

```lean
@[ext [*,my_type_synonym]]
lemma my_collection.ext (a b : my_collection)
  (h : ∀ x, a.lookup x = b.lookup y) :
  a = b := ...
```

The `ext` attribute can be applied to a structure to generate its extensionality lemmas:

```lean
@[ext]
structure foo (α : Type*) :=
(x y : ℕ)
(z : {z // z < x})
(k : α)
(h : x < y)
```

will generate:

```lean
@[ext] lemma foo.ext : ∀ {α : Type u_1} (x y : foo α),
x.x = y.x → x.y = y.y → x.z == y.z → x.k = y.k → x = y
lemma foo.ext_iff : ∀ {α : Type u_1} (x y : foo α),
x = y ↔ x.x = y.x ∧ x.y = y.y ∧ x.z == y.z ∧ x.k = y.k
```

-/
@[user_attribute]
meta def extensional_attribute : user_attribute unit (list ext_param_type) :=
{ name := `ext,
  descr := "lemmas usable by `ext` tactic",
  parser := pure <$> ext_param <|> list_of ext_param <|> pure [],
  after_set := some $ λ n prio b,
    do ls ← extensional_attribute.get_param n,
       e ← get_env,
       n ← if (e.structure_fields n).is_some
         then derive_struct_ext_lemma n
         else pure n,
       s ← mk_const n >>= infer_type >>= get_ext_subject,
       let (rs,ls'') := if ls.empty
                           then ([],[s])
                           else ls.partition_map (sum.map (flip option.get_or_else s)
                                                    (flip option.get_or_else s)),
       ls''.mmap' (equiv_type_constr s),
       ls' ← get_ext_lemmas,
       let l := ls'' ∪ (ls'.to_list.filter $ λ l, prod.snd l = n).map prod.fst \ rs,
       l.mmap' $ λ l, do
        ext_attr_core.set l n b prio,
        ext_lemma_attr_core.set n () b prio }

add_tactic_doc
{ name                     := "ext",
  category                 := doc_category.attr,
  decl_names               := [`extensional_attribute],
  tags                     := ["rewrite", "logic"] }

/--
When possible, `ext` lemmas are stated without a full set of arguments. As an example, for bundled
homs `f`, `g`, and `of`, `f.comp of = g.comp of → f = g` is a better `ext` lemma than
`(∀ x, f (of x) = g (of x)) → f = g`, as the former allows a second type-specific extensionality
lemmas to be applied to `f.comp of = g.comp of`.
If the domain of `of` is `ℕ` or `ℤ` and `of` is a `ring_hom`, such a lemma could then make the goal
`f (of 1) = g (of 1)`.

For bundled morphisms, there is a `ext` lemma that always applies of the form
`(∀ x, ⇑f x = ⇑g x) → f = g`. When adding type-specific `ext` lemmas like the one above, we want
these to be tried first. This happens automatically since the type-specific lemmas are inevitably
defined later.
-/
library_note "partially-applied ext lemmas"

-- We mark some existing extensionality lemmas.
attribute [ext] array.ext propext function.hfunext
attribute [ext [(→),thunk]] _root_.funext

-- We create some extensionality lemmas for existing structures.
attribute [ext] ulift

namespace plift
-- This is stronger than the one generated automatically.
@[ext] lemma ext {P : Prop} (a b : plift P) : a = b :=
begin
  cases a, cases b, refl
end
end plift

-- Conservatively, we'll only add extensionality lemmas for `has_*` structures
-- as they become useful.
attribute [ext] has_zero

@[ext] lemma unit.ext {x y : unit} : x = y := by { cases x, cases y, refl, }
@[ext] lemma punit.ext {x y : punit} : x = y := by { cases x, cases y, refl, }

namespace tactic

/-- Helper structure for `ext` and `ext1`. `lemmas` keeps track of extensionality lemmas
  applied so far. -/
meta structure ext_state : Type :=
(patts : list rcases_patt := [])
(trace_msg : list string := [])
(fuel : option ℕ := none)

/-- Helper function for `try_intros`. Additionally populates the `trace_msg` field
  of `ext_state`. -/
private meta def try_intros_core : state_t ext_state tactic unit :=
do ⟨patts, trace_msg, fuel⟩ ← get,
   match patts with
   | [] := do { es ← state_t.lift intros, when (es.length > 0) $ do
                let msg := "intros " ++ (" ".intercalate (es.map (λ e, e.local_pp_name.to_string))),
                modify (λ ⟨patts, trace_msg, fuel⟩, ⟨patts, trace_msg ++ [msg], fuel⟩) }
             <|> pure ()
   | (x::xs) :=
     do tgt ← state_t.lift (target >>= whnf),
        when tgt.is_pi $
          do state_t.lift (rintro [x]),
             msg ← state_t.lift (((++) "rintro ") <$> format.to_string <$> x.format ff),
             modify (λ ⟨_, trace_msg, fuel⟩, ⟨xs, trace_msg ++ [msg], fuel⟩),
             try_intros_core
   end

/-- Try to introduce as many arguments as possible, using the given patterns to destruct the
  introduced variables. Returns the unused patterns. -/
meta def try_intros (patts : list rcases_patt) : tactic (list rcases_patt) :=
let σ := ext_state.mk patts [] none in
  (ext_state.patts ∘ prod.snd) <$> state_t.run try_intros_core σ

/-- Apply one extensionality lemma, and destruct the arguments using the patterns
  in the ext_state. -/
meta def ext1_core (cfg : apply_cfg := {}) : state_t ext_state tactic unit :=
do ⟨patts, trace_msg, _⟩ ← get,
   (new_msgs) ← state_t.lift $ focus1 $
   do { m ← get_ext_lemmas,
         subject ← (target >>= get_ext_subject),
         new_trace_msg ←
           do { rule ← (m.find subject),
                (applyc rule cfg),
                pure (["apply " ++ rule.to_string]) } <|>
             do { ls ← get_ext_lemma_names,
                  let nms := ls.map name.to_string,
                  rule ← (ls.any_of (λ n, applyc n cfg *> pure n)),
                  pure (["apply " ++ rule.to_string]) } <|>
               (fail format!"no applicable extensionality rule found for {subject}"),
         pure new_trace_msg },
    modify (λ ⟨patts, trace_msg, fuel⟩, ⟨patts, trace_msg ++ new_msgs, fuel⟩),
    try_intros_core

/-- Apply multiple extensionality lemmas, destructing the arguments using the given patterns. -/
meta def ext_core (cfg : apply_cfg := {}) : state_t ext_state tactic unit :=
do acc@⟨_, _, fuel⟩ ← get,
   match fuel with
   | (some 0) := pure ()
   | n        := do { ext1_core cfg,
                      modify (λ ⟨patts, lemmas, _⟩, ⟨patts, lemmas, nat.pred <$> n⟩),
                      ext_core <|> pure () }
   end

/-- Apply one extensionality lemma, and destruct the arguments using the given patterns.
  Returns the unused patterns. -/
meta def ext1 (xs : list rcases_patt) (cfg : apply_cfg := {})
  (trace : bool := ff) : tactic (list rcases_patt) :=
do ⟨_, σ⟩ ← state_t.run (ext1_core cfg) {patts := xs},
   when trace $ tactic.trace $ "Try this: " ++  ", ".intercalate σ.trace_msg,
   pure σ.patts

/-- Apply multiple extensionality lemmas, destructing the arguments using the given patterns.
  `ext ps (some n)` applies at most `n` extensionality lemmas. Returns the unused patterns. -/
meta def ext (xs : list rcases_patt) (fuel : option ℕ) (cfg : apply_cfg := {})
  (trace : bool := ff): tactic (list rcases_patt) :=
do ⟨_, σ⟩ ← state_t.run (ext_core cfg) {patts := xs, fuel := fuel},
   when trace $ tactic.trace $ "Try this: " ++  ", ".intercalate σ.trace_msg,
   pure σ.patts

local postfix `?`:9001 := optional
local postfix *:9001 := many

/--
`ext1 id` selects and apply one extensionality lemma (with attribute
`ext`), using `id`, if provided, to name a local constant
introduced by the lemma. If `id` is omitted, the local constant is
named automatically, as per `intro`. Placing a `?` after `ext1`
 (e.g. `ext1? i ⟨a,b⟩ : 3`) will display a sequence of tactic
applications that can replace the call to `ext1`.
-/
meta def interactive.ext1 (trace : parse (tk "?")?)
  (xs : parse (rcases_patt_parse tt)*) : tactic unit :=
ext1 xs {} trace.is_some $> ()

/--
- `ext` applies as many extensionality lemmas as possible;
- `ext ids`, with `ids` a list of identifiers, finds extentionality and applies them
  until it runs out of identifiers in `ids` to name the local constants.
- `ext` can also be given an `rcases` pattern in place of an identifier.
  This will destruct the introduced local constant.
- Placing a `?` after `ext` (e.g. `ext? i ⟨a,b⟩ : 3`) will display
  a sequence of tactic applications that can replace the call to `ext`.

When trying to prove:

```lean
α β : Type,
f g : α → set β
⊢ f = g
```

applying `ext x y` yields:

```lean
α β : Type,
f g : α → set β,
x : α,
y : β
⊢ y ∈ f x ↔ y ∈ f x
```

by applying functional extensionality and set extensionality.

When trying to prove:

```lean
α β γ : Type
f g : α × β → γ
⊢ f = g
```

applying `ext ⟨a, b⟩` yields:

```lean
α β γ : Type,
f g : α × β → γ,
a : α,
b : β
⊢ f (a, b) = g (a, b)
```

by applying functional extensionality and destructing the introduced pair.

In the previous example, applying `ext? ⟨a,b⟩` will produce the trace message:

```lean
Try this: apply funext, rintro ⟨a, b⟩
```

A maximum depth can be provided with `ext x y z : 3`.
-/
meta def interactive.ext :
  (parse $ (tk "?")?) → parse (rcases_patt_parse tt)* → parse (tk ":" *> small_nat)? → tactic unit
 | trace [] (some n)  := iterate_range 1 n (ext1 [] {} trace.is_some $> ())
 | trace [] none      := repeat1 (ext1 [] {} trace.is_some $> ())
 | trace xs n         := ext xs n {} trace.is_some $> ()

/--
* `ext1 id` selects and apply one extensionality lemma (with
  attribute `ext`), using `id`, if provided, to name a
  local constant introduced by the lemma. If `id` is omitted, the
  local constant is named automatically, as per `intro`.

* `ext` applies as many extensionality lemmas as possible;
* `ext ids`, with `ids` a list of identifiers, finds extensionality lemmas
  and applies them until it runs out of identifiers in `ids` to name
  the local constants.
* `ext` can also be given an `rcases` pattern in place of an identifier.
  This will destruct the introduced local constant.
- Placing a `?` after `ext`/`ext1` (e.g. `ext? i ⟨a,b⟩ : 3`) will display
  a sequence of tactic applications that can replace the call to `ext`/`ext1`.

When trying to prove:

```lean
α β : Type,
f g : α → set β
⊢ f = g
```

applying `ext x y` yields:

```lean
α β : Type,
f g : α → set β,
x : α,
y : β
⊢ y ∈ f x ↔ y ∈ g x
```
by applying functional extensionality and set extensionality.

When trying to prove:

```lean
α β γ : Type
f g : α × β → γ
⊢ f = g
```

applying `ext ⟨a, b⟩` yields:

```lean
α β γ : Type,
f g : α × β → γ,
a : α,
b : β
⊢ f (a, b) = g (a, b)
```

by applying functional extensionality and destructing the introduced pair.

In the previous example, applying `ext? ⟨a,b⟩` will produce the trace message:

```lean
Try this: apply funext, rintro ⟨a, b⟩
```

A maximum depth can be provided with `ext x y z : 3`.
-/
add_tactic_doc
{ name        := "ext1 / ext",
  category    := doc_category.tactic,
  decl_names  := [`tactic.interactive.ext1, `tactic.interactive.ext],
  tags        := ["rewriting", "logic"] }

end tactic
end ext