/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura

! This file was ported from Lean 3 source module init.meta.simp_tactic
! leanprover-community/mathlib commit 4a03bdeb31b3688c31d02d7ff8e0ff2e5d6174db
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
prelude
import Leanbin.Init.Meta.Tactic
import Leanbin.Init.Meta.Attribute
import Leanbin.Init.Meta.ConstructorTactic
import Leanbin.Init.Meta.RelationTactics
import Leanbin.Init.Meta.Occurrences
import Leanbin.Init.Data.Option.Basic

open Tactic

def Tactic.IdTag.simp : Unit :=
  ()
#align tactic.id_tag.simp Tactic.IdTag.simp

def Simp.defaultMaxSteps :=
  10000000
#align simp.default_max_steps Simp.defaultMaxSteps

/-- Prefix the given `attr_name` with `"simp_attr"`. -/
unsafe axiom mk_simp_attr_decl_name (attr_name : Name) : Name
#align mk_simp_attr_decl_name mk_simp_attr_decl_name

/-- Simp lemmas are used by the "simplifier" family of tactics.
`simp_lemmas` is essentially a pair of tables `rb_map (expr_type × name) (priority_list simp_lemma)`.
One of the tables is for congruences and one is for everything else.
An individual simp lemma is:
- A kind which can be `Refl`, `Simp` or `Congr`.
- A pair of `expr`s `l ~> r`. The rb map is indexed by the name of `get_app_fn(l)`.
- A proof that `l = r` or `l ↔ r`.
- A list of the metavariables that must be filled before the proof can be applied.
- A priority number
-/
unsafe axiom simp_lemmas : Type
#align simp_lemmas simp_lemmas

/-- Make a new table of simp lemmas -/
unsafe axiom simp_lemmas.mk : simp_lemmas
#align simp_lemmas.mk simp_lemmas.mk

/-- Merge the simp_lemma tables. -/
unsafe axiom simp_lemmas.join : simp_lemmas → simp_lemmas → simp_lemmas
#align simp_lemmas.join simp_lemmas.join

/-- Remove the given lemmas from the table. Use the names of the lemmas. -/
unsafe axiom simp_lemmas.erase : simp_lemmas → List Name → simp_lemmas
#align simp_lemmas.erase simp_lemmas.erase

/-- Remove all simp lemmas from the table. -/
unsafe axiom simp_lemmas.erase_simp_lemmas : simp_lemmas → simp_lemmas
#align simp_lemmas.erase_simp_lemmas simp_lemmas.erase_simp_lemmas

/-- Makes the default simp_lemmas table which is composed of all lemmas tagged with `simp`. -/
unsafe axiom simp_lemmas.mk_default : tactic simp_lemmas
#align simp_lemmas.mk_default simp_lemmas.mk_default

/--
Add a simplification lemma by an expression `p`. Some conditions on `p` must hold for it to be added, see list below.
If your lemma is not being added, you can see the reasons by setting `set_option trace.simp_lemmas true`.

- `p` must have the type `Π (h₁ : _) ... (hₙ : _), LHS ~ RHS` for some reflexive, transitive relation (usually `=`).
- Any of the hypotheses `hᵢ` should either be present in `LHS` or otherwise a `Prop` or a typeclass instance.
- `LHS` should not occur within `RHS`.
- `LHS` should not occur within a hypothesis `hᵢ`.

 -/
unsafe axiom simp_lemmas.add (s : simp_lemmas) (e : expr) (symm : Bool := False) :
    tactic simp_lemmas
#align simp_lemmas.add simp_lemmas.add

/--
Add a simplification lemma by it's declaration name. See `simp_lemmas.add` for more information.-/
unsafe axiom simp_lemmas.add_simp (s : simp_lemmas) (id : Name) (symm : Bool := False) :
    tactic simp_lemmas
#align simp_lemmas.add_simp simp_lemmas.add_simp

/-- Adds a congruence simp lemma to simp_lemmas.
A congruence simp lemma is a lemma that breaks the simplification down into separate problems.
For example, to simplify `a ∧ b` to `c ∧ d`, we should try to simp `a` to `c` and `b` to `d`.
For examples of congruence simp lemmas look for lemmas with the `@[congr]` attribute.
```lean
lemma if_simp_congr ... (h_c : b ↔ c) (h_t : x = u) (h_e : y = v) : ite b x y = ite c u v := ...
lemma imp_congr_right (h : a → (b ↔ c)) : (a → b) ↔ (a → c) := ...
lemma and_congr (h₁ : a ↔ c) (h₂ : b ↔ d) : (a ∧ b) ↔ (c ∧ d) := ...
```
-/
unsafe axiom simp_lemmas.add_congr : simp_lemmas → Name → tactic simp_lemmas
#align simp_lemmas.add_congr simp_lemmas.add_congr

/-- Add expressions to a set of simp lemmas using `simp_lemmas.add`.

  This is the new version of `simp_lemmas.append`,
  which also allows you to set the `symm` flag.
-/
unsafe def simp_lemmas.append_with_symm (s : simp_lemmas) (hs : List (expr × Bool)) :
    tactic simp_lemmas :=
  hs.foldlM (fun s h => simp_lemmas.add s h.fst h.snd) s
#align simp_lemmas.append_with_symm simp_lemmas.append_with_symm

/-- Add expressions to a set of simp lemmas using `simp_lemmas.add`.

  This is the backwards-compatibility version of `simp_lemmas.append_with_symm`,
  and sets all `symm` flags to `ff`.
-/
unsafe def simp_lemmas.append (s : simp_lemmas) (hs : List expr) : tactic simp_lemmas :=
  hs.foldlM (fun s h => simp_lemmas.add s h false) s
#align simp_lemmas.append simp_lemmas.append

/-- `simp_lemmas.rewrite s e prove R` apply a simplification lemma from 's'

   - 'e'     is the expression to be "simplified"
   - 'prove' is used to discharge proof obligations.
   - 'r'     is the equivalence relation being used (e.g., 'eq', 'iff')
   - 'md'    is the transparency; how aggresively should the simplifier perform reductions.

   Result (new_e, pr) is the new expression 'new_e' and a proof (pr : e R new_e) -/
unsafe axiom simp_lemmas.rewrite (s : simp_lemmas) (e : expr) (prove : tactic Unit := failed)
    (r : Name := `eq) (md := reducible) : tactic (expr × expr)
#align simp_lemmas.rewrite simp_lemmas.rewrite

unsafe axiom simp_lemmas.rewrites (s : simp_lemmas) (e : expr) (prove : tactic Unit := failed)
    (r : Name := `eq) (md := reducible) : tactic <| List (expr × expr)
#align simp_lemmas.rewrites simp_lemmas.rewrites

/-- `simp_lemmas.drewrite s e` tries to rewrite 'e' using only refl lemmas in 's' -/
unsafe axiom simp_lemmas.drewrite (s : simp_lemmas) (e : expr) (md := reducible) : tactic expr
#align simp_lemmas.drewrite simp_lemmas.drewrite

unsafe axiom is_valid_simp_lemma_cnst : Name → tactic Bool
#align is_valid_simp_lemma_cnst is_valid_simp_lemma_cnst

unsafe axiom is_valid_simp_lemma : expr → tactic Bool
#align is_valid_simp_lemma is_valid_simp_lemma

unsafe axiom simp_lemmas.pp : simp_lemmas → tactic format
#align simp_lemmas.pp simp_lemmas.pp

unsafe instance : has_to_tactic_format simp_lemmas :=
  ⟨simp_lemmas.pp⟩

namespace Tactic

-- Remark: `transform` should not change the target.
/-- Revert a local constant, change its type using `transform`.  -/
unsafe def revert_and_transform (transform : expr → tactic expr) (h : expr) : tactic Unit := do
  let num_reverted : ℕ ← revert h
  let t ← target
  match t with
    | expr.pi n bi d b => do
      let h_simp ← transform d
      unsafe_change <| expr.pi n bi h_simp b
    | expr.elet n g e f => do
      let h_simp ← transform g
      unsafe_change <| expr.elet n h_simp e f
    | _ => fail "reverting hypothesis created neither a pi nor an elet expr (unreachable?)"
  intron num_reverted
#align tactic.revert_and_transform tactic.revert_and_transform

/--
`get_eqn_lemmas_for deps d` returns the automatically generated equational lemmas for definition d.
   If deps is tt, then lemmas for automatically generated auxiliary declarations used to define d are also included. -/
unsafe def get_eqn_lemmas_for (deps : Bool) (d : Name) : tactic (List Name) := do
  let env ← get_env
  pure <| if deps then env d else env d
#align tactic.get_eqn_lemmas_for tactic.get_eqn_lemmas_for

structure DsimpConfig where
  md := reducible
  -- reduction mode: how aggressively constants are replaced with their definitions.
  maxSteps : Nat := Simp.defaultMaxSteps
  -- The maximum number of steps allowed before failing.
  canonizeInstances : Bool := true
  -- See the documentation in `src/library/defeq_canonizer.h`
  singlePass : Bool := false
  -- Visit each subterm no more than once.
  failIfUnchanged := true
  -- Don't throw if dsimp didn't do anything.
  eta := true
  -- allow eta-equivalence: `(λ x, F $ x) ↝ F`
  zeta : Bool := true
  -- do zeta-reductions: `let x : a := b in c ↝ c[x/b]`.
  beta : Bool := true
  -- do beta-reductions: `(λ x, E) $ (y) ↝ E[x/y]`.
  proj : Bool := true
  -- reduce projections: `⟨a,b⟩.1 ↝ a`.
  iota : Bool := true
  -- reduce recursors for inductive datatypes: eg `nat.rec_on (succ n) Z R ↝ R n $ nat.rec_on n Z R`
  unfoldReducible := false
  -- if tt, definitions with `reducible` transparency will be unfolded (delta-reduced)
  memoize := true
#align tactic.dsimp_config Tactic.DsimpConfig

-- Perform caching of dsimps of subterms.
end Tactic

/--
(Definitional) Simplify the given expression using *only* reflexivity equality lemmas from the given set of lemmas.
   The resulting expression is definitionally equal to the input.

   The list `u` contains defintions to be delta-reduced, and projections to be reduced.-/
unsafe axiom simp_lemmas.dsimplify (s : simp_lemmas) (u : List Name := []) (e : expr)
    (cfg : Tactic.DsimpConfig := { }) : tactic expr
#align simp_lemmas.dsimplify simp_lemmas.dsimplify

namespace Tactic

/-- Remark: the configuration parameters `cfg.md` and `cfg.eta` are ignored by this tactic. -/
unsafe axiom dsimplify_core
    -- The user state type.
    {α : Type}
    -- Initial user data
    (a : α)
    /- (pre a e) is invoked before visiting the children of subterm 'e',
         if it succeeds the result (new_a, new_e, flag) where
           - 'new_a' is the new value for the user data
           - 'new_e' is a new expression that must be definitionally equal to 'e',
           - 'flag'  if tt 'new_e' children should be visited, and 'post' invoked. -/
    (pre : α → expr → tactic (α × expr × Bool))
    /- (post a e) is invoked after visiting the children of subterm 'e',
         The output is similar to (pre a e), but the 'flag' indicates whether
         the new expression should be revisited or not. -/
    (post : α → expr → tactic (α × expr × Bool))
    (e : expr) (cfg : DsimpConfig := { }) : tactic (α × expr)
#align tactic.dsimplify_core tactic.dsimplify_core

unsafe def dsimplify (pre : expr → tactic (expr × Bool)) (post : expr → tactic (expr × Bool)) :
    expr → tactic expr := fun e => do
  let (a, new_e) ←
    dsimplify_core ()
        (fun u e => do
          let r ← pre e
          return (u, r))
        (fun u e => do
          let r ← post e
          return (u, r))
        e
  return new_e
#align tactic.dsimplify tactic.dsimplify

unsafe def get_simp_lemmas_or_default : Option simp_lemmas → tactic simp_lemmas
  | none => simp_lemmas.mk_default
  | some s => return s
#align tactic.get_simp_lemmas_or_default tactic.get_simp_lemmas_or_default

unsafe def dsimp_target (s : Option simp_lemmas := none) (u : List Name := [])
    (cfg : DsimpConfig := { }) : tactic Unit := do
  let s ← get_simp_lemmas_or_default s
  let t ← target >>= instantiate_mvars
  s u t cfg >>= unsafe_change
#align tactic.dsimp_target tactic.dsimp_target

unsafe def dsimp_hyp (h : expr) (s : Option simp_lemmas := none) (u : List Name := [])
    (cfg : DsimpConfig := { }) : tactic Unit := do
  let s ← get_simp_lemmas_or_default s
  revert_and_transform (fun e => s u e cfg) h
#align tactic.dsimp_hyp tactic.dsimp_hyp

/- Remark: we use transparency.instances by default to make sure that we
   can unfold projections of type classes. Example:

          (@has_add.add nat nat.has_add a b)
-/
/-- Tries to unfold `e` if it is a constant or a constant application.
    Remark: this is not a recursive procedure. -/
unsafe axiom dunfold_head (e : expr) (md := Transparency.instances) : tactic expr
#align tactic.dunfold_head tactic.dunfold_head

structure DunfoldConfig extends DsimpConfig where
  md := Transparency.instances
#align tactic.dunfold_config Tactic.DunfoldConfig

/-! Remark: in principle, dunfold can be implemented on top of dsimp. We don't do it for
   performance reasons. -/

unsafe axiom dunfold (cs : List Name) (e : expr) (cfg : DunfoldConfig := { }) : tactic expr
#align tactic.dunfold tactic.dunfold

unsafe def dunfold_target (cs : List Name) (cfg : DunfoldConfig := { }) : tactic Unit := do
  let t ← target
  dunfold cs t cfg >>= unsafe_change
#align tactic.dunfold_target tactic.dunfold_target

unsafe def dunfold_hyp (cs : List Name) (h : expr) (cfg : DunfoldConfig := { }) : tactic Unit :=
  revert_and_transform (fun e => dunfold cs e cfg) h
#align tactic.dunfold_hyp tactic.dunfold_hyp

structure DeltaConfig where
  maxSteps := Simp.defaultMaxSteps
  visitInstances := true
#align tactic.delta_config Tactic.DeltaConfig

private unsafe def is_delta_target (e : expr) (cs : List Name) : Bool :=
  cs.any fun c =>
    if e.is_app_of c then true
    else-- Exact match
      let f := e.get_app_fn
      -- f is an auxiliary constant generated when compiling c
            f.is_constant &&
          f.const_name.is_internal &&
        f.const_name.getPrefix = c
#align tactic.is_delta_target tactic.is_delta_target

/-- Delta reduce the given constant names -/
unsafe def delta (cs : List Name) (e : expr) (cfg : DeltaConfig := { }) : tactic expr :=
  let unfold (u : Unit) (e : expr) : tactic (Unit × expr × Bool) := do
    guard (is_delta_target e cs)
    let expr.const f_name f_lvls ← return e.get_app_fn
    let env ← get_env
    let decl ← env.get f_name
    let new_f ← decl.instantiate_value_univ_params f_lvls
    let new_e ← head_beta (expr.mk_app new_f e.get_app_args)
    return (u, new_e, tt)
  do
  let (c, new_e) ←
    dsimplify_core () (fun c e => failed) unfold e
        { maxSteps := cfg.maxSteps
          canonizeInstances := cfg.visitInstances }
  return new_e
#align tactic.delta tactic.delta

unsafe def delta_target (cs : List Name) (cfg : DeltaConfig := { }) : tactic Unit := do
  let t ← target
  delta cs t cfg >>= unsafe_change
#align tactic.delta_target tactic.delta_target

unsafe def delta_hyp (cs : List Name) (h : expr) (cfg : DeltaConfig := { }) : tactic Unit :=
  revert_and_transform (fun e => delta cs e cfg) h
#align tactic.delta_hyp tactic.delta_hyp

structure UnfoldProjConfig extends DsimpConfig where
  md := Transparency.instances
#align tactic.unfold_proj_config Tactic.UnfoldProjConfig

/-- If `e` is a projection application, try to unfold it, otherwise fail. -/
unsafe axiom unfold_proj (e : expr) (md := Transparency.instances) : tactic expr
#align tactic.unfold_proj tactic.unfold_proj

unsafe def unfold_projs (e : expr) (cfg : UnfoldProjConfig := { }) : tactic expr :=
  let unfold (changed : Bool) (e : expr) : tactic (Bool × expr × Bool) := do
    let new_e ← unfold_proj e cfg.md
    return (tt, new_e, tt)
  do
  let (tt, new_e) ← dsimplify_core false (fun c e => failed) unfold e cfg.toDsimpConfig |
    fail "no projections to unfold"
  return new_e
#align tactic.unfold_projs tactic.unfold_projs

unsafe def unfold_projs_target (cfg : UnfoldProjConfig := { }) : tactic Unit := do
  let t ← target
  unfold_projs t cfg >>= unsafe_change
#align tactic.unfold_projs_target tactic.unfold_projs_target

unsafe def unfold_projs_hyp (h : expr) (cfg : UnfoldProjConfig := { }) : tactic Unit :=
  revert_and_transform (fun e => unfold_projs e cfg) h
#align tactic.unfold_projs_hyp tactic.unfold_projs_hyp

structure SimpConfig where
  maxSteps : Nat := Simp.defaultMaxSteps
  contextual : Bool := false
  liftEq : Bool := true
  canonizeInstances : Bool := true
  canonizeProofs : Bool := false
  useAxioms : Bool := true
  zeta : Bool := true
  beta : Bool := true
  eta : Bool := true
  proj : Bool := true
  -- reduce projections
  iota : Bool := true
  iotaEqn : Bool := false
  -- reduce using all equation lemmas generated by equation/pattern-matching compiler
  constructorEq : Bool := true
  singlePass : Bool := false
  failIfUnchanged := true
  memoize := true
  traceLemmas := false
#align tactic.simp_config Tactic.SimpConfig

/-- `simplify s e cfg r prove` simplify `e` using `s` using bottom-up traversal.
  `discharger` is a tactic for dischaging new subgoals created by the simplifier.
   If it fails, the simplifier tries to discharge the subgoal by simplifying it to `true`.

   The parameter `to_unfold` specifies definitions that should be delta-reduced,
   and projection applications that should be unfolded.
-/
unsafe axiom simplify (s : simp_lemmas) (to_unfold : List Name := []) (e : expr)
    (cfg : SimpConfig := { }) (r : Name := `eq) (discharger : tactic Unit := failed) :
    tactic (expr × expr × name_set)
#align tactic.simplify tactic.simplify

unsafe def simp_target (s : simp_lemmas) (to_unfold : List Name := []) (cfg : SimpConfig := { })
    (discharger : tactic Unit := failed) : tactic name_set := do
  let t ← target >>= instantiate_mvars
  let (new_t, pr, lms) ← simplify s to_unfold t cfg `eq discharger
  replace_target new_t pr `` id_tag.simp
  return lms
#align tactic.simp_target tactic.simp_target

unsafe def simp_hyp (s : simp_lemmas) (to_unfold : List Name := []) (h : expr)
    (cfg : SimpConfig := { }) (discharger : tactic Unit := failed) : tactic (expr × name_set) := do
  when (expr.is_local_constant h = ff)
      (fail "tactic simp_at failed, the given expression is not a hypothesis")
  let htype ← infer_type h
  let (h_new_type, pr, lms) ← simplify s to_unfold htype cfg `eq discharger
  let new_hyp ← replace_hyp h h_new_type pr `` id_tag.simp
  return (new_hyp, lms)
#align tactic.simp_hyp tactic.simp_hyp

/-- `ext_simplify_core a c s discharger pre post r e`:

- `a : α` - initial user data
- `c : simp_config` - simp configuration options
- `s : simp_lemmas` - the set of simp_lemmas to use. Remark: the simplification lemmas are not applied automatically like in the simplify tactic. The caller must use them at pre/post.
- `discharger : α → tactic α` - tactic for dischaging hypothesis in conditional rewriting rules. The argument 'α' is the current user data.
- `pre a s r p e` is invoked before visiting the children of subterm 'e'.
  + arguments:
    - `a` is the current user data
    - `s` is the updated set of lemmas if 'contextual' is `tt`,
    - `r` is the simplification relation being used,
    - `p` is the "parent" expression (if there is one).
    - `e` is the current subexpression in question.
  + if it succeeds the result is `(new_a, new_e, new_pr, flag)` where
    - `new_a` is the new value for the user data
    - `new_e` is a new expression s.t. `r e new_e`
    - `new_pr` is a proof for `r e new_e`, If it is none, the proof is assumed to be by reflexivity
    - `flag`  if tt `new_e` children should be visited, and `post` invoked.
- `(post a s r p e)` is invoked after visiting the children of subterm `e`,
  The output is similar to `(pre a r s p e)`, but the 'flag' indicates whether the new expression should be revisited or not.
- `r` is the simplification relation. Usually `=` or `↔`.
- `e` is the input expression to be simplified.

The method returns `(a,e,pr)` where

 - `a` is the final user data
 - `e` is the new expression
 - `pr` is the proof that the given expression equals the input expression.

Note that `ext_simplify_core` will succeed even if `pre` and `post` fail, as failures are used to indicate that the method should move on to the next subterm.
If it is desirable to propagate errors from `pre`, they can be propagated through the "user data".
An easy way to do this is to call `tactic.capture (do ...)` in the parts of `pre`/`post` where errors matter, and then use `tactic.unwrap a` on the result.

Additionally, `ext_simplify_core` does not propagate changes made to the tactic state by `pre` and `post.
If it is desirable to propagate changes to the tactic state in addition to errors, use `tactic.resume` instead of `tactic.unwrap`.
-/
unsafe axiom ext_simplify_core {α : Type} (a : α) (c : SimpConfig) (s : simp_lemmas)
    (discharger : α → tactic α)
    (pre : α → simp_lemmas → Name → Option expr → expr → tactic (α × expr × Option expr × Bool))
    (post : α → simp_lemmas → Name → Option expr → expr → tactic (α × expr × Option expr × Bool))
    (r : Name) : expr → tactic (α × expr × expr)
#align tactic.ext_simplify_core tactic.ext_simplify_core

private unsafe def is_equation : expr → Bool
  | expr.pi n bi d b => is_equation b
  | e =>
    match expr.is_eq e with
    | some a => true
    | none => false
#align tactic.is_equation tactic.is_equation

unsafe def collect_ctx_simps : tactic (List expr) :=
  local_context
#align tactic.collect_ctx_simps tactic.collect_ctx_simps

section SimpIntros

unsafe def intro1_aux : Bool → List Name → tactic expr
  | ff, _ => intro1
  | tt, n :: ns => intro n
  | _, _ => failed
#align tactic.intro1_aux tactic.intro1_aux

structure SimpIntrosConfig extends SimpConfig where
  useHyps := false
#align tactic.simp_intros_config Tactic.SimpIntrosConfig

unsafe def simp_intros_aux (cfg : SimpConfig) (use_hyps : Bool) (to_unfold : List Name) :
    simp_lemmas → Bool → List Name → tactic simp_lemmas
  | S, tt, [] => try (simp_target S to_unfold cfg) >> return S
  | S, use_ns, ns => do
    let t ← target
    if t `not 1 then intro1_aux use_ns ns >> simp_intros_aux S use_ns ns
      else
        if t then
          (do
              let d ← return t
              let (new_d, h_d_eq_new_d, lms) ← simplify S to_unfold d cfg
              let h_d ← intro1_aux use_ns ns
              let h_new_d ← mk_eq_mp h_d_eq_new_d h_d
              assertv_core h_d new_d h_new_d
              clear h_d
              let h_new ← intro1
              let new_S ←
                if use_hyps then condM (is_prop new_d) (S h_new ff) (return S) else return S
              simp_intros_aux new_S use_ns
                  ns) <|>-- failed to simplify... we just introduce and continue
                intro1_aux
                use_ns ns >>
              simp_intros_aux S use_ns ns
        else
          if t || t then intro1_aux use_ns ns >> simp_intros_aux S use_ns ns
          else do
            let new_t ← whnf t reducible
            if new_t then unsafe_change new_t >> simp_intros_aux S use_ns ns
              else
                try (simp_target S to_unfold cfg) >>
                  condM (expr.is_pi <$> target) (simp_intros_aux S use_ns ns)
                    (if use_ns ∧ ¬ns then failed else return S)
#align tactic.simp_intros_aux tactic.simp_intros_aux

unsafe def simp_intros (s : simp_lemmas) (to_unfold : List Name := []) (ids : List Name := [])
    (cfg : SimpIntrosConfig := { }) : tactic Unit :=
  step <| simp_intros_aux cfg.toSimpConfig cfg.useHyps to_unfold s (not ids.Empty) ids
#align tactic.simp_intros tactic.simp_intros

end SimpIntros

unsafe def mk_eq_simp_ext (simp_ext : expr → tactic (expr × expr)) : tactic Unit := do
  let (lhs, rhs) ← target >>= match_eq
  let (new_rhs, HEq) ← simp_ext lhs
  unify rhs new_rhs
  exact HEq
#align tactic.mk_eq_simp_ext tactic.mk_eq_simp_ext

/-! Simp attribute support -/

unsafe def to_simp_lemmas : simp_lemmas → List Name → tactic simp_lemmas
  | S, [] => return S
  | S, n :: ns => do
    let S' ← has_attribute `congr n >> S.add_congr n <|> S.add_simp n false
    to_simp_lemmas S' ns
#align tactic.to_simp_lemmas tactic.to_simp_lemmas

unsafe def mk_simp_attr (attr_name : Name) (attr_deps : List Name := []) : Tactic := do
  let t := q(user_attribute simp_lemmas)
  let v :=
    q(({  Name := attr_name
          descr := "simplifier attribute"
          cache_cfg :=
            { mk_cache := fun ns => do
                let s ← tactic.to_simp_lemmas simp_lemmas.mk ns
                let s ←
                  attr_deps.foldlM
                      (fun s attr_name => do
                        let ns ← attribute.get_instances attr_name
                        to_simp_lemmas s ns)
                      s
                return s
              dependencies := `reducibility :: attr_deps } } :
        user_attribute simp_lemmas))
  let n := mk_simp_attr_decl_name attr_name
  add_decl (declaration.defn n [] t v ReducibilityHints.abbrev ff)
  attribute.register n
#align tactic.mk_simp_attr tactic.mk_simp_attr

/-- ### Example usage:
```lean
-- make a new simp attribute called "my_reduction"
run_cmd mk_simp_attr `my_reduction
-- Add "my_reduction" attributes to these if-reductions
attribute [my_reduction] if_pos if_neg dif_pos dif_neg

-- will return the simp_lemmas with the `my_reduction` attribute.
#eval get_user_simp_lemmas `my_reduction

```
 -/
unsafe def get_user_simp_lemmas (attr_name : Name) : tactic simp_lemmas :=
  if attr_name = `default then simp_lemmas.mk_default
  else get_attribute_cache_dyn (mk_simp_attr_decl_name attr_name)
#align tactic.get_user_simp_lemmas tactic.get_user_simp_lemmas

unsafe def join_user_simp_lemmas_core : simp_lemmas → List Name → tactic simp_lemmas
  | S, [] => return S
  | S, attr_name :: R => do
    let S' ← get_user_simp_lemmas attr_name
    join_user_simp_lemmas_core (S S') R
#align tactic.join_user_simp_lemmas_core tactic.join_user_simp_lemmas_core

unsafe def join_user_simp_lemmas (no_dflt : Bool) (attrs : List Name) : tactic simp_lemmas := do
  let s ← simp_lemmas.mk_default
  let s := if no_dflt then s.erase_simp_lemmas else s
  join_user_simp_lemmas_core s attrs
#align tactic.join_user_simp_lemmas tactic.join_user_simp_lemmas

unsafe def simplify_top_down {α} (a : α) (pre : α → expr → tactic (α × expr × expr)) (e : expr)
    (cfg : SimpConfig := { }) : tactic (α × expr × expr) :=
  ext_simplify_core a cfg simp_lemmas.mk (fun _ => failed)
    (fun a _ _ _ e => do
      let (new_a, new_e, pr) ← pre a e
      guard ¬new_e == e
      return (new_a, new_e, some pr, tt))
    (fun _ _ _ _ _ => failed) `eq e
#align tactic.simplify_top_down tactic.simplify_top_down

unsafe def simp_top_down (pre : expr → tactic (expr × expr)) (cfg : SimpConfig := { }) :
    tactic Unit := do
  let t ← target
  let (_, new_target, pr) ←
    simplify_top_down ()
        (fun _ e => do
          let (new_e, pr) ← pre e
          return ((), new_e, pr))
        t cfg
  replace_target new_target pr `` id_tag.simp
#align tactic.simp_top_down tactic.simp_top_down

unsafe def simplify_bottom_up {α} (a : α) (post : α → expr → tactic (α × expr × expr)) (e : expr)
    (cfg : SimpConfig := { }) : tactic (α × expr × expr) :=
  ext_simplify_core a cfg simp_lemmas.mk (fun _ => failed) (fun _ _ _ _ _ => failed)
    (fun a _ _ _ e => do
      let (new_a, new_e, pr) ← post a e
      guard ¬new_e == e
      return (new_a, new_e, some pr, tt))
    `eq e
#align tactic.simplify_bottom_up tactic.simplify_bottom_up

unsafe def simp_bottom_up (post : expr → tactic (expr × expr)) (cfg : SimpConfig := { }) :
    tactic Unit := do
  let t ← target
  let (_, new_target, pr) ←
    simplify_bottom_up ()
        (fun _ e => do
          let (new_e, pr) ← post e
          return ((), new_e, pr))
        t cfg
  replace_target new_target pr `` id_tag.simp
#align tactic.simp_bottom_up tactic.simp_bottom_up

private unsafe def remove_deps (s : name_set) (h : expr) : name_set :=
  if s.Empty then s
  else h.fold s fun e o s => if e.is_local_constant then s.eraseₓ e.local_uniq_name else s
#align tactic.remove_deps tactic.remove_deps

/-- Return the list of hypothesis that are propositions and do not have
   forward dependencies. -/
unsafe def non_dep_prop_hyps : tactic (List expr) := do
  let ctx ← local_context
  let s ←
    ctx.foldlM
        (fun s h => do
          let h_type ← infer_type h
          let s := remove_deps s h_type
          let h_val ← head_zeta h
          let s := if h_val == h then s else remove_deps s h_val
          condM (is_prop h_type) (return <| s h) (return s))
        mk_name_set
  let t ← target
  let s := remove_deps s t
  return <| ctx fun h => s h
#align tactic.non_dep_prop_hyps tactic.non_dep_prop_hyps

section SimpAll

unsafe structure simp_all_entry where
  h : expr
  -- hypothesis
  new_type : expr
  -- new type
  pr : Option expr
  -- proof that type of h is equal to new_type
  s : simp_lemmas
#align tactic.simp_all_entry tactic.simp_all_entry

-- simplification lemmas for simplifying new_type
private unsafe def update_simp_lemmas (es : List simp_all_entry) (h : expr) :
    tactic (List simp_all_entry) :=
  es.mapM fun e => do
    let new_s ← e.s.add h false
    return { e with s := new_s }
#align tactic.update_simp_lemmas tactic.update_simp_lemmas

/-- Helper tactic for `init`.
   Remark: the following tactic is quadratic on the length of list expr (the list of non dependent propositions).
   We can make it more efficient as soon as we have an efficient simp_lemmas.erase. -/
private unsafe def init_aux :
    List expr → simp_lemmas → List simp_all_entry → tactic (simp_lemmas × List simp_all_entry)
  | [], s, r => return (s, r)
  | h :: hs, s, r => do
    let new_r ← update_simp_lemmas r h
    let new_s ← s.add h false
    let h_type ← infer_type h
    init_aux hs new_s (⟨h, h_type, none, s⟩ :: new_r)
#align tactic.init_aux tactic.init_aux

private unsafe def init (s : simp_lemmas) (hs : List expr) :
    tactic (simp_lemmas × List simp_all_entry) :=
  init_aux hs s []
#align tactic.init tactic.init

private unsafe def add_new_hyps (es : List simp_all_entry) : tactic Unit :=
  es.mapM' fun e =>
    match e.pr with
    | none => return ()
    | some pr => assert e.h.local_pp_name e.new_type >> mk_eq_mp pr e.h >>= exact
#align tactic.add_new_hyps tactic.add_new_hyps

private unsafe def clear_old_hyps (es : List simp_all_entry) : tactic Unit :=
  es.mapM' fun e => when (e.pr ≠ none) (try (clear e.h))
#align tactic.clear_old_hyps tactic.clear_old_hyps

private unsafe def join_pr : Option expr → expr → tactic expr
  | none, pr₂ => return pr₂
  | some pr₁, pr₂ => mk_eq_trans pr₁ pr₂
#align tactic.join_pr tactic.join_pr

private unsafe def loop (cfg : SimpConfig) (discharger : tactic Unit) (to_unfold : List Name) :
    List simp_all_entry → List simp_all_entry → simp_lemmas → Bool → tactic name_set
  | [], r, s, m =>
    if m then loop r [] s false
    else do
      add_new_hyps r
      let (lms, target_changed) ←
        (simp_target s to_unfold cfg discharger >>= fun ns => return (ns, true)) <|>
            return (mk_name_set, false)
      guard (cfg = ff ∨ target_changed ∨ r fun e => e ≠ none) <|>
          fail "simp_all tactic failed to simplify"
      clear_old_hyps r
      return lms
  | e :: es, r, s, m => do
    let ⟨h, h_type, h_pr, s'⟩ := e
    let (new_h_type, new_pr, lms) ←
      simplify s' to_unfold h_type { cfg with failIfUnchanged := false } `eq discharger
    if h_type == new_h_type then do
        let new_lms ← loop es (e :: r) s m
        return (new_lms lms fun n ns => name_set.insert ns n)
      else do
        let new_pr ← join_pr h_pr new_pr
        let new_fact_pr ← mk_eq_mp new_pr h
        if new_h_type = q(False) then do
            let tgt ← target
            to_expr ``(@False.ndrec $(tgt) $(new_fact_pr)) >>= exact
            return mk_name_set
          else do
            let h0_type ← infer_type h
            let new_fact_pr := mk_tagged_proof new_h_type new_fact_pr `` id_tag.simp
            let new_es ← update_simp_lemmas es new_fact_pr
            let new_r ← update_simp_lemmas r new_fact_pr
            let new_r :=
              { e with
                  new_type := new_h_type
                  pr := new_pr } ::
                new_r
            let new_s ← s new_fact_pr ff
            let new_lms ← loop new_es new_r new_s tt
            return (new_lms lms fun n ns => name_set.insert ns n)
#align tactic.loop tactic.loop

unsafe def simp_all (s : simp_lemmas) (to_unfold : List Name) (cfg : SimpConfig := { })
    (discharger : tactic Unit := failed) : tactic name_set := do
  let hs ← non_dep_prop_hyps
  let (s, es) ← init s hs
  loop cfg discharger to_unfold es [] s ff
#align tactic.simp_all tactic.simp_all

end SimpAll

/-! debugging support for algebraic normalizer -/

unsafe axiom trace_algebra_info : expr → tactic Unit
#align tactic.trace_algebra_info tactic.trace_algebra_info

end Tactic

export Tactic (mk_simp_attr)

run_cmd
  mk_simp_attr `norm [`simp]


@[simp] theorem liftOn_mk (a : α) (f : α → γ) (h : ∀ a₁ a₂, r a₁ a₂ → f a₁ = f a₂) :
    Quot.liftOn (Quot.mk r a) f h = f a := rfl

theorem eq_iff_true_of_subsingleton [Subsingleton α] (x y : α) : x = y ↔ True :=
  iff_true _ ▸ Subsingleton.elim ..

section attribute [simp] eq_iff_true_of_subsingleton end

@[simp] theorem PUnit.default_eq_unit : (default : PUnit) = PUnit.unit := rfl

set_option trace.Meta.Tactic.simp.discharge true
set_option trace.Meta.Tactic.simp.unify true
set_option trace.Meta.Tactic.simp.rewrite true
example : (default : PUnit) = x := by simp


/-
Copyright (c) 2016 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Leonardo de Moura
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.tactic.doc_commands
import Mathlib.tactic.reserved_notation
import Mathlib.PostPort

universes u_1 u_2 u_3 u_4 u l w v

namespace Mathlib

/-!
# Basic logic properties

This file is one of the earliest imports in mathlib.

## Implementation notes

Theorems that require decidability hypotheses are in the namespace "decidable".
Classical versions are in the namespace "classical".

In the presence of automation, this whole file may be unnecessary. On the other hand,
maybe it is useful for writing automation.
-/

/- We add the `inline` attribute to optimize VM computation using these declarations. For example,
  `if p ∧ q then ... else ...` will not evaluate the decidability of `q` if `p` is false. -/

/-- An identity function with its main argument implicit. This will be printed as `hidden` even
if it is applied to a large term, so it can be used for elision,
as done in the `elide` and `unelide` tactics. -/
def hidden {α : Sort u_1} {a : α} : α :=
  a

/-- Ex falso, the nondependent eliminator for the `empty` type. -/
def empty.elim {C : Sort u_1} : empty → C :=
  sorry

protected instance empty.subsingleton : subsingleton empty :=
  subsingleton.intro fun (a : empty) => empty.elim a

protected instance subsingleton.prod {α : Type u_1} {β : Type u_2} [subsingleton α] [subsingleton β] : subsingleton (α × β) :=
  subsingleton.intro
    fun (a b : α × β) =>
      prod.cases_on a
        fun (a_fst : α) (a_snd : β) =>
          prod.cases_on b
            fun (b_fst : α) (b_snd : β) =>
              (fun (fst fst_1 : α) (snd snd_1 : β) =>
                  Eq.trans ((fun (fst : α) (snd : β) => Eq.refl (fst, snd)) fst snd)
                    (congr (congr (Eq.refl Prod.mk) (subsingleton.elim fst fst_1)) (subsingleton.elim snd snd_1)))
                a_fst b_fst a_snd b_snd

protected instance empty.decidable_eq : DecidableEq empty :=
  fun (a : empty) => empty.elim a

protected instance sort.inhabited : Inhabited (Sort u_1) :=
  { default := PUnit }

protected instance sort.inhabited' : Inhabited Inhabited.default :=
  { default := PUnit.unit }

protected instance psum.inhabited_left {α : Sort u_1} {β : Sort u_2} [Inhabited α] : Inhabited (psum α β) :=
  { default := psum.inl Inhabited.default }

protected instance psum.inhabited_right {α : Sort u_1} {β : Sort u_2} [Inhabited β] : Inhabited (psum α β) :=
  { default := psum.inr Inhabited.default }

protected instance decidable_eq_of_subsingleton {α : Sort u_1} [subsingleton α] : DecidableEq α :=
  sorry

@[simp] theorem eq_iff_true_of_subsingleton {α : Type u_1} [subsingleton α] (x : α) (y : α) : x = y ↔ True :=
  of_eq_true (Eq.trans (iff_eq_of_eq_true_right (Eq.refl True)) (eq_true_intro (Eq.symm (subsingleton.elim y x))))

/-- Add an instance to "undo" coercion transitivity into a chain of coercions, because
   most simp lemmas are stated with respect to simple coercions and will not match when
   part of a chain. -/
@[simp] theorem coe_coe {α : Sort u_1} {β : Sort u_2} {γ : Sort u_3} [has_coe α β] [has_coe_t β γ] (a : α) : ↑a = ↑↑a :=
  rfl

theorem coe_fn_coe_trans {α : Sort u_1} {β : Sort u_2} {γ : Sort u_3} [has_coe α β] [has_coe_t_aux β γ] [has_coe_to_fun γ] (x : α) : ⇑x = ⇑↑x :=
  rfl

@[simp] theorem coe_fn_coe_base {α : Sort u_1} {β : Sort u_2} [has_coe α β] [has_coe_to_fun β] (x : α) : ⇑x = ⇑↑x :=
  rfl

theorem coe_sort_coe_trans {α : Sort u_1} {β : Sort u_2} {γ : Sort u_3} [has_coe α β] [has_coe_t_aux β γ] [has_coe_to_sort γ] (x : α) : ↥x = ↥↑x :=
  rfl

/--
Many structures such as bundled morphisms coerce to functions so that you can
transparently apply them to arguments. For example, if `e : α ≃ β` and `a : α`
then you can write `e a` and this is elaborated as `⇑e a`. This type of
coercion is implemented using the `has_coe_to_fun` type class. There is one
important consideration:

If a type coerces to another type which in turn coerces to a function,
then it **must** implement `has_coe_to_fun` directly:
```lean
structure sparkling_equiv (α β) extends α ≃ β

-- if we add a `has_coe` instance,

-- if we add a `has_coe` instance,
instance {α β} : has_coe (sparkling_equiv α β) (α ≃ β) :=
⟨sparkling_equiv.to_equiv⟩

-- then a `has_coe_to_fun` instance **must** be added as well:

-- then a `has_coe_to_fun` instance **must** be added as well:
instance {α β} : has_coe_to_fun (sparkling_equiv α β) :=
⟨λ _, α → β, λ f, f.to_equiv.to_fun⟩
```

(Rationale: if we do not declare the direct coercion, then `⇑e a` is not in
simp-normal form. The lemma `coe_fn_coe_base` will unfold it to `⇑↑e a`. This
often causes loops in the simplifier.)
-/
@[simp] theorem coe_sort_coe_base {α : Sort u_1} {β : Sort u_2} [has_coe α β] [has_coe_to_sort β] (x : α) : ↥x = ↥↑x :=
  rfl

/-- `pempty` is the universe-polymorphic analogue of `empty`. -/
inductive pempty
where

/-- Ex falso, the nondependent eliminator for the `pempty` type. -/
def pempty.elim {C : Sort u_1} : pempty → C :=
  sorry

protected instance subsingleton_pempty : subsingleton pempty :=
  subsingleton.intro fun (a : pempty) => pempty.elim a

@[simp] theorem not_nonempty_pempty : ¬Nonempty pempty :=
  fun (_x : Nonempty pempty) =>
    (fun (_a : Nonempty pempty) => nonempty.dcases_on _a fun (val : pempty) => idRhs False (pempty.elim val)) _x

@[simp] theorem forall_pempty {P : pempty → Prop} : (∀ (x : pempty), P x) ↔ True :=
  { mp := fun (h : ∀ (x : pempty), P x) => trivial,
    mpr := fun (h : True) (x : pempty) => pempty.cases_on (fun (x : pempty) => P x) x }

@[simp] theorem exists_pempty {P : pempty → Prop} : (∃ (x : pempty), P x) ↔ False := sorry

theorem congr_arg_heq {α : Sort u_1} {β : α → Sort u_2} (f : (a : α) → β a) {a₁ : α} {a₂ : α} : a₁ = a₂ → f a₁ == f a₂ := sorry

theorem plift.down_inj {α : Sort u_1} (a : plift α) (b : plift α) : plift.down a = plift.down b → a = b := sorry

-- missing [symm] attribute for ne in core.

theorem ne_comm {α : Sort u_1} {a : α} {b : α} : a ≠ b ↔ b ≠ a :=
  { mp := ne.symm, mpr := ne.symm }

@[simp] theorem eq_iff_eq_cancel_left {α : Type u_1} {b : α} {c : α} : (∀ {a : α}, a = b ↔ a = c) ↔ b = c :=
  { mp :=
      fun (h : ∀ {a : α}, a = b ↔ a = c) => eq.mpr (id (Eq._oldrec (Eq.refl (b = c)) (Eq.symm (propext h)))) (Eq.refl b),
    mpr := fun (h : b = c) (a : α) => eq.mpr (id (Eq._oldrec (Eq.refl (a = b ↔ a = c)) h)) (iff.refl (a = c)) }

@[simp] theorem eq_iff_eq_cancel_right {α : Type u_1} {a : α} {b : α} : (∀ {c : α}, a = c ↔ b = c) ↔ a = b :=
  { mp := fun (h : ∀ {c : α}, a = c ↔ b = c) => eq.mpr (id (Eq._oldrec (Eq.refl (a = b)) (propext h))) (Eq.refl b),
    mpr := fun (h : a = b) (a_1 : α) => eq.mpr (id (Eq._oldrec (Eq.refl (a = a_1 ↔ b = a_1)) h)) (iff.refl (b = a_1)) }

/-- Wrapper for adding elementary propositions to the type class systems.
Warning: this can easily be abused. See the rest of this docstring for details.

Certain propositions should not be treated as a class globally,
but sometimes it is very convenient to be able to use the type class system
in specific circumstances.

For example, `zmod p` is a field if and only if `p` is a prime number.
In order to be able to find this field instance automatically by type class search,
we have to turn `p.prime` into an instance implicit assumption.

On the other hand, making `nat.prime` a class would require a major refactoring of the library,
and it is questionable whether making `nat.prime` a class is desirable at all.
The compromise is to add the assumption `[fact p.prime]` to `zmod.field`.

In particular, this class is not intended for turning the type class system
into an automated theorem prover for first order logic. -/
def fact (p : Prop) :=
  p

theorem fact.elim {p : Prop} (h : fact p) : p :=
  h

/-!
### Declarations about propositional connectives
-/

theorem false_ne_true : False ≠ True :=
  fun (ᾰ : False = True) => idRhs ((fun (_x : Prop) => _x) False) (Eq.symm ᾰ ▸ trivial)

/-! ### Declarations about `implies` -/

theorem iff_of_eq {a : Prop} {b : Prop} (e : a = b) : a ↔ b :=
  e ▸ iff.rfl

theorem iff_iff_eq {a : Prop} {b : Prop} : a ↔ b ↔ a = b :=
  { mp := propext, mpr := iff_of_eq }

@[simp] theorem eq_iff_iff {p : Prop} {q : Prop} : p = q ↔ (p ↔ q) :=
  iff.symm iff_iff_eq

@[simp] theorem imp_self {a : Prop} : a → a ↔ True :=
  iff_true_intro id

theorem imp_intro {α : Prop} {β : Prop} (h : α) : β → α :=
  fun (_x : β) => h

theorem imp_false {a : Prop} : a → False ↔ ¬a :=
  iff.rfl

theorem imp_and_distrib {b : Prop} {c : Prop} {α : Sort u_1} : α → b ∧ c ↔ (α → b) ∧ (α → c) :=
  { mp := fun (h : α → b ∧ c) => { left := fun (ha : α) => and.left (h ha), right := fun (ha : α) => and.right (h ha) },
    mpr := fun (h : (α → b) ∧ (α → c)) (ha : α) => { left := and.left h ha, right := and.right h ha } }

@[simp] theorem and_imp {a : Prop} {b : Prop} {c : Prop} : a ∧ b → c ↔ a → b → c := sorry

theorem iff_def {a : Prop} {b : Prop} : a ↔ b ↔ (a → b) ∧ (b → a) :=
  iff_iff_implies_and_implies a b

theorem iff_def' {a : Prop} {b : Prop} : a ↔ b ↔ (b → a) ∧ (a → b) :=
  iff.trans iff_def and.comm

theorem imp_true_iff {α : Sort u_1} : α → True ↔ True :=
  iff_true_intro fun (_x : α) => trivial

@[simp] theorem imp_iff_right {a : Prop} {b : Prop} (ha : a) : a → b ↔ b :=
  { mp := fun (f : a → b) => f ha, mpr := imp_intro }

/-! ### Declarations about `not` -/

/-- Ex falso for negation. From `¬ a` and `a` anything follows. This is the same as `absurd` with
the arguments flipped, but it is in the `not` namespace so that projection notation can be used. -/
def not.elim {a : Prop} {α : Sort u_1} (H1 : ¬a) (H2 : a) : α :=
  absurd H2 H1

theorem not.imp {a : Prop} {b : Prop} (H2 : ¬b) (H1 : a → b) : ¬a :=
  mt H1 H2

theorem not_not_of_not_imp {a : Prop} {b : Prop} : ¬(a → b) → ¬¬a :=
  mt not.elim

theorem not_of_not_imp {b : Prop} {a : Prop} : ¬(a → b) → ¬b :=
  mt imp_intro

theorem dec_em (p : Prop) [Decidable p] : p ∨ ¬p :=
  decidable.em p

theorem em (p : Prop) : p ∨ ¬p :=
  classical.em p

theorem or_not {p : Prop} : p ∨ ¬p :=
  em p

theorem by_contradiction {p : Prop} : (¬p → False) → p :=
  decidable.by_contradiction

-- alias by_contradiction ← by_contra

theorem by_contra {p : Prop} : (¬p → False) → p :=
  decidable.by_contradiction

/--
In most of mathlib, we use the law of excluded middle (LEM) and the axiom of choice (AC) freely.
The `decidable` namespace contains versions of lemmas from the root namespace that explicitly
attempt to avoid the axiom of choice, usually by adding decidability assumptions on the inputs.

You can check if a lemma uses the axiom of choice by using `#print axioms foo` and seeing if
`classical.choice` appears in the list.
-/
-- See Note [decidable namespace]

protected theorem decidable.not_not {a : Prop} [Decidable a] : ¬¬a ↔ a :=
  { mp := decidable.by_contradiction, mpr := not_not_intro }

/-- The Double Negation Theorem: `¬ ¬ P` is equivalent to `P`.
The left-to-right direction, double negation elimination (DNE),
is classically true but not constructively. -/
@[simp] theorem not_not {a : Prop} : ¬¬a ↔ a :=
  decidable.not_not

theorem of_not_not {a : Prop} : ¬¬a → a :=
  by_contra

-- See Note [decidable namespace]

protected theorem decidable.of_not_imp {a : Prop} {b : Prop} [Decidable a] (h : ¬(a → b)) : a :=
  decidable.by_contradiction (not_not_of_not_imp h)

theorem of_not_imp {a : Prop} {b : Prop} : ¬(a → b) → a :=
  decidable.of_not_imp

-- See Note [decidable namespace]

protected theorem decidable.not_imp_symm {a : Prop} {b : Prop} [Decidable a] (h : ¬a → b) (hb : ¬b) : a :=
  decidable.by_contradiction (hb ∘ h)

theorem not.decidable_imp_symm {a : Prop} {b : Prop} [Decidable a] : (¬a → b) → ¬b → a :=
  decidable.not_imp_symm

theorem not.imp_symm {a : Prop} {b : Prop} : (¬a → b) → ¬b → a :=
  not.decidable_imp_symm

-- See Note [decidable namespace]

protected theorem decidable.not_imp_comm {a : Prop} {b : Prop} [Decidable a] [Decidable b] : ¬a → b ↔ ¬b → a :=
  { mp := not.decidable_imp_symm, mpr := not.decidable_imp_symm }

theorem not_imp_comm {a : Prop} {b : Prop} : ¬a → b ↔ ¬b → a :=
  decidable.not_imp_comm

@[simp] theorem imp_not_self {a : Prop} : a → ¬a ↔ ¬a :=
  { mp := fun (h : a → ¬a) (ha : a) => h ha ha, mpr := fun (h : ¬a) (_x : a) => h }

theorem decidable.not_imp_self {a : Prop} [Decidable a] : ¬a → a ↔ a :=
  eq.mp (Eq._oldrec (Eq.refl (¬a → ¬¬a ↔ ¬¬a)) (propext decidable.not_not)) imp_not_self

@[simp] theorem not_imp_self {a : Prop} : ¬a → a ↔ a :=
  decidable.not_imp_self

theorem imp.swap {a : Prop} {b : Prop} {c : Prop} : a → b → c ↔ b → a → c :=
  { mp := function.swap, mpr := function.swap }

theorem imp_not_comm {a : Prop} {b : Prop} : a → ¬b ↔ b → ¬a :=
  imp.swap

/-! ### Declarations about `and` -/

theorem and_congr_left {a : Prop} {b : Prop} {c : Prop} (h : c → (a ↔ b)) : a ∧ c ↔ b ∧ c :=
  iff.trans and.comm (iff.trans (and_congr_right h) and.comm)

theorem and_congr_left' {a : Prop} {b : Prop} {c : Prop} (h : a ↔ b) : a ∧ c ↔ b ∧ c :=
  and_congr h iff.rfl

theorem and_congr_right' {a : Prop} {b : Prop} {c : Prop} (h : b ↔ c) : a ∧ b ↔ a ∧ c :=
  and_congr iff.rfl h

theorem not_and_of_not_left {a : Prop} (b : Prop) : ¬a → ¬(a ∧ b) :=
  mt and.left

theorem not_and_of_not_right (a : Prop) {b : Prop} : ¬b → ¬(a ∧ b) :=
  mt and.right

theorem and.imp_left {a : Prop} {b : Prop} {c : Prop} (h : a → b) : a ∧ c → b ∧ c :=
  and.imp h id

theorem and.imp_right {a : Prop} {b : Prop} {c : Prop} (h : a → b) : c ∧ a → c ∧ b :=
  and.imp id h

theorem and.right_comm {a : Prop} {b : Prop} {c : Prop} : (a ∧ b) ∧ c ↔ (a ∧ c) ∧ b := sorry

theorem and.rotate {a : Prop} {b : Prop} {c : Prop} : a ∧ b ∧ c ↔ b ∧ c ∧ a := sorry

theorem and_not_self_iff (a : Prop) : a ∧ ¬a ↔ False :=
  { mp := fun (h : a ∧ ¬a) => and.right h (and.left h), mpr := fun (h : False) => false.elim h }

theorem not_and_self_iff (a : Prop) : ¬a ∧ a ↔ False := sorry

theorem and_iff_left_of_imp {a : Prop} {b : Prop} (h : a → b) : a ∧ b ↔ a :=
  { mp := and.left, mpr := fun (ha : a) => { left := ha, right := h ha } }

theorem and_iff_right_of_imp {a : Prop} {b : Prop} (h : b → a) : a ∧ b ↔ b :=
  { mp := and.right, mpr := fun (hb : b) => { left := h hb, right := hb } }

@[simp] theorem and_iff_left_iff_imp {a : Prop} {b : Prop} : a ∧ b ↔ a ↔ a → b :=
  { mp := fun (h : a ∧ b ↔ a) (ha : a) => and.right (iff.mpr h ha), mpr := and_iff_left_of_imp }

@[simp] theorem and_iff_right_iff_imp {a : Prop} {b : Prop} : a ∧ b ↔ b ↔ b → a :=
  { mp := fun (h : a ∧ b ↔ b) (ha : b) => and.left (iff.mpr h ha), mpr := and_iff_right_of_imp }

@[simp] theorem and.congr_right_iff {a : Prop} {b : Prop} {c : Prop} : a ∧ b ↔ a ∧ c ↔ a → (b ↔ c) := sorry

@[simp] theorem and.congr_left_iff {a : Prop} {b : Prop} {c : Prop} : a ∧ c ↔ b ∧ c ↔ c → (a ↔ b) := sorry

@[simp] theorem and_self_left {a : Prop} {b : Prop} : a ∧ a ∧ b ↔ a ∧ b :=
  { mp := fun (h : a ∧ a ∧ b) => { left := and.left h, right := and.right (and.right h) },
    mpr := fun (h : a ∧ b) => { left := and.left h, right := { left := and.left h, right := and.right h } } }

@[simp] theorem and_self_right {a : Prop} {b : Prop} : (a ∧ b) ∧ b ↔ a ∧ b :=
  { mp := fun (h : (a ∧ b) ∧ b) => { left := and.left (and.left h), right := and.right h },
    mpr := fun (h : a ∧ b) => { left := { left := and.left h, right := and.right h }, right := and.right h } }

/-! ### Declarations about `or` -/

theorem or_congr_left {a : Prop} {b : Prop} {c : Prop} (h : a ↔ b) : a ∨ c ↔ b ∨ c :=
  or_congr h iff.rfl

theorem or_congr_right {a : Prop} {b : Prop} {c : Prop} (h : b ↔ c) : a ∨ b ↔ a ∨ c :=
  or_congr iff.rfl h

theorem or.right_comm {a : Prop} {b : Prop} {c : Prop} : (a ∨ b) ∨ c ↔ (a ∨ c) ∨ b :=
  eq.mpr (id (Eq._oldrec (Eq.refl ((a ∨ b) ∨ c ↔ (a ∨ c) ∨ b)) (propext (or_assoc a b))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a ∨ b ∨ c ↔ (a ∨ c) ∨ b)) (propext (or_assoc a c))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (a ∨ b ∨ c ↔ a ∨ c ∨ b)) (propext (or_comm b c)))) (iff.refl (a ∨ c ∨ b))))

theorem or_of_or_of_imp_of_imp {a : Prop} {b : Prop} {c : Prop} {d : Prop} (h₁ : a ∨ b) (h₂ : a → c) (h₃ : b → d) : c ∨ d :=
  or.imp h₂ h₃ h₁

theorem or_of_or_of_imp_left {a : Prop} {b : Prop} {c : Prop} (h₁ : a ∨ c) (h : a → b) : b ∨ c :=
  or.imp_left h h₁

theorem or_of_or_of_imp_right {a : Prop} {b : Prop} {c : Prop} (h₁ : c ∨ a) (h : a → b) : c ∨ b :=
  or.imp_right h h₁

theorem or.elim3 {a : Prop} {b : Prop} {c : Prop} {d : Prop} (h : a ∨ b ∨ c) (ha : a → d) (hb : b → d) (hc : c → d) : d :=
  or.elim h ha fun (h₂ : b ∨ c) => or.elim h₂ hb hc

theorem or_imp_distrib {a : Prop} {b : Prop} {c : Prop} : a ∨ b → c ↔ (a → c) ∧ (b → c) := sorry

-- See Note [decidable namespace]

protected theorem decidable.or_iff_not_imp_left {a : Prop} {b : Prop} [Decidable a] : a ∨ b ↔ ¬a → b :=
  { mp := or.resolve_left, mpr := fun (h : ¬a → b) => dite a Or.inl (Or.inr ∘ h) }

theorem or_iff_not_imp_left {a : Prop} {b : Prop} : a ∨ b ↔ ¬a → b :=
  decidable.or_iff_not_imp_left

-- See Note [decidable namespace]

protected theorem decidable.or_iff_not_imp_right {a : Prop} {b : Prop} [Decidable b] : a ∨ b ↔ ¬b → a :=
  iff.trans or.comm decidable.or_iff_not_imp_left

theorem or_iff_not_imp_right {a : Prop} {b : Prop} : a ∨ b ↔ ¬b → a :=
  decidable.or_iff_not_imp_right

-- See Note [decidable namespace]

protected theorem decidable.not_imp_not {a : Prop} {b : Prop} [Decidable a] : ¬a → ¬b ↔ b → a :=
  { mp := fun (h : ¬a → ¬b) (hb : b) => decidable.by_contradiction fun (na : ¬a) => h na hb, mpr := mt }

theorem not_imp_not {a : Prop} {b : Prop} : ¬a → ¬b ↔ b → a :=
  decidable.not_imp_not

@[simp] theorem or_iff_left_iff_imp {a : Prop} {b : Prop} : a ∨ b ↔ a ↔ b → a :=
  { mp := fun (h : a ∨ b ↔ a) (hb : b) => iff.mp h (Or.inr hb), mpr := or_iff_left_of_imp }

@[simp] theorem or_iff_right_iff_imp {a : Prop} {b : Prop} : a ∨ b ↔ b ↔ a → b :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a ∨ b ↔ b ↔ a → b)) (propext (or_comm a b))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (b ∨ a ↔ b ↔ a → b)) (propext or_iff_left_iff_imp))) (iff.refl (a → b)))

/-! ### Declarations about distributivity -/

/-- `∧` distributes over `∨` (on the left). -/
theorem and_or_distrib_left {a : Prop} {b : Prop} {c : Prop} : a ∧ (b ∨ c) ↔ a ∧ b ∨ a ∧ c := sorry

/-- `∧` distributes over `∨` (on the right). -/
theorem or_and_distrib_right {a : Prop} {b : Prop} {c : Prop} : (a ∨ b) ∧ c ↔ a ∧ c ∨ b ∧ c :=
  iff.trans (iff.trans and.comm and_or_distrib_left) (or_congr and.comm and.comm)

/-- `∨` distributes over `∧` (on the left). -/
theorem or_and_distrib_left {a : Prop} {b : Prop} {c : Prop} : a ∨ b ∧ c ↔ (a ∨ b) ∧ (a ∨ c) :=
  { mp := Or._oldrec (fun (ha : a) => { left := Or.inl ha, right := Or.inl ha }) (and.imp Or.inr Or.inr),
    mpr := And._oldrec (Or._oldrec (imp_intro ∘ Or.inl) (or.imp_right ∘ And.intro)) }

/-- `∨` distributes over `∧` (on the right). -/
theorem and_or_distrib_right {a : Prop} {b : Prop} {c : Prop} : a ∧ b ∨ c ↔ (a ∨ c) ∧ (b ∨ c) :=
  iff.trans (iff.trans or.comm or_and_distrib_left) (and_congr or.comm or.comm)

@[simp] theorem or_self_left {a : Prop} {b : Prop} : a ∨ a ∨ b ↔ a ∨ b :=
  { mp := fun (h : a ∨ a ∨ b) => or.elim h Or.inl id, mpr := fun (h : a ∨ b) => or.elim h Or.inl (Or.inr ∘ Or.inr) }

@[simp] theorem or_self_right {a : Prop} {b : Prop} : (a ∨ b) ∨ b ↔ a ∨ b :=
  { mp := fun (h : (a ∨ b) ∨ b) => or.elim h id Or.inr, mpr := fun (h : a ∨ b) => or.elim h (Or.inl ∘ Or.inl) Or.inr }

/-! Declarations about `iff` -/

theorem iff_of_true {a : Prop} {b : Prop} (ha : a) (hb : b) : a ↔ b :=
  { mp := fun (_x : a) => hb, mpr := fun (_x : b) => ha }

theorem iff_of_false {a : Prop} {b : Prop} (ha : ¬a) (hb : ¬b) : a ↔ b :=
  { mp := not.elim ha, mpr := not.elim hb }

theorem iff_true_left {a : Prop} {b : Prop} (ha : a) : a ↔ b ↔ b :=
  { mp := fun (h : a ↔ b) => iff.mp h ha, mpr := iff_of_true ha }

theorem iff_true_right {a : Prop} {b : Prop} (ha : a) : b ↔ a ↔ b :=
  iff.trans iff.comm (iff_true_left ha)

theorem iff_false_left {a : Prop} {b : Prop} (ha : ¬a) : a ↔ b ↔ ¬b :=
  { mp := fun (h : a ↔ b) => mt (iff.mpr h) ha, mpr := iff_of_false ha }

theorem iff_false_right {a : Prop} {b : Prop} (ha : ¬a) : b ↔ a ↔ ¬b :=
  iff.trans iff.comm (iff_false_left ha)

-- See Note [decidable namespace]

protected theorem decidable.not_or_of_imp {a : Prop} {b : Prop} [Decidable a] (h : a → b) : ¬a ∨ b :=
  dite a (fun (ha : a) => Or.inr (h ha)) fun (ha : ¬a) => Or.inl ha

theorem not_or_of_imp {a : Prop} {b : Prop} : (a → b) → ¬a ∨ b :=
  decidable.not_or_of_imp

-- See Note [decidable namespace]

protected theorem decidable.imp_iff_not_or {a : Prop} {b : Prop} [Decidable a] : a → b ↔ ¬a ∨ b :=
  { mp := decidable.not_or_of_imp, mpr := or.neg_resolve_left }

theorem imp_iff_not_or {a : Prop} {b : Prop} : a → b ↔ ¬a ∨ b :=
  decidable.imp_iff_not_or

-- See Note [decidable namespace]

protected theorem decidable.imp_or_distrib {a : Prop} {b : Prop} {c : Prop} [Decidable a] : a → b ∨ c ↔ (a → b) ∨ (a → c) := sorry

theorem imp_or_distrib {a : Prop} {b : Prop} {c : Prop} : a → b ∨ c ↔ (a → b) ∨ (a → c) :=
  decidable.imp_or_distrib

-- See Note [decidable namespace]

protected theorem decidable.imp_or_distrib' {a : Prop} {b : Prop} {c : Prop} [Decidable b] : a → b ∨ c ↔ (a → b) ∨ (a → c) := sorry

theorem imp_or_distrib' {a : Prop} {b : Prop} {c : Prop} : a → b ∨ c ↔ (a → b) ∨ (a → c) :=
  decidable.imp_or_distrib'

theorem not_imp_of_and_not {a : Prop} {b : Prop} : a ∧ ¬b → ¬(a → b) :=
  fun (ᾰ : a ∧ ¬b) (ᾰ_1 : a → b) => and.dcases_on ᾰ fun (ᾰ_left : a) (ᾰ_right : ¬b) => idRhs False (ᾰ_right (ᾰ_1 ᾰ_left))

-- See Note [decidable namespace]

protected theorem decidable.not_imp {a : Prop} {b : Prop} [Decidable a] : ¬(a → b) ↔ a ∧ ¬b :=
  { mp := fun (h : ¬(a → b)) => { left := decidable.of_not_imp h, right := not_of_not_imp h }, mpr := not_imp_of_and_not }

theorem not_imp {a : Prop} {b : Prop} : ¬(a → b) ↔ a ∧ ¬b :=
  decidable.not_imp

-- for monotonicity

theorem imp_imp_imp {a : Prop} {b : Prop} {c : Prop} {d : Prop} (h₀ : c → a) (h₁ : b → d) : (a → b) → c → d :=
  fun (h₂ : a → b) => h₁ ∘ h₂ ∘ h₀

-- See Note [decidable namespace]

protected theorem decidable.peirce (a : Prop) (b : Prop) [Decidable a] : ((a → b) → a) → a :=
  dite a (fun (ha : a) (h : (a → b) → a) => ha) fun (ha : ¬a) (h : (a → b) → a) => h (not.elim ha)

theorem peirce (a : Prop) (b : Prop) : ((a → b) → a) → a :=
  decidable.peirce a b

theorem peirce' {a : Prop} (H : ∀ (b : Prop), (a → b) → a) : a :=
  H a id

-- See Note [decidable namespace]

protected theorem decidable.not_iff_not {a : Prop} {b : Prop} [Decidable a] [Decidable b] : ¬a ↔ ¬b ↔ (a ↔ b) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (¬a ↔ ¬b ↔ (a ↔ b))) (propext iff_def)))
    (eq.mpr (id (Eq._oldrec (Eq.refl ((¬a → ¬b) ∧ (¬b → ¬a) ↔ (a ↔ b))) (propext iff_def')))
      (and_congr decidable.not_imp_not decidable.not_imp_not))

theorem not_iff_not {a : Prop} {b : Prop} : ¬a ↔ ¬b ↔ (a ↔ b) :=
  decidable.not_iff_not

-- See Note [decidable namespace]

protected theorem decidable.not_iff_comm {a : Prop} {b : Prop} [Decidable a] [Decidable b] : ¬a ↔ b ↔ (¬b ↔ a) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (¬a ↔ b ↔ (¬b ↔ a))) (propext iff_def)))
    (eq.mpr (id (Eq._oldrec (Eq.refl ((¬a → b) ∧ (b → ¬a) ↔ (¬b ↔ a))) (propext iff_def)))
      (and_congr decidable.not_imp_comm imp_not_comm))

theorem not_iff_comm {a : Prop} {b : Prop} : ¬a ↔ b ↔ (¬b ↔ a) :=
  decidable.not_iff_comm

-- See Note [decidable namespace]

protected theorem decidable.not_iff {a : Prop} {b : Prop} [Decidable b] : ¬(a ↔ b) ↔ (¬a ↔ b) := sorry

theorem not_iff {a : Prop} {b : Prop} : ¬(a ↔ b) ↔ (¬a ↔ b) :=
  decidable.not_iff

-- See Note [decidable namespace]

protected theorem decidable.iff_not_comm {a : Prop} {b : Prop} [Decidable a] [Decidable b] : a ↔ ¬b ↔ (b ↔ ¬a) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a ↔ ¬b ↔ (b ↔ ¬a))) (propext iff_def)))
    (eq.mpr (id (Eq._oldrec (Eq.refl ((a → ¬b) ∧ (¬b → a) ↔ (b ↔ ¬a))) (propext iff_def)))
      (and_congr imp_not_comm decidable.not_imp_comm))

theorem iff_not_comm {a : Prop} {b : Prop} : a ↔ ¬b ↔ (b ↔ ¬a) :=
  decidable.iff_not_comm

-- See Note [decidable namespace]

protected theorem decidable.iff_iff_and_or_not_and_not {a : Prop} {b : Prop} [Decidable b] : a ↔ b ↔ a ∧ b ∨ ¬a ∧ ¬b := sorry

theorem iff_iff_and_or_not_and_not {a : Prop} {b : Prop} : a ↔ b ↔ a ∧ b ∨ ¬a ∧ ¬b :=
  decidable.iff_iff_and_or_not_and_not

theorem decidable.iff_iff_not_or_and_or_not {a : Prop} {b : Prop} [Decidable a] [Decidable b] : a ↔ b ↔ (¬a ∨ b) ∧ (a ∨ ¬b) := sorry

theorem iff_iff_not_or_and_or_not {a : Prop} {b : Prop} : a ↔ b ↔ (¬a ∨ b) ∧ (a ∨ ¬b) :=
  decidable.iff_iff_not_or_and_or_not

-- See Note [decidable namespace]

protected theorem decidable.not_and_not_right {a : Prop} {b : Prop} [Decidable b] : ¬(a ∧ ¬b) ↔ a → b := sorry

theorem not_and_not_right {a : Prop} {b : Prop} : ¬(a ∧ ¬b) ↔ a → b :=
  decidable.not_and_not_right

/-- Transfer decidability of `a` to decidability of `b`, if the propositions are equivalent.
**Important**: this function should be used instead of `rw` on `decidable b`, because the
kernel will get stuck reducing the usage of `propext` otherwise,
and `dec_trivial` will not work. -/
def decidable_of_iff {b : Prop} (a : Prop) (h : a ↔ b) [D : Decidable a] : Decidable b :=
  decidable_of_decidable_of_iff D h

/-- Transfer decidability of `b` to decidability of `a`, if the propositions are equivalent.
This is the same as `decidable_of_iff` but the iff is flipped. -/
def decidable_of_iff' {a : Prop} (b : Prop) (h : a ↔ b) [D : Decidable b] : Decidable a :=
  decidable_of_decidable_of_iff D (iff.symm h)

/-- Prove that `a` is decidable by constructing a boolean `b` and a proof that `b ↔ a`.
(This is sometimes taken as an alternate definition of decidability.) -/
def decidable_of_bool {a : Prop} (b : Bool) (h : ↥b ↔ a) : Decidable a :=
  sorry

/-! ### De Morgan's laws -/

theorem not_and_of_not_or_not {a : Prop} {b : Prop} (h : ¬a ∨ ¬b) : ¬(a ∧ b) :=
  fun (ᾰ : a ∧ b) =>
    and.dcases_on ᾰ fun (ᾰ_left : a) (ᾰ_right : b) => idRhs False (or.elim h (absurd ᾰ_left) (absurd ᾰ_right))

-- See Note [decidable namespace]

protected theorem decidable.not_and_distrib {a : Prop} {b : Prop} [Decidable a] : ¬(a ∧ b) ↔ ¬a ∨ ¬b := sorry

-- See Note [decidable namespace]

protected theorem decidable.not_and_distrib' {a : Prop} {b : Prop} [Decidable b] : ¬(a ∧ b) ↔ ¬a ∨ ¬b := sorry

/-- One of de Morgan's laws: the negation of a conjunction is logically equivalent to the
disjunction of the negations. -/
theorem not_and_distrib {a : Prop} {b : Prop} : ¬(a ∧ b) ↔ ¬a ∨ ¬b :=
  decidable.not_and_distrib

@[simp] theorem not_and {a : Prop} {b : Prop} : ¬(a ∧ b) ↔ a → ¬b :=
  and_imp

theorem not_and' {a : Prop} {b : Prop} : ¬(a ∧ b) ↔ b → ¬a :=
  iff.trans not_and imp_not_comm

/-- One of de Morgan's laws: the negation of a disjunction is logically equivalent to the
conjunction of the negations. -/
theorem not_or_distrib {a : Prop} {b : Prop} : ¬(a ∨ b) ↔ ¬a ∧ ¬b := sorry

-- See Note [decidable namespace]

protected theorem decidable.or_iff_not_and_not {a : Prop} {b : Prop} [Decidable a] [Decidable b] : a ∨ b ↔ ¬(¬a ∧ ¬b) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a ∨ b ↔ ¬(¬a ∧ ¬b))) (Eq.symm (propext not_or_distrib))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a ∨ b ↔ ¬¬(a ∨ b))) (propext decidable.not_not))) (iff.refl (a ∨ b)))

theorem or_iff_not_and_not {a : Prop} {b : Prop} : a ∨ b ↔ ¬(¬a ∧ ¬b) :=
  decidable.or_iff_not_and_not

-- See Note [decidable namespace]

protected theorem decidable.and_iff_not_or_not {a : Prop} {b : Prop} [Decidable a] [Decidable b] : a ∧ b ↔ ¬(¬a ∨ ¬b) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a ∧ b ↔ ¬(¬a ∨ ¬b))) (Eq.symm (propext decidable.not_and_distrib))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a ∧ b ↔ ¬¬(a ∧ b))) (propext decidable.not_not))) (iff.refl (a ∧ b)))

theorem and_iff_not_or_not {a : Prop} {b : Prop} : a ∧ b ↔ ¬(¬a ∨ ¬b) :=
  decidable.and_iff_not_or_not

/-! ### Declarations about equality -/

@[simp] theorem heq_iff_eq {α : Sort u_1} {a : α} {b : α} : a == b ↔ a = b :=
  { mp := eq_of_heq, mpr := heq_of_eq }

theorem proof_irrel_heq {p : Prop} {q : Prop} (hp : p) (hq : q) : hp == hq :=
  (fun (this : p = q) => Eq._oldrec (fun (hq : p) => HEq.refl hp) this hq)
    (propext { mp := fun (_x : p) => hq, mpr := fun (_x : q) => hp })

theorem ne_of_mem_of_not_mem {α : outParam (Type u_1)} {β : Type u_2} [has_mem α β] {s : β} {a : α} {b : α} (h : a ∈ s) : ¬b ∈ s → a ≠ b :=
  mt fun (e : a = b) => e ▸ h

theorem eq_equivalence {α : Sort u_1} : equivalence Eq :=
  { left := Eq.refl, right := { left := Eq.symm, right := Eq.trans } }

/-- Transport through trivial families is the identity. -/
@[simp] theorem eq_rec_constant {α : Sort u_1} {a : α} {a' : α} {β : Sort u_2} (y : β) (h : a = a') : Eq._oldrec y h = y := sorry

@[simp] theorem eq_mp_rfl {α : Sort u_1} {a : α} : eq.mp (Eq.refl α) a = a :=
  rfl

@[simp] theorem eq_mpr_rfl {α : Sort u_1} {a : α} : eq.mpr (Eq.refl α) a = a :=
  rfl

theorem heq_of_eq_mp {α : Sort u_1} {β : Sort u_1} {a : α} {a' : β} (e : α = β) (h₂ : eq.mp e a = a') : a == a' := sorry

theorem rec_heq_of_heq {α : Sort u_1} {a : α} {b : α} {β : Sort u_2} {C : α → Sort u_2} {x : C a} {y : β} (eq : a = b) (h : x == y) : Eq._oldrec x eq == y :=
  eq.drec h eq

@[simp] theorem eq_mpr_heq {α : Sort u} {β : Sort u} (h : β = α) (x : α) : eq.mpr h x == x :=
  eq.drec (fun (x : β) => HEq.refl (eq.mpr (Eq.refl β) x)) h x

protected theorem eq.congr {α : Sort u_1} {x₁ : α} {x₂ : α} {y₁ : α} {y₂ : α} (h₁ : x₁ = y₁) (h₂ : x₂ = y₂) : x₁ = x₂ ↔ y₁ = y₂ :=
  Eq._oldrec (Eq._oldrec (iff.refl (x₁ = x₂)) h₂) h₁

theorem eq.congr_left {α : Sort u_1} {x : α} {y : α} {z : α} (h : x = y) : x = z ↔ y = z :=
  eq.mpr (id (Eq._oldrec (Eq.refl (x = z ↔ y = z)) h)) (iff.refl (y = z))

theorem eq.congr_right {α : Sort u_1} {x : α} {y : α} {z : α} (h : x = y) : z = x ↔ z = y :=
  eq.mpr (id (Eq._oldrec (Eq.refl (z = x ↔ z = y)) h)) (iff.refl (z = y))

theorem congr_arg2 {α : Type u_1} {β : Type u_2} {γ : Type u_3} (f : α → β → γ) {x : α} {x' : α} {y : β} {y' : β} (hx : x = x') (hy : y = y') : f x y = f x' y' :=
  Eq._oldrec (Eq._oldrec (Eq.refl (f x y)) hy) hx

/-! ### Declarations about quantifiers -/

theorem forall_imp {α : Sort u_1} {p : α → Prop} {q : α → Prop} (h : ∀ (a : α), p a → q a) : (∀ (a : α), p a) → ∀ (a : α), q a :=
  fun (h' : ∀ (a : α), p a) (a : α) => h a (h' a)

theorem forall₂_congr {α : Sort u_1} {β : Sort u_2} {p : α → β → Prop} {q : α → β → Prop} (h : ∀ (a : α) (b : β), p a b ↔ q a b) : (∀ (a : α) (b : β), p a b) ↔ ∀ (a : α) (b : β), q a b :=
  forall_congr fun (a : α) => forall_congr (h a)

theorem forall₃_congr {α : Sort u_1} {β : Sort u_2} {γ : Sort u_3} {p : α → β → γ → Prop} {q : α → β → γ → Prop} (h : ∀ (a : α) (b : β) (c : γ), p a b c ↔ q a b c) : (∀ (a : α) (b : β) (c : γ), p a b c) ↔ ∀ (a : α) (b : β) (c : γ), q a b c :=
  forall_congr fun (a : α) => forall₂_congr (h a)

theorem forall₄_congr {α : Sort u_1} {β : Sort u_2} {γ : Sort u_3} {δ : Sort u_4} {p : α → β → γ → δ → Prop} {q : α → β → γ → δ → Prop} (h : ∀ (a : α) (b : β) (c : γ) (d : δ), p a b c d ↔ q a b c d) : (∀ (a : α) (b : β) (c : γ) (d : δ), p a b c d) ↔ ∀ (a : α) (b : β) (c : γ) (d : δ), q a b c d :=
  forall_congr fun (a : α) => forall₃_congr (h a)

theorem Exists.imp {α : Sort u_1} {q : α → Prop} {p : α → Prop} (h : ∀ (a : α), p a → q a) : (∃ (a : α), p a) → ∃ (a : α), q a :=
  fun (p_1 : ∃ (a : α), p a) => exists_imp_exists h p_1

theorem exists_imp_exists' {α : Sort u_1} {β : Sort u_2} {p : α → Prop} {q : β → Prop} (f : α → β) (hpq : ∀ (a : α), p a → q (f a)) (hp : ∃ (a : α), p a) : ∃ (b : β), q b :=
  exists.elim hp fun (a : α) (hp' : p a) => Exists.intro (f a) (hpq a hp')

theorem exists₂_congr {α : Sort u_1} {β : Sort u_2} {p : α → β → Prop} {q : α → β → Prop} (h : ∀ (a : α) (b : β), p a b ↔ q a b) : (∃ (a : α), ∃ (b : β), p a b) ↔ ∃ (a : α), ∃ (b : β), q a b :=
  exists_congr fun (a : α) => exists_congr (h a)

theorem exists₃_congr {α : Sort u_1} {β : Sort u_2} {γ : Sort u_3} {p : α → β → γ → Prop} {q : α → β → γ → Prop} (h : ∀ (a : α) (b : β) (c : γ), p a b c ↔ q a b c) : (∃ (a : α), ∃ (b : β), ∃ (c : γ), p a b c) ↔ ∃ (a : α), ∃ (b : β), ∃ (c : γ), q a b c :=
  exists_congr fun (a : α) => exists₂_congr (h a)

theorem exists₄_congr {α : Sort u_1} {β : Sort u_2} {γ : Sort u_3} {δ : Sort u_4} {p : α → β → γ → δ → Prop} {q : α → β → γ → δ → Prop} (h : ∀ (a : α) (b : β) (c : γ) (d : δ), p a b c d ↔ q a b c d) : (∃ (a : α), ∃ (b : β), ∃ (c : γ), ∃ (d : δ), p a b c d) ↔ ∃ (a : α), ∃ (b : β), ∃ (c : γ), ∃ (d : δ), q a b c d :=
  exists_congr fun (a : α) => exists₃_congr (h a)

theorem forall_swap {α : Sort u_1} {β : Sort u_2} {p : α → β → Prop} : (∀ (x : α) (y : β), p x y) ↔ ∀ (y : β) (x : α), p x y :=
  { mp := function.swap, mpr := function.swap }

theorem exists_swap {α : Sort u_1} {β : Sort u_2} {p : α → β → Prop} : (∃ (x : α), ∃ (y : β), p x y) ↔ ∃ (y : β), ∃ (x : α), p x y := sorry

@[simp] theorem exists_imp_distrib {α : Sort u_1} {p : α → Prop} {b : Prop} : (∃ (x : α), p x) → b ↔ ∀ (x : α), p x → b := sorry

/--
Extract an element from a existential statement, using `classical.some`.
-/
-- This enables projection notation.

def Exists.some {α : Sort u_1} {p : α → Prop} (P : ∃ (a : α), p a) : α :=
  classical.some P

/--
Show that an element extracted from `P : ∃ a, p a` using `P.some` satisfies `p`.
-/
theorem Exists.some_spec {α : Sort u_1} {p : α → Prop} (P : ∃ (a : α), p a) : p (Exists.some P) :=
  classical.some_spec P

--theorem forall_not_of_not_exists (h : ¬ ∃ x, p x) : ∀ x, ¬ p x :=

--forall_imp_of_exists_imp h

theorem not_exists_of_forall_not {α : Sort u_1} {p : α → Prop} (h : ∀ (x : α), ¬p x) : ¬∃ (x : α), p x :=
  iff.mpr exists_imp_distrib h

@[simp] theorem not_exists {α : Sort u_1} {p : α → Prop} : (¬∃ (x : α), p x) ↔ ∀ (x : α), ¬p x :=
  exists_imp_distrib

theorem not_forall_of_exists_not {α : Sort u_1} {p : α → Prop} : (∃ (x : α), ¬p x) → ¬∀ (x : α), p x :=
  fun (ᾰ : ∃ (x : α), ¬p x) (ᾰ_1 : ∀ (x : α), p x) =>
    Exists.dcases_on ᾰ fun (ᾰ_w : α) (ᾰ_h : ¬p ᾰ_w) => idRhs False (ᾰ_h (ᾰ_1 ᾰ_w))

-- See Note [decidable namespace]

protected theorem decidable.not_forall {α : Sort u_1} {p : α → Prop} [Decidable (∃ (x : α), ¬p x)] [(x : α) → Decidable (p x)] : (¬∀ (x : α), p x) ↔ ∃ (x : α), ¬p x := sorry

@[simp] theorem not_forall {α : Sort u_1} {p : α → Prop} : (¬∀ (x : α), p x) ↔ ∃ (x : α), ¬p x :=
  decidable.not_forall

-- See Note [decidable namespace]

protected theorem decidable.not_forall_not {α : Sort u_1} {p : α → Prop} [Decidable (∃ (x : α), p x)] : (¬∀ (x : α), ¬p x) ↔ ∃ (x : α), p x :=
  iff.mp decidable.not_iff_comm not_exists

theorem not_forall_not {α : Sort u_1} {p : α → Prop} : (¬∀ (x : α), ¬p x) ↔ ∃ (x : α), p x :=
  decidable.not_forall_not

-- See Note [decidable namespace]

protected theorem decidable.not_exists_not {α : Sort u_1} {p : α → Prop} [(x : α) → Decidable (p x)] : (¬∃ (x : α), ¬p x) ↔ ∀ (x : α), p x := sorry

@[simp] theorem not_exists_not {α : Sort u_1} {p : α → Prop} : (¬∃ (x : α), ¬p x) ↔ ∀ (x : α), p x :=
  decidable.not_exists_not

@[simp] theorem forall_true_iff {α : Sort u_1} : α → True ↔ True :=
  iff_true_intro fun (_x : α) => trivial

-- Unfortunately this causes simp to loop sometimes, so we

-- add the 2 and 3 cases as simp lemmas instead

theorem forall_true_iff' {α : Sort u_1} {p : α → Prop} (h : ∀ (a : α), p a ↔ True) : (∀ (a : α), p a) ↔ True :=
  iff_true_intro fun (_x : α) => of_iff_true (h _x)

@[simp] theorem forall_2_true_iff {α : Sort u_1} {β : α → Sort u_2} : (∀ (a : α), β a → True) ↔ True :=
  forall_true_iff' fun (_x : α) => forall_true_iff

@[simp] theorem forall_3_true_iff {α : Sort u_1} {β : α → Sort u_2} {γ : (a : α) → β a → Sort u_3} : (∀ (a : α) (b : β a), γ a b → True) ↔ True :=
  forall_true_iff' fun (_x : α) => forall_2_true_iff

@[simp] theorem forall_const {b : Prop} (α : Sort u_1) [i : Nonempty α] : α → b ↔ b :=
  { mp := nonempty.elim i, mpr := fun (hb : b) (x : α) => hb }

@[simp] theorem exists_const {b : Prop} (α : Sort u_1) [i : Nonempty α] : (∃ (x : α), b) ↔ b :=
  { mp := fun (_x : ∃ (x : α), b) => (fun (_a : ∃ (x : α), b) => Exists.dcases_on _a fun (w : α) (h : b) => idRhs b h) _x,
    mpr := nonempty.elim i exists.intro }

theorem forall_and_distrib {α : Sort u_1} {p : α → Prop} {q : α → Prop} : (∀ (x : α), p x ∧ q x) ↔ (∀ (x : α), p x) ∧ ∀ (x : α), q x := sorry

theorem exists_or_distrib {α : Sort u_1} {p : α → Prop} {q : α → Prop} : (∃ (x : α), p x ∨ q x) ↔ (∃ (x : α), p x) ∨ ∃ (x : α), q x := sorry

@[simp] theorem exists_and_distrib_left {α : Sort u_1} {q : Prop} {p : α → Prop} : (∃ (x : α), q ∧ p x) ↔ q ∧ ∃ (x : α), p x := sorry

@[simp] theorem exists_and_distrib_right {α : Sort u_1} {q : Prop} {p : α → Prop} : (∃ (x : α), p x ∧ q) ↔ (∃ (x : α), p x) ∧ q := sorry

@[simp] theorem forall_eq {α : Sort u_1} {p : α → Prop} {a' : α} : (∀ (a : α), a = a' → p a) ↔ p a' :=
  { mp := fun (h : ∀ (a : α), a = a' → p a) => h a' rfl, mpr := fun (h : p a') (a : α) (e : a = a') => Eq.symm e ▸ h }

@[simp] theorem forall_eq' {α : Sort u_1} {p : α → Prop} {a' : α} : (∀ (a : α), a' = a → p a) ↔ p a' := sorry

-- this lemma is needed to simplify the output of `list.mem_cons_iff`

@[simp] theorem forall_eq_or_imp {α : Sort u_1} {p : α → Prop} {q : α → Prop} {a' : α} : (∀ (a : α), a = a' ∨ q a → p a) ↔ p a' ∧ ∀ (a : α), q a → p a := sorry

@[simp] theorem exists_eq {α : Sort u_1} {a' : α} : ∃ (a : α), a = a' :=
  Exists.intro a' rfl

@[simp] theorem exists_eq' {α : Sort u_1} {a' : α} : ∃ (a : α), a' = a :=
  Exists.intro a' rfl

@[simp] theorem exists_eq_left {α : Sort u_1} {p : α → Prop} {a' : α} : (∃ (a : α), a = a' ∧ p a) ↔ p a' := sorry

@[simp] theorem exists_eq_right {α : Sort u_1} {p : α → Prop} {a' : α} : (∃ (a : α), p a ∧ a = a') ↔ p a' :=
  iff.trans (exists_congr fun (a : α) => and.comm) exists_eq_left

@[simp] theorem exists_eq_right_right {α : Sort u_1} {p : α → Prop} {b : Prop} {a' : α} : (∃ (a : α), p a ∧ b ∧ a = a') ↔ p a' ∧ b := sorry

@[simp] theorem exists_eq_right_right' {α : Sort u_1} {p : α → Prop} {b : Prop} {a' : α} : (∃ (a : α), p a ∧ b ∧ a' = a) ↔ p a' ∧ b := sorry

@[simp] theorem exists_apply_eq_apply {α : Type u_1} {β : Type u_2} (f : α → β) (a' : α) : ∃ (a : α), f a = f a' :=
  Exists.intro a' rfl

@[simp] theorem exists_apply_eq_apply' {α : Type u_1} {β : Type u_2} (f : α → β) (a' : α) : ∃ (a : α), f a' = f a :=
  Exists.intro a' rfl

@[simp] theorem exists_exists_and_eq_and {α : Sort u_1} {β : Sort u_2} {f : α → β} {p : α → Prop} {q : β → Prop} : (∃ (b : β), (∃ (a : α), p a ∧ f a = b) ∧ q b) ↔ ∃ (a : α), p a ∧ q (f a) := sorry

@[simp] theorem exists_exists_eq_and {α : Sort u_1} {β : Sort u_2} {f : α → β} {p : β → Prop} : (∃ (b : β), (∃ (a : α), f a = b) ∧ p b) ↔ ∃ (a : α), p (f a) := sorry

@[simp] theorem forall_apply_eq_imp_iff {α : Sort u_1} {β : Sort u_2} {f : α → β} {p : β → Prop} : (∀ (a : α) (b : β), f a = b → p b) ↔ ∀ (a : α), p (f a) :=
  { mp := fun (h : ∀ (a : α) (b : β), f a = b → p b) (a : α) => h a (f a) rfl,
    mpr := fun (h : ∀ (a : α), p (f a)) (a : α) (b : β) (hab : f a = b) => hab ▸ h a }

@[simp] theorem forall_apply_eq_imp_iff' {α : Sort u_1} {β : Sort u_2} {f : α → β} {p : β → Prop} : (∀ (b : β) (a : α), f a = b → p b) ↔ ∀ (a : α), p (f a) := sorry

@[simp] theorem forall_eq_apply_imp_iff {α : Sort u_1} {β : Sort u_2} {f : α → β} {p : β → Prop} : (∀ (a : α) (b : β), b = f a → p b) ↔ ∀ (a : α), p (f a) := sorry

@[simp] theorem forall_eq_apply_imp_iff' {α : Sort u_1} {β : Sort u_2} {f : α → β} {p : β → Prop} : (∀ (b : β) (a : α), b = f a → p b) ↔ ∀ (a : α), p (f a) := sorry

@[simp] theorem forall_apply_eq_imp_iff₂ {α : Sort u_1} {β : Sort u_2} {f : α → β} {p : α → Prop} {q : β → Prop} : (∀ (b : β) (a : α), p a → f a = b → q b) ↔ ∀ (a : α), p a → q (f a) :=
  { mp := fun (h : ∀ (b : β) (a : α), p a → f a = b → q b) (a : α) (ha : p a) => h (f a) a ha rfl,
    mpr := fun (h : ∀ (a : α), p a → q (f a)) (b : β) (a : α) (ha : p a) (hb : f a = b) => hb ▸ h a ha }

@[simp] theorem exists_eq_left' {α : Sort u_1} {p : α → Prop} {a' : α} : (∃ (a : α), a' = a ∧ p a) ↔ p a' := sorry

@[simp] theorem exists_eq_right' {α : Sort u_1} {p : α → Prop} {a' : α} : (∃ (a : α), p a ∧ a' = a) ↔ p a' := sorry

theorem exists_comm {α : Sort u_1} {β : Sort u_2} {p : α → β → Prop} : (∃ (a : α), ∃ (b : β), p a b) ↔ ∃ (b : β), ∃ (a : α), p a b := sorry

theorem forall_or_of_or_forall {α : Sort u_1} {p : α → Prop} {b : Prop} (h : b ∨ ∀ (x : α), p x) (x : α) : b ∨ p x :=
  or.imp_right (fun (h₂ : ∀ (x : α), p x) => h₂ x) h

-- See Note [decidable namespace]

protected theorem decidable.forall_or_distrib_left {α : Sort u_1} {q : Prop} {p : α → Prop} [Decidable q] : (∀ (x : α), q ∨ p x) ↔ q ∨ ∀ (x : α), p x := sorry

theorem forall_or_distrib_left {α : Sort u_1} {q : Prop} {p : α → Prop} : (∀ (x : α), q ∨ p x) ↔ q ∨ ∀ (x : α), p x :=
  decidable.forall_or_distrib_left

-- See Note [decidable namespace]

protected theorem decidable.forall_or_distrib_right {α : Sort u_1} {q : Prop} {p : α → Prop} [Decidable q] : (∀ (x : α), p x ∨ q) ↔ (∀ (x : α), p x) ∨ q := sorry

theorem forall_or_distrib_right {α : Sort u_1} {q : Prop} {p : α → Prop} : (∀ (x : α), p x ∨ q) ↔ (∀ (x : α), p x) ∨ q :=
  decidable.forall_or_distrib_right

/-- A predicate holds everywhere on the image of a surjective functions iff
    it holds everywhere. -/
theorem forall_iff_forall_surj {α : Type u_1} {β : Type u_2} {f : α → β} (h : function.surjective f) {P : β → Prop} : (∀ (a : α), P (f a)) ↔ ∀ (b : β), P b := sorry

@[simp] theorem exists_prop {p : Prop} {q : Prop} : (∃ (h : p), q) ↔ p ∧ q := sorry

@[simp] theorem exists_false {α : Sort u_1} : ¬∃ (a : α), False :=
  fun (_x : ∃ (a : α), False) =>
    (fun (_a : ∃ (a : α), False) => Exists.dcases_on _a fun (w : α) (h : False) => idRhs False h) _x

@[simp] theorem exists_unique_false {α : Sort u_1} : ¬exists_unique fun (a : α) => False := sorry

theorem Exists.fst {b : Prop} {p : b → Prop} : Exists p → b :=
  fun (ᾰ : Exists p) => Exists.dcases_on ᾰ fun (ᾰ_w : b) (ᾰ_h : p ᾰ_w) => idRhs b ᾰ_w

theorem Exists.snd {b : Prop} {p : b → Prop} (h : Exists p) : p (Exists.fst h) :=
  Exists.dcases_on h fun (h_w : b) (h_h : p h_w) => idRhs (p h_w) h_h

@[simp] theorem forall_prop_of_true {p : Prop} {q : p → Prop} (h : p) : (∀ (h' : p), q h') ↔ q h :=
  forall_const p

@[simp] theorem exists_prop_of_true {p : Prop} {q : p → Prop} (h : p) : (∃ (h' : p), q h') ↔ q h :=
  exists_const p

@[simp] theorem forall_prop_of_false {p : Prop} {q : p → Prop} (hn : ¬p) : (∀ (h' : p), q h') ↔ True :=
  iff_true_intro fun (h : p) => not.elim hn h

@[simp] theorem exists_prop_of_false {p : Prop} {q : p → Prop} : ¬p → ¬∃ (h' : p), q h' :=
  mt Exists.fst

theorem exists_unique.exists {α : Sort u_1} {p : α → Prop} (h : exists_unique fun (x : α) => p x) : ∃ (x : α), p x :=
  exists.elim h fun (x : α) (hx : (fun (x : α) => p x) x ∧ ∀ (y : α), p y → y = x) => Exists.intro x (and.left hx)

theorem exists_unique.unique {α : Sort u_1} {p : α → Prop} (h : exists_unique fun (x : α) => p x) {y₁ : α} {y₂ : α} (py₁ : p y₁) (py₂ : p y₂) : y₁ = y₂ :=
  unique_of_exists_unique h py₁ py₂

@[simp] theorem exists_unique_iff_exists {α : Sort u_1} [subsingleton α] {p : α → Prop} : (exists_unique fun (x : α) => p x) ↔ ∃ (x : α), p x :=
  { mp := fun (h : exists_unique fun (x : α) => p x) => exists_unique.exists h,
    mpr := Exists.imp fun (x : α) (hx : p x) => { left := hx, right := fun (y : α) (_x : p y) => subsingleton.elim y x } }

theorem exists_unique.elim2 {α : Sort u_1} {p : α → Sort u_2} [∀ (x : α), subsingleton (p x)] {q : (x : α) → p x → Prop} {b : Prop} (h₂ : exists_unique fun (x : α) => exists_unique fun (h : p x) => q x h) (h₁ : ∀ (x : α) (h : p x), q x h → (∀ (y : α) (hy : p y), q y hy → y = x) → b) : b := sorry

theorem exists_unique.intro2 {α : Sort u_1} {p : α → Sort u_2} [∀ (x : α), subsingleton (p x)] {q : (x : α) → p x → Prop} (w : α) (hp : p w) (hq : q w hp) (H : ∀ (y : α) (hy : p y), q y hy → y = w) : exists_unique fun (x : α) => exists_unique fun (hx : p x) => q x hx := sorry

theorem exists_unique.exists2 {α : Sort u_1} {p : α → Sort u_2} {q : (x : α) → p x → Prop} (h : exists_unique fun (x : α) => exists_unique fun (hx : p x) => q x hx) : ∃ (x : α), ∃ (hx : p x), q x hx :=
  Exists.imp (fun (x : α) (hx : exists_unique fun (hx : p x) => q x hx) => exists_unique.exists hx)
    (exists_unique.exists h)

theorem exists_unique.unique2 {α : Sort u_1} {p : α → Sort u_2} [∀ (x : α), subsingleton (p x)] {q : (x : α) → p x → Prop} (h : exists_unique fun (x : α) => exists_unique fun (hx : p x) => q x hx) {y₁ : α} {y₂ : α} (hpy₁ : p y₁) (hqy₁ : q y₁ hpy₁) (hpy₂ : p y₂) (hqy₂ : q y₂ hpy₂) : y₁ = y₂ := sorry

/-! ### Classical lemmas -/

namespace classical

theorem cases {p : Prop → Prop} (h1 : p True) (h2 : p False) (a : Prop) : p a :=
  cases_on a h1 h2

/- use shortened names to avoid conflict when classical namespace is open. -/

theorem dec (p : Prop) : Decidable p :=
  prop_decidable p

theorem dec_pred {α : Sort u_1} (p : α → Prop) : decidable_pred p :=
  fun (a : α) => prop_decidable (p a)

theorem dec_rel {α : Sort u_1} (p : α → α → Prop) : DecidableRel p :=
  fun (a b : α) => prop_decidable (p a b)

theorem dec_eq (α : Sort u_1) : DecidableEq α :=
  fun (a b : α) => prop_decidable (a = b)

/--
We make decidability results that depends on `classical.choice` noncomputable lemmas.
* We have to mark them as noncomputable, because otherwise Lean will try to generate bytecode
  for them, and fail because it depends on `classical.choice`.
* We make them lemmas, and not definitions, because otherwise later definitions will raise
  \"failed to generate bytecode\" errors when writing something like
  `letI := classical.dec_eq _`.
Cf. <https://leanprover-community.github.io/archive/113488general/08268noncomputabletheorem.html>
-/
/-- Construct a function from a default value `H0`, and a function to use if there exists a value
satisfying the predicate. -/
def exists_cases {α : Sort u_1} {p : α → Prop} {C : Sort u} (H0 : C) (H : (a : α) → p a → C) : C :=
  dite (∃ (a : α), p a) (fun (h : ∃ (a : α), p a) => H (some h) sorry) fun (h : ¬∃ (a : α), p a) => H0

theorem some_spec2 {α : Sort u_1} {p : α → Prop} {h : ∃ (a : α), p a} (q : α → Prop) (hpq : ∀ (a : α), p a → q a) : q (some h) :=
  hpq (some h) (some_spec h)

/-- A version of classical.indefinite_description which is definitionally equal to a pair -/
def subtype_of_exists {α : Type u_1} {P : α → Prop} (h : ∃ (x : α), P x) : Subtype fun (x : α) => P x :=
  { val := some h, property := sorry }

end classical

/-- This function has the same type as `exists.rec_on`, and can be used to case on an equality,
but `exists.rec_on` can only eliminate into Prop, while this version eliminates into any universe
using the axiom of choice. -/
def exists.classical_rec_on {α : Sort u_1} {p : α → Prop} (h : ∃ (a : α), p a) {C : Sort u} (H : (a : α) → p a → C) : C :=
  H (classical.some h) sorry

/-! ### Declarations about bounded quantifiers -/

theorem bex_def {α : Sort u_1} {p : α → Prop} {q : α → Prop} : (∃ (x : α), ∃ (h : p x), q x) ↔ ∃ (x : α), p x ∧ q x := sorry

theorem bex.elim {α : Sort u_1} {p : α → Prop} {P : (x : α) → p x → Prop} {b : Prop} : (∃ (x : α), ∃ (h : p x), P x h) → (∀ (a : α) (h : p a), P a h → b) → b := sorry

theorem bex.intro {α : Sort u_1} {p : α → Prop} {P : (x : α) → p x → Prop} (a : α) (h₁ : p a) (h₂ : P a h₁) : ∃ (x : α), ∃ (h : p x), P x h :=
  Exists.intro a (Exists.intro h₁ h₂)

theorem ball_congr {α : Sort u_1} {p : α → Prop} {P : (x : α) → p x → Prop} {Q : (x : α) → p x → Prop} (H : ∀ (x : α) (h : p x), P x h ↔ Q x h) : (∀ (x : α) (h : p x), P x h) ↔ ∀ (x : α) (h : p x), Q x h :=
  forall_congr fun (x : α) => forall_congr (H x)

theorem bex_congr {α : Sort u_1} {p : α → Prop} {P : (x : α) → p x → Prop} {Q : (x : α) → p x → Prop} (H : ∀ (x : α) (h : p x), P x h ↔ Q x h) : (∃ (x : α), ∃ (h : p x), P x h) ↔ ∃ (x : α), ∃ (h : p x), Q x h :=
  exists_congr fun (x : α) => exists_congr (H x)

theorem bex_eq_left {α : Sort u_1} {p : α → Prop} {a : α} : (∃ (x : α), ∃ (_x : x = a), p x) ↔ p a := sorry

theorem ball.imp_right {α : Sort u_1} {p : α → Prop} {P : (x : α) → p x → Prop} {Q : (x : α) → p x → Prop} (H : ∀ (x : α) (h : p x), P x h → Q x h) (h₁ : ∀ (x : α) (h : p x), P x h) (x : α) (h : p x) : Q x h :=
  H x h (h₁ x h)

theorem bex.imp_right {α : Sort u_1} {p : α → Prop} {P : (x : α) → p x → Prop} {Q : (x : α) → p x → Prop} (H : ∀ (x : α) (h : p x), P x h → Q x h) : (∃ (x : α), ∃ (h : p x), P x h) → ∃ (x : α), ∃ (h : p x), Q x h := sorry

theorem ball.imp_left {α : Sort u_1} {r : α → Prop} {p : α → Prop} {q : α → Prop} (H : ∀ (x : α), p x → q x) (h₁ : ∀ (x : α), q x → r x) (x : α) (h : p x) : r x :=
  h₁ x (H x h)

theorem bex.imp_left {α : Sort u_1} {r : α → Prop} {p : α → Prop} {q : α → Prop} (H : ∀ (x : α), p x → q x) : (∃ (x : α), ∃ (_x : p x), r x) → ∃ (x : α), ∃ (_x : q x), r x := sorry

theorem ball_of_forall {α : Sort u_1} {p : α → Prop} (h : ∀ (x : α), p x) (x : α) : p x :=
  h x

theorem forall_of_ball {α : Sort u_1} {p : α → Prop} {q : α → Prop} (H : ∀ (x : α), p x) (h : ∀ (x : α), p x → q x) (x : α) : q x :=
  h x (H x)

theorem bex_of_exists {α : Sort u_1} {p : α → Prop} {q : α → Prop} (H : ∀ (x : α), p x) : (∃ (x : α), q x) → ∃ (x : α), ∃ (_x : p x), q x :=
  fun (ᾰ : ∃ (x : α), q x) =>
    Exists.dcases_on ᾰ
      fun (ᾰ_w : α) (ᾰ_h : q ᾰ_w) => idRhs (∃ (x : α), ∃ (_x : p x), q x) (Exists.intro ᾰ_w (Exists.intro (H ᾰ_w) ᾰ_h))

theorem exists_of_bex {α : Sort u_1} {p : α → Prop} {q : α → Prop} : (∃ (x : α), ∃ (_x : p x), q x) → ∃ (x : α), q x := sorry

@[simp] theorem bex_imp_distrib {α : Sort u_1} {p : α → Prop} {P : (x : α) → p x → Prop} {b : Prop} : (∃ (x : α), ∃ (h : p x), P x h) → b ↔ ∀ (x : α) (h : p x), P x h → b := sorry

theorem not_bex {α : Sort u_1} {p : α → Prop} {P : (x : α) → p x → Prop} : (¬∃ (x : α), ∃ (h : p x), P x h) ↔ ∀ (x : α) (h : p x), ¬P x h :=
  bex_imp_distrib

theorem not_ball_of_bex_not {α : Sort u_1} {p : α → Prop} {P : (x : α) → p x → Prop} : (∃ (x : α), ∃ (h : p x), ¬P x h) → ¬∀ (x : α) (h : p x), P x h := sorry

-- See Note [decidable namespace]

protected theorem decidable.not_ball {α : Sort u_1} {p : α → Prop} {P : (x : α) → p x → Prop} [Decidable (∃ (x : α), ∃ (h : p x), ¬P x h)] [(x : α) → (h : p x) → Decidable (P x h)] : (¬∀ (x : α) (h : p x), P x h) ↔ ∃ (x : α), ∃ (h : p x), ¬P x h := sorry

theorem not_ball {α : Sort u_1} {p : α → Prop} {P : (x : α) → p x → Prop} : (¬∀ (x : α) (h : p x), P x h) ↔ ∃ (x : α), ∃ (h : p x), ¬P x h :=
  decidable.not_ball

theorem ball_true_iff {α : Sort u_1} (p : α → Prop) : (∀ (x : α), p x → True) ↔ True :=
  iff_true_intro fun (h : α) (hrx : p h) => trivial

theorem ball_and_distrib {α : Sort u_1} {p : α → Prop} {P : (x : α) → p x → Prop} {Q : (x : α) → p x → Prop} : (∀ (x : α) (h : p x), P x h ∧ Q x h) ↔ (∀ (x : α) (h : p x), P x h) ∧ ∀ (x : α) (h : p x), Q x h :=
  iff.trans (forall_congr fun (x : α) => forall_and_distrib) forall_and_distrib

theorem bex_or_distrib {α : Sort u_1} {p : α → Prop} {P : (x : α) → p x → Prop} {Q : (x : α) → p x → Prop} : (∃ (x : α), ∃ (h : p x), P x h ∨ Q x h) ↔ (∃ (x : α), ∃ (h : p x), P x h) ∨ ∃ (x : α), ∃ (h : p x), Q x h :=
  iff.trans (exists_congr fun (x : α) => exists_or_distrib) exists_or_distrib

theorem ball_or_left_distrib {α : Sort u_1} {r : α → Prop} {p : α → Prop} {q : α → Prop} : (∀ (x : α), p x ∨ q x → r x) ↔ (∀ (x : α), p x → r x) ∧ ∀ (x : α), q x → r x :=
  iff.trans (forall_congr fun (x : α) => or_imp_distrib) forall_and_distrib

theorem bex_or_left_distrib {α : Sort u_1} {r : α → Prop} {p : α → Prop} {q : α → Prop} : (∃ (x : α), ∃ (_x : p x ∨ q x), r x) ↔ (∃ (x : α), ∃ (_x : p x), r x) ∨ ∃ (x : α), ∃ (_x : q x), r x := sorry

namespace classical

theorem not_ball {α : Sort u_1} {p : α → Prop} {P : (x : α) → p x → Prop} : (¬∀ (x : α) (h : p x), P x h) ↔ ∃ (x : α), ∃ (h : p x), ¬P x h :=
  not_ball

end classical

theorem ite_eq_iff {α : Sort u_1} {p : Prop} [Decidable p] {a : α} {b : α} {c : α} : ite p a b = c ↔ p ∧ a = c ∨ ¬p ∧ b = c := sorry

@[simp] theorem ite_eq_left_iff {α : Sort u_1} {p : Prop} [Decidable p] {a : α} {b : α} : ite p a b = a ↔ ¬p → b = a := sorry

@[simp] theorem ite_eq_right_iff {α : Sort u_1} {p : Prop} [Decidable p] {a : α} {b : α} : ite p a b = b ↔ p → a = b := sorry

/-! ### Declarations about `nonempty` -/

protected instance has_zero.nonempty {α : Type u} [HasZero α] : Nonempty α :=
  Nonempty.intro 0

protected instance has_one.nonempty {α : Type u} [HasOne α] : Nonempty α :=
  Nonempty.intro 1

theorem exists_true_iff_nonempty {α : Sort u_1} : (∃ (a : α), True) ↔ Nonempty α := sorry

@[simp] theorem nonempty_Prop {p : Prop} : Nonempty p ↔ p :=
  { mp := fun (_x : Nonempty p) => (fun (_a : Nonempty p) => nonempty.dcases_on _a fun (val : p) => idRhs p val) _x,
    mpr := fun (h : p) => Nonempty.intro h }

theorem not_nonempty_iff_imp_false {α : Type u} : ¬Nonempty α ↔ α → False := sorry

@[simp] theorem nonempty_sigma {α : Type u} {γ : α → Type w} : Nonempty (sigma fun (a : α) => γ a) ↔ ∃ (a : α), Nonempty (γ a) := sorry

@[simp] theorem nonempty_subtype {α : Sort u} {p : α → Prop} : Nonempty (Subtype p) ↔ ∃ (a : α), p a := sorry

@[simp] theorem nonempty_prod {α : Type u} {β : Type v} : Nonempty (α × β) ↔ Nonempty α ∧ Nonempty β := sorry

@[simp] theorem nonempty_pprod {α : Sort u} {β : Sort v} : Nonempty (PProd α β) ↔ Nonempty α ∧ Nonempty β := sorry

@[simp] theorem nonempty_sum {α : Type u} {β : Type v} : Nonempty (α ⊕ β) ↔ Nonempty α ∨ Nonempty β := sorry

@[simp] theorem nonempty_psum {α : Sort u} {β : Sort v} : Nonempty (psum α β) ↔ Nonempty α ∨ Nonempty β := sorry

@[simp] theorem nonempty_psigma {α : Sort u} {β : α → Sort v} : Nonempty (psigma β) ↔ ∃ (a : α), Nonempty (β a) := sorry

@[simp] theorem nonempty_empty : ¬Nonempty empty :=
  fun (_x : Nonempty empty) =>
    (fun (_a : Nonempty empty) => nonempty.dcases_on _a fun (val : empty) => idRhs False (empty.elim val)) _x

@[simp] theorem nonempty_ulift {α : Type u} : Nonempty (ulift α) ↔ Nonempty α := sorry

@[simp] theorem nonempty_plift {α : Sort u} : Nonempty (plift α) ↔ Nonempty α := sorry

@[simp] theorem nonempty.forall {α : Sort u} {p : Nonempty α → Prop} : (∀ (h : Nonempty α), p h) ↔ ∀ (a : α), p (Nonempty.intro a) := sorry

@[simp] theorem nonempty.exists {α : Sort u} {p : Nonempty α → Prop} : (∃ (h : Nonempty α), p h) ↔ ∃ (a : α), p (Nonempty.intro a) := sorry

theorem classical.nonempty_pi {α : Sort u} {β : α → Sort v} : Nonempty ((a : α) → β a) ↔ ∀ (a : α), Nonempty (β a) := sorry

/-- Using `classical.choice`, lifts a (`Prop`-valued) `nonempty` instance to a (`Type`-valued)
  `inhabited` instance. `classical.inhabited_of_nonempty` already exists, in
  `core/init/classical.lean`, but the assumption is not a type class argument,
  which makes it unsuitable for some applications. -/
def classical.inhabited_of_nonempty' {α : Sort u} [h : Nonempty α] : Inhabited α :=
  { default := Classical.choice h }

/-- Using `classical.choice`, extracts a term from a `nonempty` type. -/
protected def nonempty.some {α : Sort u} (h : Nonempty α) : α :=
  Classical.choice h

/-- Using `classical.choice`, extracts a term from a `nonempty` type. -/
protected def classical.arbitrary (α : Sort u) [h : Nonempty α] : α :=
  Classical.choice h

/-- Given `f : α → β`, if `α` is nonempty then `β` is also nonempty.
  `nonempty` cannot be a `functor`, because `functor` is restricted to `Type`. -/
theorem nonempty.map {α : Sort u} {β : Sort v} (f : α → β) : Nonempty α → Nonempty β :=
  fun (ᾰ : Nonempty α) => nonempty.dcases_on ᾰ fun (ᾰ : α) => idRhs (Nonempty β) (Nonempty.intro (f ᾰ))

protected theorem nonempty.map2 {α : Sort u_1} {β : Sort u_2} {γ : Sort u_3} (f : α → β → γ) : Nonempty α → Nonempty β → Nonempty γ :=
  fun (ᾰ : Nonempty α) (ᾰ_1 : Nonempty β) =>
    nonempty.dcases_on ᾰ
      fun (ᾰ_1_1 : α) => nonempty.dcases_on ᾰ_1 fun (ᾰ : β) => idRhs (Nonempty γ) (Nonempty.intro (f ᾰ_1_1 ᾰ))

protected theorem nonempty.congr {α : Sort u} {β : Sort v} (f : α → β) (g : β → α) : Nonempty α ↔ Nonempty β :=
  { mp := nonempty.map f, mpr := nonempty.map g }

theorem nonempty.elim_to_inhabited {α : Sort u_1} [h : Nonempty α] {p : Prop} (f : Inhabited α → p) : p :=
  nonempty.elim h (f ∘ Inhabited.mk)

protected instance prod.nonempty {α : Type u_1} {β : Type u_2} [h : Nonempty α] [h2 : Nonempty β] : Nonempty (α × β) :=
  nonempty.elim h fun (g : α) => nonempty.elim h2 fun (g2 : β) => Nonempty.intro (g, g2)

/-- A function applied to a `dite` is a `dite` of that function applied to each of the branches. -/
theorem apply_dite {α : Sort u_1} {β : Sort u_2} (f : α → β) (P : Prop) [Decidable P] (x : P → α) (y : ¬P → α) : f (dite P x y) = dite P (fun (h : P) => f (x h)) fun (h : ¬P) => f (y h) := sorry

/-- A function applied to a `ite` is a `ite` of that function applied to each of the branches. -/
theorem apply_ite {α : Sort u_1} {β : Sort u_2} (f : α → β) (P : Prop) [Decidable P] (x : α) (y : α) : f (ite P x y) = ite P (f x) (f y) :=
  apply_dite f P (fun (_x : P) => x) fun (_x : ¬P) => y

/-- A two-argument function applied to two `dite`s is a `dite` of that two-argument function
applied to each of the branches. -/
theorem apply_dite2 {α : Sort u_1} {β : Sort u_2} {γ : Sort u_3} (f : α → β → γ) (P : Prop) [Decidable P] (a : P → α) (b : ¬P → α) (c : P → β) (d : ¬P → β) : f (dite P a b) (dite P c d) = dite P (fun (h : P) => f (a h) (c h)) fun (h : ¬P) => f (b h) (d h) := sorry

/-- A two-argument function applied to two `ite`s is a `ite` of that two-argument function
applied to each of the branches. -/
theorem apply_ite2 {α : Sort u_1} {β : Sort u_2} {γ : Sort u_3} (f : α → β → γ) (P : Prop) [Decidable P] (a : α) (b : α) (c : β) (d : β) : f (ite P a b) (ite P c d) = ite P (f a c) (f b d) :=
  apply_dite2 f P (fun (_x : P) => a) (fun (_x : ¬P) => b) (fun (_x : P) => c) fun (_x : ¬P) => d

/-- A 'dite' producing a `Pi` type `Π a, β a`, applied to a value `x : α`
is a `dite` that applies either branch to `x`. -/
theorem dite_apply {α : Sort u_1} {β : α → Sort u_2} (P : Prop) [Decidable P] (f : P → (a : α) → β a) (g : ¬P → (a : α) → β a) (x : α) : dite P f g x = dite P (fun (h : P) => f h x) fun (h : ¬P) => g h x := sorry

/-- A 'ite' producing a `Pi` type `Π a, β a`, applied to a value `x : α`
is a `ite` that applies either branch to `x` -/
theorem ite_apply {α : Sort u_1} {β : α → Sort u_2} (P : Prop) [Decidable P] (f : (a : α) → β a) (g : (a : α) → β a) (x : α) : ite P f g x = ite P (f x) (g x) :=
  dite_apply P (fun (_x : P) => f) (fun (_x : ¬P) => g) x

/-- Negation of the condition `P : Prop` in a `dite` is the same as swapping the branches. -/
@[simp] theorem dite_not {α : Sort u_1} (P : Prop) [Decidable P] (x : ¬P → α) (y : ¬¬P → α) : dite (¬P) x y = dite P (fun (h : P) => y (not_not_intro h)) x := sorry

/-- Negation of the condition `P : Prop` in a `ite` is the same as swapping the branches. -/
@[simp] theorem ite_not {α : Sort u_1} (P : Prop) [Decidable P] (x : α) (y : α) : ite (¬P) x y = ite P y x :=
  dite_not P (fun (_x : ¬P) => x) fun (_x : ¬¬P) => y

theorem ite_and {α : Sort u_1} {p : Prop} {q : Prop} [Decidable p] [Decidable q] {x : α} {y : α} : ite (p ∧ q) x y = ite p (ite q x y) y := sorry