@@ -205,13 +205,16 @@ private def reduceStep (e : Expr) : SimpM Expr := do
205205 match (← reduceRecMatcher? e) with
206206 | some e => return e
207207 | none => pure ()
208- if cfg.zeta then
209- if cfg.zetaHave then
210- if let some (args, _, _, v, b) := e.letFunAppArgs? then
211- return mkAppN (b.instantiate1 v) args
212- if let .letE _ _ v b nondep := e then
213- if !nondep || cfg.zetaHave then
214- return b.instantiate1 v
208+ if let .letE _ _ v b nondep := e then
209+ if cfg.zeta && (!nondep || cfg.zetaHave) then
210+ return expandLet b #[v] (zetaHave := cfg.zetaHave)
211+ else if cfg.zetaUnused && !b.hasLooseBVars then
212+ return consumeUnusedLet b
213+ if let some (args, _, _, v, b) := e.letFunAppArgs? then
214+ if cfg.zeta && cfg.zetaHave then
215+ return mkAppN (expandLet b #[v] (zetaHave := cfg.zetaHave)) args
216+ else if cfg.zetaUnused && !b.hasLooseBVars then
217+ return mkAppN (consumeUnusedLet b) args
215218 match (← unfold? e) with
216219 | some e' =>
217220 trace[Meta.Tactic.simp.rewrite] "unfold {.ofConst e.getAppFn}, {e} ==> {e'}"
@@ -462,19 +465,18 @@ where
462465
463466/--
464467Computes which `have`s in the telescope are fixed and which are unused.
465- The unused array might be too short : use `unused.getD i true`.
468+ The length of the unused array may be less than the number of `have`s : use `unused.getD i true`.
466469-/
467470def HaveTelescopeInfo.computeFixedUsed (info : HaveTelescopeInfo) (keepUnused : Bool) :
468471 MetaM (Std.HashSet Nat × Array Bool) := do
469472 if keepUnused then
470473 return (info.bodyTypeDeps, #[])
471474 let numHaves := info.haveInfo.size
472475 let updateArrayFromBackDeps (arr : Array Bool) (s : Std.HashSet Nat) : Array Bool :=
473- if s.isEmpty then arr
474- else s.fold (init := arr) fun arr idx => arr.set! idx true
476+ s.fold (init := arr) fun arr idx => arr.set! idx true
475477 let mut used : Array Bool := Array.replicate numHaves false
476478 -- Initialize `used` with the body's dependencies.
477- -- There is no need to consider `info.bodyTypeDeps`.
479+ -- There is no need to consider `info.bodyTypeDeps` in this computation .
478480 used := updateArrayFromBackDeps used info.bodyDeps
479481 -- For each used `have`, in reverse order, update `used`.
480482 for i in [0 :numHaves] do
@@ -515,7 +517,9 @@ private def SimpHaveResult.toResult : SimpHaveResult → Result
515517 | { expr, proof, modified, .. } => { expr, proof? := if modified then some proof else none }
516518
517519/--
518- Routine for simplifying `let` expressions.
520+ Routine for simplifying `have` telescopes. Used by `simpLet`.
521+ This is optimized to be able to handle deep `have` telescopes while avoiding quadratic complexity
522+ arising from the locally nameless expression representations.
519523
520524Consider a `have` telescope:
521525```
@@ -530,84 +534,38 @@ If `xᵢ` neither appears in `b` nor any `Tⱼ` nor any `vⱼ`, then its `have`
530534Unused `have`s can be eliminated, which we do if `cfg.zetaUnused` is true.
531535Note that it is best to clear unused `have`s from the right,
532536to eliminate any uses from unused `have`s.
537+ This is why we honor `zetaUnused` here even though `reduceStep` is also responsible for it.
533538
534- We assume that dependent `let`s are dependent.
535- At the first `let` in a `let`/`have` telescope,
536- we attempt to transform it into a `have` if `cfg.letToHave` is true.
537- If that does not change it, then it is only `dsimp`ed.
539+ If `debug.simp.check` is enabled then we typecheck the resulting expression and proof.
538540-/
539- partial def simpLet (e : Expr) : SimpM Result := do
540- assert! e.isLet
541- trace[Debug.Meta.Tactic.simp] "let{indentExpr e}"
542- let cfg ← getConfig
543- /-
544- In `zeta` mode, we zeta reduce the whole telescope,
545- unless `zetaHave` is false, in which case we zeta reduce up to the first `have`.
546- The `if` makes sure that reduction will make progress.
547- -/
548- if cfg.zeta && (!e.letNondep! || cfg.zetaHave) then
549- let expandLet (e : Expr) (vs : Array Expr) : Expr :=
550- match e with
551- | .letE _ _ v b nondep =>
552- if !nondep || cfg.zetaHave then
553- expandLet b (vs.push v)
554- else
555- e.instantiateRev vs
556- | _ => e.instantiateRev vs
557- return { expr := expandLet e #[] }
558- else if !e.letNondep! then
559- /-
560- This is a `let` and not a `have`.
561- Ideally non-dependent `let`s have already been transformed to `have`s.
562- First, we can still respect `cfg.zetaUnused`:
563- -/
564- if cfg.zetaUnused && !e.letBody!.hasLooseBVars then
565- return ← simp e.letBody!
566- /-
567- When `cfg.letToHave` is true we apply the transformation, which we commit to only if
568- the head `let` becomes a `have`.
569- Note: we applied `cfg.zetaUnused` ourselves above even though `dsimp` does it so that we can avoid an unnecessary `letToHave`.
570- -/
571- if cfg.letToHave then
572- let e' ← letToHave e
573- if e'.isLet && e'.letNondep! then
574- trace[Debug.Meta.Tactic.simp] "let => have{indentExpr e'}"
575- return ← simpHaveTelescope e'
576- /-
577- At this point, this is (very likely) a dependent `let`.
578- We fall back to doing only definitional simplification.
579-
580- Note that for `let x := v; b`, if we had a rewrite `h : b = b'` given `x := v` in the local context,
581- we could abstract `x` to get `(let x := v; h) : (let x := v; b = b')` and then use the fact that
582- this type is definitionally equal to `(let x := v; b) = (let x := v; b')`.
583- -/
584- return { expr := (← dsimp e) }
585- else
586- simpHaveTelescope e
587- where
588- simpHaveTelescope (e : Expr) : SimpM Result := do
589- let cfg ← getConfig
541+ def simpHaveTelescope (e : Expr) : SimpM Result := do
542+ Prod.fst <$> withTraceNode `Debug.Meta.Tactic.simp (fun
543+ | .ok (_, used, fixed, modified) => pure m!"{checkEmoji} have telescope; used: {used}; fixed: {fixed.toArray}; modified: {modified}"
544+ | .error ex => pure m!"{crossEmoji} {ex.toMessageData}" ) do
590545 let info ← getHaveTelescopeInfo e
591546 assert! !info.haveInfo.isEmpty
592- let (fixed, used) ← info.computeFixedUsed (keepUnused := !cfg.zetaUnused)
593- trace[Debug.Meta.Tactic.simp] "have telescope; used: {used}; fixed: {fixed.toArray}"
594- let sz := info.haveInfo.size
595- let r ← simpHaveTelescopeAux info fixed used e 0 (Array.mkEmpty sz)
596- trace[Debug.Meta.Tactic.simp] "have telescope; used: {used}; fixed: {fixed.toArray}; (modified: {r.modified})"
547+ let (fixed, used) ← info.computeFixedUsed (keepUnused := !(← getConfig).zetaUnused)
548+ let r ← simpHaveTelescopeAux info fixed used e 0 (Array.mkEmpty info.haveInfo.size)
597549 if r.modified && debug.simp.check.get (← getOptions) then
598550 check r.expr
599551 check r.proof
600- return r.toResult
552+ return (r.toResult, used, fixed, r.modified)
553+ where
601554 /-
602- Re-enters the telescope in `info` while simplifying according to `fixed`/`used`.
555+ Re-enters the telescope recorded in `info` using `withExistingLocalDecls ` while simplifying according to `fixed`/`used`.
603556 Note that we must use the low-level `Expr` APIs because the expressions may contain loose bound variables.
557+ Note also that we don't enter the body's local context all at once, since we need to be sure that
558+ when we simplify values they have their correct local context.
604559 -/
605560 simpHaveTelescopeAux (info : HaveTelescopeInfo) (fixed : Std.HashSet Nat) (used : Array Bool) (e : Expr) (i : Nat) (xs : Array Expr) : SimpM SimpHaveResult := do
606561 if h : i < info.haveInfo.size then
607562 let hinfo := info.haveInfo[i]
563+ -- `x` and `val` are the fvar and value with respect to the local context.
608564 let x := hinfo.decl.toExpr
609565 let val := hinfo.decl.value true
566+ -- Invariant: `v == val.abstract xs`.
610567 let .letE n t v b true := e | unreachable!
568+ -- Universe levels to use for each of the `have_*` theorems. It's the level of `val` and the level of the body.
611569 let us := [hinfo.level, info.level]
612570 if !used.getD i true then
613571 /-
@@ -618,7 +576,7 @@ where
618576 Complication: Unused `have`s might only be transitively unused.
619577 This means that `b.hasLooseBVar 0` might actually be true.
620578 This matters because `t` and `v` appear in the proof term.
621- We use a dummy `x` for debugging purposes. (Recall that `Expr.abstract` ignores non-fvar/mvars.)
579+ We use a dummy `x` for debugging purposes. (Recall that `Expr.abstract` skips non-fvar/mvars.)
622580 -/
623581 let x := Expr.const `_simp_let_unused_dummy []
624582 let { expr, exprType, proof, modified } ← simpHaveTelescopeAux info fixed used b (i + 1 ) (xs.push x)
@@ -629,7 +587,7 @@ where
629587 (mkLambda n .default t proof)
630588 return { expr, exprType, proof, modified := true }
631589 else
632- -- If not modified, this must have been a truly unused `have`.
590+ -- If not modified, this must have been a non-transitively unused `have`.
633591 let proof := mkApp6 (mkConst ``have_unused us) t exprType v expr expr
634592 (mkApp2 (mkConst ``Eq.refl [info.level]) exprType expr)
635593 return { expr, exprType, proof, modified := true }
@@ -699,6 +657,49 @@ where
699657 let proof := (← r.getProof).abstract xs
700658 return { expr, exprType, proof, modified := true }
701659
660+ /--
661+ Routine for simplifying `let` expressions.
662+
663+ If it is a `have`, we use `simpHaveTelescope` to simplify entire telescopes at once, to avoid quadratic behavior
664+ arising from locally nameless expression representations.
665+
666+ We assume that dependent `let`s are dependent,
667+ but if `Config.letToHave` is enabled then we attempt to transform it into a `have`.
668+ If that does not change it, then it is only `dsimp`ed.
669+ -/
670+ def simpLet (e : Expr) : SimpM Result := do
671+ withTraceNode `Debug.Meta.Tactic.simp (return m!"{exceptEmoji ·} let{indentExpr e}" ) do
672+ assert! e.isLet
673+ /-
674+ Recall: `simpLet` is called after `reduceStep` is applied, so `simpLet` is not responsible for zeta reduction.
675+ Hence, the expression is a `let` or `have` that is not reducible in the current configuration.
676+ -/
677+ if e.letNondep! then
678+ simpHaveTelescope e
679+ else
680+ /-
681+ When `cfg.letToHave` is true, we use `letToHave` to decide whether or not this `let` is dependent.
682+ If it becomes a `have`, then we can jump right into simplifying the `have` telescope.
683+ -/
684+ let e ←
685+ if (← getConfig).letToHave then
686+ let eNew ← letToHave e
687+ if eNew.isLet && eNew.letNondep! then
688+ trace[Debug.Meta.Tactic.simp] "letToHave ==>{indentExpr eNew}"
689+ return ← simpHaveTelescope eNew
690+ pure eNew
691+ else
692+ pure e
693+ /-
694+ The `let` is dependent.
695+ We fall back to doing only definitional simplification.
696+
697+ Note that for `let x := v; b`, if we had a rewrite `h : b = b'` given `x := v` in the local context,
698+ we could abstract `x` to get `(let x := v; h) : (let x := v; b = b')` and then use the fact that
699+ this type is definitionally equal to `(let x := v; b) = (let x := v; b')`.
700+ -/
701+ return { expr := (← dsimp e) }
702+
702703private def dsimpReduce : DSimproc := fun e => do
703704 let mut eNew ← reduce e
704705 if eNew.isFVar then
0 commit comments