lean4-htt/src/Lean/Elab/Print.lean
Joachim Breitner d975e4302e
feat: fine-grained equational lemmas for non-recursive functions (#4154)
This is part of #3983.

Fine-grained equational lemmas are useful even for non-recursive
functions, so this adds them.

The new option `eqns.nonrecursive` can be set to `false` to have the old
behavior.

### Breaking channge

This is a breaking change: Previously, `rw [Option.map]` would rewrite
`Option.map f o` to `match o with … `. Now this rewrite will fail
because the equational lemmas require constructors here (like they do
for, say, `List.map`).

Remedies:

 * Split on `o` before rewriting.
* Use `rw [Option.map.eq_def]`, which rewrites any (saturated)
application of `Option.map`
* Use `set_option eqns.nonrecursive false` when *defining* the function
in question.

### Interaction with simp

The `simp` tactic so far had a special provision for non-recursive
functions so that `simp [f]` will try to use the equational lemmas, but
will also unfold `f` else, so less breakage here (but maybe performance
improvements with functions with many cases when applied to a
constructor, as the simplifier will no longer unfold to a large
`match`-statement and then collapse it right away).

For projection functions and functions marked `[reducible]`, `simp [f]`
won’t use the equational theorems, and will only use its internal
unfolding machinery.

### Implementation notes

It uses the same `mkEqnTypes` function as for recursive functions, so we
are close to a consistency here. There is still the wrinkle that for
recursive functions we don't split matches without an interesting
recursive call inside. Unifying that is future work.
2024-08-22 13:26:58 +00:00

150 lines
6.9 KiB
Text

/-
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Meta.Eqns
import Lean.Util.CollectAxioms
import Lean.Elab.Command
namespace Lean.Elab.Command
private def throwUnknownId (id : Name) : CommandElabM Unit :=
throwError "unknown identifier '{mkConst id}'"
private def levelParamsToMessageData (levelParams : List Name) : MessageData :=
match levelParams with
| [] => ""
| u::us => Id.run do
let mut m := m!".\{{u}"
for u in us do
m := m ++ ", " ++ toMessageData u
return m ++ "}"
private def mkHeader (kind : String) (id : Name) (levelParams : List Name) (type : Expr) (safety : DefinitionSafety) : CommandElabM MessageData := do
let m : MessageData :=
match (← getReducibilityStatus id) with
| ReducibilityStatus.irreducible => "@[irreducible] "
| ReducibilityStatus.reducible => "@[reducible] "
| ReducibilityStatus.semireducible => ""
let m :=
m ++
match safety with
| DefinitionSafety.unsafe => "unsafe "
| DefinitionSafety.partial => "partial "
| DefinitionSafety.safe => ""
let m := if isProtected (← getEnv) id then m ++ "protected " else m
let (m, id) := match privateToUserName? id with
| some id => (m ++ "private ", id)
| none => (m, id)
let m := m ++ kind ++ " " ++ id ++ levelParamsToMessageData levelParams ++ " : " ++ type
pure m
private def mkHeader' (kind : String) (id : Name) (levelParams : List Name) (type : Expr) (isUnsafe : Bool) : CommandElabM MessageData :=
mkHeader kind id levelParams type (if isUnsafe then DefinitionSafety.unsafe else DefinitionSafety.safe)
private def printDefLike (kind : String) (id : Name) (levelParams : List Name) (type : Expr) (value : Expr) (safety := DefinitionSafety.safe) : CommandElabM Unit := do
let m ← mkHeader kind id levelParams type safety
let m := m ++ " :=" ++ Format.line ++ value
logInfo m
private def printAxiomLike (kind : String) (id : Name) (levelParams : List Name) (type : Expr) (isUnsafe := false) : CommandElabM Unit := do
logInfo (← mkHeader' kind id levelParams type isUnsafe)
private def printQuot (id : Name) (levelParams : List Name) (type : Expr) : CommandElabM Unit := do
printAxiomLike "Quotient primitive" id levelParams type
private def printInduct (id : Name) (levelParams : List Name) (numParams : Nat) (type : Expr)
(ctors : List Name) (isUnsafe : Bool) : CommandElabM Unit := do
let mut m ← mkHeader' "inductive" id levelParams type isUnsafe
m := m ++ Format.line ++ "number of parameters: " ++ toString numParams
m := m ++ Format.line ++ "constructors:"
for ctor in ctors do
let cinfo ← getConstInfo ctor
m := m ++ Format.line ++ ctor ++ " : " ++ cinfo.type
logInfo m
open Meta in
private def printStructure (id : Name) (levelParams : List Name) (numParams : Nat) (type : Expr)
(ctor : Name) (fields : Array Name) (isUnsafe : Bool) (isClass : Bool) : CommandElabM Unit := do
let kind := if isClass then "class" else "structure"
let mut m ← mkHeader' kind id levelParams type isUnsafe
m := m ++ Format.line ++ "number of parameters: " ++ toString numParams
m := m ++ Format.line ++ "constructor:"
let cinfo ← getConstInfo ctor
m := m ++ Format.line ++ ctor ++ " : " ++ cinfo.type
m := m ++ Format.line ++ "fields:" ++ (← doFields)
logInfo m
where
doFields := liftTermElabM do
forallTelescope (← getConstInfo id).type fun params _ =>
withLocalDeclD `self (mkAppN (Expr.const id (levelParams.map .param)) params) fun self => do
let params := params.push self
let mut m : MessageData := ""
for field in fields do
match getProjFnForField? (← getEnv) id field with
| some proj =>
let field : Format := if isPrivateName proj then "private " ++ toString field else toString field
let cinfo ← getConstInfo proj
let ftype ← instantiateForall cinfo.type params
m := m ++ Format.line ++ field ++ " : " ++ ftype
| none => panic! "missing structure field info"
addMessageContext m
private def printIdCore (id : Name) : CommandElabM Unit := do
let env ← getEnv
match env.find? id with
| ConstantInfo.axiomInfo { levelParams := us, type := t, isUnsafe := u, .. } => printAxiomLike "axiom" id us t u
| ConstantInfo.defnInfo { levelParams := us, type := t, value := v, safety := s, .. } => printDefLike "def" id us t v s
| ConstantInfo.thmInfo { levelParams := us, type := t, value := v, .. } => printDefLike "theorem" id us t v
| ConstantInfo.opaqueInfo { levelParams := us, type := t, isUnsafe := u, .. } => printAxiomLike "opaque" id us t u
| ConstantInfo.quotInfo { levelParams := us, type := t, .. } => printQuot id us t
| ConstantInfo.ctorInfo { levelParams := us, type := t, isUnsafe := u, .. } => printAxiomLike "constructor" id us t u
| ConstantInfo.recInfo { levelParams := us, type := t, isUnsafe := u, .. } => printAxiomLike "recursor" id us t u
| ConstantInfo.inductInfo { levelParams := us, numParams, type := t, ctors, isUnsafe := u, .. } =>
match getStructureInfo? env id with
| some { fieldNames, .. } =>
let [ctor] := ctors | panic! "structures have only one constructor"
printStructure id us numParams t ctor fieldNames u (isClass env id)
| none => printInduct id us numParams t ctors u
| none => throwUnknownId id
private def printId (id : Syntax) : CommandElabM Unit := do
addCompletionInfo <| CompletionInfo.id id id.getId (danglingDot := false) {} none
let cs ← liftCoreM <| realizeGlobalConstWithInfos id
cs.forM printIdCore
@[builtin_command_elab «print»] def elabPrint : CommandElab
| `(#print%$tk $id:ident) => withRef tk <| printId id
| `(#print%$tk $s:str) => logInfoAt tk s.getString
| _ => throwError "invalid #print command"
private def printAxiomsOf (constName : Name) : CommandElabM Unit := do
let axioms ← collectAxioms constName
if axioms.isEmpty then
logInfo m!"'{constName}' does not depend on any axioms"
else
logInfo m!"'{constName}' depends on axioms: {axioms.qsort Name.lt |>.toList}"
@[builtin_command_elab «printAxioms»] def elabPrintAxioms : CommandElab
| `(#print%$tk axioms $id) => withRef tk do
let cs ← liftCoreM <| realizeGlobalConstWithInfos id
cs.forM printAxiomsOf
| _ => throwUnsupportedSyntax
private def printEqnsOf (constName : Name) : CommandElabM Unit := do
let some eqns ← liftTermElabM <| Meta.getEqnsFor? constName |
logInfo m!"'{constName}' does not have equations"
let mut m := m!"equations:"
for eq in eqns do
let cinfo ← getConstInfo eq
m := m ++ Format.line ++ (← mkHeader "theorem" eq cinfo.levelParams cinfo.type .safe)
logInfo m
@[builtin_command_elab «printEqns»] def elabPrintEqns : CommandElab := fun stx => do
let id := stx[2]
let cs ← liftCoreM <| realizeGlobalConstWithInfos id
cs.forM printEqnsOf
end Lean.Elab.Command