set_option maxHeartbeats 500000 in
theorem comp_assoc (r : FormalMultilinearSeries 𝕜 G H) (q : FormalMultilinearSeries 𝕜 F G)
    (p : FormalMultilinearSeries 𝕜 E F) : (r.comp q).comp p = r.comp (q.comp p) := by
  ext n v
  /- First, rewrite the two compositions appearing in the theorem as two sums over complicated
    sigma types, as in the description of the proof above. -/
  let f : (Σ a : Composition n, Composition a.length) → H := fun c =>
    r c.2.length (applyComposition q c.2 (applyComposition p c.1 v))
  let g : (Σ c : Composition n, ∀ i : Fin c.length, Composition (c.blocksFun i)) → H := fun c =>
    r c.1.length fun i : Fin c.1.length =>
      q (c.2 i).length (applyComposition p (c.2 i) (v ∘ c.1.embedding i))
  suffices ∑ c, f c = ∑ c, g c by
    simpa (config := { unfoldPartialApp := true }) only [FormalMultilinearSeries.comp,
      ContinuousMultilinearMap.sum_apply, compAlongComposition_apply, Finset.sum_sigma',
      applyComposition, ContinuousMultilinearMap.map_sum]
  /- Now, we use `Composition.sigmaEquivSigmaPi n` to change
    variables in the second sum, and check that we get exactly the same sums. -/
  rw [← (sigmaEquivSigmaPi n).sum_comp]
  /- To check that we have the same terms, we should check that we apply the same component of
    `r`, and the same component of `q`, and the same component of `p`, to the same coordinate of
    `v`. This is true by definition, but at each step one needs to convince Lean that the types
    one considers are the same, using a suitable congruence lemma to avoid dependent type issues.
    This dance has to be done three times, one for `r`, one for `q` and one for `p`.-/
  apply Finset.sum_congr rfl
  rintro ⟨a, b⟩ _
  dsimp [sigmaEquivSigmaPi]
  -- check that the `r` components are the same. Based on `Composition.length_gather`
  apply r.congr (Composition.length_gather a b).symm
  intro i hi1 hi2
  -- check that the `q` components are the same. Based on `length_sigma_composition_aux`
  apply q.congr (length_sigmaCompositionAux a b _).symm
  intro j hj1 hj2
  -- check that the `p` components are the same. Based on `blocks_fun_sigma_composition_aux`
  apply p.congr (blocksFun_sigmaCompositionAux a b _ _).symm
  intro k hk1 hk2
  -- finally, check that the coordinates of `v` one is using are the same. Based on
  -- `size_up_to_size_up_to_add`.
  

Proof state:
case h.H.mk
𝕜 : Type u_1
E : Type u_2
F : Type u_3
G : Type u_4
H : Type u_5
inst✝⁸ : NontriviallyNormedField 𝕜
inst✝⁷ : NormedAddCommGroup E
inst✝⁶ : NormedSpace 𝕜 E
inst✝⁵ : NormedAddCommGroup F
inst✝⁴ : NormedSpace 𝕜 F
inst✝³ : NormedAddCommGroup G
inst✝² : NormedSpace 𝕜 G
inst✝¹ : NormedAddCommGroup H
inst✝ : NormedSpace 𝕜 H
r : FormalMultilinearSeries 𝕜 G H
q : FormalMultilinearSeries 𝕜 F G
p : FormalMultilinearSeries 𝕜 E F
n : ℕ
v : Fin n → E
f : (a : Composition n) × Composition (Composition.length a) → H :=
  fun c => (r (Composition.length c.snd)) (applyComposition q c.snd (applyComposition p c.fst v))
g : (c : Composition n) × ((i : Fin (Composition.length c)) → Composition (blocksFun c i)) → H :=
  fun c =>
    (r (Composition.length c.fst)) fun i =>
      (q (Composition.length (Sigma.snd c i))) (applyComposition p (Sigma.snd c i) (v ∘ ⇑(embedding c.fst i)))
a : Composition n
b : Composition (Composition.length a)
a✝ : { fst := a, snd := b } ∈ Finset.univ
i : ℕ
hi1 : i < Composition.length b
hi2 : i < Composition.length (gather a b)
j : ℕ
hj1 : j < blocksFun b { val := i, isLt := hi1 }
hj2 :
  j <
    Composition.length
      (sigmaCompositionAux a b
        { val := ↑{ val := i, isLt := hi1 },
          isLt := (_ : ↑{ val := i, isLt := hi1 } < Composition.length (gather a b)) })
k : ℕ
hk1 : k < blocksFun a ((embedding b { val := i, isLt := hi1 }) { val := j, isLt := hj1 })
hk2 :
  k <
    blocksFun
      (sigmaCompositionAux a b
        { val := ↑{ val := i, isLt := hi1 },
          isLt := (_ : ↑{ val := i, isLt := hi1 } < Composition.length (gather a b)) })
      { val := ↑{ val := j, isLt := hj1 },
        isLt :=
          (_ :
            ↑{ val := j, isLt := hj1 } <
              Composition.length
                (sigmaCompositionAux a b
                  { val := ↑{ val := i, isLt := hi1 },
                    isLt := (_ : ↑{ val := i, isLt := hi1 } < Composition.length (gather a b)) })) }
⊢ (v ∘ ⇑(embedding a ((embedding b { val := i, isLt := hi1 }) { val := j, isLt := hj1 }))) { val := k, isLt := hk1 } =
    ((v ∘ ⇑(embedding (gather a b) { val := i, isLt := hi2 })) ∘
        ⇑(embedding (sigmaCompositionAux a b { val := i, isLt := hi2 }) { val := j, isLt := hj2 }))
      { val := k, isLt := hk2 }

Next tactic:
refine' congr_arg v (Fin.eq_of_veq _)