Zulip Chat Archive

Stream: lean4

Topic: Unused definitions


Sky Wilshaw (Sep 03 2023 at 19:50):

Is there a way to determine which definitions/theorems a given theorem depends on (for the purpose of deleting unused ones)?

Bhavik Mehta (Sep 03 2023 at 21:36):

I don't have an answer but I would also find this useful - especially if it's restricted to a particular folder / set of files rather than all of mathlib

Patrick Massot (Sep 03 2023 at 21:41):

There was a tool in mathlib3 but I don't think it got ported. It was https://leanprover-community.github.io/mathlib_docs/tactic/find_unused.html

Sky Wilshaw (Sep 03 2023 at 21:43):

This looks really useful!

Scott Morrison (Sep 03 2023 at 23:18):

This is should be easy to implement on top of src#Expr.getUsedConstants or src#ConstantInfo.getUsedConstants.

A nicer implementation than the mathlib3 approach would just be a command #find_unused in ..., that prints a list of the constants declared in the current file (up to this point) not used in the declarations constructed in the ....

I'd hope that this would actually be a relatively easy learning project for someone wanting to start metaprogramming.

Sky Wilshaw (Sep 03 2023 at 23:36):

Learning metaprogramming has been on my to-do list for a while - if I get time I might try to implement it, but don't let that stop anyone with more knowledge/experience than me.

Kyle Miller (Sep 04 2023 at 00:41):

I got sniped by this because of the breadcrumbs Scott laid out. It seemed like it would be straightforward to do this by starting with the whatsnew code, but there was a gotcha: certain declarations can be artificially used by additional internal declarations that Lean generates when processing.

This reports both unused declarations and declarations that have finally become used by the new declarations.

import Mathlib.Lean.Expr.Basic

open Lean Elab Command Meta

-- Generalized monad for `Lean.Name.isBlackListed`
def Lean.Name.isBlackListed' {m : Type  Type} [Monad m] [MonadEnv m]
    (declName : Name) : m Bool := do
  if declName == ``sorryAx then return true
  if declName matches .str _ "inj" then return true
  if declName matches .str _ "noConfusionType" then return true
  let env  getEnv
  pure $ declName.isInternal'
   || isAuxRecursor env declName
   || isNoConfusion env declName
  <||> isRec declName <||> isMatcher declName

namespace Mathlib.FindUnused

/-- Remove all blacklisted names from the given environment.
This might not be accurate since the blacklist function is a heuristic. -/
def filterNames {m : Type  Type} [Monad m] [MonadEnv m] {α : Type _}
    (env : PHashMap Name α) : m (PHashMap Name α) := do
  let mut env' := env
  for (n, _) in env do
    if  n.isBlackListed' then
      env' := env'.erase n
  return env'

/-- `#find_unused in $command` executes the command and then prints the
pre-existing unused declarations in this module that remain unused. -/
elab "#find_unused " "in" ppLine cmd:command : command => do
  let oldEnv  filterNames ( getEnv).constants.map₂
  try
    elabCommand cmd
  finally
    let newEnv  filterNames ( getEnv).constants.map₂
    let mut usedNames : NameSet := {}
    let mut uniquelyUsedNames : NameSet := {}
    let mut unusedNames : NameSet := {}
    for (_, c) in oldEnv do
      usedNames := usedNames.union c.getUsedConstants
    for (n, c) in newEnv do
      unless oldEnv.contains n do
        for n' in c.getUsedConstants do
          unless usedNames.contains n' do
            uniquelyUsedNames := uniquelyUsedNames.insert n'
          usedNames := usedNames.insert n'
    for (n, _) in oldEnv do
      unless usedNames.contains n do
        unusedNames := unusedNames.insert n
    let pp (names : NameSet) : CommandElabM (List MessageData) := do
      let consts := names.toArray
      let consts  consts.mapM fun n =>
        return ( findDeclarationRanges? n, n, newEnv.find! n)
      let consts := consts.insertionSort fun (d?, _) (d'?, _) =>
        if let (some d, some d') := (d?, d'?) then
          Position.lt d.range.pos d'.range.pos
        else
          false
      let consts  consts.mapM fun (_, n, c) =>
        return m!"{mkConst n (c.levelParams.map .param)}"
      return consts.toList
    let unusedNames'  pp unusedNames
    let uniquelyUsedNames'  pp uniquelyUsedNames
    unless unusedNames'.isEmpty do
      logInfo m!"Declarations that are still unused:{
        indentD <| .joinSep unusedNames' ", "}"
    unless uniquelyUsedNames'.isEmpty do
      logInfo m!"Declarations are only used by this command:{
        indentD <| .joinSep uniquelyUsedNames' ", "}"

Kyle Miller (Sep 04 2023 at 00:41):

def fAux (x : Nat) := x
def f (x : Nat) := fAux x
def g (x : Nat) := x

#find_unused in
def h (x : Nat) := f x

/-
Declarations that are still unused:
  g

Declarations are only used by this command:
  f
-/

Last updated: Dec 20 2023 at 11:08 UTC