fix: prevent cbv crash on dependent projections with @[cbv_eval] rewrites (#12612)

This PR fixes a crash in the `cbv` tactic's `handleProj` simproc when
processing a dependent projection (e.g. `Sigma.snd`) whose struct is
rewritten via `@[cbv_eval]` to a non-definitionally-equal term that
cannot be further reduced.

- Previously, `handleProj` returned `.rfl (done := false)`, causing the
`.proj` expression to flow into `simpStep` which throws "unexpected
kernel projection term"
- The fix marks the result as `done := true` so that `cbv` gracefully
gets stuck instead of crashing
- Adds regression tests for dependent projections on `Sigma`, custom
structures, and `Subtype`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Wojciech Różowski 2026-02-20 14:38:15 +00:00 committed by GitHub
parent 78df48bdf4
commit 14e1d4328f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 74 additions and 1 deletions

View file

@ -121,7 +121,7 @@ def handleProj : Simproc := fun e => do
unless (← isDefEq struct e') do
-- If we rewrote the projection body using something that holds up to propositional equality, then there is nothing we can do.
-- TODO: Check if there is a need to report this to a user, or shall we fail silently.
return .rfl
return .rfl (done := true)
let hcongr ← mkHCongr congrArgFun
let newProof := mkApp3 (hcongr.proof) struct e' proof
-- We have already checked if `struct` and `e'` are defEq, so we can skip the check.

View file

@ -0,0 +1,73 @@
set_option cbv.warning false
/-! Tests for `cbv` handling of dependent projections.
When a struct is rewritten (e.g. via `@[cbv_eval]`) but the projection is
dependent and the original struct can't be reduced, `cbv` should gracefully
get stuck rather than crash.
-/
-- Sigma: .1 is non-dependent, .2 is dependent
def myPair : Σ n : Nat, Fin (n + 1) := ⟨5, ⟨3, by omega⟩⟩
axiom otherPair : Σ n : Nat, Fin (n + 1)
@[cbv_eval ←] axiom otherPair_eq : myPair = otherPair
example : otherPair.1 = 5 := by cbv
/--
error: unsolved goals
⊢ otherPair.2.1 = 3
-/
#guard_msgs in
example : otherPair.2.val = 3 := by cbv
-- Custom structure with a dependent field
structure DepPkg where
n : Nat
val : Fin (n + 1)
def concretePkg : DepPkg := ⟨5, ⟨3, by omega⟩⟩
axiom opaquePkg : DepPkg
@[cbv_eval ←] axiom opaquePkg_eq : concretePkg = opaquePkg
example : opaquePkg.n = 5 := by cbv
/--
error: unsolved goals
⊢ opaquePkg.2.1 = 3
-/
#guard_msgs in
example : opaquePkg.val.val = 3 := by cbv
-- Subtype: .val is non-dependent, so the rewrite goes through
def concreteSubtype : { n : Nat // n > 0 } := ⟨5, by omega⟩
axiom opaqueSubtype : { n : Nat // n > 0 }
@[cbv_eval ←] axiom opaqueSubtype_eq : concreteSubtype = opaqueSubtype
example : opaqueSubtype.val = 5 := by cbv
-- Non-dependent projections: function projected from a structure and applied
structure FnStruct where
fn : Nat → Nat
def myFnStruct : FnStruct := ⟨fun n => n + 1⟩
example : myFnStruct.fn 5 = 6 := by cbv
-- Nested non-dependent projections
structure Layer1 where val : Nat
structure Layer2 where inner : Layer1
structure Layer3 where inner : Layer2
def nested : Layer3 := ⟨⟨⟨42⟩⟩⟩
example : nested.inner.inner.val = 42 := by cbv
-- Double projection through a product
structure FnPairS where
fst : Nat → Nat
snd : Nat → Nat
def fnPairPair : FnPairS × FnPairS :=
(⟨fun n => n + 1, fun n => n + 2⟩, ⟨fun n => n + 3, fun n => n + 4⟩)
example : fnPairPair.1.fst 5 = 6 := by cbv
example : fnPairPair.2.snd 5 = 9 := by cbv