Zulip Chat Archive
Stream: general
Topic: Tactic to match and dispel cases
l1mey (Sep 12 2025 at 21:56):
Consider this inductive type:
inductive iN (bits : Nat) : Type where
| bitvec : BitVec bits → iN bits
| poison : iN bits
A repeated proof technique is to match on all the iN n inputs, and immediately clear the cases where it is poison, before actually proving the useful section. The proof ends up looking like this:
theorem addNsw_refine {n} (x y : iN n)
: x +nsw y ~> x + y := by
cases x
case bitvec a =>
cases y
case bitvec b =>
simp [iN_unwrap_inst]
if h : a.saddOverflow b then
simp [h]
else
simp [h]
case poison => simp [iN_unwrap_inst]
case poison => simp [iN_unwrap_inst]
theorem addNsw_assoc_if_same_sign {n} {hn : Bits n} (x y z : iN n)
(h :
(x ≥ₛ 0) &&& (y ≥ₛ 0) &&& (z ≥ₛ 0) ~> 1 ∨
(x <ₛ 0) &&& (y <ₛ 0) &&& (z <ₛ 0) ~> 1)
: (x +nsw y) +nsw z <~> x +nsw (y +nsw z) := by
cases x
case bitvec a =>
cases y
case bitvec b =>
cases z
case bitvec c =>
simp [iN_unwrap_inst] at *
have h' : a.msb = b.msb ∧ b.msb = c.msb := by
bv_decide
rw [BitVec.saddOverflow_assoc_monotone a b c h']
rw [BitVec.add_assoc a b c]
exact hn
case poison => simp [iN_unwrap_inst]
case poison => simp [iN_unwrap_inst]
case poison => simp [iN_unwrap_inst]
In both cases, there is a repeated cases x; case bitvec a => for every single argument in the function. My question, is there a nice way to represent this without making the cases explicit?
It would be nice for it to look like poison_unroll x y z => a b c, maybe metaprogramming could help?
l1mey (Sep 12 2025 at 21:58):
I have tried using match, and it does bind a etc, but it keeps around x y z : iN n as you can see in the commented goal and doesn't actually perform substitutions of of them into the hypotheses and goal.
l1mey (Sep 12 2025 at 22:11):
I've tried this macro (MWE below), but getting multiple errors:
import Lean
open Lean Elab Tactic Meta
syntax (name := poisonUnroll) "poison_unroll " ident+ " => " ident+ : tactic
macro_rules
| `(tactic| poison_unroll $xs:ident* => $ys:ident*) => do
if ¬xs.size == ys.size then
Macro.throwError s!"poison_unroll: got {xs.size} variables but {ys.size} names"
let rec go (i : Nat) : MacroM Syntax := do
if h : i < xs.size then
let xi := xs[i]'h
let yi := ys[i]!
let inner ← go (i+1)
`(tactic|
cases $xi <;>
first
| case bitvec $yi => $inner
| case poison => simp [iN_unwrap_inst])
else
`(tactic| skip)
go 0
How could I remedy this?
l1mey (Sep 12 2025 at 22:37):
Okay, so this macro worked and type checks:
macro_rules
| `(tactic| poison_unroll $xs:ident* => $ys:ident*) => do
if ¬xs.size == ys.size then
Macro.throwError s!"poison_unroll: got {xs.size} variables but {ys.size} names"
let rec go (i : Nat) : MacroM (TSyntax `tactic) := do
if h : i < xs.size then
let xi := xs[i]'h
let yi := ys[i]!
let inner ← go (i+1)
`(tactic|
cases $xi:ident with
| bitvec $yi => $inner:tactic
| poison => simp [iN_unwrap_inst])
else
`(tactic| skip)
go 0
But is seeming to not work with tactics:
It isn't able to bring the goal down like in this example:
l1mey (Sep 12 2025 at 22:54):
Solved. The macro
macro_rules
| `(tactic| poison_unroll $xs:ident* => $ys:ident*) =>
`(tactic|
($[cases $xs:ident with
| bitvec $ys:ident => ?_
| poison => simp [iN_unwrap_inst]];*))
worked.
Last updated: Dec 20 2025 at 21:32 UTC