This PR replaces the default `instantiateMVars` implementation with a two-pass variant that fuses fvar substitution into the traversal, avoiding separate `replace_fvars` calls for delayed-assigned MVars and preserving sharing. The old single-pass implementation is removed entirely. The previous implementation had quadratic complexity when instantiating expressions with long chains of nested delayed-assigned MVars. Such chains arise naturally from repeated `intro`/`apply` tactic sequences, where each step creates a new delayed assignment wrapping the previous one. The new two-pass approach resolves the entire chain in a single traversal with a fused fvar substitution, reducing this to linear complexity. ### Terminology (used in this PR and in the source) * **Direct MVar**: an MVar that is not delayed-assigned. * **Pending MVar**: the direct MVar stored in a `DelayedMetavarAssignment`. * **Assigned MVar**: a direct MVar with an assignment, or a delayed-assigned MVar with an assigned pending MVar. * **MVar DAG**: the directed acyclic graph of MVars reachable from the expression. * **Resolvable MVar**: an MVar where all MVars reachable from it (including itself) are assigned. * **Updateable MVar**: an assigned direct MVar, or a delayed-assigned MVar that is resolvable but not reachable from any other resolvable delayed-assigned MVar. In the MVar DAG, the updateable delayed-assigned MVars form a cut (the **updateable-MVar cut**) with only assigned MVars behind it and no resolvable delayed-assigned MVars before it. ### Two-pass architecture **Pass 1** (`instantiate_direct_fn`): Traverses all MVars and expressions reachable from the initial expression and instantiates all updateable direct MVars (updating their assignment with the result), instantiates all level MVars, and determines if there are any updateable delayed-assigned MVars. **Pass 2** (`instantiate_delayed_fn`): Only run if pass 1 found updateable delayed-assigned MVars. Has an **outer** and an **inner** mode, depending on whether it has crossed the updateable-MVar cut. In outer mode (empty fvar substitution), all MVars are either unassigned direct MVars (left alone), non-updateable delayed-assigned MVars (pending MVar traversed in outer mode and updated with the result), or updateable delayed-assigned MVars. When a delayed-assigned MVar is encountered, its MVar DAG is explored (via `is_resolvable_pending`) to determine if it is resolvable (and thus updateable). Results are cached across invocations. If it is updateable, the substitution is initialized from its arguments and traversal continues with the value of its pending MVar in inner mode. In inner mode (non-empty substitution), all encountered delayed-assigned MVars are, by construction, resolvable but not updateable. The substitution is carried along and extended as we cross such MVars. Pending MVars of these delayed-assigned MVars are NOT updated with the result (as the result is valid only for this substitution, not in general). Applying the substitution in one go, rather than instantiating each delayed-assigned MVar on its own from inside out, avoids the quadratic overhead of that approach when there are long chains of delayed-assigned MVars. **Write-back behavior**: Pass 2 writes back the normalized pending MVar values of delayed-assigned MVars above the updateable-MVar cut (the non-resolvable ones whose children may have been resolved). This is exactly the right set: these MVars are visited in outer mode, so their normalized values are suitable for storing in the mctx. MVars below the cut are visited in inner mode, so their intermediate values cannot be written back. ### Pass 2 scope-tracked caching A `scope_cache` data structure ensures that sharing is preserved even across different delayed-assigned MVars (and hence with different substitutions), when possible. Each `visit_delayed` call pushes a new scope with fresh fvar bindings. The cache correctly handles cross-scope reuse, fvar shadowing, and late-binding via generation counters and scope-level tracking. The `scope_cache` has been formally verified: `tests/elab/scopeCacheProofs.lean` contains a complete Lean proof that the lazy generation-based implementation refines the eager specification, covering all operations (push, pop, lookup, insert) including the rewind lazy cleanup with scope re-entry and degradation. The key correctness invariant is inter-entry gen list consistency (GensConsistent), which, unlike per-entry alignment with `currentGens`, survives pop+push cycles. ### Behavioral differences from original `instantiateMVars` The implementation matches the original single-pass `instantiateMVars` behavior with one cosmetic difference: the new implementation substitutes fvars inline during traversal rather than constructing intermediate beta-redexes, producing more beta-reduced terms in some edge cases. This changes the pretty-printed output for two elab tests (`1179b`, `depElim1`) but all terms remain definitionally equal. ### Tests Correctness and performance tests for the new implementation were added in #12808. ### Files - `src/library/instantiate_mvars.cpp` — C++ implementation of both passes (replaces `src/kernel/instantiate_mvars.cpp`) - `src/library/scope_cache.h` — scope-aware cache data structure - `src/Lean/MetavarContext.lean` — exported accessors for `DelayedMetavarAssignment` fields - `tests/elab/scopeCacheProofs.lean` — formal verification of `scope_cache` correctness - `tests/elab/1179b.lean.out.expected`, `tests/elab/depElim1.lean.out.expected` — updated expected output Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
70 lines
5.3 KiB
Text
70 lines
5.3 KiB
Text
elimTest0 : forall (motive : Nat -> Sort.{u_1}) (x : Nat), (forall (y : Nat), motive y) -> (motive x)
|
||
def elimTest0.{u_1} : (motive : Nat → Sort u_1) → (x : Nat) → ((y : Nat) → motive y) → motive x :=
|
||
fun motive x h_1 => h_1 x
|
||
elimTest1 : forall (α : Type.{u_1}) (β : Type.{u_2}) (motive : (List.{u_1} α) -> (List.{u_2} β) -> Sort.{u_3}) (x : List.{u_1} α) (y : List.{u_2} β), (Unit -> (motive (List.nil.{u_1} α) (List.nil.{u_2} β))) -> (forall (a : α) (as : List.{u_1} α) (b : β) (bs : List.{u_2} β), motive (List.cons.{u_1} α a as) (List.cons.{u_2} β b bs)) -> (forall (a : α) (as : List.{u_1} α), motive (List.cons.{u_1} α a as) (List.nil.{u_2} β)) -> (forall (b : β) (bs : List.{u_2} β), motive (List.nil.{u_1} α) (List.cons.{u_2} β b bs)) -> (motive x y)
|
||
def elimTest1.{u_1, u_2, u_3} : (α : Type u_1) →
|
||
(β : Type u_2) →
|
||
(motive : List α → List β → Sort u_3) →
|
||
(x : List α) →
|
||
(y : List β) →
|
||
(Unit → motive [] []) →
|
||
((a : α) → (as : List α) → (b : β) → (bs : List β) → motive (a :: as) (b :: bs)) →
|
||
((a : α) → (as : List α) → motive (a :: as) []) →
|
||
((b : β) → (bs : List β) → motive [] (b :: bs)) → motive x y :=
|
||
fun α β motive x y h_1 h_2 h_3 h_4 =>
|
||
List.casesOn x (List.casesOn y (h_1 ()) fun head tail => h_4 head tail) fun head tail =>
|
||
List.casesOn y (h_3 head tail) fun head_1 tail_1 => h_2 head tail head_1 tail_1
|
||
elimTest2 : forall (α : Type.{u_1}) (motive : forall (n : Nat), (Vec.{u_1} α n) -> (Vec.{u_1} α n) -> Sort.{u_2}) (n : Nat) (xs : Vec.{u_1} α n) (ys : Vec.{u_1} α n), (Unit -> (motive ([mdata _inaccessible:1 OfNat.ofNat.{0} Nat 0 (instOfNatNat 0)]) (Vec.nil.{u_1} α) (Vec.nil.{u_1} α))) -> (forall (n : Nat) (x : α) (xs : Vec.{u_1} α n) (y : α) (ys : Vec.{u_1} α n), motive ([mdata _inaccessible:1 HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))]) (Vec.cons.{u_1} α n x xs) (Vec.cons.{u_1} α n y ys)) -> (motive n xs ys)
|
||
def elimTest2.{u_1, u_2} : (α : Type u_1) →
|
||
(motive : (n : Nat) → Vec α n → Vec α n → Sort u_2) →
|
||
(n : Nat) →
|
||
(xs ys : Vec α n) →
|
||
(Unit → motive 0 Vec.nil Vec.nil) →
|
||
((n : Nat) →
|
||
(x : α) → (xs : Vec α n) → (y : α) → (ys : Vec α n) → motive (n + 1) (Vec.cons x xs) (Vec.cons y ys)) →
|
||
motive n xs ys :=
|
||
fun α motive n xs ys h_1 h_2 =>
|
||
Vec.casesOn (motive := fun a x => n = a → xs ≍ x → motive n xs ys) xs
|
||
(fun h =>
|
||
Eq.ndrec (motive := fun n => (xs ys : Vec α n) → xs ≍ Vec.nil → motive n xs ys)
|
||
(fun xs ys h =>
|
||
⋯ ▸
|
||
Vec.casesOn (motive := fun a x => 0 = a → ys ≍ x → motive 0 Vec.nil ys) ys (fun h h_3 => ⋯ ▸ h_1 ())
|
||
(fun {n} a a_1 h => False.elim ⋯) ⋯ ⋯)
|
||
⋯ xs ys)
|
||
(fun {n_1} a a_1 h =>
|
||
Eq.ndrec (motive := fun n => (xs ys : Vec α n) → xs ≍ Vec.cons a a_1 → motive n xs ys)
|
||
(fun xs ys h =>
|
||
⋯ ▸
|
||
Vec.casesOn (motive := fun a_2 x => n_1 + 1 = a_2 → ys ≍ x → motive (n_1 + 1) (Vec.cons a a_1) ys) ys
|
||
(fun h => False.elim ⋯)
|
||
(fun {n} a_2 a_3 h =>
|
||
n_1.elimOffset n 1 h fun x =>
|
||
Eq.ndrec (motive := fun {n} =>
|
||
(a_4 : Vec α n) → ys ≍ Vec.cons a_2 a_4 → motive (n_1 + 1) (Vec.cons a a_1) ys)
|
||
(fun a_4 h => ⋯ ▸ h_2 n_1 a a_1 a_2 a_4) x a_3)
|
||
⋯ ⋯)
|
||
⋯ xs ys)
|
||
⋯ ⋯
|
||
elimTest3 : forall (α : Type.{u_1}) (β : Type.{u_2}) (motive : (List.{u_1} α) -> (List.{u_2} β) -> Sort.{u_3}) (x : List.{u_1} α) (y : List.{u_2} β), (Unit -> (motive (List.nil.{u_1} α) (List.nil.{u_2} β))) -> (forall (a : α) (b : β), motive (List.cons.{u_1} α a (List.nil.{u_1} α)) (List.cons.{u_2} β b (List.nil.{u_2} β))) -> (forall (a₁ : α) (a₂ : α) (as : List.{u_1} α) (b₁ : β) (b₂ : β) (bs : List.{u_2} β), motive (List.cons.{u_1} α a₁ (List.cons.{u_1} α a₂ as)) (List.cons.{u_2} β b₁ (List.cons.{u_2} β b₂ bs))) -> (forall (as : List.{u_1} α) (bs : List.{u_2} β), motive as bs) -> (motive x y)
|
||
def elimTest3.{u_1, u_2, u_3} : (α : Type u_1) →
|
||
(β : Type u_2) →
|
||
(motive : List α → List β → Sort u_3) →
|
||
(x : List α) →
|
||
(y : List β) →
|
||
(Unit → motive [] []) →
|
||
((a : α) → (b : β) → motive [a] [b]) →
|
||
((a₁ a₂ : α) → (as : List α) → (b₁ b₂ : β) → (bs : List β) → motive (a₁ :: a₂ :: as) (b₁ :: b₂ :: bs)) →
|
||
((as : List α) → (bs : List β) → motive as bs) → motive x y :=
|
||
fun α β motive x y h_1 h_2 h_3 h_4 =>
|
||
List.casesOn x (_sparseCasesOn_112 y (h_1 ()) fun h => h_4 [] y) fun head tail =>
|
||
List.casesOn tail
|
||
(_sparseCasesOn_113 y
|
||
(fun head_1 tail => _sparseCasesOn_112 tail (h_2 head head_1) fun h => h_4 [head] (head_1 :: tail)) fun h =>
|
||
h_4 [head] y)
|
||
fun head_1 tail =>
|
||
_sparseCasesOn_113 y
|
||
(fun head_2 tail_1 =>
|
||
_sparseCasesOn_113 tail_1 (fun head_3 tail_2 => h_3 head head_1 tail head_2 head_3 tail_2) fun h =>
|
||
h_4 (head :: head_1 :: tail) (head_2 :: tail_1))
|
||
fun h => h_4 (head :: head_1 :: tail) y
|