feat: use nondep flag in Expr.letE and LocalContext.ldecl (#8804)

This PR implements first-class support for nondependent let expressions
in the elaborator; recall that a let expression `let x : t := v; b` is
called *nondependent* if `fun x : t => b` typechecks, and the notation
for a nondependent let expression is `have x := v; b`. Previously we
encoded `have` using the `letFun` function, but now we make use of the
`nondep` flag in the `Expr.letE` constructor for the encoding. This has
been given full support throughout the metaprogramming interface and the
elaborator. Key changes to the metaprogramming interface:
- Local context `ldecl`s with `nondep := true` are generally treated as
`cdecl`s. This is because in the body of a `have` expression the
variable is opaque. Functions like `LocalDecl.isLet` by default return
`false` for nondependent `ldecl`s. In the rare case where it is needed,
they take an additional optional `allowNondep : Bool` flag (defaults to
`false`) if the variable is being processed in a context where the value
is relevant.
- Functions such as `mkLetFVars` by default generalize nondependent let
variables and create lambda expressions for them. The
`generalizeNondepLet` flag (default true) can be set to false if `have`
expressions should be produced instead. **Breaking change:** Uses of
`letLambdaTelescope`/`mkLetFVars` need to use `generalizeNondepLet :=
false`. See the next item.
- There are now some mapping functions to make telescoping operations
more convenient. See `mapLetTelescope` and `mapLambdaLetTelescope`.
There is also `mapLetDecl` as a counterpart to `withLetDecl` for
creating `let`/`have` expressions.
- Important note about the `generalizeNondepLet` flag: it should only be
used for variables in a local context that the metaprogram "owns". Since
nondependent let variables are treated as constants in most cases, the
`value` field might refer to variables that do not exist, if for example
those variables were cleared or reverted. Using `mapLetDecl` is always
fine.
- The simplifier will cache its let dependence calculations in the
nondep field of let expressions.
- The `intro` tactic still produces *dependent* local variables. Given
that the simplifier will transform lets into haves, it would be
surprising if that would prevent `intro` from creating a local variable
whose value cannot be used.

Note that nondependence of lets is not checked by the kernel. To
external checker authors: If the elaborator gets the nondep flag wrong,
we consider this to be an elaborator error. Feel free to typecheck `letE
n t v b true` as if it were `app (lam n t b default) v` and please
report issues.

This PR follows up from #8751, which made sure the nondep flag was
preserved in the C++ interface.
This commit is contained in:
Kyle Miller 2025-06-22 14:54:57 -07:00 committed by GitHub
parent 2ebc001dd1
commit 02c8c2f9e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 647 additions and 293 deletions

View file

@ -785,54 +785,33 @@ def elabLetDeclAux (id : Syntax) (binders : Array Syntax) (typeStx : Syntax) (va
pure (type, val, binders)
let kind := kindOfBinderName id.getId
trace[Elab.let.decl] "{id.getId} : {type} := {val}"
let elabBody : TermElabM Expr := do
let body ← elabTermEnsuringType body expectedType?
instantiateMVars body
let result ←
if config.zeta then
let elabZetaCore (x : Expr) : TermElabM Expr := do
addLocalVarInfo id x
if let some h := config.eq? then
let hTy ← mkEq x val
withLocalDeclD h.getId hTy fun h' => do
addLocalVarInfo h h'
let body ← elabBody
pure <| (← body.abstractM #[x, h']).instantiateRev #[val, ← mkEqRefl val]
else
let body ← elabBody
withLetDecl id.getId (kind := kind) type val (nondep := config.nondep) fun x => do
let elabBody : TermElabM Expr :=
elabTermEnsuringType body expectedType? >>= instantiateMVars
addLocalVarInfo id x
match config.eq? with
| none =>
let body ← elabBody
if config.zeta then
pure <| (← body.abstractM #[x]).instantiate1 val
if !config.nondep then
withLetDecl id.getId (kind := kind) type val elabZetaCore
else
withLocalDecl id.getId (kind := kind) .default type elabZetaCore
else
if !config.nondep then
withLetDecl id.getId (kind := kind) type val fun x => do
addLocalVarInfo id x
if let some h := config.eq? then
let hTy ← mkEq x val
withLocalDeclD h.getId hTy fun h' => do
addLocalVarInfo h h'
let body ← elabBody
let body := (← body.abstractM #[h']).instantiate1 (← mkEqRefl x)
mkLetFVars #[x] body (usedLetOnly := config.usedOnly)
else
mkLetFVars #[x] body (usedLetOnly := config.usedOnly) (generalizeNondepLet := false)
| some h =>
let hTy ← mkEq x val
withLetDecl h.getId hTy (← mkEqRefl x) (nondep := true) fun h' => do
addLocalVarInfo h h'
let body ← elabBody
if config.zeta then
pure <| (← body.abstractM #[x, h']).instantiateRev #[val, ← mkEqRefl val]
else if config.nondep then
-- TODO(kmill): Think more about how to encode this case.
-- Currently we produce `(fun (x : α) (h : x = val) => b) val rfl`.
-- N.B. the nondep lets become lambdas here.
let f ← mkLambdaFVars #[x, h'] body
return mkApp2 f val (← mkEqRefl val)
else
let body ← elabBody
mkLetFVars #[x] body (usedLetOnly := config.usedOnly)
else
withLocalDecl id.getId (kind := kind) .default type fun x => do
addLocalVarInfo id x
if let some h := config.eq? then
-- TODO(kmill): Think about how to encode this case.
let hTy ← mkEq x val
withLocalDeclD h.getId hTy fun h' => do
addLocalVarInfo h h'
let body ← elabBody
let f ← mkLambdaFVars #[x, h'] body
return mkApp2 f val (← mkEqRefl val)
else
let body ← elabBody
mkLetFun x val body
mkLetFVars #[x, h'] body (usedLetOnly := config.usedOnly) (generalizeNondepLet := false)
if config.postponeValue then
forallBoundedTelescope type binders.size fun xs type => do
-- the original `fvars` from above are gone, so add back info manually

View file

@ -670,7 +670,7 @@ where
match p with
| .forallE n d b bi => withLocalDecl n bi (← go d) fun x => do mkForallFVars #[x] (← go (b.instantiate1 x))
| .lam n d b bi => withLocalDecl n bi (← go d) fun x => do mkLambdaFVars #[x] (← go (b.instantiate1 x))
| .letE n t v b .. => withLetDecl n (← go t) (← go v) fun x => do mkLetFVars #[x] (← go (b.instantiate1 x))
| .letE n t v b nondep => mapLetDecl n (← go t) (← go v) (nondep := nondep) fun x => go (b.instantiate1 x)
| .app f a => return mkApp (← go f) (← go a)
| .proj _ _ b => return p.updateProj! (← go b)
| .mdata k b =>

View file

@ -869,10 +869,11 @@ private partial def mkClosureForAux (toProcess : Array FVarId) : StateRefT Closu
| .cdecl _ _ userName type bi k =>
let toProcess ← pushLocalDecl toProcess fvarId userName type bi k
mkClosureForAux toProcess
| .ldecl _ _ userName type val _ k =>
| .ldecl _ _ userName type val nondep k =>
let zetaDeltaFVarIds ← getZetaDeltaFVarIds
if !zetaDeltaFVarIds.contains fvarId then
/- Non-dependent let-decl. See comment at src/Lean/Meta/Closure.lean -/
-- Note: If `nondep` is true then `zetaDeltaFVarIds.contains fvarId` must be false.
if nondep || !zetaDeltaFVarIds.contains fvarId then
/- Nondependent let-decl. See comment at src/Lean/Meta/Closure.lean -/
let toProcess ← pushLocalDecl toProcess fvarId userName type .default k
mkClosureForAux toProcess
else

View file

@ -93,7 +93,7 @@ private partial def ensureNoUnassignedLevelMVarsAtPreDef (preDef : PreDefinition
checkCache { val := e : ExprStructEq } fun _ => do
match e with
| .forallE n d b c | .lam n d b c => withExpr e do visit d; withLocalDecl n c d fun x => visit (b.instantiate1 x)
| .letE n t v b _ => withExpr e do visit t; visit v; withLetDecl n t v fun x => visit (b.instantiate1 x)
| .letE n t v b nondep => withExpr e do visit t; visit v; withLetDecl n t v (nondep := nondep) fun x => visit (b.instantiate1 x)
| .mdata _ b => withExpr e do visit b
| .proj _ _ b => withExpr e do visit b
| .sort u => visitLevel u (← read)

View file

@ -133,9 +133,9 @@ private partial def replaceRecApps (recArgInfos : Array RecArgInfo) (positions :
| Expr.forallE n d b c =>
withLocalDecl n c (← loop below d) fun x => do
mkForallFVars #[x] (← loop below (b.instantiate1 x))
| Expr.letE n type val body _ =>
withLetDecl n (← loop below type) (← loop below val) fun x => do
mkLetFVars #[x] (← loop below (body.instantiate1 x)) (usedLetOnly := false)
| Expr.letE n type val body nondep =>
mapLetDecl n (← loop below type) (← loop below val) (nondep := nondep) (usedLetOnly := false) fun x => do
loop below (body.instantiate1 x)
| Expr.mdata d b =>
if let some stx := getRecAppSyntax? e then
withRef stx <| loop below b

View file

@ -50,9 +50,9 @@ private partial def replaceIndPredRecApps (recArgInfo : RecArgInfo) (funType : E
| Expr.forallE n d b c =>
withLocalDecl n c (← loop d) fun x => do
mkForallFVars #[x] (← loop (b.instantiate1 x))
| Expr.letE n type val body _ =>
withLetDecl n (← loop type) (← loop val) fun x => do
mkLetFVars #[x] (← loop (body.instantiate1 x))
| Expr.letE n type val body nondep =>
mapLetDecl n (← loop type) (← loop val) (nondep := nondep) fun x => do
loop (body.instantiate1 x)
| Expr.mdata d b => do
if let some stx := getRecAppSyntax? e then
withRef stx <| loop b

View file

@ -32,8 +32,9 @@ where
match e with
| Expr.lam .. => lambdaTelescope e fun xs b => do mkLambdaFVars xs (← visit b)
| Expr.forallE .. => forallTelescope e fun xs b => do mkForallFVars xs (← visit b)
| Expr.letE n type val body _ =>
withLetDecl n type (← visit val) fun x => do mkLetFVars #[x] (← visit (body.instantiate1 x))
| Expr.letE n type val body nondep =>
mapLetDecl n type (← visit val) (nondep := nondep) fun x => do
visit (body.instantiate1 x)
| Expr.mdata d b => return mkMData d (← visit b)
| Expr.proj n i s => return mkProj n i (← visit s)
| Expr.app .. =>

View file

@ -84,9 +84,9 @@ where
| Expr.forallE n d b c =>
withLocalDecl n c (← loop F d) fun x => do
mkForallFVars #[x] (← loop F (b.instantiate1 x))
| Expr.letE n type val body _ =>
withLetDecl n (← loop F type) (← loop F val) fun x => do
mkLetFVars #[x] (← loop F (body.instantiate1 x)) (usedLetOnly := false)
| Expr.letE n type val body nondep =>
mapLetDecl n (← loop F type) (← loop F val) (nondep := nondep) (usedLetOnly := false) fun x => do
loop F (body.instantiate1 x)
| Expr.mdata d b =>
if let some stx := getRecAppSyntax? e then
withRef stx <| loop F b

View file

@ -241,10 +241,10 @@ where
loop param d
withLocalDecl n c d fun x => do
loop param (b.instantiate1 x)
| Expr.letE n type val body _ =>
| Expr.letE n type val body nondep =>
loop param type
loop param val
withLetDecl n type val fun x => do
withLetDecl n type val (nondep := nondep) fun x => do
loop param (body.instantiate1 x)
| Expr.mdata _d b =>
if let some stx := getRecAppSyntax? e then

View file

@ -110,14 +110,68 @@ builtin_dsimproc paramMatcher (_) := fun e => do
let matcherApp' := { matcherApp with discrs := discrs', alts := alts' }
return .continue <| matcherApp'.toExpr
/-- `let x := (wfParam e); body[x] ==> let x := e; body[wfParam y] -/
private def anyLetValueIsWfParam (e : Expr) : Bool :=
match e with
| .letE _ _ v b _ => (isWfParam? v).isSome || anyLetValueIsWfParam b
| _ => false
private def numLetsWithValueNotIsWfParam (e : Expr) (acc := 0) : Nat :=
match e with
| .letE _ _ v b _ => if (isWfParam? v).isSome then acc else numLetsWithValueNotIsWfParam b (acc + 1)
| _ => acc
private partial def processParamLet (e : Expr) : MetaM Expr := do
if let .letE _ t v b _ := e then
if let some v' := isWfParam? v then
if ← Meta.isProp t then
processParamLet <| e.updateLetE! t v' b
else
let u ← getLevel t
let b' := b.instantiate1 <| mkApp2 (.const ``wfParam [u]) t (.bvar 0)
processParamLet <| e.updateLetE! t v' b'
else
let num := numLetsWithValueNotIsWfParam e
assert! num > 0
letBoundedTelescope e num fun xs b => do
let b' ← processParamLet b
mkLetFVars (usedLetOnly := false) (generalizeNondepLet := false) xs b'
else
return e
/--
`let x : T := (wfParam e); body[x] ==> let x : T := e; body[wfParam y]` if `T` is not a proposition,
otherwise `... ==> let x : T := e; body[x]`. (Applies to `have`s too.)
Note: simprocs are provided the head of a let telescope, but not intermediate lets.
-/
builtin_dsimproc paramLet (_) := fun e => do
unless e.isLet do return .continue
let some v := isWfParam? e.letValue! | return .continue
let u ← getLevel e.letType!
let body' := e.letBody!.instantiate1 <|
mkApp2 (.const ``wfParam [u]) e.letType! (.bvar 0)
return .continue <| e.updateLetE! e.letType! v body'
unless e.isLet || anyLetValueIsWfParam e do return .continue
return .continue (← processParamLet e)
/--
Transforms non-Prop `have`s to `let`s, so that the values can be used in GuessLex and decreasing-by proofs.
These `have`s may have been introdued by `simp`, which converts `let`s to `have`s.
-/
private def nonPropHaveToLet (e : Expr) : MetaM Expr := do
Meta.transform e (pre := fun e => do
if (← Meta.isProof e) then
return .done e
else if e.isLet then
-- Recall that `Meta.transform` processes entire let telescopes,
-- so we need to handle the telescope all at once.
let lctx ← getLCtx
let e' ← letTelescope e fun xs b => do
let lctx' ← xs.foldlM (init := lctx) fun lctx' x => do
let decl ← x.fvarId!.getDecl
-- Clear the flag if it's not a prop.
let decl' := decl.setNondep <| ← pure decl.isNondep <&&> Meta.isProp decl.type
pure <| lctx'.addDecl decl'
withLCtx' lctx' do
mkLetFVars (usedLetOnly := false) (generalizeNondepLet := false) xs b
return .continue e'
else
return .continue
)
def preprocess (e : Expr) : MetaM Simp.Result := do
unless wf.preprocess.get (← getOptions) do
@ -141,9 +195,13 @@ def preprocess (e : Expr) : MetaM Simp.Result := do
if h : as.size ≥ 2 then
return .continue (mkAppN as[1] as[2:])
return .continue
-- Transform `have`s to `let`s for non-propositions.
let e'' ← nonPropHaveToLet e''
let result := { result with expr := e'' }
trace[Elab.definition.wf] "Attach-introduction:{indentExpr e'}\nto{indentExpr result.expr}\ncleaned up as{indentExpr e''}"
trace[Elab.definition.wf] "Attach-introduction:{indentExpr e'}\nto{indentExpr result.expr}"
result.addLambdas xs
end Lean.Elab.WF

View file

@ -134,7 +134,7 @@ def solveMonoStep (failK : ∀ {α}, Expr → Array Name → MetaM α := @defaul
return [goal'.mvarId!]
-- Handle let
if let .letE n t v b _nonDep := e then
if let .letE n t v b nondep := e then
if t.hasLooseBVars || v.hasLooseBVars then
-- We cannot float the let to the context, so just zeta-reduce.
let b' := f.updateLambdaE! f.bindingDomain! (b.instantiate1 v)
@ -143,10 +143,10 @@ def solveMonoStep (failK : ∀ {α}, Expr → Array Name → MetaM α := @defaul
return [goal'.mvarId!]
else
-- No recursive call in t or v, so float out
let goal' ← withLetDecl n t v fun x => do
let goal' ← withLetDecl n t v (nondep := nondep) fun x => do
let b' := f.updateLambdaE! f.bindingDomain! (b.instantiate1 x)
let goal' ← mkFreshExprSyntheticOpaqueMVar (mkApp type.appFn! b')
goal.assign (← mkLetFVars #[x] goal')
goal.assign (← mkLetFVars (generalizeNondepLet := false) #[x] goal')
pure goal'
return [goal'.mvarId!]

View file

@ -48,10 +48,36 @@ See `LocalDecl.index`, `LocalDecl.fvarId`, `LocalDecl.userName`, `LocalDecl.type
arguments common to both constructors.
-/
inductive LocalDecl where
/-- A local variable. -/
/-- A local variable without any value.
`Lean.LocalContext.mkBinding` creates lambdas or foralls from `cdecl`s. -/
| cdecl (index : Nat) (fvarId : FVarId) (userName : Name) (type : Expr) (bi : BinderInfo) (kind : LocalDeclKind)
/-- A let-bound free variable, with a `value : Expr`. -/
| ldecl (index : Nat) (fvarId : FVarId) (userName : Name) (type : Expr) (value : Expr) (nonDep : Bool) (kind : LocalDeclKind)
/-- A let-bound free variable, with a value `value : Expr`.
If `nondep := false`, then the variable is definitionally equal to its value.
If `nondep := true`, then the variable has an opaque value; we call these "have-bound free variables."
`Lean.LocalContext.mkBinding` creates let/have expressions from `ldecl`s.
**Important:** The `nondep := true` case is subtle; it is not merely an opaque `ldecl`!
- In most contexts, nondependent `ldecl`s should be treated like `cdecl`s.
For example, suppose we have a tactic goal `x : α := v (nondep) ⊢ b`.
It would be incorrect for `revert x` to produce the goal `⊢ have x : α := v; b`,
since this would be saying "to prove `b` without knowledge of the value of `x`, it suffices to
prove `have x : α := v; b` for this particular value of `x`."
Instead, `revert x` *must* produce the goal `⊢ ∀ x : α, b`.
Furthermore, given a goal `⊢ have x : α := v; b`, the `intro x` tactic should yield a *dependent* `ldecl`,
since users expect to be able to make use of the value of `x`,
plus, as discussed, if `intro` yielded a nondep `ldecl` then `intro x; revert x` would convert the goal into a forall, not a `have`.
- Also: `value` might not be type correct. Metaprograms may decide to pretend that all `nondep := true`
`ldecl`s are `cdecl`s (for example, when reverting variables). As a consequence, nondep `ldecl`s may
have type-incorrect values. This design decision allows metaprograms to not have to think about nondep `ldecl`s,
so long as `LocalDecl` values are consumed through `LocalDecl.isLet` and `LocalDecl.value?` with `(allowNondep := false)`.
**Rule:** never use `(generalizeNondepLet := false)` in `mkBinding`-family functions within a local context you do not own.
See `LocalDecl.setNondep` for some additional discussion.
- Where then do nondep ldecls come from? Common functions are `Meta.mapLetDecl`, `Meta.withLetDecl`, and `Meta.letTelescope`.
The `have` term syntax makes use of a nondep ldecl as well.
Therefore, `nondep := true` should be used with consideration.
Its primary use is in metaprograms that enter `let`/`have` telescopes and wish to reconstruct them. -/
| ldecl (index : Nat) (fvarId : FVarId) (userName : Name) (type : Expr) (value : Expr) (nondep : Bool) (kind : LocalDeclKind)
deriving Inhabited
@[export lean_mk_local_decl]
@ -66,9 +92,15 @@ def LocalDecl.binderInfoEx : LocalDecl → BinderInfo
| _ => BinderInfo.default
namespace LocalDecl
def isLet : LocalDecl → Bool
| cdecl .. => false
| ldecl .. => true
/--
Returns true if this is an `ldecl` with a visible value.
If `allowNondep` is true then includes `ldecl`s with `nondep := true`, whose values are normally hidden.
-/
def isLet : LocalDecl → (allowNondep : Bool := false) → Bool
| cdecl .., _ => false
| ldecl (nondep := false) .., _ => true
| ldecl (nondep := true) .., allowNondep => allowNondep
/-- The position of the decl in the local context. -/
def index : LocalDecl → Nat
@ -115,22 +147,81 @@ Is the local declaration an implementation-detail hypothesis
def isImplementationDetail (d : LocalDecl) : Bool :=
d.kind != .default
def value? : LocalDecl → Option Expr
| cdecl .. => none
| ldecl (value := v) .. => some v
/--
Returns the value of the `ldecl` if it has a visible value.
def value : LocalDecl → Expr
| cdecl .. => panic! "let declaration expected"
| ldecl (value := v) .. => v
If `allowNondep` is true, then allows nondependent `ldecl`s, whose values are normally hidden.
-/
def value? : LocalDecl → (allowNondep : Bool := false) → Option Expr
| ldecl (nondep := false) (value := v) .., _ => some v
| ldecl (nondep := true) (value := v) .., true => some v
| _, _ => none
def hasValue : LocalDecl → Bool
| cdecl .. => false
| ldecl .. => true
/--
Returns the value of the `ldecl` if it has a visible value.
If `allowNondep` is true, then allows nondependent `ldecl`s, whose values are normally hidden.
-/
def value : LocalDecl → (allowNondep : Bool := false) → Expr
| cdecl .., _ => panic! "let declaration expected"
| ldecl (nondep := false) (value := v) .., _ => v
| ldecl (nondep := true) (value := v) .., true => v
| ldecl (nondep := true) .., false => panic! "dependent let declaration expected"
/--
Returns `true` if `LocalDecl.value?` is not `none`.
-/
def hasValue : LocalDecl → (allowNondep : Bool := false) → Bool
| cdecl .., _ => false
| ldecl (nondep := nondep) .., allowNondep => !nondep || allowNondep
/-- Sets the value of an `ldecl`, otherwise returns `cdecl`s unchanged. -/
def setValue : LocalDecl → Expr → LocalDecl
| ldecl idx id n t _ nd k, v => ldecl idx id n t v nd k
| d, _ => d
/--
Sets the `nondep` flag of an `ldecl`, otherwise returns `cdecl`s unchanged.
This is a low-level function, and it is the responsibility of the caller to ensure that
transitions of `nondep` are valid.
Rules:
- If the declaration is not under the caller's control, then setting `nondep := false` must not be done.
General nondependent `ldecl`s should be treated like `cdecl`s.
See also the docstring for `LocalDecl.ldecl` about the `value` not necessarily being type correct.
- Setting `nondep := true` is usually fine.
- Caution: be sure any relevant caches are cleared so that the value associated to this `FVarId` does not leak.
- Caution: be sure that metavariables dependent on this declaration created before and after the transition are not mixed,
since unification does not check "`nondep`-compatibility" of local contexts when assigning metavariables.
For example, setting `nondep := false` is fine from within a telescope combinator, to update the local context
right before calling `mkLetFVars`:
```lean
let lctx ← getLCtx
letTelescope e fun xs b => do
let lctx' ← xs.foldlM (init := lctx) fun lctx' x => do
let decl ← x.fvarId!.getDecl
-- Clear the flag if it's not a prop.
let decl' := decl.setNondep <| ← pure decl.isNondep <&&> Meta.isProp decl.type
pure <| lctx'.addDecl decl'
withLCtx' lctx' do
mkLetFVars (usedLetOnly := false) (generalizeNondepLet := false) xs b
```
1. The declarations for `xs` are in the control of this metaprogram.
2. `mkLetFVars` does make use of `MetaM` caches.
3. Even if `e` has metavariables, these do not include `xs` in their contexts,
so the change of the `nondep` flag does not cause any issues in the `abstractM` system used by `mkLetFVars`.
-/
def setNondep : LocalDecl → Bool → LocalDecl
| ldecl idx id n t v _ k, nd => ldecl idx id n t v nd k
| d, _ => d
/-- Returns `true` if this is an `ldecl` with `nondep := true`. -/
def isNondep : LocalDecl → Bool
| ldecl (nondep := nondep) .. => nondep
| _ => false
def setUserName : LocalDecl → Name → LocalDecl
| cdecl index id _ type bi k, userName => cdecl index id userName type bi k
| ldecl index id _ type val nd k, userName => ldecl index id userName type val nd k
@ -152,8 +243,8 @@ Set the kind of a `LocalDecl`.
def setKind : LocalDecl → LocalDeclKind → LocalDecl
| cdecl index fvarId userName type bi _, kind =>
cdecl index fvarId userName type bi kind
| ldecl index fvarId userName type value nonDep _, kind =>
ldecl index fvarId userName type value nonDep kind
| ldecl index fvarId userName type value nondep _, kind =>
ldecl index fvarId userName type value nondep kind
end LocalDecl
@ -182,7 +273,7 @@ def empty : LocalContext := {}
def isEmpty (lctx : LocalContext) : Bool :=
lctx.fvarIdToDecl.isEmpty
/-- Low level API for creating local declarations.
/-- Low level API for creating local declarations (`LocalDecl.cdecl`).
It is used to implement actions in the monads `Elab` and `Tactic`.
It should not be used directly since the argument `(fvarId : FVarId)` is
assumed to be unique. You can create a unique fvarId with `mkFreshFVarId`. -/
@ -199,16 +290,16 @@ private def mkLocalDeclExported (lctx : LocalContext) (fvarId : FVarId) (userNam
mkLocalDecl lctx fvarId userName type bi
/-- Low level API for let declarations. Do not use directly.-/
def mkLetDecl (lctx : LocalContext) (fvarId : FVarId) (userName : Name) (type : Expr) (value : Expr) (nonDep := false) (kind : LocalDeclKind := default) : LocalContext :=
def mkLetDecl (lctx : LocalContext) (fvarId : FVarId) (userName : Name) (type : Expr) (value : Expr) (nondep := false) (kind : LocalDeclKind := default) : LocalContext :=
match lctx with
| { fvarIdToDecl := map, decls := decls, auxDeclToFullName } =>
let idx := decls.size
let decl := LocalDecl.ldecl idx fvarId userName type value nonDep kind
let decl := LocalDecl.ldecl idx fvarId userName type value nondep kind
{ fvarIdToDecl := map.insert fvarId decl, decls := decls.push decl, auxDeclToFullName }
@[export lean_local_ctx_mk_let_decl]
private def mkLetDeclExported (lctx : LocalContext) (fvarId : FVarId) (userName : Name) (type : Expr) (value : Expr) (nonDep : Bool) : LocalContext :=
mkLetDecl lctx fvarId userName type value nonDep
private def mkLetDeclExported (lctx : LocalContext) (fvarId : FVarId) (userName : Name) (type : Expr) (value : Expr) (nondep : Bool) : LocalContext :=
mkLetDecl lctx fvarId userName type value nondep
/-- Low level API for auxiliary declarations. Do not use directly. -/
def mkAuxDecl (lctx : LocalContext) (fvarId : FVarId) (userName : Name) (type : Expr) (fullName : Name) : LocalContext :=
@ -431,35 +522,39 @@ partial def isSubPrefixOfAux (a₁ a₂ : PArray (Option LocalDecl)) (exceptFVar
def isSubPrefixOf (lctx₁ lctx₂ : LocalContext) (exceptFVars : Array Expr := #[]) : Bool :=
isSubPrefixOfAux lctx₁.decls lctx₂.decls exceptFVars 0 0
@[inline] def mkBinding (isLambda : Bool) (lctx : LocalContext) (xs : Array Expr) (b : Expr) : Expr :=
@[inline] def mkBinding (isLambda : Bool) (lctx : LocalContext) (xs : Array Expr) (b : Expr) (generalizeNondepLet := false) : Expr :=
let b := b.abstract xs
xs.size.foldRev (init := b) fun i _ b =>
let x := xs[i]
match lctx.findFVar? x with
| some (.cdecl _ _ n ty bi _) =>
let handleCDecl (n : Name) (ty : Expr) (bi : BinderInfo) : Expr :=
let ty := ty.abstractRange i xs;
if isLambda then
Lean.mkLambda n bi ty b
else
Lean.mkForall n bi ty b
| some (.ldecl _ _ n ty val nonDep _) =>
if b.hasLooseBVar 0 then
match lctx.findFVar? x with
| some (.cdecl _ _ n ty bi _) =>
handleCDecl n ty bi
| some (.ldecl _ _ n ty val nondep _) =>
if nondep && generalizeNondepLet then
handleCDecl n ty .default
else if b.hasLooseBVar 0 then
let ty := ty.abstractRange i xs
let val := val.abstractRange i xs
mkLet n ty val b nonDep
mkLet n ty val b nondep
else
b.lowerLooseBVars 1 1
| none => panic! "unknown free variable"
/-- Creates the expression `fun x₁ .. xₙ => b` for free variables `xs = #[x₁, .., xₙ]`,
suitably abstracting `b` and the types for each of the `xᵢ`. -/
def mkLambda (lctx : LocalContext) (xs : Array Expr) (b : Expr) : Expr :=
mkBinding true lctx xs b
def mkLambda (lctx : LocalContext) (xs : Array Expr) (b : Expr) (generalizeNondepLet := false) : Expr :=
mkBinding true lctx xs b generalizeNondepLet
/-- Creates the expression `(x₁:α₁) → .. → (xₙ:αₙ) → b` for free variables `xs = #[x₁, .., xₙ]`,
suitably abstracting `b` and the types for each of the `xᵢ`, `αᵢ`. -/
def mkForall (lctx : LocalContext) (xs : Array Expr) (b : Expr) : Expr :=
mkBinding false lctx xs b
def mkForall (lctx : LocalContext) (xs : Array Expr) (b : Expr) (generalizeNondepLet := false) : Expr :=
mkBinding false lctx xs b generalizeNondepLet
@[inline] def anyM [Monad m] (lctx : LocalContext) (p : LocalDecl → m Bool) : m Bool :=
lctx.decls.anyM fun d => match d with
@ -539,7 +634,7 @@ def LocalDecl.replaceFVarId (fvarId : FVarId) (e : Expr) (d : LocalDecl) : Local
if d.fvarId == fvarId then d
else match d with
| .cdecl idx id n type bi k => .cdecl idx id n (type.replaceFVarId fvarId e) bi k
| .ldecl idx id n type val nonDep k => .ldecl idx id n (type.replaceFVarId fvarId e) (val.replaceFVarId fvarId e) nonDep k
| .ldecl idx id n type val nondep k => .ldecl idx id n (type.replaceFVarId fvarId e) (val.replaceFVarId fvarId e) nondep k
def LocalContext.replaceFVarId (fvarId : FVarId) (e : Expr) (lctx : LocalContext) : LocalContext :=
let lctx := lctx.erase fvarId

View file

@ -60,7 +60,7 @@ partial def visit (e : Expr) : M Expr := do
let localDecl ← xFVarId.getDecl
let type ← visit localDecl.type
let localDecl := localDecl.setType type
let localDecl ← match localDecl.value? with
let localDecl ← match localDecl.value? (allowNondep := true) with
| some value => let value ← visit value; pure <| localDecl.setValue value
| none => pure localDecl
lctx := lctx.modifyLocalDecl xFVarId fun _ => localDecl
@ -70,8 +70,8 @@ partial def visit (e : Expr) : M Expr := do
/- Ensure proofs nested in type are also abstracted -/
abstractProof e (← read).cache visit
else match e with
| .lam .. => lambdaLetTelescope e fun xs b => visitBinders xs do mkLambdaFVars xs (← visit b) (usedLetOnly := false)
| .letE .. => lambdaLetTelescope e fun xs b => visitBinders xs do mkLambdaFVars xs (← visit b) (usedLetOnly := false)
| .lam ..
| .letE .. => lambdaLetTelescope e fun xs b => visitBinders xs do mkLambdaFVars xs (← visit b) (usedLetOnly := false) (generalizeNondepLet := false)
| .forallE .. => forallTelescope e fun xs b => visitBinders xs do mkForallFVars xs (← visit b)
| .mdata _ b => return e.updateMData! (← visit b)
| .proj _ _ b => return e.updateProj! (← visit b)

View file

@ -445,8 +445,8 @@ structure Context where
When `trackZetaDelta = true`, we track all free variables that have been zetaDelta-expanded.
That is, suppose the local context contains
the declaration `x : t := v`, and we reduce `x` to `v`, then we insert `x` into `State.zetaDeltaFVarIds`.
We use `trackZetaDelta` to discover which let-declarations `let x := v; e` can be represented as `(fun x => e) v`.
When we find these declarations we set their `nonDep` flag with `true`.
We use `trackZetaDelta` to discover which let-declarations `let x := v; e` can be represented as `have x := v; e`.
When we find these declarations we set their `nondep` flag with `true`.
To find these let-declarations in a given term `s`, we
1- Reset `State.zetaDeltaFVarIds`
2- Set `trackZetaDelta := true`
@ -978,17 +978,27 @@ def _root_.Lean.FVarId.getType (fvarId : FVarId) : MetaM Expr :=
def _root_.Lean.FVarId.getBinderInfo (fvarId : FVarId) : MetaM BinderInfo :=
return (← fvarId.getDecl).binderInfo
/-- Return `some value` if the given free variable is a let-declaration, and `none` otherwise. -/
def _root_.Lean.FVarId.getValue? (fvarId : FVarId) : MetaM (Option Expr) :=
return (← fvarId.getDecl).value?
/--
Returns `some value` if the given free let-variable has a visible local definition in the current local context
(using `Lean.LocalDecl.value?`), and `none` otherwise.
Setting `allowNondep := true` allows access of the normally hidden value of a nondependent let declaration.
-/
def _root_.Lean.FVarId.getValue? (fvarId : FVarId) (allowNondep : Bool := false) : MetaM (Option Expr) :=
return (← fvarId.getDecl).value? allowNondep
/-- Return the user-facing name for the given free variable. -/
def _root_.Lean.FVarId.getUserName (fvarId : FVarId) : MetaM Name :=
return (← fvarId.getDecl).userName
/-- Return `true` is the free variable is a let-variable. -/
def _root_.Lean.FVarId.isLetVar (fvarId : FVarId) : MetaM Bool :=
return (← fvarId.getDecl).isLet
/--
Returns `true` if the free variable is a let-variable with a visible local definition in the current local context
(using `Lean.LocalDecl.isLet`).
Setting `allowNondep := true` includes nondependent let declarations, whose values are normally hidden.
-/
def _root_.Lean.FVarId.isLetVar (fvarId : FVarId) (allowNondep : Bool := false) : MetaM Bool :=
return (← fvarId.getDecl).isLet allowNondep
/-- Get the local declaration associated to the given `Expr` in the current local
context. Fails if the given expression is not a fvar or if no such declaration exists. -/
@ -1054,26 +1064,30 @@ def _root_.Lean.Expr.abstractM (e : Expr) (xs : Array Expr) : MetaM Expr :=
/--
Collect forward dependencies for the free variables in `toRevert`.
Recall that when reverting free variables `xs`, we must also revert their forward dependencies.
When `generalizeNondepLet := true` (the default), then the values of nondependent lets are not considered
when computing forward dependencies.
-/
def collectForwardDeps (toRevert : Array Expr) (preserveOrder : Bool) : MetaM (Array Expr) := do
liftMkBindingM <| MetavarContext.collectForwardDeps toRevert preserveOrder
def collectForwardDeps (toRevert : Array Expr) (preserveOrder : Bool) (generalizeNondepLet := true) : MetaM (Array Expr) := do
liftMkBindingM <| MetavarContext.collectForwardDeps toRevert preserveOrder generalizeNondepLet
/-- Takes an array `xs` of free variables or metavariables and a term `e` that may contain those variables, and abstracts and binds them as universal quantifiers.
- if `usedOnly = true` then only variables that the expression body depends on will appear.
- if `usedLetOnly = true` same as `usedOnly` except for let-bound variables. (That is, local constants which have been assigned a value.)
- if `generalizeNondepLet = true` then nondependent `ldecl`s become foralls too.
-/
def mkForallFVars (xs : Array Expr) (e : Expr) (usedOnly : Bool := false) (usedLetOnly : Bool := true) (binderInfoForMVars := BinderInfo.implicit) : MetaM Expr :=
if xs.isEmpty then return e else liftMkBindingM <| MetavarContext.mkForall xs e usedOnly usedLetOnly binderInfoForMVars
def mkForallFVars (xs : Array Expr) (e : Expr) (usedOnly : Bool := false) (usedLetOnly : Bool := true) (generalizeNondepLet := true) (binderInfoForMVars := BinderInfo.implicit) : MetaM Expr :=
if xs.isEmpty then return e else liftMkBindingM <| MetavarContext.mkForall xs e usedOnly usedLetOnly generalizeNondepLet binderInfoForMVars
/-- Takes an array `xs` of free variables and metavariables and a
body term `e` and creates `fun ..xs => e`, suitably
abstracting `e` and the types in `xs`. -/
def mkLambdaFVars (xs : Array Expr) (e : Expr) (usedOnly : Bool := false) (usedLetOnly : Bool := true) (etaReduce : Bool := false) (binderInfoForMVars := BinderInfo.implicit) : MetaM Expr :=
if xs.isEmpty then return e else liftMkBindingM <| MetavarContext.mkLambda xs e usedOnly usedLetOnly etaReduce binderInfoForMVars
def mkLambdaFVars (xs : Array Expr) (e : Expr) (usedOnly : Bool := false) (usedLetOnly : Bool := true) (etaReduce : Bool := false) (generalizeNondepLet := true) (binderInfoForMVars := BinderInfo.implicit) : MetaM Expr :=
if xs.isEmpty then return e else liftMkBindingM <| MetavarContext.mkLambda xs e usedOnly usedLetOnly etaReduce generalizeNondepLet binderInfoForMVars
def mkLetFVars (xs : Array Expr) (e : Expr) (usedLetOnly := true) (binderInfoForMVars := BinderInfo.implicit) : MetaM Expr :=
mkLambdaFVars xs e (usedLetOnly := usedLetOnly) (binderInfoForMVars := binderInfoForMVars)
def mkLetFVars (xs : Array Expr) (e : Expr) (usedLetOnly := true) (generalizeNondepLet := true) (binderInfoForMVars := BinderInfo.implicit) : MetaM Expr :=
mkLambdaFVars xs e (usedLetOnly := usedLetOnly) (generalizeNondepLet := generalizeNondepLet) (binderInfoForMVars := binderInfoForMVars)
/-- `fun _ : Unit => a` -/
def mkFunUnit (a : Expr) : MetaM Expr :=
@ -1519,40 +1533,48 @@ private def forallBoundedTelescopeImp (type : Expr) (maxFVars? : Option Nat) (k
def forallBoundedTelescope (type : Expr) (maxFVars? : Option Nat) (k : Array Expr → Expr → n α) (cleanupAnnotations := false) (whnfType := false) : n α :=
map2MetaM (fun k => forallBoundedTelescopeImp type maxFVars? k cleanupAnnotations (whnfType := whnfType)) k
private partial def lambdaTelescopeImp (e : Expr) (consumeLet : Bool) (maxFVars? : Option Nat)
private partial def lambdaTelescopeImp (e : Expr) (consumeLambda : Bool) (consumeLet : Bool) (preserveNondepLet : Bool) (nondepLetOnly : Bool) (maxFVars? : Option Nat)
(k : Array Expr → Expr → MetaM α) (cleanupAnnotations := false) : MetaM α := do
process consumeLet (← getLCtx) #[] e
process consumeLambda consumeLet (← getLCtx) #[] e
where
process (consumeLet : Bool) (lctx : LocalContext) (fvars : Array Expr) (e : Expr) : MetaM α := do
match fvarsSizeLtMaxFVars fvars maxFVars?, consumeLet, e with
| true, _, .lam n d b bi =>
process (consumeLambda : Bool) (consumeLet : Bool) (lctx : LocalContext) (fvars : Array Expr) (e : Expr) : MetaM α := do
let finish (e : Expr) : MetaM α :=
let e := e.instantiateRevRange 0 fvars.size fvars
withReader (fun ctx => { ctx with lctx := lctx }) do
withNewLocalInstancesImp fvars 0 do
k fvars e
match fvarsSizeLtMaxFVars fvars maxFVars?, consumeLambda, consumeLet, e with
| true, true, _, .lam n d b bi =>
let d := d.instantiateRevRange 0 fvars.size fvars
let d := if cleanupAnnotations then d.cleanupAnnotations else d
let fvarId ← mkFreshFVarId
let lctx := lctx.mkLocalDecl fvarId n d bi
let fvar := mkFVar fvarId
process consumeLet lctx (fvars.push fvar) b
| true, true, .letE n t v b _ => do
let t := t.instantiateRevRange 0 fvars.size fvars
let t := if cleanupAnnotations then t.cleanupAnnotations else t
let v := v.instantiateRevRange 0 fvars.size fvars
let fvarId ← mkFreshFVarId
let lctx := lctx.mkLetDecl fvarId n t v
let fvar := mkFVar fvarId
process true lctx (fvars.push fvar) b
| _, _, e =>
let e := e.instantiateRevRange 0 fvars.size fvars
withReader (fun ctx => { ctx with lctx := lctx }) do
withNewLocalInstancesImp fvars 0 do
k fvars e
process true consumeLet lctx (fvars.push fvar) b
| true, _, true, .letE n t v b nondep => do
if !nondep && nondepLetOnly then
finish e
else
let t := t.instantiateRevRange 0 fvars.size fvars
let t := if cleanupAnnotations then t.cleanupAnnotations else t
let v := v.instantiateRevRange 0 fvars.size fvars
let fvarId ← mkFreshFVarId
let lctx := lctx.mkLetDecl fvarId n t v (nondep && preserveNondepLet)
let fvar := mkFVar fvarId
process consumeLambda true lctx (fvars.push fvar) b
| _, _, _, e =>
finish e
/--
Similar to `lambdaTelescope` but for lambda and let expressions.
If `cleanupAnnotations` is `true`, we apply `Expr.cleanupAnnotations` to each type in the telescope.
- If `cleanupAnnotations` is `true`, we apply `Expr.cleanupAnnotations` to each type in the telescope.
- If `preserveNondep` is `false`, all `have`s are converted to `let`s.
See also `mapLambdaLetTelescope` for entering and rebuilding the telescope.
-/
def lambdaLetTelescope (e : Expr) (k : Array Expr → Expr → n α) (cleanupAnnotations := false) : n α :=
map2MetaM (fun k => lambdaTelescopeImp e true .none k (cleanupAnnotations := cleanupAnnotations)) k
def lambdaLetTelescope (e : Expr) (k : Array Expr → Expr → n α) (cleanupAnnotations := false) (preserveNondepLet := true) : n α :=
map2MetaM (fun k => lambdaTelescopeImp e true true preserveNondepLet false .none k (cleanupAnnotations := cleanupAnnotations)) k
/--
Given `e` of the form `fun ..xs => A`, execute `k xs A`.
@ -1562,7 +1584,7 @@ def lambdaLetTelescope (e : Expr) (k : Array Expr → Expr → n α) (cleanupAnn
If `cleanupAnnotations` is `true`, we apply `Expr.cleanupAnnotations` to each type in the telescope.
-/
def lambdaTelescope (e : Expr) (k : Array Expr → Expr → n α) (cleanupAnnotations := false) : n α :=
map2MetaM (fun k => lambdaTelescopeImp e false none k (cleanupAnnotations := cleanupAnnotations)) k
map2MetaM (fun k => lambdaTelescopeImp e true false true false none k (cleanupAnnotations := cleanupAnnotations)) k
/--
Given `e` of the form `fun ..xs ..ys => A`, execute `k xs (fun ..ys => A)` where
@ -1573,7 +1595,42 @@ def lambdaTelescope (e : Expr) (k : Array Expr → Expr → n α) (cleanupAnnota
If `cleanupAnnotations` is `true`, we apply `Expr.cleanupAnnotations` to each type in the telescope.
-/
def lambdaBoundedTelescope (e : Expr) (maxFVars : Nat) (k : Array Expr → Expr → n α) (cleanupAnnotations := false) : n α :=
map2MetaM (fun k => lambdaTelescopeImp e false (.some maxFVars) k (cleanupAnnotations := cleanupAnnotations)) k
map2MetaM (fun k => lambdaTelescopeImp e true false true false (.some maxFVars) k (cleanupAnnotations := cleanupAnnotations)) k
/--
Given `e` of the form `let x₁ := v₁; ...; let xₙ := vₙ; A`, executes `k xs A`,
where `xs` is an array of free variables for the binders.
The `let`s can also be `have`s.
- If `cleanupAnnotations` is `true`, applies `Expr.cleanupAnnotations` to each type in the telescope.
- If `preserveNondep` is `false`, all `have`s are converted to `let`s.
- If `nondepLetOnly` is `true`, then only `have`s are consumed (it stops at the first dependent `let`).
See also `mapLetTelescope` for entering and rebuilding the telescope.
-/
def letTelescope (e : Expr) (k : Array Expr → Expr → n α) (cleanupAnnotations := false) (preserveNondepLet := true) (nondepLetOnly := false) : n α :=
map2MetaM (fun k => lambdaTelescopeImp e false true preserveNondepLet nondepLetOnly none k (cleanupAnnotations := cleanupAnnotations)) k
/--
Like `letTelescope`, but limits the number of `let`/`have`s consumed to `maxFVars?`.
If `maxFVars?` is none, then this is the same as `letTelescope`.
-/
def letBoundedTelescope (e : Expr) (maxFVars? : Option Nat) (k : Array Expr → Expr → n α) (cleanupAnnotations := false) (preserveNondepLet := true) (nondepLetOnly := false) : n α :=
map2MetaM (fun k => lambdaTelescopeImp e false true preserveNondepLet nondepLetOnly maxFVars? k (cleanupAnnotations := cleanupAnnotations)) k
/--
Evaluates `k` from within a `lambdaLetTelescope`, then uses `mkLetFVars` to rebuild the telescope.
-/
def mapLambdaLetTelescope [MonadLiftT MetaM n] (e : Expr) (k : Array Expr → Expr → n Expr) (cleanupAnnotations := false) (preserveNondepLet := true) (usedLetOnly := true) : n Expr :=
lambdaLetTelescope e (cleanupAnnotations := cleanupAnnotations) (preserveNondepLet := preserveNondepLet) fun xs b => do
mkLambdaFVars (usedLetOnly := usedLetOnly) (generalizeNondepLet := false) xs (← k xs b)
/--
Evaluates `k` from within a `letTelescope`, then uses `mkLetFVars` to rebuild the telescope.
-/
def mapLetTelescope [MonadLiftT MetaM n] (e : Expr) (k : Array Expr → Expr → n Expr) (cleanupAnnotations := false) (preserveNondepLet := true) (nondepLetOnly := false) (usedLetOnly := true) : n Expr :=
letTelescope e (cleanupAnnotations := cleanupAnnotations) (preserveNondepLet := preserveNondepLet) (nondepLetOnly := nondepLetOnly) fun xs b => do
mkLetFVars (usedLetOnly := usedLetOnly) (generalizeNondepLet := false) xs (← k xs b)
/-- Return the parameter names for the given global declaration. -/
def getParamNames (declName : Name) : MetaM (Array Name) := do
@ -1754,10 +1811,10 @@ def withInstImplicitAsImplict (xs : Array Expr) (k : MetaM α) : MetaM α := do
return none
withNewBinderInfos newBinderInfos k
private def withLetDeclImp (n : Name) (type : Expr) (val : Expr) (k : Expr → MetaM α) (kind : LocalDeclKind) : MetaM α := do
private def withLetDeclImp (n : Name) (type : Expr) (val : Expr) (k : Expr → MetaM α) (nondep : Bool) (kind : LocalDeclKind) : MetaM α := do
let fvarId ← mkFreshFVarId
let ctx ← read
let lctx := ctx.lctx.mkLetDecl fvarId n type val (nonDep := false) kind
let lctx := ctx.lctx.mkLetDecl fvarId n type val nondep kind
let fvar := mkFVar fvarId
withReader (fun ctx => { ctx with lctx := lctx }) do
withNewFVar fvar type k
@ -1766,8 +1823,17 @@ private def withLetDeclImp (n : Name) (type : Expr) (val : Expr) (k : Expr → M
Add the local declaration `<name> : <type> := <val>` to the local context and execute `k x`, where `x` is a new
free variable corresponding to the `let`-declaration. After executing `k x`, the local context is restored.
-/
def withLetDecl (name : Name) (type : Expr) (val : Expr) (k : Expr → n α) (kind : LocalDeclKind := .default) : n α :=
map1MetaM (fun k => withLetDeclImp name type val k kind) k
def withLetDecl (name : Name) (type : Expr) (val : Expr) (k : Expr → n α) (nondep : Bool := false) (kind : LocalDeclKind := .default) : n α :=
map1MetaM (fun k => withLetDeclImp name type val k nondep kind) k
/--
Runs `k x` with the local declaration `<name> : <type> := <val>` added to the local context, where `x` is the new free variable.
Afterwards, the result is wrapped in the given `let`/`have` expression (according to the value of `nondep`).
- If `usedLetOnly := true` (the default) then the the `let`/`have` is not created if the variable is unused.
-/
def mapLetDecl [MonadLiftT MetaM n] (name : Name) (type : Expr) (val : Expr) (k : Expr → n Expr) (nondep : Bool := false) (kind : LocalDeclKind := .default) (usedLetOnly : Bool := true) : n Expr :=
withLetDecl name type val (nondep := nondep) (kind := kind) fun x => do
mkLetFVars (usedLetOnly := usedLetOnly) (generalizeNondepLet := false) #[x] (← k x)
def withLocalInstancesImp (decls : List LocalDecl) (k : MetaM α) : MetaM α := do
let mut localInsts := (← read).localInstances

View file

@ -183,10 +183,11 @@ where
def throwLetTypeMismatchMessage {α} (fvarId : FVarId) : MetaM α := do
let lctx ← getLCtx
match lctx.find? fvarId with
| some (LocalDecl.ldecl _ _ _ t v _ _) => do
| some (LocalDecl.ldecl _ _ _ t v nondep _) => do
let vType ← inferType v
let (vType, t) ← addPPExplicitToExposeDiff vType t
throwError "invalid let declaration, term{indentExpr v}\nhas type{indentExpr vType}\nbut is expected to have type{indentExpr t}"
let declKind := if nondep then "have" else "let"
throwError "invalid {declKind} declaration, term{indentExpr v}\nhas type{indentExpr vType}\nbut is expected to have type{indentExpr t}"
| _ => unreachable!
/--

View file

@ -286,9 +286,10 @@ partial def process : ClosureM Unit := do
pushLocalDecl newFVarId userName type bi
pushFVarArg (mkFVar fvarId)
process
| .ldecl _ _ userName type val _ _ =>
| .ldecl _ _ userName type val nondep _ =>
let zetaDeltaFVarIds ← getZetaDeltaFVarIds
if !zetaDeltaFVarIds.contains fvarId then
-- Note: If `nondep` is true then `zetaDeltaFVarIds.contains fvarId` must be false.
if nondep || !zetaDeltaFVarIds.contains fvarId then
/- Non-dependent let-decl
Recall that if `fvarId` is in `zetaDeltaFVarIds`, then we zetaDelta-expanded it
@ -321,11 +322,11 @@ partial def process : ClosureM Unit := do
Lean.mkLambda n bi ty b
else
Lean.mkForall n bi ty b
| .ldecl _ _ n ty val nonDep _ =>
| .ldecl _ _ n ty val nondep _ =>
if b.hasLooseBVar 0 then
let ty := ty.abstractRange i xs
let val := val.abstractRange i xs
mkLet n ty val b nonDep
mkLet n ty val b nondep
else
b.lowerLooseBVars 1 1

View file

@ -426,7 +426,7 @@ private partial def mkLambdaFVarsWithLetDeps (xs : Array Expr) (v : Expr) : Meta
mkLambdaFVars ys v (etaReduce := true)
where
/-- Return true if there are let-declarions between `xs[0]` and `xs[xs.size-1]`.
/-- Return true if there are let-declarations between `xs[0]` and `xs[xs.size-1]`.
We use it a quick-check to avoid the more expensive collection procedure. -/
hasLetDeclsInBetween : MetaM Bool := do
let check (lctx : LocalContext) : Bool := Id.run do
@ -728,7 +728,21 @@ mutual
else
let lctx := ctxMeta.lctx
match lctx.findFVar? fvar with
| some (.ldecl (value := v) ..) => check v
/-
Recall: if `nondep := true`, then the ldecl is locally a cdecl, so the `value` field is not relevant.
In the following example, switching the indicated `have` for a `let` causes the unification to fail,
since then `v` depends on a variable not in `?mvar`'s local context.
```
example : Nat → Nat :=
let f : Nat → Nat := ?mvar
let x : Nat := 2
-- if this is a `let`, then `refine rfl` fails.
have v := x
have : ?mvar v = v := by refine rfl
f
```
-/
| some (.ldecl (nondep := false) (value := v) ..) => check v
| _ =>
if ctx.fvars.contains fvar then pure fvar
else
@ -917,7 +931,10 @@ unsafe def checkImpl
| .fvar fvarId .. =>
if mvarDecl.lctx.contains fvarId then
return true
if let some (LocalDecl.ldecl ..) := lctx.find? fvarId then
/-
Recall: if `nondep := true` then the ldecl is locally a cdecl. See comment in `CheckAssignment.checkFVar`.
-/
if let some (LocalDecl.ldecl (nondep := false) ..) := lctx.find? fvarId then
return false -- need expensive CheckAssignment.check
if fvars.any fun x => x.fvarId! == fvarId then
return true

View file

@ -38,7 +38,7 @@ private def lensCoord (g : Expr → M Expr) (n : Nat) (e : Expr) : M Expr := do
| 1, .forallE n y b c => withLocalDecl n c y fun x => do mkForallFVars #[x] <|← g <| b.instantiateRev #[x]
| 0, .letE _ y a b _ => return e.updateLetE! (← g y) a b
| 1, .letE _ y a b _ => return e.updateLetE! y (← g a) b
| 2, .letE n y a b _ => withLetDecl n y a fun x => do mkLetFVars #[x] <|← g <| b.instantiateRev #[x]
| 2, .letE n y a b nondep => mapLetDecl n y a (nondep := nondep) (usedLetOnly := false) fun x => g <| b.instantiate1 x
| 0, .proj _ _ b => e.updateProj! <$> g b
| n, .mdata _ a => e.updateMData! <$> lensCoord g n a
| 3, _ => throwError "Lensing on types is not supported"

View file

@ -121,7 +121,7 @@ private def inferProjType (structName : Name) (idx : Nat) (e : Expr) : MetaM Exp
| .forallE _ d _ _ => return d.consumeTypeAnnotations
| _ => failed ()
def throwTypeExcepted {α} (type : Expr) : MetaM α :=
def throwTypeExpected {α} (type : Expr) : MetaM α :=
throwError "type expected{indentExpr type}"
def getLevel (type : Expr) : MetaM Level := do
@ -131,12 +131,12 @@ def getLevel (type : Expr) : MetaM Level := do
| Expr.sort lvl => return lvl
| Expr.mvar mvarId =>
if (← mvarId.isReadOnlyOrSyntheticOpaque) then
throwTypeExcepted type
throwTypeExpected type
else
let lvl ← mkFreshLevelMVar
mvarId.assign (mkSort lvl)
return lvl
| _ => throwTypeExcepted type
| _ => throwTypeExpected type
private def inferForallType (e : Expr) : MetaM Expr :=
forallTelescope e fun xs e => do
@ -151,7 +151,7 @@ private def inferForallType (e : Expr) : MetaM Expr :=
private def inferLambdaType (e : Expr) : MetaM Expr :=
lambdaLetTelescope e fun xs e => do
let type ← inferType e
mkForallFVars xs type
mkForallFVars (generalizeNondepLet := false) xs type
def throwUnknownMVar {α} (mvarId : MVarId) : MetaM α :=
throwError "unknown metavariable '?{mvarId.name}'"

View file

@ -102,7 +102,8 @@ def ppGoal (mvarId : MVarId) : MetaM Format := do
return fmt ++ (Format.joinSep ids.reverse (format " ") ++ " :" ++ Format.nest indent (Format.line ++ typeFmt)).group
let rec ppVars (varNames : List Name) (prevType? : Option Expr) (fmt : Format) (localDecl : LocalDecl) : MetaM (List Name × Option Expr × Format) := do
match localDecl with
| .cdecl _ _ varName type _ _ =>
| .cdecl _ _ varName type ..
| .ldecl _ _ varName type (nondep := true) .. =>
let varName := varName.simpMacroScopes
let type ← instantiateMVars type
if prevType? == none || prevType? == some type then
@ -110,7 +111,7 @@ def ppGoal (mvarId : MVarId) : MetaM Format := do
else do
let fmt ← pushPending varNames prevType? fmt
return ([varName], some type, fmt)
| .ldecl _ _ varName type val _ _ => do
| .ldecl _ _ varName type val (nondep := false) .. => do
let varName := varName.simpMacroScopes
let fmt ← pushPending varNames prevType? fmt
let fmt := addLine fmt

View file

@ -379,13 +379,13 @@ partial def foldAndCollect (oldIH newIH : FVarId) (isRecCall : Expr → Option E
let body' ← foldAndCollect oldIH newIH isRecCall (body.instantiate1 x)
mkForallFVars #[x] body'
| .letE n t v b _ =>
| .letE n t v b nondep =>
let t' ← foldAndCollect oldIH newIH isRecCall t
let v' ← foldAndCollect oldIH newIH isRecCall v
withLetDecl n t' v' fun x => do
M.localMapM (mkLetFVars (usedLetOnly := true) #[x] ·) do
withLetDecl n t' v' (nondep := nondep) fun x => do
M.localMapM (mkLetFVars (usedLetOnly := true) (generalizeNondepLet := false) #[x] ·) do
let b' ← foldAndCollect oldIH newIH isRecCall (b.instantiate1 x)
mkLetFVars #[x] b'
mkLetFVars (generalizeNondepLet := false) #[x] b'
| .mdata m b =>
pure <| .mdata m (← foldAndCollect oldIH newIH isRecCall b)
@ -474,6 +474,11 @@ where
for localDecl in (← getLCtx) do
if localDecl.index > index && (!firstPass || localDecl.userName.hasMacroScopes) then
if localDecl.isLet then
if ← Meta.isProp localDecl.type then
if let some mvarId' ← observing? <| mvarId.clearValue localDecl.fvarId then
return some mvarId'
else
continue
if let some mvarId' ← observing? <| mvarId.clear localDecl.fvarId then
return some mvarId'
if let some mvarId' ← substVar? mvarId localDecl.fvarId then
@ -908,10 +913,10 @@ partial def buildInductionBody (toErase toClear : Array FVarId) (goal : Expr)
if let some (n, t, v, b) := e.letFun? then
let t' ← foldAndCollect oldIH newIH isRecCall t
let v' ← foldAndCollect oldIH newIH isRecCall v
return ← withLocalDeclD n t' fun x => M2.branch do
return ← withLetDecl n t' v' fun x => M2.branch do
let b' ← withRewrittenMotiveArg goal (rwHaveWith x) fun goal' =>
buildInductionBody toErase toClear goal' oldIH newIH isRecCall (b.instantiate1 x)
mkLetFun x v' b'
mkLetFVars #[x] b' (usedLetOnly := false)
-- Special case for traversing the PProded bodies in our encoding of structural mutual recursion
if let .lam n t b bi := e then

View file

@ -31,6 +31,11 @@ namespace Lean.Meta
let val := val.instantiateRevRange j fvars.size fvars
let fvarId ← mkFreshFVarId
let (n, s) ← mkName lctx n true s
/-
We have both dependent and non-dependent `let` expressions result in dependent `ldecl`s.
It is counterintuitive if `have` expressions are introduced with opaque values,
especially when we run transformations to aggressively turn `let`s into `have`s.
-/
let lctx := lctx.mkLetDecl fvarId n type val
let fvar := mkFVar fvarId
let fvars := fvars.push fvar

View file

@ -17,17 +17,18 @@ namespace Lean.Meta
/-!
### `let` extraction
Extracting `let`s means to locate `let`/`letFun`s in a term and to extract them
Extracting `let`s means to locate `let`/`have`s in a term and to extract them
from the term, extending the local context with new declarations in the process.
A related process is lifting `lets`, which means to move `let`/`letFun`s toward the root of a term.
A related process is lifting `lets`, which means to move `let`/`have`s toward the root of a term.
-/
namespace ExtractLets
structure LocalDecl' where
/-- An `ldecl` with `nondep := false`. -/
decl : LocalDecl
/--
If true, is a `let`, if false, is a `letFun`.
If true, is a `let`, if false, is a `have`.
Used in `lift` mode.
-/
isLet : Bool
@ -90,13 +91,13 @@ def isExtractableLet (fvars : List Expr) (n : Name) (t v : Expr) : M (Bool × Na
if let some n ← nextNameForBinderName? n then
return (true, n)
-- In lift mode, we temporarily extract non-extractable lets, but we do not make use of `givenNames` for them.
-- These will be flushed as let/letFun expressions, and we wish to preserve the original binder name.
-- These will be flushed as let/have expressions, and we wish to preserve the original binder name.
if (← read).lift then
return (true, n)
return (false, n)
/--
Adds the `decl` to the `decls` list. Assumes that `decl` is an ldecl.
Adds the `decl` to the `decls` list. Assumes that `decl` is an ldecl with `nondep := false`.
-/
def addDecl (decl : LocalDecl) (isLet : Bool) : M Unit := do
let cfg ← read
@ -140,13 +141,9 @@ This should *not* be used when closing lets for new goal metavariables, since
1. The goal contains the decls in its local context, violating the assumption.
2. We need to use true `let`s in that case, since tactics may zeta-delta reduce these declarations.
-/
def mkLetDecls (decls : Array LocalDecl') (e : Expr) : MetaM Expr := do
withEnsuringDeclsInContext decls do
decls.foldrM (init := e) fun { decl, isLet } e => do
if isLet then
return .letE decl.userName decl.type decl.value (e.abstract #[decl.toExpr]) false
else
mkLetFun decl.toExpr decl.value e
def mkLetDecls (decls : Array LocalDecl') (e : Expr) : Expr :=
decls.foldr (init := e) fun { decl, isLet } e =>
Expr.letE decl.userName decl.type decl.value (e.abstract #[decl.toExpr]) (nondep := !isLet)
/--
Makes sure the declaration for `fvarId` is marked with `isLet := true`.
@ -227,7 +224,7 @@ partial def extractCore (fvars : List Expr) (e : Expr) (topLevel : Bool := false
match e with
| .bvar .. | .fvar .. | .mvar .. | .sort .. | .const .. | .lit .. => unreachable!
| .mdata _ e' => return e.updateMData! (← extractCore fvars e' (topLevel := topLevel))
| .letE n t v b _ => extractLetLike true n t v b (fun t v b => pure <| e.updateLetE! t v b) (topLevel := topLevel)
| .letE n t v b nondep => extractLetLike (!nondep) n t v b (fun t v b => pure <| e.updateLetE! t v b) (topLevel := topLevel)
| .app .. =>
if e.isLetFun then
extractLetFun e (topLevel := topLevel)
@ -244,7 +241,7 @@ where
let b ← extractCore (x :: fvars) (b.instantiate1 x)
if (← read).lift then
let toFlush ← flushDecls x.fvarId!
let b mkLetDecls toFlush b
let b := mkLetDecls toFlush b
return mk t (b.abstract #[x])
else
return mk t (b.abstract #[x])
@ -326,7 +323,7 @@ private def extractLetsImp (es : Array Expr) (givenNames : List Name)
withExistingLocalDecls decls.toList <| k (decls.map (·.fvarId)) es givenNames'
/--
Extracts `let` and `letFun` expressions into local definitions,
Extracts `let` and `have` expressions into local definitions,
evaluating `k` at the post-extracted expressions and the extracted fvarids, within a context containing those local declarations.
- The `givenNames` is a list of explicit names to use for extracted local declarations.
If a name is `_` (or if there is no provided given name and `config.onlyGivenNames` is true) then uses a hygienic name
@ -337,11 +334,11 @@ def extractLets [Monad m] [MonadControlT MetaM m] (es : Array Expr) (givenNames
map3MetaM (fun k => extractLetsImp es givenNames k config) k
/--
Lifts `let` and `letFun` expressions in the given expression as far out as possible.
Lifts `let` and `have` expressions in the given expression as far out as possible.
-/
def liftLets (e : Expr) (config : LiftLetsConfig := {}) : MetaM Expr := do
let (es, st) ← ExtractLets.extract #[e] |>.run { config with onlyGivenNames := true } |>.run' {} |>.run { givenNames := [] }
ExtractLets.mkLetDecls st.decls es[0]!
return ExtractLets.mkLetDecls st.decls es[0]!
end Lean.Meta
@ -349,7 +346,7 @@ private def throwMadeNoProgress (tactic : Name) (mvarId : MVarId) : MetaM α :=
throwTacticEx tactic mvarId m!"made no progress"
/--
Extracts `let` and `letFun` expressions from the target,
Extracts `let` and `have` expressions from the target,
returning `FVarId`s for the extracted let declarations along with the new goal.
- The `givenNames` is a list of explicit names to use for extracted local declarations.
If a name is `_` (or if there is no provided given name and `config.onlyGivenNames` is true) then uses a hygienic name
@ -397,7 +394,7 @@ def Lean.MVarId.extractLetsLocalDecl (mvarId : MVarId) (fvarId : FVarId) (givenN
| _ => throwTacticEx `extract_lets mvarId "unexpected auxiliary target"
/--
Lifts `let` and `letFun` expressions in target as far out as possible.
Lifts `let` and `have` expressions in target as far out as possible.
Throws an exception if nothing is lifted.
Like `Lean.MVarId.extractLets`, but top-level lets are not added to the local context.

View file

@ -235,9 +235,23 @@ inductive SimpLetCase where
| nondep -- `let x := v; b` is equivalent to `(fun x => b) v`, and result type does not depend on `x`
deriving Repr
def getSimpLetCase (n : Name) (t : Expr) (b : Expr) : MetaM SimpLetCase := do
def getSimpLetCase (n : Name) (t : Expr) (b : Expr) (nondep : Bool) : MetaM SimpLetCase := do
withLocalDeclD n t fun x => do
let bx := b.instantiate1 x
/-
If the let has `nondep := true`, then we know the body does not depend on the value already.
Then there are two cases, depending on whether or not the type of the body refers to the variable.
-/
if nondep then
let bxType ← whnf (← inferType bx)
if (← dependsOn bxType x.fvarId!) then
return .nondepDepVar
else
return .nondep
/-
Otherwise, we first detect whether `nondep := true` *should* have been set by checking type correctness of the body.
If that fails, the let is dependent.
-/
/- The following step is potentially very expensive when we have many nested let-decls.
TODO: handle a block of nested let decls in a single pass if this becomes a performance problem. -/
if (← isTypeCorrect bx) then
@ -389,11 +403,13 @@ def simpForall (e : Expr) : SimpM Result := withParent e do
return { expr := (← dsimp e) }
def simpLet (e : Expr) : SimpM Result := do
let .letE n t v b _ := e | unreachable!
let .letE n t v b nondep := e | unreachable!
if (← getConfig).zeta then
return { expr := b.instantiate1 v }
else if !b.hasLooseBVars && (← getConfig).zetaUnused then
return { expr := b.lowerLooseBVars 1 1 }
else
let simpLetCase ← getSimpLetCase n t b
let simpLetCase ← getSimpLetCase n t b nondep
trace[Debug.Meta.Tactic.simp] "getSimpLetCase is {repr simpLetCase}:{indentExpr e}"
match simpLetCase with
| SimpLetCase.dep => return { expr := (← dsimp e) }
@ -405,7 +421,7 @@ def simpLet (e : Expr) : SimpM Result := do
let hb? ← match rbx.proof? with
| none => pure none
| some h => pure (some (← mkLambdaFVars #[x] h))
let e' := mkLet n t rv.expr (← rbx.expr.abstractM #[x])
let e' := mkLet n t rv.expr (← rbx.expr.abstractM #[x]) (nondep := true)
match rv.proof?, hb? with
| none, none => return { expr := e' }
| some h, none => return { expr := e', proof? := some (← mkLetValCongr (← mkLambdaFVars #[x] rbx.expr) h) }
@ -415,7 +431,7 @@ def simpLet (e : Expr) : SimpM Result := do
withLocalDeclD n t fun x => withNewLemmas #[x] do
let bx := b.instantiate1 x
let rbx ← simp bx
let e' := mkLet n t v' (← rbx.expr.abstractM #[x])
let e' := mkLet n t v' (← rbx.expr.abstractM #[x]) (nondep := true)
match rbx.proof? with
| none => return { expr := e' }
| some h =>
@ -721,8 +737,16 @@ def simpApp (e : Expr) : SimpM Result := do
if isOfNatNatLit e || isOfScientificLit e || isCharLit e then
-- Recall that we fold "orphan" kernel Nat literals `n` into `OfNat.ofNat n`
return { expr := e }
else if isNonDepLetFun e then
simpNonDepLetFun e
else if let some (args, n, t, v, b) := e.letFunAppArgs? then
/-
Note: we will be removing `letFun`, and we want everything to be in terms of `nondep := true` lets.
This used to call `simpNonDepLetFun`, which is optimized for letFun telescopes.
Considerations:
- we will soon replace `simpNonDepLetFun` with a `let` version
- simp is now using the `nondep` flag to cache which `let`s are nondependent.
- even without the optimized version we still manage to pass the test suite for now without timing out.
-/
return { expr := mkAppN (Expr.letE n t v b true) args }
else
congr e

View file

@ -113,10 +113,10 @@ partial def transformWithCache {m} [Monad m] [MonadLiftT MetaM m] [MonadControlT
| e => visitPost (← mkForallFVars (usedLetOnly := usedLetOnly) fvars (← visit (e.instantiateRev fvars)))
let rec visitLet (fvars : Array Expr) (e : Expr) : MonadCacheT ExprStructEq Expr m Expr := do
match e with
| .letE n t v b _ =>
withLetDecl n (← visit (t.instantiateRev fvars)) (← visit (v.instantiateRev fvars)) fun x =>
| .letE n t v b nondep =>
withLetDecl n (← visit (t.instantiateRev fvars)) (← visit (v.instantiateRev fvars)) (nondep := nondep) fun x =>
visitLet (fvars.push x) b
| e => visitPost (← mkLetFVars (usedLetOnly := usedLetOnly) fvars (← visit (e.instantiateRev fvars)))
| e => visitPost (← mkLetFVars (usedLetOnly := usedLetOnly) (generalizeNondepLet := false) fvars (← visit (e.instantiateRev fvars)))
let visitApp (e : Expr) : MonadCacheT ExprStructEq Expr m Expr :=
e.withApp fun f args => do
if skipConstInApp && f.isConst then

View file

@ -372,8 +372,7 @@ end
| .fvar fvarId =>
let decl ← fvarId.getDecl
match decl with
| .cdecl .. => return e
| .ldecl (value := v) .. =>
| .ldecl (value := v) (nondep := false) .. =>
-- Let-declarations marked as implementation detail should always be unfolded
-- We initially added this feature for `simp`, and added it here for consistency.
let cfg ← getConfig
@ -383,6 +382,7 @@ end
if (← read).trackZetaDelta then
modify fun s => { s with zetaDeltaFVarIds := s.zetaDeltaFVarIds.insert fvarId }
whnfEasyCases v k
| _ => return e
| .mvar mvarId =>
match (← getExprMVarAssignment? mvarId) with
| some v => whnfEasyCases v k
@ -697,7 +697,7 @@ partial def smartUnfoldingReduce? (e : Expr) : MetaM (Option Expr) :=
where
go (e : Expr) : OptionT MetaM Expr := do
match e with
| .letE n t v b _ => withLetDecl n t (← go v) fun x => do mkLetFVars #[x] (← go (b.instantiate1 x))
| .letE n t v b nondep => mapLetDecl n t (← go v) (nondep := nondep) fun x => go (b.instantiate1 x)
| .lam .. => lambdaTelescope e fun xs b => do mkLambdaFVars xs (← go b)
| .app f a .. => return mkApp (← go f) (← go a)
| .proj _ _ s => return e.updateProj! (← go s)

View file

@ -144,7 +144,7 @@ the requirements imposed by these modules.
process above is recursive. We claim it terminates because we keep
creating new metavariables with smaller local contexts.
- Suppose, we have `t[?m]` and we want to create a let-expression by
- Suppose we have `t[?m]` and we want to create a `let`-expression by
abstracting a let-decl free variable `x`, and the local context of
`?m` contains `x`. Similarly to the previous case
```
@ -153,13 +153,14 @@ the requirements imposed by these modules.
will be ill-formed if we later assign a term `s` to `?m`, and
`s` contains free variable `x`. Again, assume the type of `?m` is `A[x]`.
1. If `?m` is natural or synthetic, then we create `?n : (let x : T := v; A[x])` with
and local context := local context of `?m` - `x`, we assign `?m := ?n`,
and produce the term `let x : T := v; t[?n]`. That is, we are just making
1. If `?m` is natural or synthetic, then we create `?n : (let x : T := v; A[x])` whose
local context is the local context of `?m` minus `x`, we assign `?m := ?n`
(which is correct since the types of `?m` and `?n` both reduce to `A[v]`),
and then produce the term `let x : T := v; t[?n]`. That is, we are just making
sure `?n` must never be assigned to a term containing `x`.
2. If `?m` is syntheticOpaque, we create a fresh syntheticOpaque `?n`
with type `?n : T -> (let x : T := v; A[x])` and local context := local context of `?m` - `x`,
with type `?n : T -> (let x : T := v; A[x])` whose local context is the local context of `?m` minus `x`,
create the delayed assignment `?n #[x] := ?m`, and produce the term `let x : T := v; t[?n x]`.
Now suppose we assign `s` to `?m`. We do not assign the term `fun (x : T) => s` to `?n`, since
@ -170,6 +171,18 @@ the requirements imposed by these modules.
We are essentially using the pair "delayed assignment + application" to implement a delayed
substitution.
- Suppose we have `t[?m]` and we want to create a `have`-expression
by abstracting a *nondependent* let-decl free variable `x`.
This needs a different procedure since `A[x]` does not reduce to `A[v]`.
It is the same as abstracting for lambda expressions, but it produces `have` instead of lambda terms:
1. If `?m` is natural or synthetic, then we create `?n : ∀ (x : T), A[x]` whose
local context is the local context of `?m` minus `x`, and then we assign `?m := ?n x`,
and we produce the term `have x : T := v; t[?n x]`.
2. If `?m` is syntheticOpaque, we create the same `?n` but as syntheticOpaque,
create the delayed assignment `?n #[x] := ?m`, and produce the term `have x : T := v; t[?n x]`.
- We use TC for implementing coercions. Both Joe Hendrix and Reid Barton
reported a nasty limitation. In Lean3, TC will not be used if there are
metavariables in the TC problem. For example, the elaborator will not try
@ -600,10 +613,10 @@ def instantiateLCtxMVars [Monad m] [MonadMCtx m] (lctx : LocalContext) : m Local
| .cdecl _ fvarId userName type bi k =>
let type ← instantiateMVars type
return lctx.mkLocalDecl fvarId userName type bi k
| .ldecl _ fvarId userName type value nonDep k =>
| .ldecl _ fvarId userName type value nondep k =>
let type ← instantiateMVars type
let value ← instantiateMVars value
return lctx.mkLetDecl fvarId userName type value nonDep k
return lctx.mkLetDecl fvarId userName type value nondep k
def instantiateMVarDeclMVars [Monad m] [MonadMCtx m] (mvarId : MVarId) : m Unit := do
let mvarDecl := (← getMCtx).getDecl mvarId
@ -616,8 +629,8 @@ def instantiateLocalDeclMVars [Monad m] [MonadMCtx m] (localDecl : LocalDecl) :
match localDecl with
| .cdecl idx id n type bi k =>
return .cdecl idx id n (← instantiateMVars type) bi k
| .ldecl idx id n type val nonDep k =>
return .ldecl idx id n (← instantiateMVars type) (← instantiateMVars val) nonDep k
| .ldecl idx id n type val nondep k =>
return .ldecl idx id n (← instantiateMVars type) (← instantiateMVars val) nondep k
namespace DependsOn
@ -698,15 +711,21 @@ end DependsOn
return result
/--
Similar to `findExprDependsOn`, but checks the expressions in the given local declaration
depends on a free variable `x` s.t. `pf x` is `true` or an unassigned metavariable `?m` s.t. `pm ?m` is true. -/
@[inline] def findLocalDeclDependsOn [Monad m] [MonadMCtx m] (localDecl : LocalDecl) (pf : FVarId → Bool := fun _ => false) (pm : MVarId → Bool := fun _ => false) : m Bool := do
Similar to `findExprDependsOn`, but checks the expressions in the given local declaration
depends on a free variable `x` s.t. `pf x` is `true` or an unassigned metavariable `?m` s.t. `pm ?m` is true.
- When `generalizeNondepLet := true` (the default), then values of nondependent lets are ignored,
for computing dependencies from "within" a telescope.
-/
@[inline] def findLocalDeclDependsOn [Monad m] [MonadMCtx m] (localDecl : LocalDecl) (pf : FVarId → Bool := fun _ => false) (pm : MVarId → Bool := fun _ => false) (generalizeNondepLet := true) : m Bool := do
match localDecl with
| .cdecl (type := t) .. => findExprDependsOn t pf pm
| .ldecl (type := t) (value := v) .. =>
let (result, { mctx, .. }) := (DependsOn.main pf pm t <||> DependsOn.main pf pm v).run { mctx := (← getMCtx) }
setMCtx mctx
return result
| .ldecl (type := t) (value := v) (nondep := nondep) .. =>
if generalizeNondepLet && nondep then
findExprDependsOn t pf pm
else
let (result, { mctx, .. }) := (DependsOn.main pf pm t <||> DependsOn.main pf pm v).run { mctx := (← getMCtx) }
setMCtx mctx
return result
def exprDependsOn [Monad m] [MonadMCtx m] (e : Expr) (fvarId : FVarId) : m Bool :=
findExprDependsOn e (fvarId == ·)
@ -715,9 +734,13 @@ def exprDependsOn [Monad m] [MonadMCtx m] (e : Expr) (fvarId : FVarId) : m Bool
def dependsOn [Monad m] [MonadMCtx m] (e : Expr) (fvarId : FVarId) : m Bool :=
exprDependsOn e fvarId
/-- Return true iff `localDecl` depends on the free variable `fvarId` -/
def localDeclDependsOn [Monad m] [MonadMCtx m] (localDecl : LocalDecl) (fvarId : FVarId) : m Bool :=
findLocalDeclDependsOn localDecl (fvarId == ·)
/--
Returns true iff `localDecl` depends on the free variable `fvarId`
- When `generalizeNondepLet := true` (the default), then values of nondependent lets are ignored,
for computing dependencies from "within" a telescope.
-/
def localDeclDependsOn [Monad m] [MonadMCtx m] (localDecl : LocalDecl) (fvarId : FVarId) (generalizeNondepLet := true) : m Bool :=
findLocalDeclDependsOn localDecl (fvarId == ·) (generalizeNondepLet := generalizeNondepLet)
/-- Similar to `exprDependsOn`, but `x` can be a free variable or an unassigned metavariable. -/
def exprDependsOn' [Monad m] [MonadMCtx m] (e : Expr) (x : Expr) : m Bool :=
@ -729,11 +752,11 @@ def exprDependsOn' [Monad m] [MonadMCtx m] (e : Expr) (x : Expr) : m Bool :=
return false
/-- Similar to `localDeclDependsOn`, but `x` can be a free variable or an unassigned metavariable. -/
def localDeclDependsOn' [Monad m] [MonadMCtx m] (localDecl : LocalDecl) (x : Expr) : m Bool :=
def localDeclDependsOn' [Monad m] [MonadMCtx m] (localDecl : LocalDecl) (x : Expr) (generalizeNondepLet := true) : m Bool :=
if x.isFVar then
findLocalDeclDependsOn localDecl (x.fvarId! == ·)
findLocalDeclDependsOn localDecl (x.fvarId! == ·) (generalizeNondepLet := generalizeNondepLet)
else if x.isMVar then
findLocalDeclDependsOn localDecl (pm := (x.mvarId! == ·))
findLocalDeclDependsOn localDecl (pm := (x.mvarId! == ·)) (generalizeNondepLet := generalizeNondepLet)
else
return false
@ -742,8 +765,8 @@ def dependsOnPred [Monad m] [MonadMCtx m] (e : Expr) (pf : FVarId → Bool := fu
findExprDependsOn e pf pm
/-- Return true iff the local declaration `localDecl` depends on a free variable `x` s.t. `pf x`, an unassigned metavariable `?m` s.t. `pm ?m` is true. -/
def localDeclDependsOnPred [Monad m] [MonadMCtx m] (localDecl : LocalDecl) (pf : FVarId → Bool := fun _ => false) (pm : MVarId → Bool := fun _ => false) : m Bool := do
findLocalDeclDependsOn localDecl pf pm
def localDeclDependsOnPred [Monad m] [MonadMCtx m] (localDecl : LocalDecl) (pf : FVarId → Bool := fun _ => false) (pm : MVarId → Bool := fun _ => false) (generalizeNondepLet := true) : m Bool := do
findLocalDeclDependsOn localDecl pf pm (generalizeNondepLet := generalizeNondepLet)
namespace MetavarContext
@ -949,6 +972,9 @@ private def getLocalDeclWithSmallestIdx (lctx : LocalContext) (xs : Array Expr)
return a new array of free variables that contains `toRevert` and all variables
in `lctx` that may depend on `toRevert`.
When `generalizeNondepLet := true` (the default), then the values of nondependent lets are not considered
when computing forward dependencies.
Remark: the result is sorted by `LocalDecl` indices.
Remark: We used to throw an `Exception.revertFailure` exception when an auxiliary declaration
@ -969,7 +995,7 @@ private def getLocalDeclWithSmallestIdx (lctx : LocalContext) (xs : Array Expr)
When we try to create the lambda `fun {α : Type} (a : α) => ?m`, we first need to create
an auxiliary `?n` which does not contain `α` and `a` in its context. That is,
we create the metavariable `?n : {α : Type} → (a : α) → (f : α → List α) → List α`,
add the delayed assignment `?n #[α, a, f] := ?m`, and create the lambda
add the delayed assignment `?n #[l, a, f] := ?m`, and create the lambda
`fun {α : Type} (a : α) => ?n α a f`.
See `elimMVarDeps` for more information.
@ -980,7 +1006,7 @@ private def getLocalDeclWithSmallestIdx (lctx : LocalContext) (xs : Array Expr)
Note that <https://github.com/leanprover/lean/issues/1258> is not an issue in Lean4 because
we have changed how we compile recursive definitions.
-/
def collectForwardDeps (lctx : LocalContext) (toRevert : Array Expr) : M (Array Expr) := do
def collectForwardDeps (lctx : LocalContext) (toRevert : Array Expr) (generalizeNondepLet := true) : M (Array Expr) := do
if toRevert.size == 0 then
pure toRevert
else
@ -991,7 +1017,7 @@ def collectForwardDeps (lctx : LocalContext) (toRevert : Array Expr) : M (Array
i.forM fun j _ => do
let prevFVar := toRevert[j]
let prevDecl := lctx.getFVar! prevFVar
if (← localDeclDependsOn prevDecl fvar.fvarId!) then
if (← localDeclDependsOn prevDecl fvar.fvarId! (generalizeNondepLet := generalizeNondepLet)) then
throw (Exception.revertFailure (← getMCtx) lctx toRevert prevDecl.userName.toString)
let newToRevert := if (← preserveOrder) then toRevert else Array.mkEmpty toRevert.size
let firstDeclToVisit := getLocalDeclWithSmallestIdx lctx toRevert
@ -1001,7 +1027,7 @@ def collectForwardDeps (lctx : LocalContext) (toRevert : Array Expr) : M (Array
return newToRevert
else if toRevert.any fun x => decl.fvarId == x.fvarId! then
return newToRevert.push decl.toExpr
else if (← findLocalDeclDependsOn decl (newToRevert.any fun x => x.fvarId! == ·)) then
else if (← findLocalDeclDependsOn decl (newToRevert.any fun x => x.fvarId! == ·) (generalizeNondepLet := generalizeNondepLet)) then
return newToRevert.push decl.toExpr
else
return newToRevert
@ -1076,12 +1102,16 @@ mutual
let type := type.headBeta
let type ← abstractRangeAux xs i type
return Lean.mkForall n bi type e
| LocalDecl.ldecl _ _ n type value nonDep _ =>
| LocalDecl.ldecl (nondep := true) _ _ n type _ _ =>
let type := type.headBeta
let type ← abstractRangeAux xs i type
return Lean.mkForall n .default type e
| LocalDecl.ldecl (nondep := false) _ _ n type value _ =>
if !usedLetOnly || e.hasLooseBVar 0 then
let type := type.headBeta
let type ← abstractRangeAux xs i type
let value ← abstractRangeAux xs i value
let e := mkLet n type value e nonDep
let e := mkLet n type value e false
match kind with
| MetavarKind.syntheticOpaque =>
-- See "Gruesome details" section in the beginning of the file
@ -1233,16 +1263,25 @@ private def mkLambda' (x : Name) (bi : BinderInfo) (t : Expr) (b : Expr) (etaRed
mkLambda x bi t b
/--
Similar to `LocalContext.mkBinding`, but handles metavariables correctly.
If `usedOnly == true` then `forall` and `lambda` expressions are created only for used variables.
If `usedLetOnly == true` then `let` expressions are created only for used (let-) variables. -/
def mkBinding (isLambda : Bool) (lctx : LocalContext) (xs : Array Expr) (e : Expr) (usedOnly : Bool) (usedLetOnly : Bool) (etaReduce : Bool) : M Expr := do
Similar to `LocalContext.mkBinding`, but handles metavariables correctly.
This function trusts that `xs` has all forward dependencies that appear in `e` and that the variables are in order.
- If `usedOnly := true` then `forall` and `lambda` expressions are created only for used variables.
- If `usedLetOnly := true` then `let` expressions are created only for used (let-) variables.
- If `generalizeNondepLet := true` then nondependent let variables become `forall` or `lambda` expressions
according to the value of `usedOnly`.
Generally, `generalizeNondepLet` should be `true` *unless* `mkBinding` is being used when leaving a telescope combinator (like `Meta.lambdaLetTelescope`).
This needs to be `true` when making terms that should remain type correct with respect to the same `lctx`;
for example, if `e' ← mkBinding true lctx xs e (generalizeNondepLet := true)` and `xs' ← xs.filterM (FVarId.isLetVar · false)`,
then one has that `mkAppN e' xs'` is definitionally equal to `e` with respect to `lctx`.
**Note:** `generalizeNondepLet := true` is the common case, so `mkBinding` API uses it as the default.
-/
def mkBinding (isLambda : Bool) (lctx : LocalContext) (xs : Array Expr) (e : Expr) (usedOnly : Bool) (usedLetOnly : Bool) (etaReduce : Bool) (generalizeNondepLet : Bool) : M Expr := do
let e ← abstractRange xs xs.size e
xs.size.foldRevM (init := e) fun i _ e => do
let x := xs[i]
if x.isFVar then
match lctx.getFVar! x with
| LocalDecl.cdecl _ _ n type bi _ =>
let handleCDecl (n : Name) (type : Expr) (bi : BinderInfo) : M Expr := do
if !usedOnly || e.hasLooseBVar 0 then
let type := type.headBeta;
let type ← abstractRange xs i type
@ -1252,11 +1291,16 @@ def mkBinding (isLambda : Bool) (lctx : LocalContext) (xs : Array Expr) (e : Exp
return Lean.mkForall n bi type e
else
return e.lowerLooseBVars 1 1
| LocalDecl.ldecl _ _ n type value nonDep _ =>
if !usedLetOnly || e.hasLooseBVar 0 then
match lctx.getFVar! x with
| LocalDecl.cdecl _ _ n type bi _ =>
handleCDecl n type bi
| LocalDecl.ldecl _ _ n type value nondep _ =>
if generalizeNondepLet && nondep then
handleCDecl n type .default
else if !usedLetOnly || e.hasLooseBVar 0 then
let type ← abstractRange xs i type
let value ← abstractRange xs i value
return mkLet n type value e nonDep
return mkLet n type value e nondep
else
return e.lowerLooseBVars 1 1
else
@ -1283,21 +1327,21 @@ def elimMVarDeps (xs : Array Expr) (e : Expr) (preserveOrder : Bool) : MkBinding
def revert (xs : Array Expr) (mvarId : MVarId) (preserveOrder : Bool) : MkBindingM (Expr × Array Expr) := fun ctx =>
MkBinding.revert xs mvarId { preserveOrder, mainModule := ctx.mainModule }
def mkBinding (isLambda : Bool) (xs : Array Expr) (e : Expr) (usedOnly : Bool := false) (usedLetOnly : Bool := true) (etaReduce := false) (binderInfoForMVars := BinderInfo.implicit) : MkBindingM Expr := fun ctx =>
def mkBinding (isLambda : Bool) (xs : Array Expr) (e : Expr) (usedOnly : Bool := false) (usedLetOnly : Bool := true) (etaReduce := false) (generalizeNondepLet := true) (binderInfoForMVars := BinderInfo.implicit) : MkBindingM Expr := fun ctx =>
let mvarIdsToAbstract := xs.foldl (init := {}) fun s x => if x.isMVar then s.insert x.mvarId! else s
MkBinding.mkBinding isLambda ctx.lctx xs e usedOnly usedLetOnly etaReduce { preserveOrder := false, binderInfoForMVars, mvarIdsToAbstract, mainModule := ctx.mainModule }
MkBinding.mkBinding isLambda ctx.lctx xs e usedOnly usedLetOnly etaReduce generalizeNondepLet { preserveOrder := false, binderInfoForMVars, mvarIdsToAbstract, mainModule := ctx.mainModule }
@[inline] def mkLambda (xs : Array Expr) (e : Expr) (usedOnly : Bool := false) (usedLetOnly : Bool := true) (etaReduce := false) (binderInfoForMVars := BinderInfo.implicit) : MkBindingM Expr :=
mkBinding (isLambda := true) xs e usedOnly usedLetOnly etaReduce binderInfoForMVars
@[inline] def mkLambda (xs : Array Expr) (e : Expr) (usedOnly : Bool := false) (usedLetOnly : Bool := true) (etaReduce := false) (generalizeNondepLet := true) (binderInfoForMVars := BinderInfo.implicit) : MkBindingM Expr :=
mkBinding (isLambda := true) xs e usedOnly usedLetOnly etaReduce generalizeNondepLet binderInfoForMVars
@[inline] def mkForall (xs : Array Expr) (e : Expr) (usedOnly : Bool := false) (usedLetOnly : Bool := true) (binderInfoForMVars := BinderInfo.implicit) : MkBindingM Expr :=
mkBinding (isLambda := false) xs e usedOnly usedLetOnly false binderInfoForMVars
@[inline] def mkForall (xs : Array Expr) (e : Expr) (usedOnly : Bool := false) (usedLetOnly : Bool := true) (generalizeNondepLet := true) (binderInfoForMVars := BinderInfo.implicit) : MkBindingM Expr :=
mkBinding (isLambda := false) xs e usedOnly usedLetOnly false generalizeNondepLet binderInfoForMVars
@[inline] def abstractRange (e : Expr) (n : Nat) (xs : Array Expr) : MkBindingM Expr := fun ctx =>
MkBinding.abstractRange xs n e { preserveOrder := false, mainModule := ctx.mainModule }
@[inline] def collectForwardDeps (toRevert : Array Expr) (preserveOrder : Bool) : MkBindingM (Array Expr) := fun ctx =>
MkBinding.collectForwardDeps ctx.lctx toRevert { preserveOrder, mainModule := ctx.mainModule }
@[inline] def collectForwardDeps (toRevert : Array Expr) (preserveOrder : Bool) (generalizeNondepLet := true) : MkBindingM (Array Expr) := fun ctx =>
MkBinding.collectForwardDeps ctx.lctx toRevert generalizeNondepLet { preserveOrder, mainModule := ctx.mainModule }
/--
`isWellFormed lctx e` returns true iff

View file

@ -61,7 +61,7 @@ variable {α} (ctx : Context α) (builtin : Bool) (force : Bool) in
partial def compileParserExpr (e : Expr) : MetaM Expr := do
let e ← whnfCore e
match e with
| .lam .. => lambdaLetTelescope e fun xs b => compileParserExpr b >>= mkLambdaFVars xs
| .lam .. => mapLambdaLetTelescope e fun _ b => compileParserExpr b
| .fvar .. => return e
| _ => do
let fn := e.getAppFn

View file

@ -1031,7 +1031,7 @@ def delabLetE : Delab := do
let Expr.letE n t v b nondep ← getExpr | unreachable!
let n ← getUnusedName n b
let stxV ← descend v 1 delab
let (stxN, stxB) ← withLetDecl n t v fun fvar => do
let (stxN, stxB) ← withLetDecl n t v (nondep := nondep) fun fvar => do
let b := b.instantiate1 fvar
return (← mkAnnotatedIdent n fvar, ← descend b 2 delab)
if ← getPPOption getPPLetVarTypes <||> getPPOption getPPAnalysisLetVarType then
@ -1305,7 +1305,7 @@ partial def delabDoElems : DelabM (List Syntax) := do
let n ← getUnusedName n b
let stxT ← descend t 0 delab
let stxV ← descend v 1 delab
withLetDecl n t v fun fvar =>
withLetDecl n t v (nondep := nondep) fun fvar =>
let b := b.instantiate1 fvar
descend b 2 $
if nondep then

View file

@ -121,8 +121,8 @@ def withLetValue (x : m α) : m α := do
descend v 1 x
def withLetBody (x : m α) : m α := do
let Expr.letE n t v b _ ← getExpr | unreachable!
Meta.withLetDecl n t v fun fvar =>
let Expr.letE n t v b nondep ← getExpr | unreachable!
Meta.withLetDecl n t v (nondep := nondep) fun fvar =>
let b := b.instantiate1 fvar
descend b 2 x

View file

@ -176,7 +176,8 @@ def goalToInteractive (mvarId : MVarId) : MetaM InteractiveGoal := do
continue
else
match localDecl with
| LocalDecl.cdecl _index fvarId varName type _ _ =>
| LocalDecl.cdecl _index fvarId varName type ..
| LocalDecl.ldecl _index fvarId varName type (nondep := true) .. =>
-- We rely on the fact that `withGoalCtx` runs `LocalContext.sanitizeNames`,
-- so the `userName`s of local hypotheses are already pretty-printed
-- and it suffices to simply `toString` them.
@ -188,7 +189,7 @@ def goalToInteractive (mvarId : MVarId) : MetaM InteractiveGoal := do
hyps ← pushPending varNames prevType? hyps
varNames := #[(varName, fvarId)]
prevType? := some type
| LocalDecl.ldecl _index fvarId varName type val _ _ => do
| LocalDecl.ldecl _index fvarId varName type val (nondep := false) .. => do
let varName := toString varName
hyps ← pushPending varNames prevType? hyps
let type ← instantiateMVars type

View file

@ -1,34 +1,34 @@
import Lean.Elab.Command
/-!
# Tests for `let_fun x := v; b` notation
# Tests for `have x := v; b` notation
-/
/-!
Checks that types can be inferred and that default instances work with `let_fun`.
Checks that types can be inferred and that default instances work with `have`.
-/
#check
let_fun f x := x * 2
let_fun x := 1
let_fun y := x + 1
have f x := x * 2
have x := 1
have y := x + 1
f (y + x)
/-!
Checks that `simp` can do zeta reduction of `let_fun`s
Checks that `simp` can do zeta reduction of `have`s
-/
example (a b : Nat) (h1 : a = 0) (h2 : b = 0) : (let_fun x := a + 1; x + x) > b := by
example (a b : Nat) (h1 : a = 0) (h2 : b = 0) : (have x := a + 1; x + x) > b := by
simp (config := { zeta := false }) [h1]
trace_state
simp (config := { decide := true }) [h2]
/-!
Checks that the underlying encoding for `let_fun` can be overapplied.
This still pretty prints with `let_fun` notation.
Checks that the underlying encoding for `have` can be overapplied.
This still pretty prints with `have` notation.
-/
#check (show Nat → Nat from id) 1
/-!
Checks that zeta reduction still occurs even if the `let_fun` is applied to an argument.
Checks that zeta reduction still occurs even if the `have` is applied to an argument.
-/
example (a b : Nat) (h : a > b) : (show Nat → Nat from id) a > b := by
simp
@ -36,23 +36,23 @@ example (a b : Nat) (h : a > b) : (show Nat → Nat from id) a > b := by
exact h
/-!
Checks that the type of a `let_fun` can depend on the value.
Checks that the type of a `have` can depend on the value.
-/
#check let_fun n := 5
#check have n := 5
(⟨[], Nat.zero_le n⟩ : { as : List Bool // as.length ≤ n })
/-!
Check that `let_fun` is reducible.
Check that `have` is reducible.
-/
#check (rfl : (let_fun n := 5; n) = 5)
#check (rfl : (have n := 5; n) = 5)
/-!
Exercise `isDefEqQuick` for `let_fun`.
Exercise `isDefEqQuick` for `have`.
-/
#check (rfl : (let_fun _n := 5; 2) = 2)
#check (rfl : (have _n := 5; 2) = 2)
/-!
Check that `let_fun` responds to WHNF's `zeta` option.
Check that `have` responds to WHNF's `zeta` option.
-/
open Lean Meta Elab Term in
@ -61,5 +61,5 @@ elab "#whnfCore " z?:(&"noZeta")? t:term : command => Command.runTermElabM fun _
let e ← withConfig (fun c => { c with zeta := z?.isNone }) <| Meta.whnfCore e
logInfo m!"{e}"
#whnfCore let_fun n := 5; n
#whnfCore noZeta let_fun n := 5; n
#whnfCore have n := 5; n
#whnfCore noZeta have n := 5; n

View file

@ -15,7 +15,8 @@ a b : Nat
h : a > b
⊢ b < a
have n := 5;
⟨[], ⋯⟩ : { as // as.length ≤ 5 }
⟨[], ⋯⟩ : have n := 5;
{ as // as.length ≤ n }
rfl : (have n := 5;
n) =
have n := 5;

View file

@ -9,8 +9,8 @@ example (f : Nat → Nat) : f x = 0 → f x + 1 = y := by
sorry
example (f : Nat → Nat) : let _ : f x = 0 := sorryAx _ false; f x + 1 = y := by
simp (config := { contextual := true, zeta := false })
guard_target =ₛ let _ : f x = 0 := sorryAx _ false; 1 = y
simp (config := { contextual := true, zeta := false, zetaUnused := false })
guard_target =ₛ have _ : f x = 0 := sorryAx _ false; 1 = y
sorry
def overlap : Nat → Nat
@ -46,6 +46,6 @@ example : (if p x then g x else g x + 1) + g x = y := by
sorry
example : (let _ : p x := sorryAx _ false; g x + 1 = y) ↔ g x = y := by
simp (config := { zeta := false }) (discharger := assumption)
guard_target =ₛ (let _ : p x := sorryAx _ false; x + 1 = y) ↔ g x = y
simp (config := { zeta := false, zetaUnused := false }) (discharger := assumption)
guard_target =ₛ (have _ : p x := sorryAx _ false; x + 1 = y) ↔ g x = y
sorry

View file

@ -16,6 +16,21 @@ example : let x := 22; 0 ≤ x := by
trace_state
exact Nat.zero_le _
/-!
Check that `clear_value` preserves the order of the local context.
-/
/--
trace: x : Nat
y : Nat := 23
⊢ True
-/
#guard_msgs in
example : let _x := 22; let _y := 23; True := by
intro x y
clear_value x
trace_state
trivial
/-!
Test `*` mode.
-/

View file

@ -109,8 +109,8 @@ def let_tailrec : Nat → Nat
termination_by n => n
/--
info: let_tailrec.induct (motive : Nat → Prop) (case1 : motive 0) (case2 : ∀ (n : Nat), motive n → motive n.succ) (a✝ : Nat) :
motive a✝
info: let_tailrec.induct (motive : Nat → Prop) (case1 : motive 0) (case2 : ∀ (n : Nat), n < n + 1 → motive n → motive n.succ)
(a✝ : Nat) : motive a✝
-/
#guard_msgs in
#check let_tailrec.induct
@ -545,7 +545,7 @@ termination_by xs => xs
/--
info: LetFun.bar.induct.{u_1} {α : Type u_1} (x : α) (motive : List α → Prop) (case1 : motive [])
(case2 : ∀ (_y : α) (ys : List α) (this : Nat), motive ys → motive (_y :: ys)) (a✝ : List α) : motive a✝
(case2 : ∀ (_y : α) (ys : List α), motive ys → motive (_y :: ys)) (a✝ : List α) : motive a✝
-/
#guard_msgs in
#check bar.induct

View file

@ -4,6 +4,8 @@ import Lean
This file exercises the Lean/C++ interface to make sure that the `nondep` field
is successfully part of the data model.
It also tests the metaprogramming API.
-/
open Lean
@ -83,3 +85,22 @@ info: Lean.Expr.letE `n (Lean.Expr.const `Nat []) (Lean.Expr.const `Nat.zero [])
let m ← Meta.mkFreshExprMVar none
m.mvarId!.assign (mkConst ``Unit)
Lean.instantiateMVars (Lean.mkLet `n (mkConst ``Nat) (mkConst ``Nat.zero) m true)
namespace TestLambdaLetTelescope
/-!
Check that `lambdaLetTelescope` consumes `haves`. Also checks that `preserveNondepLet := false` turns `have`s into `let`s.
-/
def c : Nat → Nat → Bool := fun x => have y := 1; fun z => x == y + z
/--
info: #[false, true, false]
#[false, false, false]
-/
#guard_msgs in
open Lean Meta in
run_meta do
let decl ← getConstInfo ``c
lambdaLetTelescope decl.value! fun xs _ => do
IO.println <| ← xs.mapM fun x => return (← x.fvarId!.getDecl).isNondep
lambdaLetTelescope decl.value! (preserveNondepLet := false) fun xs _ => do
IO.println <| ← xs.mapM fun x => return (← x.fvarId!.getDecl).isNondep
end TestLambdaLetTelescope

View file

@ -19,6 +19,27 @@ def Tree.map (f : α → β) (t : Tree α) : Tree β :=
termination_by t
decreasing_by trace_state; cases t; decreasing_tactic
/-!
Checking that the attaches make their way through `let`s.
-/
/--
trace: α : Type u_1
t : Tree α
cs : List (Tree α) := t.cs
t' : Tree α
h✝ : t' ∈ cs
⊢ sizeOf t' < sizeOf t
-/
#guard_msgs(trace) in
def Tree.map' (f : α → β) (t : Tree α) : Tree β :=
have n := 22
let v := t.val
let cs := t.cs
have : n = n := rfl
⟨f v, cs.map (fun t' => t'.map' f)⟩
termination_by t
decreasing_by trace_state; cases t; decreasing_tactic
/--
info: equations:
theorem Tree.map.eq_1.{u_1, u_2} : ∀ {α : Type u_1} {β : Type u_2} (f : α → β) (t : Tree α),

View file

@ -19,5 +19,5 @@ example (h : z = 9) : let x := 5; let y := 4; x + y = z := by
example (h : z = 9) : let x := 5; let y := 4; x + y = z := by
intro x
simp (config := { zetaDelta := true, zeta := false })
guard_target =ₛlet y := 4; 5 + y = z
guard_target =ₛ have y := 4; 5 + y = z
rw [h]

View file

@ -1,6 +1,6 @@
x : Nat
h : f (f x) = x
⊢ (let y := x * x;
⊢ (have y := x * x;
if True then 1 else y + 1) =
1
theorem ex1 : ∀ (x : Nat),
@ -19,7 +19,7 @@ fun x h =>
x z : Nat
h : f (f x) = x
h' : z = x
⊢ (let y := x;
⊢ (have y := x;
y) =
z
theorem ex2 : ∀ (x z : Nat),
@ -37,7 +37,7 @@ x z : Nat
id
p : Prop
h : p
⊢ (let n := 10;
⊢ (have n := 10;
fun x => True) =
fun z => p
theorem ex4 : ∀ (p : Prop),