-- Mathlib/.lake/packages/lean4/src/lean/Std/Tactic/BVDecide/Bitblast/BVExpr/Circuit/Lemmas/Operations/ShiftLeft.lean
theorem twoPowShift_eq (aig : AIG α) (target : TwoPowShiftTarget aig w) (lhs : BitVec w)
    (rhs : BitVec target.n) (assign : α → Bool)
    (hleft : ∀ (idx : Nat) (hidx : idx < w), ⟦aig, target.lhs.get idx hidx, assign⟧ = lhs.getLsbD idx)
    (hright : ∀ (idx : Nat) (hidx : idx < target.n), ⟦aig, target.rhs.get idx hidx, assign⟧ = rhs.getLsbD idx) :
    ∀ (idx : Nat) (hidx : idx < w),
        ⟦
          (twoPowShift aig target).aig,
          (twoPowShift aig target).vec.get idx hidx,
          assign
        ⟧
          =
        (lhs <<< (rhs &&& BitVec.twoPow target.n target.pow)).getLsbD idx

Proof state:
α : Type
inst✝¹ : Hashable α
inst✝ : DecidableEq α
w : Nat
aig : AIG α
lhs : BitVec w
assign : α → Bool
idx : Nat
hidx : idx < w
res : RefVecEntry α w
n : Nat
lvec : aig.RefVec w
rvec : aig.RefVec n
pow : Nat
rhs : BitVec { n := n, lhs := lvec, rhs := rvec, pow := pow }.n
hleft :
  ∀ (idx : Nat) (hidx : idx < w),
    ⟦assign, { aig := aig, ref := { n := n, lhs := lvec, rhs := rvec, pow := pow }.lhs.get idx hidx }⟧ = lhs.getLsbD idx
hright :
  ∀ (idx : Nat) (hidx : idx < { n := n, lhs := lvec, rhs := rvec, pow := pow }.n),
    ⟦assign, { aig := aig, ref := { n := n, lhs := lvec, rhs := rvec, pow := pow }.rhs.get idx hidx }⟧ = rhs.getLsbD idx
h✝ : pow < n
hg :
  RefVec.ite (blastShiftLeftConst aig { vec := lvec, distance := 2 ^ pow }).aig
      { discr := (rvec.cast ⋯).get pow h✝, lhs := (blastShiftLeftConst aig { vec := lvec, distance := 2 ^ pow }).vec,
        rhs := lvec.cast ⋯ } =
    res
hif1 : rhs.getLsbD pow = false
⊢ (if
        ⟦assign,
            { aig := (blastShiftLeftConst aig { vec := lvec, distance := 2 ^ pow }).aig,
              ref := { gate := (rvec.get pow h✝).gate, hgate := ⋯ } }⟧ =
          true then
      if h : idx < 2 ^ pow then false else ⟦assign, { aig := aig, ref := lvec.get (idx - 2 ^ pow) ⋯ }⟧
    else
      ⟦assign,
        { aig := (blastShiftLeftConst aig { vec := lvec, distance := 2 ^ pow }).aig,
          ref := { gate := (lvec.get idx hidx).gate, hgate := ⋯ } }⟧) =
    (lhs <<< 0#n).getLsbD idx

Tactic:
rw [AIG.LawfulVecOperator.denote_mem_prefix (f := blastShiftLeftConst)]

Result:
α : Type
inst✝¹ : Hashable α
inst✝ : DecidableEq α
w : Nat
aig : AIG α
lhs : BitVec w
assign : α → Bool
idx : Nat
hidx : idx < w
res : RefVecEntry α w
n : Nat
lvec : aig.RefVec w
rvec : aig.RefVec n
pow : Nat
rhs : BitVec { n := n, lhs := lvec, rhs := rvec, pow := pow }.n
hleft :
  ∀ (idx : Nat) (hidx : idx < w),
    ⟦assign, { aig := aig, ref := { n := n, lhs := lvec, rhs := rvec, pow := pow }.lhs.get idx hidx }⟧ = lhs.getLsbD idx
hright :
  ∀ (idx : Nat) (hidx : idx < { n := n, lhs := lvec, rhs := rvec, pow := pow }.n),
    ⟦assign, { aig := aig, ref := { n := n, lhs := lvec, rhs := rvec, pow := pow }.rhs.get idx hidx }⟧ = rhs.getLsbD idx
h✝ : pow < n
hg :
  RefVec.ite (blastShiftLeftConst aig { vec := lvec, distance := 2 ^ pow }).aig
      { discr := (rvec.cast ⋯).get pow h✝, lhs := (blastShiftLeftConst aig { vec := lvec, distance := 2 ^ pow }).vec,
        rhs := lvec.cast ⋯ } =
    res
hif1 : rhs.getLsbD pow = false
⊢ (if ⟦assign, { aig := aig, ref := { gate := (rvec.get pow h✝).gate, hgate := ?m.79430 } }⟧ = true then
      if h : idx < 2 ^ pow then false else ⟦assign, { aig := aig, ref := lvec.get (idx - 2 ^ pow) ⋯ }⟧
    else
      ⟦assign,
        { aig := (blastShiftLeftConst aig { vec := lvec, distance := 2 ^ pow }).aig,
          ref := { gate := (lvec.get idx hidx).gate, hgate := ⋯ } }⟧) =
    (lhs <<< 0#n).getLsbD idx

α : Type
inst✝¹ : Hashable α
inst✝ : DecidableEq α
w : Nat
aig : AIG α
lhs : BitVec w
assign : α → Bool
idx : Nat
hidx : idx < w
res : RefVecEntry α w
n : Nat
lvec : aig.RefVec w
rvec : aig.RefVec n
pow : Nat
rhs : BitVec { n := n, lhs := lvec, rhs := rvec, pow := pow }.n
hleft :
  ∀ (idx : Nat) (hidx : idx < w),
    ⟦assign, { aig := aig, ref := { n := n, lhs := lvec, rhs := rvec, pow := pow }.lhs.get idx hidx }⟧ = lhs.getLsbD idx
hright :
  ∀ (idx : Nat) (hidx : idx < { n := n, lhs := lvec, rhs := rvec, pow := pow }.n),
    ⟦assign, { aig := aig, ref := { n := n, lhs := lvec, rhs := rvec, pow := pow }.rhs.get idx hidx }⟧ = rhs.getLsbD idx
h✝ : pow < n
hg :
  RefVec.ite (blastShiftLeftConst aig { vec := lvec, distance := 2 ^ pow }).aig
      { discr := (rvec.cast ⋯).get pow h✝, lhs := (blastShiftLeftConst aig { vec := lvec, distance := 2 ^ pow }).vec,
        rhs := lvec.cast ⋯ } =
    res
hif1 : rhs.getLsbD pow = false
⊢ (rvec.get pow h✝).gate < aig.decls.size

Proof state:
case mk
α : Type
inst✝¹ : Hashable α
inst✝ : DecidableEq α
w : Nat
aig : AIG α
lhs : BitVec w
assign : α → Bool
idx : Nat
hidx : idx < w
res : RefVecEntry α w
n : Nat
lvec : aig.RefVec w
rvec : aig.RefVec n
pow : Nat
rhs : BitVec { n := n, lhs := lvec, rhs := rvec, pow := pow }.n
hleft :
  ∀ (idx : Nat) (hidx : idx < w),
    ⟦assign, { aig := aig, ref := { n := n, lhs := lvec, rhs := rvec, pow := pow }.lhs.get idx hidx }⟧ = lhs.getLsbD idx
hright :
  ∀ (idx : Nat) (hidx : idx < { n := n, lhs := lvec, rhs := rvec, pow := pow }.n),
    ⟦assign, { aig := aig, ref := { n := n, lhs := lvec, rhs := rvec, pow := pow }.rhs.get idx hidx }⟧ = rhs.getLsbD idx
hg :
  (if h : pow < n then
      RefVec.ite (blastShiftLeftConst aig { vec := lvec, distance := 2 ^ pow }).aig
        { discr := (rvec.cast ⋯).get pow h, lhs := (blastShiftLeftConst aig { vec := lvec, distance := 2 ^ pow }).vec,
          rhs := lvec.cast ⋯ }
    else { aig := aig, vec := lvec }) =
    res
⊢ ⟦assign, { aig := res.aig, ref := res.vec.get idx hidx }⟧ =
    (lhs <<< if rhs.getLsbD pow = true then BitVec.twoPow n pow else 0#n).getLsbD idx

Tactic:
split at hg

Result:
case mk.isTrue
α : Type
inst✝¹ : Hashable α
inst✝ : DecidableEq α
w : Nat
aig : AIG α
lhs : BitVec w
assign : α → Bool
idx : Nat
hidx : idx < w
res : RefVecEntry α w
n : Nat
lvec : aig.RefVec w
rvec : aig.RefVec n
pow : Nat
rhs : BitVec { n := n, lhs := lvec, rhs := rvec, pow := pow }.n
hleft :
  ∀ (idx : Nat) (hidx : idx < w),
    ⟦assign, { aig := aig, ref := { n := n, lhs := lvec, rhs := rvec, pow := pow }.lhs.get idx hidx }⟧ = lhs.getLsbD idx
hright :
  ∀ (idx : Nat) (hidx : idx < { n := n, lhs := lvec, rhs := rvec, pow := pow }.n),
    ⟦assign, { aig := aig, ref := { n := n, lhs := lvec, rhs := rvec, pow := pow }.rhs.get idx hidx }⟧ = rhs.getLsbD idx
h✝ : pow < n
hg :
  RefVec.ite (blastShiftLeftConst aig { vec := lvec, distance := 2 ^ pow }).aig
      { discr := (rvec.cast ⋯).get pow h✝, lhs := (blastShiftLeftConst aig { vec := lvec, distance := 2 ^ pow }).vec,
        rhs := lvec.cast ⋯ } =
    res
⊢ ⟦assign, { aig := res.aig, ref := res.vec.get idx hidx }⟧ =
    (lhs <<< if rhs.getLsbD pow = true then BitVec.twoPow n pow else 0#n).getLsbD idx

case mk.isFalse
α : Type
inst✝¹ : Hashable α
inst✝ : DecidableEq α
w : Nat
aig : AIG α
lhs : BitVec w
assign : α → Bool
idx : Nat
hidx : idx < w
res : RefVecEntry α w
n : Nat
lvec : aig.RefVec w
rvec : aig.RefVec n
pow : Nat
rhs : BitVec { n := n, lhs := lvec, rhs := rvec, pow := pow }.n
hleft :
  ∀ (idx : Nat) (hidx : idx < w),
    ⟦assign, { aig := aig, ref := { n := n, lhs := lvec, rhs := rvec, pow := pow }.lhs.get idx hidx }⟧ = lhs.getLsbD idx
hright :
  ∀ (idx : Nat) (hidx : idx < { n := n, lhs := lvec, rhs := rvec, pow := pow }.n),
    ⟦assign, { aig := aig, ref := { n := n, lhs := lvec, rhs := rvec, pow := pow }.rhs.get idx hidx }⟧ = rhs.getLsbD idx
h✝ : ¬pow < n
hg : { aig := aig, vec := lvec } = res
⊢ ⟦assign, { aig := res.aig, ref := res.vec.get idx hidx }⟧ =
    (lhs <<< if rhs.getLsbD pow = true then BitVec.twoPow n pow else 0#n).getLsbD idx