Skip to content

Commit 3757160

Browse files
authored
feat: decompose typeclass-method projections in mvcgen' (#13870)
This PR lets `mvcgen'` decompose programs whose head is a typeclass method projection (e.g. `Add.add inst a b`) by reducing through the kernel projection to the instance body. `reduceHead?` reduces `.proj` at `.instances` transparency (`withReducibleAndInstances`), so concrete instances unfold to their `.mk` constructor without escalating to default transparency. An `unfoldReducible` pass after projection reduction normalizes abbrevs introduced by the instance body to match rule patterns. A new `.proj`-gated `tryHeadReduceProg` strategy in `solve` calls `reduceHead?` and rewrites the goal's program via `replaceProgDefEq`.
1 parent feb408a commit 3757160

4 files changed

Lines changed: 66 additions & 23 deletions

File tree

src/Lean/Elab/Tactic/Do/Internal/VCGen/Reduce.lean

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ module
77

88
prelude
99
public import Lean.Meta.Sym.SymM
10-
public import Lean.Meta.WHNF
10+
import Lean.Meta.WHNF
11+
import Lean.Meta.Sym
1112

1213
open Lean Meta Sym
1314

@@ -48,6 +49,7 @@ public partial def reduceHead? (e : Expr) : SymM (Option Expr) :=
4849
-- projections
4950
if ← isProjectionFn name then
5051
let some e' ← Meta.unfoldDefinition? (mkAppRev f rargs) | return lastReduction
52+
let e' ← Sym.unfoldReducible e'
5153
let e' ← Sym.shareCommonInc e'
5254
go lastReduction e'.getAppFn e'.getAppRevArgs -- intentional lastReduction! see docstring
5355
-- iota reduction: match/recursor with concrete discriminant
@@ -56,10 +58,15 @@ public partial def reduceHead? (e : Expr) : SymM (Option Expr) :=
5658
go (some e') e'.getAppFn e'.getAppRevArgs
5759
else
5860
pure lastReduction
59-
| .proj .. => match ← reduceProj? f with
61+
| .proj .. =>
62+
-- whnf at `instances` transparency: unfold `instMyAddInt32` (an instance) to
63+
-- expose its `MyAdd.mk` constructor so `reduceProj?` can project the field,
64+
-- but do not unfold arbitrary user definitions.
65+
match ← withReducibleAndInstances (reduceProj? f) with
6066
| some f' =>
67+
let f' ← Sym.unfoldReducible f'
68+
let f' ← Sym.shareCommonInc f'
6169
let e' := mkAppRev f' rargs
62-
let e' ← Sym.shareCommonInc e'
6370
go (some e') e'.getAppFn e'.getAppRevArgs
6471
| none => pure lastReduction
6572
| _ => pure lastReduction

src/Lean/Elab/Tactic/Do/Internal/VCGen/RuleConstruction.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public import Lean.Meta.Sym.AlphaShareBuilder
1313
public import Lean.Meta.Sym.Apply
1414
public import Lean.Meta.Sym.InstantiateMVarsS
1515
public import Lean.Meta.Sym.Util
16+
import Lean.Meta.WHNF
1617

1718
open Lean Meta Elab Tactic Sym
1819
open Lean.Elab.Tactic.Do.SpecAttr

src/Lean/Elab/Tactic/Do/Internal/VCGen/Solve.lean

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,13 @@ private def tryFvarZeta (goal : MVarId) (head H σs ent : Expr) (args : Array Ex
162162
let e' ← shareCommonInc (val.betaRev e.getAppRevArgs)
163163
return some <| .goals [← replaceProgDefEq goal head H σs ent args wpConst m ps instWP α e']
164164

165+
/-- Reduce a kernel `.proj` head in the program `e`. -/
166+
private def tryHeadReduceProg (goal : MVarId) (head H σs ent : Expr) (args : Array Expr)
167+
(wpConst m ps instWP α e f : Expr) : VCGenM (Option SolveResult) := do
168+
unless f matches .proj .. do return none
169+
let some e' ← reduceHead? e | return none
170+
return some <| .goals [← replaceProgDefEq goal head H σs ent args wpConst m ps instWP α e']
171+
165172
/-- Look up a registered `@[spec]` theorem for the program head and apply its cached
166173
backward rule. Falls back to `.noSpecFoundForProgram` / `.noStrategyForProgram` when no
167174
spec applies. -/
@@ -258,6 +265,11 @@ public def solve (goal : MVarId) : VCGenM SolveResult := goal.withContext do
258265
VCGen.burnOne
259266
return r
260267

268+
-- Reduce kernel `.proj` heads in the program (e.g., `instAddNat.1 a b`).
269+
if let some r ← tryHeadReduceProg goal head H σs ent args wpConst m ps instWP α e f then
270+
VCGen.burnOne
271+
return r
272+
261273
-- Apply registered specifications, or fall through to `.noStrategyForProgram`.
262274
VCGen.burnOne
263275
applySpec goal e excessArgs m σs ps instWP

tests/bench/mvcgen/sym/test_do_logic.lean

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,6 @@ The deprecation warning emitted by `mvcgen` indicates which tests still need mig
2020
2121
Tests whose proofs do not mention `mvcgen`/`mvcgen'` (manual `mspec`/`mintro` proofs)
2222
are intentionally not ported.
23-
24-
## Remaining blockers (tests still using `mvcgen`)
25-
26-
Each blocked test is annotated with a `BLOCKED:` comment explaining the cause:
27-
28-
- **`fib_triple_step`** — `mvcgen'` does not support `optConfig` (no `stepLimit`).
29-
- **`fib_triple_erase`**, **`erase_unfold`** — `mvcgen' [-name]` errors with
30-
"No spec found" instead of leaving an unsolved VC. Needs an `-errorOnMissingSpec`
31-
flag (default true) to gate this.
32-
- **`mkFreshPair_triple`** — `mvcgen'` lacks the `+trivial` flag, so schematic
33-
output-state VCs remain unsolved.
34-
- **`mem_mergeWithAll`** — `mvcgen'` errors "No spec found" for `forIn` on the
35-
universe-polymorphic `ExtTreeMap`.
36-
37-
## Notes on workarounds
38-
39-
- Some tests need `+zetaDelta` because `mvcgen'` leaves let-bindings in VCs.
40-
- `test_ite` needs an extra `exact ExceptConds.entails_false` because `mvcgen'` doesn't
41-
auto-discharge `ExceptConds.entails` for non-`.pure` PostShapes.
42-
- `fast_expo_correct` manually `obtain`s tuples that `mvcgen'` leaves un-destructured.
4323
-/
4424

4525
open Lean Meta Elab Tactic Sym Std Do SpecAttr
@@ -619,3 +599,46 @@ example (p : Nat → Prop) [DecidablePred p] (n : Nat) :
619599
with (simp_all [-Classical.not_forall]; try grind)
620600

621601
end InvariantSyntaxTests
602+
603+
namespace RflReducibility
604+
605+
-- From `mvcgenRflReducibility.lean`. Asserts that decomposing `MyShl.shl a 32` does
606+
-- not whnf at default reducibility, otherwise `UInt64.ofNat 32.toInt.toNat` would
607+
-- unfold deeply and timeout. `mvcgen'` keeps reduction at `.instances` transparency
608+
-- when stepping through class projections (`reduceProj?` in `Reduce.lean`), so this
609+
-- example decomposes cleanly.
610+
611+
abbrev RustM := Except String
612+
613+
class MyAddU (Self : Type) (Rhs : Type) where
614+
add : (Self → Rhs → RustM Self)
615+
616+
instance : MyAddU UInt64 UInt64 := {
617+
add x y := if BitVec.uaddOverflow x.toBitVec y.toBitVec then Except.error "" else pure (x + y)
618+
}
619+
620+
class MyShl (Self : Type) (Rhs : Type) where
621+
shl : (Self → Rhs → RustM Self)
622+
623+
instance : MyShl UInt64 Int32 := {
624+
shl x y := pure (x <<< (UInt64.ofNat y.toInt.toNat))
625+
}
626+
627+
/--
628+
error: unsolved goals
629+
case vc1
630+
a : UInt64
631+
h✝ : (UInt64.toBitVec 0).uaddOverflow (a <<< UInt64.ofNat (Int32.toInt 32).toNat).toBitVec = true
632+
⊢ ⊢ₛ wp⟦Except.error ""⟧ (fun r => ⌜True⌝, ExceptConds.false)
633+
-/
634+
#guard_msgs in
635+
example (a : UInt64) :
636+
⦃⌜True⌝⦄
637+
do
638+
let a ← MyShl.shl a (32: Int32)
639+
let a ← MyAddU.add (0 : UInt64) a
640+
pure a
641+
⦃PostCond.noThrow fun r => ⌜True⌝⦄ := by
642+
mvcgen' (errorOnMissingSpec := false) [MyShl.shl, MyAddU.add]
643+
644+
end RflReducibility

0 commit comments

Comments
 (0)