You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
simp [h] cannot use h : ¬ v = 0 on BitVec, because the goal's 0 becomes 0#w before h is tried #16260
simp normalises a bit-vector literal in the goal from OfNat.ofNat 0 to 0#w through the BitVec.isValue dsimproc and BitVec.ofNat_eq_ofNat. A hypothesis passed as a simp argument is turned into a rewrite rule as written, so its left side keeps OfNat.ofNat 0. The rule no longer matches, the linter reports the argument as unused, and the goal is left open:
example (v : BitVec 32) (zero : ¬ v = 0) : ¬ v = 0 := by
simp [zero]
-- unsolved goals-- zero : ¬v = 0-- ⊢ ¬v = 0#32-- warning: This simp argument is unused: zero
set_option trace.Meta.Tactic.simp true shows zero:1000, failed to unify v = 0 with v = 0#32. The literal change itself does not appear in trace.Meta.Tactic.simp.rewrite.
The same happens through a definition:
defcopied (values : List (BitVec 32)) : List (BitVec 32) := values.takeWhile (· ≠ 0)
example (v : BitVec 32) (rest : List (BitVec 32)) (zero : ¬ v = 0) :
copied (v :: rest) = v :: copied rest := by
simp [copied, List.takeWhile_cons, zero] -- leaves ⊢ ¬v = 0#32
Expected behavior
simp [zero] closes both goals. They already close with simp -bitVecOfNat [zero], or after attribute [-simp] BitVec.isValue BitVec.ofNat_eq_ofNat.
One possible fix: normalise bit-vector literals in the left side of rules built from simp arguments the same way the goal is normalised.
Notes
Downstream libraries cannot work around this by default: attribute [-simp] does not carry into importing modules, and bitVecOfNat is a per-call config.
Making a library's own tactics emit the OfNat form instead makes later default simp calls convert every literal back. One measured proof's bv_omega preprocessing went from 994k to 1785k heartbeats.
Versions
Lean (version 4.35.0), Windows x86_64. Not checked on a nightly.
Wouldn't the fix simply be: use 0#32 everywhere instead? It's not really anything new that you might not be able to use a hypothesis in simp if the hypothesis is not in simp normal form, e.g.
example (h : (true && true) = x) : (true && true) = x := by
simp [h]
so the fix should just be to use the simp normal form (in the example, h : x = true), instead; unless that does not work for you for some reason?
Yes, this works. Using 0#32 consistently in the predicate and hypotheses lets the CopyUntilZero proof use ordinary simp. The updated proof passed verification and independent kernel replay.
For an existing hypothesis written with an ordinary numeral, an explicitly typed restatement also works:
example (value : BitVec 32) (nonzero : value ≠ 0) : value ≠ 0#32 := by
simp [show value ≠ 0#32from nonzero]
We've replaced the approach that disabled bitVecOfNat in LeanCIL's tactics with canonical literals and added regression tests: NethermindEth/LeanCIL#471
Thanks for the suggestion. Closing this as resolved for our use case.
Activity
Rob23oba commentedon Nov 5, 2026
Wouldn't the fix simply be: use
0#32everywhere instead? It's not really anything new that you might not be able to use a hypothesis in simp if the hypothesis is not in simp normal form, e.g.so the fix should just be to use the simp normal form (in the example,
h : x = true), instead; unless that does not work for you for some reason?benaadams commentedon Nov 9, 2026
Yes, this works. Using
0#32consistently in the predicate and hypotheses lets the CopyUntilZero proof use ordinarysimp. The updated proof passed verification and independent kernel replay.For an existing hypothesis written with an ordinary numeral, an explicitly typed restatement also works:
We've replaced the approach that disabled
bitVecOfNatin LeanCIL's tactics with canonical literals and added regression tests: NethermindEth/LeanCIL#471Thanks for the suggestion. Closing this as resolved for our use case.