variable {α : Type*}

def is_prefix (l₁ : list α) (l₂ : list α) : Prop :=
  ∃ t, l₁ ++ t = l₂

infix ` <+: `:50 := is_prefix

@[simp]
theorem list.is_prefix_refl (l : list α) : l <+: l :=
  ⟨[], by simp⟩


/-
Copyright (c) 2019 Floris van Doorn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Floris van Doorn

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

/-!
# simps attribute

This file defines the `@[simps]` attribute, to automatically generate `simp` lemmas
reducing a definition when projections are applied to it.

## Implementation Notes

There are three attributes being defined here
* `@[simps]` is the attribute for objects of a structure or instances of a class. It will
  automatically generate simplification lemmas for each projection of the object/instance that
  contains data. See the doc strings for `simps_attr` and `simps_cfg` for more details and
  configuration options.
* `@[_simps_str]` is automatically added to structures that have been used in `@[simps]` at least
  once. This attribute contains the data of the projections used for this structure by all following
  invocations of `@[simps]`.
* `@[notation_class]` should be added to all classes that define notation, like `has_mul` and
  `has_zero`. This specifies that the projections that `@[simps]` used are the projections from
  these notation classes instead of the projections of the superclasses.
  Example: if `has_mul` is tagged with `@[notation_class]` then the projection used for `semigroup`
  will be `λ α hα, @has_mul.mul α (@semigroup.to_has_mul α hα)` instead of `@semigroup.mul`.

## Tags

structures, projections, simp, simplifier, generates declarations
-/

open Tactic Expr Option Sum

/- ./././Mathport/Syntax/Translate/Tactic/Mathlib/Core.lean:38:34: unsupported: setup_tactic_parser -/
initialize
  registerTraceClass.1 `simps.verbose

initialize
  registerTraceClass.1 `simps.debug

/-- Projection data for a single projection of a structure, consisting of the following fields:
- the name used in the generated `simp` lemmas
- an expression used by simps for the projection. It must be definitionally equal to an original
  projection (or a composition of multiple projections).
  These expressions can contain the universe parameters specified in the first argument of
  `simps_str_attr`.
- a list of natural numbers, which is the projection number(s) that have to be applied to the
  expression. For example the list `[0, 1]` corresponds to applying the first projection of the
  structure, and then the second projection of the resulting structure (this assumes that the
  target of the first projection is a structure with at least two projections).
  The composition of these projections is required to be definitionally equal to the provided
  expression.
- A boolean specifying whether `simp` lemmas are generated for this projection by default.
- A boolean specifying whether this projection is written as prefix.
-/
@[protect_proj]
unsafe structure projection_data where
  Name : Name
  expr : expr
  proj_nrs : List ℕ
  is_default : Bool
  isPrefix : Bool
  deriving has_reflect, Inhabited
#align projection_data projection_data

/-- Temporary projection data parsed from `initialize_simps_projections` before the expression
  matching this projection has been found. Only used internally in `simps_get_raw_projections`. -/
unsafe structure parsed_projection_data where
  orig_name : Name
  -- name for this projection used in the structure definition
  new_name : Name
  -- name for this projection used in the generated `simp` lemmas
  is_default : Bool
  isPrefix : Bool
#align parsed_projection_data parsed_projection_data

section

open Format

unsafe instance : has_to_tactic_format projection_data :=
  ⟨fun ⟨a, b, c, d, e⟩ =>
    (fun x =>
        group <|
          nest 1 <|
            to_fmt "⟨" ++ to_fmt a ++ to_fmt "," ++ line ++ x ++ to_fmt "," ++ line ++ to_fmt c ++
                          to_fmt "," ++
                        line ++
                      to_fmt d ++
                    to_fmt "," ++
                  line ++
                to_fmt e ++
              to_fmt "⟩") <$>
      pp b⟩

unsafe instance : has_to_format parsed_projection_data :=
  ⟨fun ⟨a, b, c, d⟩ =>
    group <|
      nest 1 <|
        to_fmt "⟨" ++ to_fmt a ++ to_fmt "," ++ line ++ to_fmt b ++ to_fmt "," ++ line ++
                  to_fmt c ++
                to_fmt "," ++
              line ++
            to_fmt d ++
          to_fmt "⟩"⟩

end

/-- The type of rules that specify how metadata for projections in changes.
  See `initialize_simps_projection`. -/
abbrev ProjectionRule :=
  Sum (Name × Name) Name × Bool
#align projection_rule ProjectionRule

/-- The `@[_simps_str]` attribute specifies the preferred projections of the given structure,
used by the `@[simps]` attribute.
- This will usually be tagged by the `@[simps]` tactic.
- You can also generate this with the command `initialize_simps_projections`.
- To change the default value, see Note [custom simps projection].
- You are strongly discouraged to add this attribute manually.
- The first argument is the list of names of the universe variables used in the structure
- The second argument is a list that consists of the projection data for each projection.
-/
@[user_attribute]
unsafe def simps_str_attr : user_attribute Unit (List Name × List projection_data)
    where
  Name := `_simps_str
  descr := "An attribute specifying the projection of the given structure."
  parser := failed
#align simps_str_attr simps_str_attr

/- ./././Mathport/Syntax/Translate/Expr.lean:207:4: warning: unsupported notation `parser.optional -/
/- ./././Mathport/Syntax/Translate/Expr.lean:207:4: warning: unsupported notation `parser.optional -/
/-- The `@[notation_class]` attribute specifies that this is a notation class,
  and this notation should be used instead of projections by @[simps].
  * The first argument `tt` for notation classes and `ff` for classes applied to the structure,
    like `has_coe_to_sort` and `has_coe_to_fun`
  * The second argument is the name of the projection (by default it is the first projection
    of the structure)
-/
@[user_attribute]
unsafe def notation_class_attr : user_attribute Unit (Bool × Option Name)
    where
  Name := `notation_class
  descr := "An attribute specifying that this is a notation class. Used by @[simps]."
  parser := Prod.mk <$> Option.isNone <$> parser.optional (tk "*") <*> parser.optional ident
#align notation_class_attr notation_class_attr

attribute [notation_class]
  Zero One Add Mul Inv Neg Sub Div Dvd Mod LE LT Append AndThen' Union Inter SDiff HasEquiv HasSubset HasSSubset EmptyCollection Insert Singleton Sep Membership Pow

attribute [notation_class* coeSort] CoeSort

attribute [notation_class* coeFn] CoeFun

/-- Returns the projection information of a structure. -/
unsafe def projections_info (l : List projection_data) (pref : String) (str : Name) :
    tactic format := do
  let ⟨defaults, nondefaults⟩ ←
    return <| l.partitionMap fun s => if s.is_default then inl s else inr s
  let to_print ←
    defaults.mapM fun s =>
        toString <$>
          let prefix_str := if s.isPrefix then "(prefix) " else ""
          f!"Projection {(← prefix_str)}{(← s.Name)}: {← s.expr}"
  let print2 :=
    String.join <| (nondefaults.map fun nm : projection_data => toString nm.1).intersperse ", "
  let to_print :=
    to_print ++
      if nondefaults.length = 0 then []
      else ["No lemmas are generated for the projections: " ++ print2 ++ "."]
  let to_print := String.join <| to_print.intersperse "\n        > "
  return
      f! "[simps] > {pref } {str }:
                > {to_print}"
#align projections_info projections_info

/-- Auxiliary function of `get_composite_of_projections`. -/
unsafe def get_composite_of_projections_aux :
    ∀ (str : Name) (proj : String) (x : expr) (pos : List ℕ) (args : List expr),
      tactic (expr × List ℕ)
  | str, proj, x, Pos, args => do
    let e ← get_env
    let projs ← e.structure_fields str
    let proj_info :=
      projs.mapIdx fun n p => (fun x => (x, n, p)) <$> proj.getRest ("_" ++ p.getLast)
    when (proj_info id = []) <|
        throwError "Failed to find constructor {(← proj 1)} in structure {← str}."
    let (proj_rest, index, proj_nm) ← return (proj_info.filterMap id).getLastI
    let str_d ← e.get str
    let proj_e : expr := const (str ++ proj_nm) str_d.univ_levels
    let proj_d ← e.get (str ++ proj_nm)
    let type ← infer_type x
    let params := get_app_args type
    let univs := proj_d.univ_params.zip type.get_app_fn.univ_levels
    let new_x := (proj_e.instantiate_univ_params univs).mk_app <| params ++ [x]
    let new_pos := Pos ++ [index]
    if proj_rest then return (new_x args, new_pos)
      else do
        let type ← infer_type new_x
        let (type_args, tgt) ← open_pis_whnf type
        let new_str := tgt
        get_composite_of_projections_aux new_str proj_rest (new_x type_args) new_pos
            (args ++ type_args)
#align get_composite_of_projections_aux get_composite_of_projections_aux

/-- Given a structure `str` and a projection `proj`, that could be multiple nested projections
  (separated by `_`), returns an expression that is the composition of these projections and a
  list of natural numbers, that are the projection numbers of the applied projections. -/
unsafe def get_composite_of_projections (str : Name) (proj : String) : tactic (expr × List ℕ) := do
  let e ← get_env
  let str_d ← e.get str
  let str_e : expr := const str str_d.univ_levels
  let type ← infer_type str_e
  let (type_args, tgt) ← open_pis_whnf type
  let str_ap := str_e.mk_app type_args
  let x ← mk_local' `x BinderInfo.default str_ap
  get_composite_of_projections_aux str ("_" ++ proj) x [] <| type_args ++ [x]
#align get_composite_of_projections get_composite_of_projections

-- if performance becomes a problem, possible heuristic: use the names of the projections to
-- skip all classes that don't have the corresponding field.
/-- Get the projections used by `simps` associated to a given structure `str`.

  The returned information is also stored in a parameter of the attribute `@[_simps_str]`, which
  is given to `str`. If `str` already has this attribute, the information is read from this
  attribute instead. See the documentation for this attribute for the data this tactic returns.

  The returned universe levels are the universe levels of the structure. For the projections there
  are three cases
  * If the declaration `{structure_name}.simps.{projection_name}` has been declared, then the value
    of this declaration is used (after checking that it is definitionally equal to the actual
    projection. If you rename the projection name, the declaration should have the *new* projection
    name.
  * You can also declare a custom projection that is a composite of multiple projections.
  * Otherwise, for every class with the `notation_class` attribute, and the structure has an
    instance of that notation class, then the projection of that notation class is used for the
    projection that is definitionally equal to it (if there is such a projection).
    This means in practice that coercions to function types and sorts will be used instead of
    a projection, if this coercion is definitionally equal to a projection. Furthermore, for
    notation classes like `has_mul` and `has_zero` those projections are used instead of the
    corresponding projection.
    Projections for coercions and notation classes are not automatically generated if they are
    composites of multiple projections (for example when you use `extend` without the
    `old_structure_cmd`).
  * Otherwise, the projection of the structure is chosen.
    For example: ``simps_get_raw_projections env `prod`` gives the default projections
```
  ([u, v], [prod.fst.{u v}, prod.snd.{u v}])
```
    while ``simps_get_raw_projections env `equiv`` gives
```
  ([u_1, u_2], [λ α β, coe_fn, λ {α β} (e : α ≃ β), ⇑(e.symm), left_inv, right_inv])
```
    after declaring the coercion from `equiv` to function and adding the declaration
```
  def equiv.simps.inv_fun {α β} (e : α ≃ β) : β → α := e.symm
```

  Optionally, this command accepts three optional arguments:
  * If `trace_if_exists` the command will always generate a trace message when the structure already
    has the attribute `@[_simps_str]`.
  * The `rules` argument accepts a list of pairs `sum.inl (old_name, new_name)`. This is used to
    change the projection name `old_name` to the custom projection name `new_name`. Example:
    for the structure `equiv` the projection `to_fun` could be renamed `apply`. This name will be
    used for parsing and generating projection names. This argument is ignored if the structure
    already has an existing attribute. If an element of `rules` is of the form `sum.inr name`, this
    means that the projection `name` will not be applied by default.
  * if `trc` is true, this tactic will trace information.
-/
unsafe def simps_get_raw_projections (e : environment) (str : Name)
    (trace_if_exists : Bool := false) (rules : List ProjectionRule := []) (trc := false) :
    tactic (List Name × List projection_data) := do
  let trc := trc || is_trace_enabled_for `simps.verbose
  let has_attr ← has_attribute' `_simps_str str
  if has_attr then do
      let data ← simps_str_attr str
      -- We always print the projections when they already exists and are called by
            -- `initialize_simps_projections`.
            when
            (trace_if_exists || is_trace_enabled_for `simps.verbose) <|
          projections_info data.2 "Already found projection information for structure" str >>= trace
      return data
    else do
      when trc
          (← do
            dbg_trace "[simps] > generating projection information for structure {← str}.")
      when_tracing `simps.debug
          (← do
            dbg_trace "[simps] > Applying the rules {← rules}.")
      let d_str ← e str
      let raw_univs := d_str
      let raw_levels := level.param <$> raw_univs
      let projs
        ←/- Figure out projections, including renamings. The information for a projection is (before we
                figure out the `expr` of the projection:
                `(original name, given name, is default, is prefix)`.
                The first projections are always the actual projections of the structure, but `rules` could
                specify custom projections that are compositions of multiple projections. -/
            e
            str
      let projs : List parsed_projection_data := projs fun nm => ⟨nm, nm, tt, ff⟩
      let projs : List parsed_projection_data :=
        rules
          (fun projs rule =>
            match rule with
            | (inl (old_nm, new_nm), is_prefix) =>
              if old_nm ∈ projs fun x => x then
                projs fun proj =>
                  if proj = old_nm then
                    { proj with
                      new_name := new_nm
                      isPrefix }
                  else proj
              else projs ++ [⟨old_nm, new_nm, tt, is_prefix⟩]
            | (inr nm, is_prefix) =>
              if nm ∈ projs fun x => x then
                projs fun proj =>
                  if proj = nm then
                    { proj with
                      is_default := ff
                      isPrefix }
                  else proj
              else projs ++ [⟨nm, nm, ff, is_prefix⟩])
          projs
      when_tracing `simps.debug
          (← do
            dbg_trace "[simps] > Projection info after applying the rules: {← projs}.")
      when ¬(projs fun x => x : List Name).Nodup <|
          fail <|
            "Invalid projection names. Two projections have the same name.\nThis is likely because a custom composition of projections was given the same name as an " ++
                "existing projection. Solution: rename the existing projection (before renaming the custom " ++
              "projection)."
      let raw_exprs_and_nrs
        ←/- Define the raw expressions for the projections, by default as the projections
                (as an expression), but this can be overriden by the user. -/
            projs
            fun ⟨orig_nm, new_nm, _, _⟩ => do
            let (raw_expr, nrs) ← get_composite_of_projections str orig_nm
            let custom_proj ←
              (do
                    let decl ← e (str ++ `simps ++ new_nm)
                    let custom_proj := decl <| decl raw_levels
                    when trc
                        (← do
                          dbg_trace "[simps] > found custom projection for {(← new_nm)}:
                                    > {← custom_proj}")
                    return custom_proj) <|>
                  return raw_expr
            is_def_eq custom_proj
                  raw_expr <|>-- if the type of the expression is different, we show a different error message, because
              -- that is more likely going to be helpful.
              do
                let custom_proj_type ← infer_type custom_proj
                let raw_expr_type ← infer_type raw_expr
                let b ← succeeds (is_def_eq custom_proj_type raw_expr_type)
                if b then
                    throwError "Invalid custom projection:
                        {(← custom_proj)}
                      Expression is not definitionally equal to
                        {← raw_expr}"
                  else
                    throwError "Invalid custom projection:
                        {(← custom_proj)}
                      Expression has different type than {(← str ++ orig_nm)}. Given type:
                        {(← custom_proj_type)}
                      Expected type:
                        {← raw_expr_type}"
            return (custom_proj, nrs)
      let raw_exprs := raw_exprs_and_nrs Prod.fst
      let-- Check for other coercions and type-class arguments to use as projections instead.
        (args, _)
        ← open_pis d_str
      let e_str := (expr.const str raw_levels).mk_app args
      let automatic_projs ← attribute.get_instances `notation_class
      let raw_exprs ←
        automatic_projs
            (fun (raw_exprs : List expr) class_nm =>
              (/- For this class, find the projection. `raw_expr` is the projection found applied to `args`,
                        and `lambda_raw_expr` has the arguments `args` abstracted. -/
                -- Note: `expr.bind_lambda` doesn't give the correct type
                /- Use this as projection, if the function reduces to a projection, and this projection has
                        not been overrriden by the user. -/
                do
                  let (is_class, proj_nm) ← notation_class_attr class_nm
                  let proj_nm ← proj_nm <|> (e class_nm).map List.headI
                  let (raw_expr, lambda_raw_expr) ←
                    if is_class then do
                        guard <| args = 1
                        let e_inst_type := (const class_nm raw_levels).mk_app args
                        let (hyp, e_inst) ← try_for 1000 (mk_conditional_instance e_str e_inst_type)
                        let raw_expr ← mk_mapp proj_nm [args, e_inst]
                        clear hyp
                        let raw_expr_lambda ← lambdas [hyp] raw_expr
                        return (raw_expr, raw_expr_lambda args)
                      else do
                        let e_inst_type ←
                          to_expr (((const class_nm []).app (pexpr.of_expr e_str)).app ``(_))
                        let e_inst ← try_for 1000 (mk_instance e_inst_type)
                        let raw_expr ← mk_mapp proj_nm [e_str, none, e_inst]
                        return (raw_expr, raw_expr args)
                  let raw_expr_whnf ← whnf raw_expr
                  let relevant_proj := raw_expr_whnf
                  guard <| projs fun x => x.1 = relevant_proj ∧ ¬e (str ++ `simps ++ x)
                  let pos := projs fun x => x.1 = relevant_proj
                  when trc
                      (← do
                        dbg_trace
                          "        > using {(←
                            proj_nm)} instead of the default projection {← relevant_proj}.")
                  when_tracing `simps.debug
                      (← do
                        dbg_trace "[simps] > The raw projection is:
                            {← lambda_raw_expr}")
                  return <| raw_exprs Pos lambda_raw_expr) <|>
                return raw_exprs)
            raw_exprs
      let positions := raw_exprs_and_nrs Prod.snd
      let proj_names := projs fun x => x
      let defaults := projs fun x => x
      let prefixes := projs fun x => x
      let projs := proj_names projection_data.mk raw_exprs positions defaults prefixes
      let projs
        ←-- make all proof non-default.
            projs
            fun proj =>
            is_proof proj >>= fun b => return <| if b then { proj with is_default := ff } else proj
      when trc <| projections_info projs "generated projections for" str >>= trace
      simps_str_attr str (raw_univs, projs) tt
      when_tracing `simps.debug
          (← do
            dbg_trace "[simps] > Generated raw projection data:
              {← (raw_univs, projs)}")
      return (raw_univs, projs)
#align simps_get_raw_projections simps_get_raw_projections

/- ./././Mathport/Syntax/Translate/Expr.lean:207:4: warning: unsupported notation `parser.optional -/
/-- Parse a rule for `initialize_simps_projections`. It is either `<name>→<name>` or `-<name>`,
  possibly following by `as_prefix`.-/
unsafe def simps_parse_rule : parser ProjectionRule :=
  Prod.mk <$>
      ((fun x y => inl (x, y)) <$> ident <*> (tk "->" >> ident) <|> inr <$> (tk "-" >> ident)) <*>
    isSome <$> parser.optional (tk "as_prefix")
#align simps_parse_rule simps_parse_rule

library_note "custom simps projection"/--
You can specify custom projections for the `@[simps]` attribute.
To do this for the projection `my_structure.original_projection` by adding a declaration
`my_structure.simps.my_projection` that is definitionally equal to
`my_structure.original_projection` but has the projection in the desired (simp-normal) form.
Then you can call
```
initialize_simps_projections (original_projection → my_projection, ...)
```
to register this projection. See `initialize_simps_projections_cmd` for more information.

You can also specify custom projections that are definitionally equal to a composite of multiple
projections. This is often desirable when extending structures (without `old_structure_cmd`).

`has_coe_to_fun` and notation class (like `has_mul`) instances will be automatically used, if they
are definitionally equal to a projection of the structure (but not when they are equal to the
composite of multiple projections).
-/

/- ./././Mathport/Syntax/Translate/Expr.lean:207:4: warning: unsupported notation `parser.optional -/
/- ./././Mathport/Syntax/Translate/Expr.lean:207:4: warning: unsupported notation `parser.many -/
/- ./././Mathport/Syntax/Translate/Expr.lean:207:4: warning: unsupported notation `parser.optional -/
/-- This command specifies custom names and custom projections for the simp attribute `simps_attr`.
* You can specify custom names by writing e.g.
  `initialize_simps_projections equiv (to_fun → apply, inv_fun → symm_apply)`.
* See Note [custom simps projection] and the examples below for information how to declare custom
  projections.
* If no custom projection is specified, the projection will be `coe_fn`/`⇑` if a `has_coe_to_fun`
  instance has been declared, or the notation of a notation class (like `has_mul`) if such an
  instance is available. If none of these cases apply, the projection itself will be used.
* You can disable a projection by default by running
  `initialize_simps_projections equiv (-inv_fun)`
  This will ensure that no simp lemmas are generated for this projection,
  unless this projection is explicitly specified by the user.
* If you want the projection name added as a prefix in the generated lemma name, you can add the
  `as_prefix` modifier:
  `initialize_simps_projections equiv (to_fun → coe as_prefix)`
  Note that this does not influence the parsing of projection names: if you have a declaration
  `foo` and you want to apply the projections `snd`, `coe` (which is a prefix) and `fst`, in that
  order you can run `@[simps snd_coe_fst] def foo ...` and this will generate a lemma with the
  name `coe_foo_snd_fst`.
  * Run `initialize_simps_projections?` (or `set_option trace.simps.verbose true`)
  to see the generated projections.
* You can declare a new name for a projection that is the composite of multiple projections, e.g.
  ```
    structure A := (proj : ℕ)
    structure B extends A
    initialize_simps_projections? B (to_A_proj → proj, -to_A)
  ```
  You can also make your custom projection that is definitionally equal to a composite of
  projections. In this case, coercions and notation classes are not automatically recognized, and
  should be manually given by giving a custom projection.
  This is especially useful when extending a structure (without `old_structure_cmd`).
  In the above example, it is desirable to add `-to_A`, so that `@[simps]` doesn't automatically
  apply the `B.to_A` projection and then recursively the `A.proj` projection in the lemmas it
  generates. If you want to get both the `foo_proj` and `foo_to_A` simp lemmas, you can use
  `@[simps, simps to_A]`.
* Running `initialize_simps_projections my_struc` without arguments is not necessary, it has the
  same effect if you just add `@[simps]` to a declaration.
* If you do anything to change the default projections, make sure to call either `@[simps]` or
  `initialize_simps_projections` in the same file as the structure declaration. Otherwise, you might
  have a file that imports the structure, but not your custom projections.

Some common uses:
* If you define a new homomorphism-like structure (like `mul_hom`) you can just run
  `initialize_simps_projections` after defining the `has_coe_to_fun` instance
  ```
    instance {mM : has_mul M} {mN : has_mul N} : has_coe_to_fun (M →ₙ* N) := ...
    initialize_simps_projections mul_hom (to_fun → apply)
  ```
  This will generate `foo_apply` lemmas for each declaration `foo`.
* If you prefer `coe_foo` lemmas that state equalities between functions, use
  `initialize_simps_projections mul_hom (to_fun → coe as_prefix)`
  In this case you have to use `@[simps {fully_applied := ff}]` or equivalently `@[simps as_fn]`
  whenever you call `@[simps]`.
* You can also initialize to use both, in which case you have to choose which one to use by default,
  by using either of the following
  ```
    initialize_simps_projections mul_hom (to_fun → apply, to_fun → coe, -coe as_prefix)
    initialize_simps_projections mul_hom (to_fun → apply, to_fun → coe as_prefix, -apply)
  ```
  In the first case, you can get both lemmas using `@[simps, simps coe as_fn]` and in the second
  case you can get both lemmas using `@[simps as_fn, simps apply]`.
* If your new homomorphism-like structure extends another structure (without `old_structure_cmd`)
  (like `rel_embedding`), then you have to specify explicitly that you want to use a coercion
  as a custom projection. For example
  ```
    def rel_embedding.simps.apply (h : r ↪r s) : α → β := h
    initialize_simps_projections rel_embedding (to_embedding_to_fun → apply, -to_embedding)
  ```
* If you have an isomorphism-like structure (like `equiv`) you often want to define a custom
  projection for the inverse:
  ```
    def equiv.simps.symm_apply (e : α ≃ β) : β → α := e.symm
    initialize_simps_projections equiv (to_fun → apply, inv_fun → symm_apply)
  ```
-/
@[user_command]
unsafe def initialize_simps_projections_cmd (_ : parse <| tk "initialize_simps_projections") :
    parser Unit := do
  let env ← get_env
  let trc ← isSome <$> parser.optional (tk "?")
  let ns ←
    parser.many
        (Prod.mk <$> ident <*>
          parser.optional (tk "(" >> sep_by (tk ",") simps_parse_rule <* tk ")"))
  ns fun data => do
      let nm ← resolve_constant data.1
      simps_get_raw_projections env nm tt (data.2.getD []) trc
#align initialize_simps_projections_cmd initialize_simps_projections_cmd

add_tactic_doc
  { Name := "initialize_simps_projections"
    category := DocCategory.cmd
    declNames := [`initialize_simps_projections_cmd]
    tags := ["simplification"] }

/-- Configuration options for the `@[simps]` attribute.
  * `attrs` specifies the list of attributes given to the generated lemmas. Default: ``[`simp]``.
    The attributes can be either basic attributes, or user attributes without parameters.
    There are two attributes which `simps` might add itself:
    * If ``[`simp]`` is in the list, then ``[`_refl_lemma]`` is added automatically if appropriate.
    * If the definition is marked with `@[to_additive ...]` then all generated lemmas are marked
      with `@[to_additive]`. This is governed by the `add_additive` configuration option.
  * if `simp_rhs` is `tt` then the right-hand-side of the generated lemmas will be put in
    simp-normal form. More precisely: `dsimp, simp` will be called on all these expressions.
    See note [dsimp, simp].
  * `type_md` specifies how aggressively definitions are unfolded in the type of expressions
    for the purposes of finding out whether the type is a function type.
    Default: `instances`. This will unfold coercion instances (so that a coercion to a function type
    is recognized as a function type), but not declarations like `set`.
  * `rhs_md` specifies how aggressively definition in the declaration are unfolded for the purposes
    of finding out whether it is a constructor.
    Default: `none`
    Exception: `@[simps]` will automatically add the options
    `{rhs_md := semireducible, simp_rhs := tt}` if the given definition is not a constructor with
    the given reducibility setting for `rhs_md`.
  * If `fully_applied` is `ff` then the generated `simp` lemmas will be between non-fully applied
    terms, i.e. equalities between functions. This does not restrict the recursive behavior of
    `@[simps]`, so only the "final" projection will be non-fully applied.
    However, it can be used in combination with explicit field names, to get a partially applied
    intermediate projection.
  * The option `not_recursive` contains the list of names of types for which `@[simps]` doesn't
    recursively apply projections. For example, given an equivalence `α × β ≃ β × α` one usually
    wants to only apply the projections for `equiv`, and not also those for `×`. This option is
    only relevant if no explicit projection names are given as argument to `@[simps]`.
  * The option `trace` is set to `tt` when you write `@[simps?]`. In this case, the attribute will
    print all generated lemmas. It is almost the same as setting the option `trace.simps.verbose`,
    except that it doesn't print information about the found projections.
  * if `add_additive` is `some nm` then `@[to_additive]` is added to the generated lemma. This
    option is automatically set to `tt` when the original declaration was tagged with
    `@[to_additive, simps]` (in that order), where `nm` is the additive name of the original
    declaration.
-/
structure SimpsCfg where
  attrs := [`simp]
  simpRhs := false
  typeMd := Transparency.instances
  rhsMd := Transparency.none
  fullyApplied := true
  notRecursive := [`prod, `pprod]
  trace := false
  addAdditive := @none Name
  deriving has_reflect, Inhabited
#align simps_cfg SimpsCfg

/-- A common configuration for `@[simps]`: generate equalities between functions instead equalities
  between fully applied expressions. -/
def asFn : SimpsCfg where fullyApplied := false
#align as_fn asFn

/-- A common configuration for `@[simps]`: don't tag the generated lemmas with `@[simp]`. -/
def lemmasOnly : SimpsCfg where attrs := []
#align lemmas_only lemmasOnly

/-- Get the projections of a structure used by `@[simps]` applied to the appropriate arguments.
  Returns a list of tuples
  ```
  (corresponding right-hand-side, given projection name, projection expression, projection numbers,
    used by default, is prefix)
  ```
  (where all fields except the first are packed in a `projection_data` structure)
  one for each projection. The given projection name is the name for the projection used by the user
  used to generate (and parse) projection names. For example, in the structure

  Example 1: ``simps_get_projection_exprs env `(α × β) `(⟨x, y⟩)`` will give the output
  ```
    [(`(x), `fst, `(@prod.fst.{u v} α β), [0], tt, ff),
     (`(y), `snd, `(@prod.snd.{u v} α β), [1], tt, ff)]
  ```

  Example 2: ``simps_get_projection_exprs env `(α ≃ α) `(⟨id, id, λ _, rfl, λ _, rfl⟩)``
  will give the output
  ```
    [(`(id), `apply, `(coe), [0], tt, ff),
     (`(id), `symm_apply, `(λ f, ⇑f.symm), [1], tt, ff),
     ...,
     ...]
  ```
-/
unsafe def simps_get_projection_exprs (e : environment) (tgt : expr) (rhs : expr) (cfg : SimpsCfg) :
    tactic <| List <| expr × projection_data := do
  let params := get_app_args tgt
  ((-- the parameters of the structure
            params <|
            (get_app_args rhs).take params).mapM'
        fun ⟨a, b⟩ => is_def_eq a b) <|>
      fail "unreachable code (1)"
  let str := tgt.get_app_fn.const_name
  let rhs_args := (get_app_args rhs).drop params.length
  let-- the fields of the object
    (raw_univs, proj_data)
    ← simps_get_raw_projections e str false [] cfg.trace
  let univs := raw_univs.zip tgt.get_app_fn.univ_levels
  let new_proj_data : List <| expr × projection_data :=
    proj_data.map fun proj =>
      (rhs_args.getI proj.proj_nrs.headI,
        {
          proj with
          expr := (proj.expr.instantiate_univ_params univs).instantiate_lambdas_or_apps params
          proj_nrs := proj.proj_nrs.tail })
  return new_proj_data
#align simps_get_projection_exprs simps_get_projection_exprs

/-- Add a lemma with `nm` stating that `lhs = rhs`. `type` is the type of both `lhs` and `rhs`,
  `args` is the list of local constants occurring, and `univs` is the list of universe variables. -/
unsafe def simps_add_projection (nm : Name) (type lhs rhs : expr) (args : List expr)
    (univs : List Name) (cfg : SimpsCfg) : tactic (List Name) := do
  when_tracing `simps.debug
      (← do
        dbg_trace "[simps] > Planning to add the equality
                  > {(← lhs)} = ({(← rhs)} : {← type})")
  let lvl ← get_univ_level type
  let-- simplify `rhs` if `cfg.simp_rhs` is true
    (rhs, prf)
    ←
    (do
          guard cfg
          let rhs' ← rhs.dsimp { failIfUnchanged := false }
          when_tracing `simps.debug <|
              when (rhs ≠ rhs')
                (← do
                  dbg_trace "[simps] > `dsimp` simplified rhs to
                            > {← rhs'}")
          let (rhsprf1, rhsprf2, ns) ← rhs'.simp { failIfUnchanged := false }
          when_tracing `simps.debug <|
              when (rhs' ≠ rhsprf1)
                (← do
                  dbg_trace "[simps] > `simp` simplified rhs to
                            > {← rhsprf1}")
          return (Prod.mk rhsprf1 rhsprf2)) <|>
        return (rhs, const `eq.refl [lvl] type lhs)
  let eq_ap := const `eq [lvl] type lhs rhs
  let decl_name ← get_unused_decl_name nm
  let decl_type := eq_ap.pis args
  let decl_value := prf.lambdas args
  let decl := declaration.thm decl_name univs decl_type (pure decl_value)
  when cfg
      (← do
        dbg_trace "[simps] > adding projection {(← decl_name)}:
                  > {← decl_type}")
  decorate_error ("Failed to add projection lemma " ++ decl_name ++ ". Nested error:") <|
      add_decl decl
  let b ← succeeds <| is_def_eq lhs rhs
  when (b ∧ `simp ∈ cfg) (set_basic_attribute `_refl_lemma decl_name tt)
  cfg fun nm => set_attribute nm decl_name tt
  when cfg <| to_additive.attr decl_name ⟨ff, cfg, cfg, none, tt⟩ tt
  pure [decl_name]
#align simps_add_projection simps_add_projection

/-- Derive lemmas specifying the projections of the declaration.
  If `todo` is non-empty, it will generate exactly the names in `todo`.
  `to_apply` is non-empty after a custom projection that is a composition of multiple projections
  was just used. In that case we need to apply these projections before we continue changing lhs. -/
unsafe def simps_add_projections :
    ∀ (e : environment) (nm : Name) (type lhs rhs : expr) (args : List expr) (univs : List Name)
      (must_be_str : Bool) (cfg : SimpsCfg) (todo : List String) (to_apply : List ℕ),
      tactic (List Name)
  | e, nm, type, lhs, rhs, args, univs, must_be_str, cfg, todo, to_apply => do
    -- we don't want to unfold non-reducible definitions (like `set`) to apply more arguments
        when_tracing
        `simps.debug
        (← do
          dbg_trace "[simps] > Type of the expression before normalizing: {← type}")
    let (type_args, tgt) ← open_pis_whnf type cfg.typeMd
    when_tracing `simps.debug
        (← do
          dbg_trace "[simps] > Type after removing pi's: {← tgt}")
    let tgt ← whnf tgt
    when_tracing `simps.debug
        (← do
          dbg_trace "[simps] > Type after reduction: {← tgt}")
    let new_args := args ++ type_args
    let lhs_ap := lhs.instantiate_lambdas_or_apps type_args
    let rhs_ap := rhs.instantiate_lambdas_or_apps type_args
    let str := tgt.get_app_fn.const_name
    let-- We want to generate the current projection if it is in `todo`
    todo_next := todo.filterₓ (· ≠ "")
    /- Don't recursively continue if `str` is not a structure or if the structure is in
            `not_recursive`. -/
        if e str ∧ ¬(todo = [] ∧ str ∈ cfg ∧ ¬must_be_str) then do
        let [intro] ← return <| e str |
          fail "unreachable code (3)"
        let rhs_whnf ← whnf rhs_ap cfg
        let-- `todo_now` means that we still have to generate the current simp lemma
          (rhs_ap, todo_now, added_lems_requested)
          ←
          if ¬is_constant_of rhs_ap intro ∧ is_constant_of rhs_whnf intro then do
              let added_lems_requested
                ←/- If this was a desired projection, we want to apply it before taking the whnf.
                            However, if the current field is an eta-expansion (see below), we first want
                            to eta-reduce it and only then construct the projection.
                            This makes the flow of this function messy. -/
                    cond
                    ("" ∈ todo ∧ to_apply = [])
                    (if cfg then simps_add_projection nm tgt lhs_ap rhs_ap new_args univs cfg
                    else simps_add_projection nm type lhs rhs args univs cfg)
                    (pure [])
              return (rhs_whnf, ff, added_lems_requested)
            else return (rhs_ap, "" ∈ todo ∧ to_apply = [], [])
        if is_constant_of (get_app_fn rhs_ap) intro then do
            let proj_info
              ←-- if the value is a constructor application
                  simps_get_projection_exprs
                  e tgt rhs_ap cfg
            when_tracing `simps.debug
                (← do
                  dbg_trace "[simps] > Raw projection information:
                      {← proj_info}")
            let eta ← rhs_ap
            let-- check whether `rhs_ap` is an eta-expansion
            rhs_ap := eta rhs_ap
            let added_lems_eta
              ←-- eta-reduce `rhs_ap`
                  /- As a special case, we want to automatically generate the current projection if `rhs_ap`
                          was an eta-expansion. Also, when this was a desired projection, we need to generate the
                          current projection if we haven't done it above. -/
                  cond
                  (todo_now ∨ todo = [] ∧ eta ∧ to_apply = [])
                  (if cfg then simps_add_projection nm tgt lhs_ap rhs_ap new_args univs cfg
                  else simps_add_projection nm type lhs rhs args univs cfg)
                  (return [])
            let added_lems_custom_proj
              ←-- If we are in the middle of a composite projection.
                  cond
                  (to_apply ≠ [])
                  (do
                    let ⟨new_rhs, proj, proj_expr, proj_nrs, is_default, is_prefix⟩ ←
                      return <| proj_info to_apply
                    let new_type ← infer_type new_rhs
                    when_tracing `simps.debug
                        (← do
                          dbg_trace "[simps] > Applying a custom composite projection. Current lhs:
                                    >  {← lhs_ap}")
                    simps_add_projections e nm new_type lhs_ap new_rhs new_args univs ff cfg todo
                        to_apply)
                  (pure [])
            let all_added_lems := added_lems_requested ++ added_lems_eta ++ added_lems_custom_proj
            /- We stop if no further projection is specified or if we just reduced an eta-expansion and we
                      automatically choose projections -/
                cond
                (¬(to_apply ≠ [] ∨ todo = [""] ∨ eta ∧ todo = []))
                (do
                  let projs : List Name := proj_info fun x => x
                  let todo := if to_apply = [] then todo_next else todo
                  -- check whether all elements in `todo` have a projection as prefix
                        guard
                        (todo fun x => projs fun proj => ("_" ++ proj).isPrefixOfₓ x) <|>
                      let x := (todo fun x => projs fun proj => ¬("_" ++ proj).isPrefixOfₓ x).iget
                      let simp_lemma := nm x
                      let needed_proj := (x '_').tail.headI
                      throwError
                        "Invalid simp lemma {(←
                          simp_lemma)}. Structure {(←
                          str)} does not have projection {(← needed_proj)}.
                        The known projections are:
                          {(← projs)}
                        You can also see this information by running
                          `initialize_simps_projections? {← str}`.
                        Note: these projection names might not correspond to the projection names of the structure."
                  let added_lems_list ←
                    proj_info
                        fun proj_nr ⟨new_rhs, proj, proj_expr, proj_nrs, is_default, is_prefix⟩ =>
                        do
                        let new_type ← infer_type new_rhs
                        let new_todo := todo fun x => x ("_" ++ proj)
                        -- we only continue with this field if it is non-propositional or mentioned in todo
                            cond
                            (is_default ∧ todo = [] ∨ new_todo ≠ [])
                            (do
                              let new_lhs := proj_expr [lhs_ap]
                              let new_nm := nm proj is_prefix
                              let new_cfg :=
                                { cfg with
                                  addAdditive :=
                                    cfg fun nm => nm (to_additive.guess_name proj) is_prefix }
                              when_tracing `simps.debug
                                  (← do
                                    dbg_trace "[simps] > Recursively add projections for:
                                              >  {← new_lhs}")
                              simps_add_projections e new_nm new_type new_lhs new_rhs new_args univs
                                  ff new_cfg new_todo proj_nrs)
                            (pure [])
                  pure <| all_added_lems ++ added_lems_list)
                (pure all_added_lems)
          else-- if I'm about to run into an error, try to set the transparency for `rhs_md` higher.
              if cfg = transparency.none ∧ (must_be_str ∨ todo_next ≠ [] ∨ to_apply ≠ []) then do
              when cfg
                  (← do
                    dbg_trace "[simps] > The given definition is not a constructor application:
                              >   {← rhs_ap}
                              > Retrying with the options \{ rhs_md := semireducible, simp_rhs := tt}.")
              let added_lems_recursive ←
                simps_add_projections e nm type lhs rhs args univs must_be_str
                    { cfg with
                      rhsMd := semireducible
                      simpRhs := tt }
                    todo to_apply
              pure <| added_lems_requested ++ added_lems_recursive
            else do
              when (to_apply ≠ []) <|
                  throwError "Invalid simp lemma {(← nm)}.
                    The given definition is not a constructor application:
                      {← rhs_ap}"
              when must_be_str <|
                  throwError "Invalid `simps` attribute. The body is not a constructor application:
                      {← rhs_ap}"
              when (todo_next ≠ []) <|
                  throwError "Invalid simp lemma {(← nm todo_next)}.
                    The given definition is not a constructor application:
                      {← rhs_ap}"
              let added_lems_no_constructor ←
                if cfg then simps_add_projection nm tgt lhs_ap rhs_ap new_args univs cfg
                  else simps_add_projection nm type lhs rhs args univs cfg
              pure <| added_lems_requested ++ added_lems_no_constructor
      else do
        when must_be_str <|
            throwError "Invalid `simps` attribute. Target {← str} is not a structure"
        when (todo_next ≠ [] ∧ str ∉ cfg) <|
            let first_todo := todo_next
            throwError "Invalid simp lemma {(← nm first_todo)}.
              Projection {←
                (first_todo '_').tail.headI} doesn't exist, because target is not a structure."
        if cfg then simps_add_projection nm tgt lhs_ap rhs_ap new_args univs cfg
          else simps_add_projection nm type lhs rhs args univs cfg
#align simps_add_projections simps_add_projections

/-- The `@[_simps_aux]` attribute specifies which lemmas are added by `simps`.
This should not be used manually and it only exists for mathport
-/
@[user_attribute]
unsafe def simps_aux : user_attribute Unit (List Name)
    where
  Name := `_simps_aux
  descr := "An attribute specifying the added simps lemmas."
  parser := failed
#align simps_aux simps_aux

/-- `simps_tac` derives `simp` lemmas for all (nested) non-Prop projections of the declaration.
  If `todo` is non-empty, it will generate exactly the names in `todo`.
  If `short_nm` is true, the generated names will only use the last projection name.
  If `trc` is true, trace as if `trace.simps.verbose` is true. -/
unsafe def simps_tac (nm : Name) (cfg : SimpsCfg := { }) (todo : List String := []) (trc := false) :
    tactic Unit := do
  let e ← get_env
  let d ← e.get nm
  let lhs : expr := const d.to_name d.univ_levels
  let todo := todo.dedup.map fun proj => "_" ++ proj
  let cfg := { cfg with trace := cfg.trace || is_trace_enabled_for `simps.verbose || trc }
  let b ← has_attribute' `to_additive nm
  let cfg ←
    if b then do
        let dict ← to_additive.aux_attr.get_cache
        when cfg
            (← do
              dbg_trace "[simps] > @[to_additive] will be added to all generated lemmas.")
        return { cfg with addAdditive := dict nm }
      else return cfg
  let added_names ← simps_add_projections e nm d.type lhs d.value [] d.univ_params true cfg todo []
  simps_aux nm added_names True
#align simps_tac simps_tac

/- ./././Mathport/Syntax/Translate/Expr.lean:207:4: warning: unsupported notation `parser.optional -/
/- ./././Mathport/Syntax/Translate/Expr.lean:207:4: warning: unsupported notation `parser.optional -/
/-- The parser for the `@[simps]` attribute. -/
unsafe def simps_parser : parser (Bool × List String × SimpsCfg) := do
  -- note: we don't check whether the user has written a nonsense namespace in an argument.
        Prod.mk <$>
        is_some <$> parser.optional (tk "?") <*>
      (Prod.mk <$> many (name.last <$> ident) <*> do
        let some e ← parser.optional parser.pexpr |
          return { }
        eval_pexpr SimpsCfg e)
#align simps_parser simps_parser

/- If one of the fields is a partially applied constructor, we will eta-expand it
  (this likely never happens, so is not included in the official doc). -/
/-- The `@[simps]` attribute automatically derives lemmas specifying the projections of this
declaration.

Example:
```lean
@[simps] def foo : ℕ × ℤ := (1, 2)
```
derives two `simp` lemmas:
```lean
@[simp] lemma foo_fst : foo.fst = 1
@[simp] lemma foo_snd : foo.snd = 2
```

* It does not derive `simp` lemmas for the prop-valued projections.
* It will automatically reduce newly created beta-redexes, but will not unfold any definitions.
* If the structure has a coercion to either sorts or functions, and this is defined to be one
  of the projections, then this coercion will be used instead of the projection.
* If the structure is a class that has an instance to a notation class, like `has_mul`, then this
  notation is used instead of the corresponding projection.
* You can specify custom projections, by giving a declaration with name
  `{structure_name}.simps.{projection_name}`. See Note [custom simps projection].

  Example:
  ```lean
  def equiv.simps.inv_fun (e : α ≃ β) : β → α := e.symm
  @[simps] def equiv.trans (e₁ : α ≃ β) (e₂ : β ≃ γ) : α ≃ γ :=
  ⟨e₂ ∘ e₁, e₁.symm ∘ e₂.symm⟩
  ```
  generates
  ```
  @[simp] lemma equiv.trans_to_fun : ∀ {α β γ} (e₁ e₂) (a : α), ⇑(e₁.trans e₂) a = (⇑e₂ ∘ ⇑e₁) a
  @[simp] lemma equiv.trans_inv_fun : ∀ {α β γ} (e₁ e₂) (a : γ),
    ⇑((e₁.trans e₂).symm) a = (⇑(e₁.symm) ∘ ⇑(e₂.symm)) a
  ```

* You can specify custom projection names, by specifying the new projection names using
  `initialize_simps_projections`.
  Example: `initialize_simps_projections equiv (to_fun → apply, inv_fun → symm_apply)`.
  See `initialize_simps_projections_cmd` for more information.

* If one of the fields itself is a structure, this command will recursively create
  `simp` lemmas for all fields in that structure.
  * Exception: by default it will not recursively create `simp` lemmas for fields in the structures
    `prod` and `pprod`. You can give explicit projection names or change the value of
    `simps_cfg.not_recursive` to override this behavior.

  Example:
  ```lean
  structure my_prod (α β : Type*) := (fst : α) (snd : β)
  @[simps] def foo : prod ℕ ℕ × my_prod ℕ ℕ := ⟨⟨1, 2⟩, 3, 4⟩
  ```
  generates
  ```lean
  @[simp] lemma foo_fst : foo.fst = (1, 2)
  @[simp] lemma foo_snd_fst : foo.snd.fst = 3
  @[simp] lemma foo_snd_snd : foo.snd.snd = 4
  ```

* You can use `@[simps proj1 proj2 ...]` to only generate the projection lemmas for the specified
  projections.
* Recursive projection names can be specified using `proj1_proj2_proj3`.
  This will create a lemma of the form `foo.proj1.proj2.proj3 = ...`.

  Example:
  ```lean
  structure my_prod (α β : Type*) := (fst : α) (snd : β)
  @[simps fst fst_fst snd] def foo : prod ℕ ℕ × my_prod ℕ ℕ := ⟨⟨1, 2⟩, 3, 4⟩
  ```
  generates
  ```lean
  @[simp] lemma foo_fst : foo.fst = (1, 2)
  @[simp] lemma foo_fst_fst : foo.fst.fst = 1
  @[simp] lemma foo_snd : foo.snd = {fst := 3, snd := 4}
  ```
* If one of the values is an eta-expanded structure, we will eta-reduce this structure.

  Example:
  ```lean
  structure equiv_plus_data (α β) extends α ≃ β := (data : bool)
  @[simps] def bar {α} : equiv_plus_data α α := { data := tt, ..equiv.refl α }
  ```
  generates the following:
  ```lean
  @[simp] lemma bar_to_equiv : ∀ {α : Sort*}, bar.to_equiv = equiv.refl α
  @[simp] lemma bar_data : ∀ {α : Sort*}, bar.data = tt
  ```
  This is true, even though Lean inserts an eta-expanded version of `equiv.refl α` in the
  definition of `bar`.
* For configuration options, see the doc string of `simps_cfg`.
* The precise syntax is `('simps' ident* e)`, where `e` is an expression of type `simps_cfg`.
* `@[simps]` reduces let-expressions where necessary.
* When option `trace.simps.verbose` is true, `simps` will print the projections it finds and the
  lemmas it generates. The same can be achieved by using `@[simps?]`, except that in this case it
  will not print projection information.
* Use `@[to_additive, simps]` to apply both `to_additive` and `simps` to a definition, making sure
  that `simps` comes after `to_additive`. This will also generate the additive versions of all
  `simp` lemmas.
-/
@[user_attribute]
unsafe def simps_attr : user_attribute Unit (Bool × List String × SimpsCfg)
    where
  Name := `simps
  descr := "Automatically derive lemmas specifying the projections of this declaration."
  parser := simps_parser
  after_set :=
    some fun n _ persistent => do
      guard persistent <|> fail "`simps` currently cannot be used as a local attribute"
      let (trc, todo, cfg) ← simps_attr.get_param n
      simps_tac n cfg todo trc
#align simps_attr simps_attr

add_tactic_doc
  { Name := "simps"
    category := DocCategory.attr
    declNames := [`simps_attr]
    tags := ["simplification"] }


/-
Copyright (c) 2018 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Kenny Lau
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.algebra.module.pi
import Mathlib.algebra.big_operators.basic
import Mathlib.data.set.finite
import Mathlib.group_theory.submonoid.basic
import Mathlib.PostPort

universes u v l v₁ v₂ w u_1 u_2 u_3 u_4 u₁ x

namespace Mathlib

/-!
# Dependent functions with finite support

For a non-dependent version see `data/finsupp.lean`.
-/

namespace dfinsupp

structure pre (ι : Type u) (β : ι → Type v) [(i : ι) → HasZero (β i)] where
  to_fun : (i : ι) → β i
  pre_support : multiset ι
  zero : ∀ (i : ι), i ∈ pre_support ∨ to_fun i = 0

protected instance inhabited_pre (ι : Type u) (β : ι → Type v) [(i : ι) → HasZero (β i)] :
    Inhabited (pre ι β) :=
  { default := pre.mk (fun (i : ι) => 0) ∅ sorry }

protected instance pre.setoid (ι : Type u) (β : ι → Type v) [(i : ι) → HasZero (β i)] :
    setoid (pre ι β) :=
  setoid.mk (fun (x y : pre ι β) => ∀ (i : ι), pre.to_fun x i = pre.to_fun y i) sorry

end dfinsupp

/-- A dependent function `Π i, β i` with finite support. -/
def dfinsupp {ι : Type u} (β : ι → Type v) [(i : ι) → HasZero (β i)] := quotient sorry

infixl:25 " →ₚ " => Mathlib.dfinsupp

namespace dfinsupp

protected instance has_coe_to_fun {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] :
    has_coe_to_fun (dfinsupp fun (i : ι) => β i) :=
  has_coe_to_fun.mk (fun (_x : dfinsupp fun (i : ι) => β i) => (i : ι) → β i)
    fun (f : dfinsupp fun (i : ι) => β i) => quotient.lift_on f pre.to_fun sorry

protected instance has_zero {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] :
    HasZero (dfinsupp fun (i : ι) => β i) :=
  { zero := quotient.mk (pre.mk (fun (i : ι) => 0) ∅ sorry) }

protected instance inhabited {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] :
    Inhabited (dfinsupp fun (i : ι) => β i) :=
  { default := 0 }

@[simp] theorem zero_apply {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] (i : ι) :
    coe_fn 0 i = 0 :=
  rfl

theorem ext {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)]
    {f : dfinsupp fun (i : ι) => β i} {g : dfinsupp fun (i : ι) => β i}
    (H : ∀ (i : ι), coe_fn f i = coe_fn g i) : f = g :=
  sorry

/-- The composition of `f : β₁ → β₂` and `g : Π₀ i, β₁ i` is
  `map_range f hf g : Π₀ i, β₂ i`, well defined when `f 0 = 0`. -/
def map_range {ι : Type u} {β₁ : ι → Type v₁} {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)]
    [(i : ι) → HasZero (β₂ i)] (f : (i : ι) → β₁ i → β₂ i) (hf : ∀ (i : ι), f i 0 = 0)
    (g : dfinsupp fun (i : ι) => β₁ i) : dfinsupp fun (i : ι) => β₂ i :=
  quotient.lift_on g
    (fun (x : pre ι fun (i : ι) => β₁ i) =>
      quotient.mk (pre.mk (fun (i : ι) => f i (pre.to_fun x i)) (pre.pre_support x) sorry))
    sorry

@[simp] theorem map_range_apply {ι : Type u} {β₁ : ι → Type v₁} {β₂ : ι → Type v₂}
    [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)] (f : (i : ι) → β₁ i → β₂ i)
    (hf : ∀ (i : ι), f i 0 = 0) (g : dfinsupp fun (i : ι) => β₁ i) (i : ι) :
    coe_fn (map_range f hf g) i = f i (coe_fn g i) :=
  quotient.induction_on g fun (x : pre ι fun (i : ι) => β₁ i) => rfl

/-- Let `f i` be a binary operation `β₁ i → β₂ i → β i` such that `f i 0 0 = 0`.
Then `zip_with f hf` is a binary operation `Π₀ i, β₁ i → Π₀ i, β₂ i → Π₀ i, β i`. -/
def zip_with {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] {β₁ : ι → Type v₁}
    {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)]
    (f : (i : ι) → β₁ i → β₂ i → β i) (hf : ∀ (i : ι), f i 0 0 = 0)
    (g₁ : dfinsupp fun (i : ι) => β₁ i) (g₂ : dfinsupp fun (i : ι) => β₂ i) :
    dfinsupp fun (i : ι) => β i :=
  quotient.lift_on₂ g₁ g₂
    (fun (x : pre ι fun (i : ι) => β₁ i) (y : pre ι fun (i : ι) => β₂ i) =>
      quotient.mk
        (pre.mk (fun (i : ι) => f i (pre.to_fun x i) (pre.to_fun y i))
          (pre.pre_support x + pre.pre_support y) sorry))
    sorry

@[simp] theorem zip_with_apply {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)]
    {β₁ : ι → Type v₁} {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)]
    (f : (i : ι) → β₁ i → β₂ i → β i) (hf : ∀ (i : ι), f i 0 0 = 0)
    (g₁ : dfinsupp fun (i : ι) => β₁ i) (g₂ : dfinsupp fun (i : ι) => β₂ i) (i : ι) :
    coe_fn (zip_with f hf g₁ g₂) i = f i (coe_fn g₁ i) (coe_fn g₂ i) :=
  quotient.induction_on₂ g₁ g₂
    fun (_x : pre ι fun (i : ι) => β₁ i) (_x_1 : pre ι fun (i : ι) => β₂ i) => rfl

protected instance has_add {ι : Type u} {β : ι → Type v} [(i : ι) → add_monoid (β i)] :
    Add (dfinsupp fun (i : ι) => β i) :=
  { add := zip_with (fun (_x : ι) => Add.add) sorry }

@[simp] theorem add_apply {ι : Type u} {β : ι → Type v} [(i : ι) → add_monoid (β i)]
    (g₁ : dfinsupp fun (i : ι) => β i) (g₂ : dfinsupp fun (i : ι) => β i) (i : ι) :
    coe_fn (g₁ + g₂) i = coe_fn g₁ i + coe_fn g₂ i :=
  zip_with_apply (fun (_x : ι) => Add.add) has_add._proof_1 g₁ g₂ i

protected instance add_monoid {ι : Type u} {β : ι → Type v} [(i : ι) → add_monoid (β i)] :
    add_monoid (dfinsupp fun (i : ι) => β i) :=
  add_monoid.mk Add.add sorry 0 sorry sorry

protected instance is_add_monoid_hom {ι : Type u} {β : ι → Type v} [(i : ι) → add_monoid (β i)]
    {i : ι} : is_add_monoid_hom fun (g : dfinsupp fun (i : ι) => β i) => coe_fn g i :=
  is_add_monoid_hom.mk (zero_apply i)

protected instance has_neg {ι : Type u} {β : ι → Type v} [(i : ι) → add_group (β i)] :
    Neg (dfinsupp fun (i : ι) => β i) :=
  { neg := fun (f : dfinsupp fun (i : ι) => β i) => map_range (fun (_x : ι) => Neg.neg) sorry f }

protected instance add_comm_monoid {ι : Type u} {β : ι → Type v} [(i : ι) → add_comm_monoid (β i)] :
    add_comm_monoid (dfinsupp fun (i : ι) => β i) :=
  add_comm_monoid.mk add_monoid.add sorry add_monoid.zero sorry sorry sorry

@[simp] theorem neg_apply {ι : Type u} {β : ι → Type v} [(i : ι) → add_group (β i)]
    (g : dfinsupp fun (i : ι) => β i) (i : ι) : coe_fn (-g) i = -coe_fn g i :=
  map_range_apply (fun (_x : ι) => Neg.neg) has_neg._proof_1 g i

protected instance add_group {ι : Type u} {β : ι → Type v} [(i : ι) → add_group (β i)] :
    add_group (dfinsupp fun (i : ι) => β i) :=
  add_group.mk add_monoid.add sorry add_monoid.zero sorry sorry Neg.neg
    (sub_neg_monoid.sub._default add_monoid.add sorry add_monoid.zero sorry sorry Neg.neg) sorry

@[simp] theorem sub_apply {ι : Type u} {β : ι → Type v} [(i : ι) → add_group (β i)]
    (g₁ : dfinsupp fun (i : ι) => β i) (g₂ : dfinsupp fun (i : ι) => β i) (i : ι) :
    coe_fn (g₁ - g₂) i = coe_fn g₁ i - coe_fn g₂ i :=
  sorry

protected instance add_comm_group {ι : Type u} {β : ι → Type v} [(i : ι) → add_comm_group (β i)] :
    add_comm_group (dfinsupp fun (i : ι) => β i) :=
  add_comm_group.mk add_group.add sorry add_group.zero sorry sorry add_group.neg add_group.sub sorry
    sorry

/-- Dependent functions with finite support inherit a semiring action from an action on each
coordinate. -/
protected instance has_scalar {ι : Type u} {β : ι → Type v} {γ : Type w} [semiring γ]
    [(i : ι) → add_comm_monoid (β i)] [(i : ι) → semimodule γ (β i)] :
    has_scalar γ (dfinsupp fun (i : ι) => β i) :=
  has_scalar.mk
    fun (c : γ) (v : dfinsupp fun (i : ι) => β i) =>
      map_range (fun (_x : ι) => has_scalar.smul c) sorry v

@[simp] theorem smul_apply {ι : Type u} {β : ι → Type v} {γ : Type w} [semiring γ]
    [(i : ι) → add_comm_monoid (β i)] [(i : ι) → semimodule γ (β i)] (b : γ)
    (v : dfinsupp fun (i : ι) => β i) (i : ι) : coe_fn (b • v) i = b • coe_fn v i :=
  map_range_apply (fun (_x : ι) => has_scalar.smul b) (has_scalar._proof_1 b) v i

/-- Dependent functions with finite support inherit a semimodule structure from such a structure on
each coordinate. -/
protected instance semimodule {ι : Type u} {β : ι → Type v} {γ : Type w} [semiring γ]
    [(i : ι) → add_comm_monoid (β i)] [(i : ι) → semimodule γ (β i)] :
    semimodule γ (dfinsupp fun (i : ι) => β i) :=
  semimodule.mk sorry sorry

/-- `filter p f` is the function which is `f i` if `p i` is true and 0 otherwise. -/
def filter {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] (p : ι → Prop) [decidable_pred p]
    (f : dfinsupp fun (i : ι) => β i) : dfinsupp fun (i : ι) => β i :=
  quotient.lift_on f
    (fun (x : pre ι fun (i : ι) => β i) =>
      quotient.mk (pre.mk (fun (i : ι) => ite (p i) (pre.to_fun x i) 0) (pre.pre_support x) sorry))
    sorry

@[simp] theorem filter_apply {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] (p : ι → Prop)
    [decidable_pred p] (i : ι) (f : dfinsupp fun (i : ι) => β i) :
    coe_fn (filter p f) i = ite (p i) (coe_fn f i) 0 :=
  quotient.induction_on f fun (x : pre ι fun (i : ι) => β i) => rfl

theorem filter_apply_pos {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] {p : ι → Prop}
    [decidable_pred p] (f : dfinsupp fun (i : ι) => β i) {i : ι} (h : p i) :
    coe_fn (filter p f) i = coe_fn f i :=
  sorry

theorem filter_apply_neg {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] {p : ι → Prop}
    [decidable_pred p] (f : dfinsupp fun (i : ι) => β i) {i : ι} (h : ¬p i) :
    coe_fn (filter p f) i = 0 :=
  sorry

theorem filter_pos_add_filter_neg {ι : Type u} {β : ι → Type v} [(i : ι) → add_monoid (β i)]
    (f : dfinsupp fun (i : ι) => β i) (p : ι → Prop) [decidable_pred p] :
    filter p f + filter (fun (i : ι) => ¬p i) f = f :=
  sorry

/-- `subtype_domain p f` is the restriction of the finitely supported function
  `f` to the subtype `p`. -/
def subtype_domain {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] (p : ι → Prop)
    [decidable_pred p] (f : dfinsupp fun (i : ι) => β i) : dfinsupp fun (i : Subtype p) => β ↑i :=
  quotient.lift_on f
    (fun (x : pre ι fun (i : ι) => β i) =>
      quotient.mk
        (pre.mk (fun (i : Subtype p) => pre.to_fun x ↑i)
          (multiset.map
            (fun (j : Subtype fun (x_1 : ι) => x_1 ∈ multiset.filter p (pre.pre_support x)) =>
              { val := ↑j, property := sorry })
            (multiset.attach (multiset.filter p (pre.pre_support x))))
          sorry))
    sorry

@[simp] theorem subtype_domain_zero {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)]
    {p : ι → Prop} [decidable_pred p] : subtype_domain p 0 = 0 :=
  rfl

@[simp] theorem subtype_domain_apply {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)]
    {p : ι → Prop} [decidable_pred p] {i : Subtype p} {v : dfinsupp fun (i : ι) => β i} :
    coe_fn (subtype_domain p v) i = coe_fn v ↑i :=
  quotient.induction_on v fun (x : pre ι fun (i : ι) => β i) => rfl

@[simp] theorem subtype_domain_add {ι : Type u} {β : ι → Type v} [(i : ι) → add_monoid (β i)]
    {p : ι → Prop} [decidable_pred p] {v : dfinsupp fun (i : ι) => β i}
    {v' : dfinsupp fun (i : ι) => β i} :
    subtype_domain p (v + v') = subtype_domain p v + subtype_domain p v' :=
  sorry

protected instance subtype_domain.is_add_monoid_hom {ι : Type u} {β : ι → Type v}
    [(i : ι) → add_monoid (β i)] {p : ι → Prop} [decidable_pred p] :
    is_add_monoid_hom (subtype_domain p) :=
  is_add_monoid_hom.mk subtype_domain_zero

@[simp] theorem subtype_domain_neg {ι : Type u} {β : ι → Type v} [(i : ι) → add_group (β i)]
    {p : ι → Prop} [decidable_pred p] {v : dfinsupp fun (i : ι) => β i} :
    subtype_domain p (-v) = -subtype_domain p v :=
  sorry

@[simp] theorem subtype_domain_sub {ι : Type u} {β : ι → Type v} [(i : ι) → add_group (β i)]
    {p : ι → Prop} [decidable_pred p] {v : dfinsupp fun (i : ι) => β i}
    {v' : dfinsupp fun (i : ι) => β i} :
    subtype_domain p (v - v') = subtype_domain p v - subtype_domain p v' :=
  sorry

theorem finite_supp {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)]
    (f : dfinsupp fun (i : ι) => β i) : set.finite (set_of fun (i : ι) => coe_fn f i ≠ 0) :=
  sorry

/-- Create an element of `Π₀ i, β i` from a finset `s` and a function `x`
defined on this `finset`. -/
def mk {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] (s : finset ι)
    (x : (i : ↥↑s) → β ↑i) : dfinsupp fun (i : ι) => β i :=
  quotient.mk
    (pre.mk
      (fun (i : ι) =>
        dite (i ∈ s) (fun (H : i ∈ s) => x { val := i, property := H }) fun (H : ¬i ∈ s) => 0)
      (finset.val s) sorry)

@[simp] theorem mk_apply {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] {s : finset ι} {x : (i : ↥↑s) → β ↑i} {i : ι} :
    coe_fn (mk s x) i =
        dite (i ∈ s) (fun (H : i ∈ s) => x { val := i, property := H }) fun (H : ¬i ∈ s) => 0 :=
  rfl

theorem mk_injective {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)]
    (s : finset ι) : function.injective (mk s) :=
  sorry

/-- The function `single i b : Π₀ i, β i` sends `i` to `b`
and all other points to `0`. -/
def single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] (i : ι)
    (b : β i) : dfinsupp fun (i : ι) => β i :=
  mk (singleton i) fun (j : ↥↑(singleton i)) => eq.rec_on sorry b

@[simp] theorem single_apply {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] {i : ι} {i' : ι} {b : β i} :
    coe_fn (single i b) i' =
        dite (i = i') (fun (h : i = i') => eq.rec_on h b) fun (h : ¬i = i') => 0 :=
  sorry

@[simp] theorem single_zero {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] {i : ι} : single i 0 = 0 :=
  sorry

@[simp] theorem single_eq_same {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] {i : ι} {b : β i} : coe_fn (single i b) i = b :=
  sorry

theorem single_eq_of_ne {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] {i : ι} {i' : ι} {b : β i} (h : i ≠ i') :
    coe_fn (single i b) i' = 0 :=
  sorry

theorem single_injective {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] {i : ι} : function.injective (single i) :=
  sorry

/-- Like `finsupp.single_eq_single_iff`, but with a `heq` due to dependent types -/
theorem single_eq_single_iff {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] (i : ι) (j : ι) (xi : β i) (xj : β j) :
    single i xi = single j xj ↔ i = j ∧ xi == xj ∨ xi = 0 ∧ xj = 0 :=
  sorry

/-- Redefine `f i` to be `0`. -/
def erase {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] (i : ι)
    (f : dfinsupp fun (i : ι) => β i) : dfinsupp fun (i : ι) => β i :=
  quotient.lift_on f
    (fun (x : pre ι fun (i : ι) => β i) =>
      quotient.mk
        (pre.mk (fun (j : ι) => ite (j = i) 0 (pre.to_fun x j)) (pre.pre_support x) sorry))
    sorry

@[simp] theorem erase_apply {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] {i : ι} {j : ι} {f : dfinsupp fun (i : ι) => β i} :
    coe_fn (erase i f) j = ite (j = i) 0 (coe_fn f j) :=
  quotient.induction_on f fun (x : pre ι fun (i : ι) => β i) => rfl

@[simp] theorem erase_same {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] {i : ι} {f : dfinsupp fun (i : ι) => β i} :
    coe_fn (erase i f) i = 0 :=
  sorry

theorem erase_ne {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)]
    {i : ι} {i' : ι} {f : dfinsupp fun (i : ι) => β i} (h : i' ≠ i) :
    coe_fn (erase i f) i' = coe_fn f i' :=
  sorry

@[simp] theorem single_add {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → add_monoid (β i)] {i : ι} {b₁ : β i} {b₂ : β i} :
    single i (b₁ + b₂) = single i b₁ + single i b₂ :=
  sorry

/-- `dfinsupp.single` as an `add_monoid_hom`. -/
@[simp] theorem single_add_hom_apply {ι : Type u} (β : ι → Type v) [dec : DecidableEq ι]
    [(i : ι) → add_monoid (β i)] (i : ι) (b : β i) : coe_fn (single_add_hom β i) b = single i b :=
  Eq.refl (coe_fn (single_add_hom β i) b)

theorem single_add_erase {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → add_monoid (β i)] {i : ι} {f : dfinsupp fun (i : ι) => β i} :
    single i (coe_fn f i) + erase i f = f :=
  sorry

theorem erase_add_single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → add_monoid (β i)] {i : ι} {f : dfinsupp fun (i : ι) => β i} :
    erase i f + single i (coe_fn f i) = f :=
  sorry

protected theorem induction {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → add_monoid (β i)] {p : (dfinsupp fun (i : ι) => β i) → Prop}
    (f : dfinsupp fun (i : ι) => β i) (h0 : p 0)
    (ha :
      ∀ (i : ι) (b : β i) (f : dfinsupp fun (i : ι) => β i),
        coe_fn f i = 0 → b ≠ 0 → p f → p (single i b + f)) :
    p f :=
  sorry

theorem induction₂ {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_monoid (β i)]
    {p : (dfinsupp fun (i : ι) => β i) → Prop} (f : dfinsupp fun (i : ι) => β i) (h0 : p 0)
    (ha :
      ∀ (i : ι) (b : β i) (f : dfinsupp fun (i : ι) => β i),
        coe_fn f i = 0 → b ≠ 0 → p f → p (f + single i b)) :
    p f :=
  sorry

@[simp] theorem add_closure_Union_range_single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → add_monoid (β i)] :
    add_submonoid.closure (set.Union fun (i : ι) => set.range (single i)) = ⊤ :=
  sorry

/-- If two additive homomorphisms from `Π₀ i, β i` are equal on each `single a b`, then
they are equal. -/
theorem add_hom_ext {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_monoid (β i)]
    {γ : Type w} [add_monoid γ] {f : (dfinsupp fun (i : ι) => β i) →+ γ}
    {g : (dfinsupp fun (i : ι) => β i) →+ γ}
    (H : ∀ (i : ι) (y : β i), coe_fn f (single i y) = coe_fn g (single i y)) : f = g :=
  sorry

/-- If two additive homomorphisms from `Π₀ i, β i` are equal on each `single a b`, then
they are equal.

See note [partially-applied ext lemmas]. -/
theorem add_hom_ext' {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → add_monoid (β i)] {γ : Type w} [add_monoid γ]
    {f : (dfinsupp fun (i : ι) => β i) →+ γ} {g : (dfinsupp fun (i : ι) => β i) →+ γ}
    (H :
      ∀ (x : ι),
        add_monoid_hom.comp f (single_add_hom β x) = add_monoid_hom.comp g (single_add_hom β x)) :
    f = g :=
  add_hom_ext fun (x : ι) => add_monoid_hom.congr_fun (H x)

@[simp] theorem mk_add {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → add_monoid (β i)] {s : finset ι} {x : (i : ↥↑s) → β ↑i} {y : (i : ↥↑s) → β ↑i} :
    mk s (x + y) = mk s x + mk s y :=
  sorry

@[simp] theorem mk_zero {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] {s : finset ι} : mk s 0 = 0 :=
  sorry

@[simp] theorem mk_neg {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → add_group (β i)] {s : finset ι} {x : (i : ↥↑s) → β (subtype.val i)} :
    mk s (-x) = -mk s x :=
  sorry

@[simp] theorem mk_sub {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → add_group (β i)] {s : finset ι} {x : (i : ↥↑s) → β (subtype.val i)}
    {y : (i : ↥↑s) → β (subtype.val i)} : mk s (x - y) = mk s x - mk s y :=
  sorry

protected instance mk.is_add_group_hom {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → add_group (β i)] {s : finset ι} : is_add_group_hom (mk s) :=
  is_add_group_hom.mk

@[simp] theorem mk_smul {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] (γ : Type w)
    [semiring γ] [(i : ι) → add_comm_monoid (β i)] [(i : ι) → semimodule γ (β i)] {s : finset ι}
    {c : γ} (x : (i : ↥↑s) → β (subtype.val i)) : mk s (c • x) = c • mk s x :=
  sorry

@[simp] theorem single_smul {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] (γ : Type w)
    [semiring γ] [(i : ι) → add_comm_monoid (β i)] [(i : ι) → semimodule γ (β i)] {i : ι} {c : γ}
    {x : β i} : single i (c • x) = c • single i x :=
  sorry

/-- Set `{i | f x ≠ 0}` as a `finset`. -/
def support {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)]
    [(i : ι) → (x : β i) → Decidable (x ≠ 0)] (f : dfinsupp fun (i : ι) => β i) : finset ι :=
  quotient.lift_on f
    (fun (x : pre ι fun (i : ι) => β i) =>
      finset.filter (fun (i : ι) => pre.to_fun x i ≠ 0) (multiset.to_finset (pre.pre_support x)))
    sorry

@[simp] theorem support_mk_subset {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {s : finset ι}
    {x : (i : ↥↑s) → β (subtype.val i)} : support (mk s x) ⊆ s :=
  fun (i : ι) (H : i ∈ support (mk s x)) =>
    iff.mp multiset.mem_to_finset (and.left (iff.mp finset.mem_filter H))

@[simp] theorem mem_support_to_fun {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)]
    (f : dfinsupp fun (i : ι) => β i) (i : ι) : i ∈ support f ↔ coe_fn f i ≠ 0 :=
  sorry

theorem eq_mk_support {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)]
    [(i : ι) → (x : β i) → Decidable (x ≠ 0)] (f : dfinsupp fun (i : ι) => β i) :
    f = mk (support f) fun (i : ↥↑(support f)) => coe_fn f ↑i :=
  sorry

@[simp] theorem support_zero {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] : support 0 = ∅ :=
  rfl

theorem mem_support_iff {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)]
    (f : dfinsupp fun (i : ι) => β i) (i : ι) : i ∈ support f ↔ coe_fn f i ≠ 0 :=
  mem_support_to_fun f

@[simp] theorem support_eq_empty {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)]
    {f : dfinsupp fun (i : ι) => β i} : support f = ∅ ↔ f = 0 :=
  sorry

protected instance decidable_zero {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] : decidable_pred (Eq 0) :=
  fun (f : dfinsupp fun (i : ι) => β i) => decidable_of_iff (support f = ∅) sorry

theorem support_subset_iff {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {s : set ι}
    {f : dfinsupp fun (i : ι) => β i} : ↑(support f) ⊆ s ↔ ∀ (i : ι), ¬i ∈ s → coe_fn f i = 0 :=
  sorry

theorem support_single_ne_zero {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {i : ι} {b : β i}
    (hb : b ≠ 0) : support (single i b) = singleton i :=
  sorry

theorem support_single_subset {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {i : ι} {b : β i} :
    support (single i b) ⊆ singleton i :=
  support_mk_subset

theorem map_range_def {ι : Type u} [dec : DecidableEq ι] {β₁ : ι → Type v₁} {β₂ : ι → Type v₂}
    [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)] [(i : ι) → (x : β₁ i) → Decidable (x ≠ 0)]
    {f : (i : ι) → β₁ i → β₂ i} {hf : ∀ (i : ι), f i 0 = 0} {g : dfinsupp fun (i : ι) => β₁ i} :
    map_range f hf g =
        mk (support g) fun (i : ↥↑(support g)) => f (subtype.val i) (coe_fn g (subtype.val i)) :=
  sorry

@[simp] theorem map_range_single {ι : Type u} [dec : DecidableEq ι] {β₁ : ι → Type v₁}
    {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)]
    {f : (i : ι) → β₁ i → β₂ i} {hf : ∀ (i : ι), f i 0 = 0} {i : ι} {b : β₁ i} :
    map_range f hf (single i b) = single i (f i b) :=
  sorry

theorem support_map_range {ι : Type u} [dec : DecidableEq ι] {β₁ : ι → Type v₁} {β₂ : ι → Type v₂}
    [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)] [(i : ι) → (x : β₁ i) → Decidable (x ≠ 0)]
    [(i : ι) → (x : β₂ i) → Decidable (x ≠ 0)] {f : (i : ι) → β₁ i → β₂ i}
    {hf : ∀ (i : ι), f i 0 = 0} {g : dfinsupp fun (i : ι) => β₁ i} :
    support (map_range f hf g) ⊆ support g :=
  sorry

theorem zip_with_def {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)]
    [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {β₁ : ι → Type v₁} {β₂ : ι → Type v₂}
    [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)] [(i : ι) → (x : β₁ i) → Decidable (x ≠ 0)]
    [(i : ι) → (x : β₂ i) → Decidable (x ≠ 0)] {f : (i : ι) → β₁ i → β₂ i → β i}
    {hf : ∀ (i : ι), f i 0 0 = 0} {g₁ : dfinsupp fun (i : ι) => β₁ i}
    {g₂ : dfinsupp fun (i : ι) => β₂ i} :
    zip_with f hf g₁ g₂ =
        mk (support g₁ ∪ support g₂)
          fun (i : ↥↑(support g₁ ∪ support g₂)) =>
            f (subtype.val i) (coe_fn g₁ (subtype.val i)) (coe_fn g₂ (subtype.val i)) :=
  sorry

theorem support_zip_with {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {β₁ : ι → Type v₁}
    {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)]
    [(i : ι) → (x : β₁ i) → Decidable (x ≠ 0)] [(i : ι) → (x : β₂ i) → Decidable (x ≠ 0)]
    {f : (i : ι) → β₁ i → β₂ i → β i} {hf : ∀ (i : ι), f i 0 0 = 0}
    {g₁ : dfinsupp fun (i : ι) => β₁ i} {g₂ : dfinsupp fun (i : ι) => β₂ i} :
    support (zip_with f hf g₁ g₂) ⊆ support g₁ ∪ support g₂ :=
  sorry

theorem erase_def {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)]
    [(i : ι) → (x : β i) → Decidable (x ≠ 0)] (i : ι) (f : dfinsupp fun (i : ι) => β i) :
    erase i f =
        mk (finset.erase (support f) i)
          fun (j : ↥↑(finset.erase (support f) i)) => coe_fn f (subtype.val j) :=
  sorry

@[simp] theorem support_erase {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] (i : ι)
    (f : dfinsupp fun (i : ι) => β i) : support (erase i f) = finset.erase (support f) i :=
  sorry

theorem filter_def {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)]
    [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {p : ι → Prop} [decidable_pred p]
    (f : dfinsupp fun (i : ι) => β i) :
    filter p f =
        mk (finset.filter p (support f))
          fun (i : ↥↑(finset.filter p (support f))) => coe_fn f (subtype.val i) :=
  sorry

@[simp] theorem support_filter {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {p : ι → Prop}
    [decidable_pred p] (f : dfinsupp fun (i : ι) => β i) :
    support (filter p f) = finset.filter p (support f) :=
  sorry

theorem subtype_domain_def {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {p : ι → Prop}
    [decidable_pred p] (f : dfinsupp fun (i : ι) => β i) :
    subtype_domain p f =
        mk (finset.subtype p (support f))
          fun (i : ↥↑(finset.subtype p (support f))) => coe_fn f ↑i :=
  sorry

@[simp] theorem support_subtype_domain {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {p : ι → Prop}
    [decidable_pred p] {f : dfinsupp fun (i : ι) => β i} :
    support (subtype_domain p f) = finset.subtype p (support f) :=
  sorry

theorem support_add {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_monoid (β i)]
    [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {g₁ : dfinsupp fun (i : ι) => β i}
    {g₂ : dfinsupp fun (i : ι) => β i} : support (g₁ + g₂) ⊆ support g₁ ∪ support g₂ :=
  support_zip_with

@[simp] theorem support_neg {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → add_group (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)]
    {f : dfinsupp fun (i : ι) => β i} : support (-f) = support f :=
  sorry

theorem support_smul {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [semiring γ]
    [(i : ι) → add_comm_monoid (β i)] [(i : ι) → semimodule γ (β i)]
    [(i : ι) → (x : β i) → Decidable (x ≠ 0)] (b : γ) (v : dfinsupp fun (i : ι) => β i) :
    support (b • v) ⊆ support v :=
  support_map_range

protected instance decidable_eq {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → HasZero (β i)] [(i : ι) → DecidableEq (β i)] :
    DecidableEq (dfinsupp fun (i : ι) => β i) :=
  fun (f g : dfinsupp fun (i : ι) => β i) =>
    decidable_of_iff (support f = support g ∧ ∀ (i : ι), i ∈ support f → coe_fn f i = coe_fn g i)
      sorry

-- [to_additive sum] for dfinsupp.prod doesn't work, the equation lemmas are not generated

/-- `sum f g` is the sum of `g i (f i)` over the support of `f`. -/
def sum {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → HasZero (β i)]
    [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ] (f : dfinsupp fun (i : ι) => β i)
    (g : (i : ι) → β i → γ) : γ :=
  finset.sum (support f) fun (i : ι) => g i (coe_fn f i)

/-- `prod f g` is the product of `g i (f i)` over the support of `f`. -/
def prod {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → HasZero (β i)]
    [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [comm_monoid γ] (f : dfinsupp fun (i : ι) => β i)
    (g : (i : ι) → β i → γ) : γ :=
  finset.prod (support f) fun (i : ι) => g i (coe_fn f i)

theorem prod_map_range_index {ι : Type u} [dec : DecidableEq ι] {γ : Type w} {β₁ : ι → Type v₁}
    {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)]
    [(i : ι) → (x : β₁ i) → Decidable (x ≠ 0)] [(i : ι) → (x : β₂ i) → Decidable (x ≠ 0)]
    [comm_monoid γ] {f : (i : ι) → β₁ i → β₂ i} {hf : ∀ (i : ι), f i 0 = 0}
    {g : dfinsupp fun (i : ι) => β₁ i} {h : (i : ι) → β₂ i → γ} (h0 : ∀ (i : ι), h i 0 = 1) :
    prod (map_range f hf g) h = prod g fun (i : ι) (b : β₁ i) => h i (f i b) :=
  sorry

theorem sum_zero_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ]
    {h : (i : ι) → β i → γ} : sum 0 h = 0 :=
  rfl

theorem sum_single_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ] {i : ι}
    {b : β i} {h : (i : ι) → β i → γ} (h_zero : h i 0 = 0) : sum (single i b) h = h i b :=
  sorry

theorem sum_neg_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    [(i : ι) → add_group (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ]
    {g : dfinsupp fun (i : ι) => β i} {h : (i : ι) → β i → γ} (h0 : ∀ (i : ι), h i 0 = 0) :
    sum (-g) h = sum g fun (i : ι) (b : β i) => h i (-b) :=
  sum_map_range_index h0

theorem sum_comm {γ : Type w} {ι₁ : Type u_1} {ι₂ : Type u_2} {β₁ : ι₁ → Type u_3}
    {β₂ : ι₂ → Type u_4} [DecidableEq ι₁] [DecidableEq ι₂] [(i : ι₁) → HasZero (β₁ i)]
    [(i : ι₂) → HasZero (β₂ i)] [(i : ι₁) → (x : β₁ i) → Decidable (x ≠ 0)]
    [(i : ι₂) → (x : β₂ i) → Decidable (x ≠ 0)] [add_comm_monoid γ]
    (f₁ : dfinsupp fun (i : ι₁) => β₁ i) (f₂ : dfinsupp fun (i : ι₂) => β₂ i)
    (h : (i : ι₁) → β₁ i → (i : ι₂) → β₂ i → γ) :
    (sum f₁ fun (i₁ : ι₁) (x₁ : β₁ i₁) => sum f₂ fun (i₂ : ι₂) (x₂ : β₂ i₂) => h i₁ x₁ i₂ x₂) =
        sum f₂ fun (i₂ : ι₂) (x₂ : β₂ i₂) => sum f₁ fun (i₁ : ι₁) (x₁ : β₁ i₁) => h i₁ x₁ i₂ x₂ :=
  finset.sum_comm

@[simp] theorem sum_apply {ι : Type u} {β : ι → Type v} {ι₁ : Type u₁} [DecidableEq ι₁]
    {β₁ : ι₁ → Type v₁} [(i₁ : ι₁) → HasZero (β₁ i₁)] [(i : ι₁) → (x : β₁ i) → Decidable (x ≠ 0)]
    [(i : ι) → add_comm_monoid (β i)] {f : dfinsupp fun (i₁ : ι₁) => β₁ i₁}
    {g : (i₁ : ι₁) → β₁ i₁ → dfinsupp fun (i : ι) => β i} {i₂ : ι} :
    coe_fn (sum f g) i₂ = sum f fun (i₁ : ι₁) (b : β₁ i₁) => coe_fn (g i₁ b) i₂ :=
  Eq.symm (finset.sum_hom (support f) fun (f : dfinsupp fun (i : ι) => β i) => coe_fn f i₂)

theorem support_sum {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {ι₁ : Type u₁}
    [DecidableEq ι₁] {β₁ : ι₁ → Type v₁} [(i₁ : ι₁) → HasZero (β₁ i₁)]
    [(i : ι₁) → (x : β₁ i) → Decidable (x ≠ 0)] [(i : ι) → add_comm_monoid (β i)]
    [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {f : dfinsupp fun (i₁ : ι₁) => β₁ i₁}
    {g : (i₁ : ι₁) → β₁ i₁ → dfinsupp fun (i : ι) => β i} :
    support (sum f g) ⊆ finset.bUnion (support f) fun (i : ι₁) => support (g i (coe_fn f i)) :=
  sorry

@[simp] theorem prod_one {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [comm_monoid γ]
    {f : dfinsupp fun (i : ι) => β i} : (prod f fun (i : ι) (b : β i) => 1) = 1 :=
  finset.prod_const_one

@[simp] theorem sum_add {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ]
    {f : dfinsupp fun (i : ι) => β i} {h₁ : (i : ι) → β i → γ} {h₂ : (i : ι) → β i → γ} :
    (sum f fun (i : ι) (b : β i) => h₁ i b + h₂ i b) = sum f h₁ + sum f h₂ :=
  finset.sum_add_distrib

@[simp] theorem sum_neg {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_group γ]
    {f : dfinsupp fun (i : ι) => β i} {h : (i : ι) → β i → γ} :
    (sum f fun (i : ι) (b : β i) => -h i b) = -sum f h :=
  finset.sum_hom (support f) Neg.neg

theorem prod_add_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [comm_monoid γ]
    {f : dfinsupp fun (i : ι) => β i} {g : dfinsupp fun (i : ι) => β i} {h : (i : ι) → β i → γ}
    (h_zero : ∀ (i : ι), h i 0 = 1)
    (h_add : ∀ (i : ι) (b₁ b₂ : β i), h i (b₁ + b₂) = h i b₁ * h i b₂) :
    prod (f + g) h = prod f h * prod g h :=
  sorry

/--
When summing over an `add_monoid_hom`, the decidability assumption is not needed, and the result is
also an `add_monoid_hom`.
-/
def sum_add_hom {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    [(i : ι) → add_monoid (β i)] [add_comm_monoid γ] (φ : (i : ι) → β i →+ γ) :
    (dfinsupp fun (i : ι) => β i) →+ γ :=
  add_monoid_hom.mk
    (fun (f : dfinsupp fun (i : ι) => β i) =>
      quotient.lift_on f
        (fun (x : pre ι fun (i : ι) => β i) =>
          finset.sum (multiset.to_finset (pre.pre_support x))
            fun (i : ι) => coe_fn (φ i) (pre.to_fun x i))
        sorry)
    sorry sorry

@[simp] theorem sum_add_hom_single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    [(i : ι) → add_monoid (β i)] [add_comm_monoid γ] (φ : (i : ι) → β i →+ γ) (i : ι) (x : β i) :
    coe_fn (sum_add_hom φ) (single i x) = coe_fn (φ i) x :=
  sorry

@[simp] theorem sum_add_hom_comp_single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    {γ : Type w} [(i : ι) → add_comm_monoid (β i)] [add_comm_monoid γ] (f : (i : ι) → β i →+ γ)
    (i : ι) : add_monoid_hom.comp (sum_add_hom f) (single_add_hom β i) = f i :=
  add_monoid_hom.ext fun (x : β i) => sum_add_hom_single f i x

/-- While we didn't need decidable instances to define it, we do to reduce it to a sum -/
theorem sum_add_hom_apply {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    [(i : ι) → add_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ]
    (φ : (i : ι) → β i →+ γ) (f : dfinsupp fun (i : ι) => β i) :
    coe_fn (sum_add_hom φ) f = sum f fun (x : ι) => ⇑(φ x) :=
  sorry

/-- The `dfinsupp` version of `finsupp.lift_add_hom`,-/
@[simp] theorem lift_add_hom_symm_apply {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    {γ : Type w} [(i : ι) → add_monoid (β i)] [add_comm_monoid γ]
    (F : (dfinsupp fun (i : ι) => β i) →+ γ) (i : ι) :
    coe_fn (add_equiv.symm lift_add_hom) F i = add_monoid_hom.comp F (single_add_hom β i) :=
  Eq.refl (coe_fn (add_equiv.symm lift_add_hom) F i)

/-- The `dfinsupp` version of `finsupp.lift_add_hom_single_add_hom`,-/
@[simp] theorem lift_add_hom_single_add_hom {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → add_comm_monoid (β i)] :
    coe_fn lift_add_hom (single_add_hom β) = add_monoid_hom.id (dfinsupp fun (i : ι) => β i) :=
  iff.mpr (equiv.apply_eq_iff_eq_symm_apply (add_equiv.to_equiv lift_add_hom)) rfl

/-- The `dfinsupp` version of `finsupp.lift_add_hom_apply_single`,-/
theorem lift_add_hom_apply_single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    [(i : ι) → add_comm_monoid (β i)] [add_comm_monoid γ] (f : (i : ι) → β i →+ γ) (i : ι)
    (x : β i) : coe_fn (coe_fn lift_add_hom f) (single i x) = coe_fn (f i) x :=
  sorry

/-- The `dfinsupp` version of `finsupp.lift_add_hom_comp_single`,-/
theorem lift_add_hom_comp_single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    [(i : ι) → add_comm_monoid (β i)] [add_comm_monoid γ] (f : (i : ι) → β i →+ γ) (i : ι) :
    add_monoid_hom.comp (coe_fn lift_add_hom f) (single_add_hom β i) = f i :=
  sorry

/-- The `dfinsupp` version of `finsupp.comp_lift_add_hom`,-/
theorem comp_lift_add_hom {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    {δ : Type u_1} [(i : ι) → add_comm_monoid (β i)] [add_comm_monoid γ] [add_comm_monoid δ]
    (g : γ →+ δ) (f : (i : ι) → β i →+ γ) :
    add_monoid_hom.comp g (coe_fn lift_add_hom f) =
        coe_fn lift_add_hom fun (a : ι) => add_monoid_hom.comp g (f a) :=
  sorry

theorem sum_sub_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    [(i : ι) → add_comm_group (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_group γ]
    {f : dfinsupp fun (i : ι) => β i} {g : dfinsupp fun (i : ι) => β i} {h : (i : ι) → β i → γ}
    (h_sub : ∀ (i : ι) (b₁ b₂ : β i), h i (b₁ - b₂) = h i b₁ - h i b₂) :
    sum (f - g) h = sum f h - sum g h :=
  sorry

theorem sum_finset_sum_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    {α : Type x} [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)]
    [add_comm_monoid γ] {s : finset α} {g : α → dfinsupp fun (i : ι) => β i} {h : (i : ι) → β i → γ}
    (h_zero : ∀ (i : ι), h i 0 = 0)
    (h_add : ∀ (i : ι) (b₁ b₂ : β i), h i (b₁ + b₂) = h i b₁ + h i b₂) :
    (finset.sum s fun (i : α) => sum (g i) h) = sum (finset.sum s fun (i : α) => g i) h :=
  sorry

theorem sum_sum_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    {ι₁ : Type u₁} [DecidableEq ι₁] {β₁ : ι₁ → Type v₁} [(i₁ : ι₁) → HasZero (β₁ i₁)]
    [(i : ι₁) → (x : β₁ i) → Decidable (x ≠ 0)] [(i : ι) → add_comm_monoid (β i)]
    [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ]
    {f : dfinsupp fun (i₁ : ι₁) => β₁ i₁} {g : (i₁ : ι₁) → β₁ i₁ → dfinsupp fun (i : ι) => β i}
    {h : (i : ι) → β i → γ} (h_zero : ∀ (i : ι), h i 0 = 0)
    (h_add : ∀ (i : ι) (b₁ b₂ : β i), h i (b₁ + b₂) = h i b₁ + h i b₂) :
    sum (sum f g) h = sum f fun (i : ι₁) (b : β₁ i) => sum (g i b) h :=
  Eq.symm (sum_finset_sum_index h_zero h_add)

@[simp] theorem sum_single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι]
    [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)]
    {f : dfinsupp fun (i : ι) => β i} : sum f single = f :=
  sorry

theorem sum_subtype_domain_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w}
    [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ]
    {v : dfinsupp fun (i : ι) => β i} {p : ι → Prop} [decidable_pred p] {h : (i : ι) → β i → γ}
    (hp : ∀ (x : ι), x ∈ support v → p x) :
    (sum (subtype_domain p v) fun (i : Subtype p) (b : β ↑i) => h (↑i) b) = sum v h :=
  sorry

theorem subtype_domain_sum {ι : Type u} {β : ι → Type v} {γ : Type w}
    [(i : ι) → add_comm_monoid (β i)] {s : finset γ} {h : γ → dfinsupp fun (i : ι) => β i}
    {p : ι → Prop} [decidable_pred p] :
    subtype_domain p (finset.sum s fun (c : γ) => h c) =
        finset.sum s fun (c : γ) => subtype_domain p (h c) :=
  Eq.symm (finset.sum_hom s (subtype_domain p))

theorem subtype_domain_finsupp_sum {ι : Type u} {β : ι → Type v} {γ : Type w} {δ : γ → Type x}
    [DecidableEq γ] [(c : γ) → HasZero (δ c)] [(c : γ) → (x : δ c) → Decidable (x ≠ 0)]
    [(i : ι) → add_comm_monoid (β i)] {p : ι → Prop} [decidable_pred p]
    {s : dfinsupp fun (c : γ) => δ c} {h : (c : γ) → δ c → dfinsupp fun (i : ι) => β i} :
    subtype_domain p (sum s h) = sum s fun (c : γ) (d : δ c) => subtype_domain p (h c d) :=
  subtype_domain_sum

end dfinsupp

/-! ### Product and sum lemmas for bundled morphisms -/

namespace monoid_hom

@[simp] theorem map_dfinsupp_prod {ι : Type u} {β : ι → Type v} [DecidableEq ι] {R : Type u_1}
    {S : Type u_2} [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)]
    [comm_monoid R] [comm_monoid S] (h : R →* S) (f : dfinsupp fun (i : ι) => β i)
    (g : (i : ι) → β i → R) :
    coe_fn h (dfinsupp.prod f g) = dfinsupp.prod f fun (a : ι) (b : β a) => coe_fn h (g a b) :=
  map_prod h (fun (i : ι) => g i (coe_fn f i)) (dfinsupp.support f)

theorem coe_dfinsupp_prod {ι : Type u} {β : ι → Type v} [DecidableEq ι] {R : Type u_1}
    {S : Type u_2} [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [monoid R]
    [comm_monoid S] (f : dfinsupp fun (i : ι) => β i) (g : (i : ι) → β i → R →* S) :
    ⇑(dfinsupp.prod f g) = dfinsupp.prod f fun (a : ι) (b : β a) => ⇑(g a b) :=
  coe_prod (fun (i : ι) => g i (coe_fn f i)) (dfinsupp.support f)

@[simp] theorem dfinsupp_prod_apply {ι : Type u} {β : ι → Type v} [DecidableEq ι] {R : Type u_1}
    {S : Type u_2} [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [monoid R]
    [comm_monoid S] (f : dfinsupp fun (i : ι) => β i) (g : (i : ι) → β i → R →* S) (r : R) :
    coe_fn (dfinsupp.prod f g) r = dfinsupp.prod f fun (a : ι) (b : β a) => coe_fn (g a b) r :=
  finset_prod_apply (fun (i : ι) => g i (coe_fn f i)) (dfinsupp.support f) r

end Mathlib