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.
142 lines
5.9 KiB
Text
142 lines
5.9 KiB
Text
/-
|
||
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
|
||
Released under Apache 2.0 license as described in the file LICENSE.
|
||
Author: Leonardo de Moura
|
||
-/
|
||
prelude
|
||
import Lean.Meta.InferType
|
||
|
||
namespace Lean.Meta
|
||
|
||
register_builtin_option pp.auxDecls : Bool := {
|
||
defValue := false
|
||
group := "pp"
|
||
descr := "display auxiliary declarations used to compile recursive functions"
|
||
}
|
||
|
||
register_builtin_option pp.implementationDetailHyps : Bool := {
|
||
defValue := false
|
||
group := "pp"
|
||
descr := "display implementation detail hypotheses in the local context"
|
||
}
|
||
|
||
register_builtin_option pp.inaccessibleNames : Bool := {
|
||
defValue := true
|
||
group := "pp"
|
||
descr := "display inaccessible declarations in the local context"
|
||
}
|
||
|
||
register_builtin_option pp.showLetValues : Bool := {
|
||
defValue := false
|
||
group := "pp"
|
||
descr := "always display let-declaration values in the info view"
|
||
}
|
||
|
||
register_builtin_option pp.showLetValues.threshold : Nat := {
|
||
defValue := 0
|
||
group := "pp"
|
||
descr := "when `pp.showLetValues` is false, the maximum size of a term allowed before it is replaced by `⋯`"
|
||
}
|
||
|
||
register_builtin_option pp.showLetValues.tactic.threshold : Nat := {
|
||
defValue := 255
|
||
group := "pp"
|
||
descr := "when `pp.showLetValues` is false, the maximum size of a term allowed before it is replaced by `⋯`, for tactic goals"
|
||
}
|
||
|
||
/--
|
||
Given the current values of the options `pp.showLetValues` and `pp.showLetValues.threshold`,
|
||
determines whether the local let declaration's value should be omitted.
|
||
|
||
- `tactic` is whether the goal is for a tactic metavariable.
|
||
In that case, uses the maximum of `pp.showLetValues.tactic.threshold` and `pp.showLetValues.threshold` for the threshold.
|
||
In tactics, we usually want to see let values.
|
||
In contrast, for the "expected type" view we usually do not.
|
||
-/
|
||
def ppGoal.shouldShowLetValue (tactic : Bool) (e : Expr) : MetaM Bool := do
|
||
-- Atomic expressions never get omitted by the following logic, so we can do an early return here.
|
||
if e.isAtomic then
|
||
return true
|
||
let options ← getOptions
|
||
if pp.showLetValues.get options then
|
||
return true
|
||
let threshold := pp.showLetValues.threshold.get options
|
||
let threshold := max threshold (if tactic then pp.showLetValues.tactic.threshold.get options else 0)
|
||
let threshold := min 254 threshold
|
||
return e.approxDepth.toNat ≤ threshold
|
||
|
||
private def addLine (fmt : Format) : Format :=
|
||
if fmt.isNil then fmt else fmt ++ "\n"
|
||
|
||
def getGoalPrefix (mvarDecl : MetavarDecl) : String :=
|
||
if isLHSGoal? mvarDecl.type |>.isSome then
|
||
-- use special prefix for `conv` goals
|
||
"| "
|
||
else
|
||
"⊢ "
|
||
|
||
def ppGoal (mvarId : MVarId) : MetaM Format := do
|
||
match (← getMCtx).findDecl? mvarId with
|
||
| none => return "unknown goal"
|
||
| some mvarDecl =>
|
||
let indent := 2 -- Use option
|
||
let ppAuxDecls := pp.auxDecls.get (← getOptions)
|
||
let ppImplDetailHyps := pp.implementationDetailHyps.get (← getOptions)
|
||
-- Heuristic: synthetic opaque metavariables are only used by tactics,
|
||
-- and tactics should always be creating synthetic opaque metavariables for new goals.
|
||
let tactic := mvarDecl.kind.isSyntheticOpaque
|
||
let lctx := mvarDecl.lctx
|
||
let lctx := lctx.sanitizeNames.run' { options := (← getOptions) }
|
||
withLCtx lctx mvarDecl.localInstances do
|
||
-- The following two `let rec`s are being used to control the generated code size.
|
||
-- Then should be remove after we rewrite the compiler in Lean
|
||
let rec pushPending (ids : List Name) (type? : Option Expr) (fmt : Format) : MetaM Format := do
|
||
if ids.isEmpty then
|
||
return fmt
|
||
else
|
||
let fmt := addLine fmt
|
||
match type? with
|
||
| none => return fmt
|
||
| some type =>
|
||
let typeFmt ← ppExpr type
|
||
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 ..
|
||
| .ldecl _ _ varName type (nondep := true) .. =>
|
||
let varName := varName.simpMacroScopes
|
||
let type ← instantiateMVars type
|
||
if prevType? == none || prevType? == some type then
|
||
return (varName :: varNames, some type, fmt)
|
||
else do
|
||
let fmt ← pushPending varNames prevType? fmt
|
||
return ([varName], some type, fmt)
|
||
| .ldecl _ _ varName type val (nondep := false) .. => do
|
||
let varName := varName.simpMacroScopes
|
||
let fmt ← pushPending varNames prevType? fmt
|
||
let fmt := addLine fmt
|
||
let type ← instantiateMVars type
|
||
let typeFmt ← ppExpr type
|
||
let mut fmtElem := format varName ++ " : " ++ typeFmt
|
||
let val ← instantiateMVars val
|
||
if ← ppGoal.shouldShowLetValue tactic val then
|
||
let valFmt ← ppExpr val
|
||
fmtElem := fmtElem ++ " :=" ++ Format.nest indent (Format.line ++ valFmt)
|
||
else
|
||
fmtElem := fmtElem ++ " := ⋯"
|
||
let fmt := fmt ++ fmtElem.group
|
||
return ([], none, fmt)
|
||
let (varNames, type?, fmt) ← lctx.foldlM (init := ([], none, Format.nil)) fun (varNames, prevType?, fmt) (localDecl : LocalDecl) =>
|
||
if !ppAuxDecls && localDecl.isAuxDecl || !ppImplDetailHyps && localDecl.isImplementationDetail then
|
||
return (varNames, prevType?, fmt)
|
||
else
|
||
ppVars varNames prevType? fmt localDecl
|
||
let fmt ← pushPending varNames type? fmt
|
||
let fmt := addLine fmt
|
||
let typeFmt ← ppExpr (← instantiateMVars mvarDecl.type)
|
||
let fmt := fmt ++ getGoalPrefix mvarDecl ++ Format.nest indent typeFmt
|
||
match mvarDecl.userName with
|
||
| Name.anonymous => return fmt
|
||
| name => return "case " ++ format name.eraseMacroScopes ++ "\n" ++ fmt
|
||
|
||
end Lean.Meta
|