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`
  

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)) })
⊢ (applyComposition p a v ∘ ⇑(embedding b { val := i, isLt := hi1 })) { val := j, isLt := hj1 } =
    applyComposition p (sigmaCompositionAux a b { val := i, isLt := hi2 })
      (v ∘ ⇑(embedding (gather a b) { val := i, isLt := hi2 })) { val := j, isLt := hj2 }

Next tactic:
apply p.congr (blocksFun_sigmaCompositionAux a b _ _).symm