Unused variable Linter #
This file implements the unused variable linter, which runs automatically on all commands and reports any local variables that are never referred to, using information from the info tree.
It is not immediately obvious but this is a surprisingly expensive check without
some optimizations. The main complication is that it can be difficult to
determine what constitutes a "use" apart from direct references to a variable
that we can easily find in the info tree. For example, we would like this to be
considered a use of x:
def foo (x : Nat) : Nat := by assumption
The final proof term is fun x => x so clearly x was used, but we can't make
use of this because the final proof term is after we have abstracted over the
original fvar for x. Instead, we make sure to store the proof term before
abstraction but after instantiation of mvars in the info tree and retrieve it in
the linter. Using the instantiated term is very important as redoing that step
in the linter can be prohibitively expensive. The downside of special-casing the
definition body in this way is that while it works for parameters, it does not
work for local variables in the body, so we ignore them by default if any tactic
infos are present (linter.unusedVariables.analyzeTactics).
If we do turn on this option and look further into the tactic state, we can see
the fvar show up in the instantiation to the original goal metavariable
?m : Nat := x, but it is not always the case that we can follow metavariable
instantiations to determine what happened after the fact, because tactics might
skip the goal metavariable and instantiate some other metavariable created prior
to it instead. Instead, we use a (much more expensive) overapproximation, which
is just to look through the entire metavariable context looking for occurrences
of x. We use caching to ensure that this is still linear in the size of the
info tree, even though there are many metavariable contexts in all the
intermediate stages of elaboration; these are highly similar and make use of
PersistentHashMap so there is a lot of subterm sharing we can take advantage
of.
The @[unused_variables_ignore_fn] attribute #
Some occurrences of variables are deliberately unused, or at least we don't want to lint on unused variables in these positions. For example:
def foo (x : Nat) : (y : Nat) → Nat := fun _ => x
-- ^ don't lint this unused variable because it is public API
They are generally a syntactic criterion, so we allow adding custom
IgnoreFunctions so that external syntax can also opt in to lint suppression,
like so:
macro (name := foobarKind) "foobar " name:ident : command => `(def foo ($name : Nat) := 0)
foobar n -- linted because n is unused in the macro expansion
@[unused_variables_ignore_fn]
def ignoreFoobar : Lean.Linter.IgnoreFunction := fun _ stack _ => stack.matches [``foobarKind]
foobar n -- not linted
Enables or disables all unused variable linter warnings
Enables or disables unused variable linter warnings in function arguments
Enables or disables unused variable linter warnings in patterns
Enables linting variables defined in tactic blocks, may be expensive for complex proofs
An IgnoreFunction receives:
- a
Syntax.identfor the unused variable - a
Syntax.Stackwith the location of this piece of syntax in the command - The
LinterOptionsset locally to this syntax
and should return true to indicate that the lint should be suppressed,
or false to proceed with linting as usual (other IgnoreFunctions may still
say it is ignored). A variable is only linted if it is unused and no
IgnoreFunction returns true on this syntax.
Equations
Instances For
Interpret an IgnoreFunction from the environment.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Interpret an IgnoreFunction from the environment.
The list of builtin IgnoreFunctions.
Adds a new builtin IgnoreFunction.
This function should only be used from within the Lean package.
Equations
Instances For
An extension which keeps track of registered IgnoreFunctions.
Get the current list of IgnoreFunctions.
Equations
- Lean.Linter.getUnusedVariablesIgnoreFns = do let __do_lift ← Lean.getEnv pure (Lean.Linter.unusedVariablesIgnoreFnsExt.getState __do_lift).snd
Instances For
Collect into a heterogeneous collection of objects with external storage. This uses pointer identity and does not store the objects, so it is important not to store the last pointer to an object in the map, or it can be freed and reused, resulting in incorrect behavior.
Returns true if the object was not already in the set.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Collect into a heterogeneous collection of objects with external storage. This uses pointer identity and does not store the objects, so it is important not to store the last pointer to an object in the map, or it can be freed and reused, resulting in incorrect behavior.
Returns true if the object was not already in the set.
Collects into fvarUses all fvars occurring in the Exprs in assignments.
This implementation respects subterm sharing in both the PersistentHashMap and the Expr
to ensure that pointer-equal subobjects are not visited multiple times, which is important
in practice because these expressions are very frequently highly shared.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Given aliases as a map from an alias to what it aliases, we get the original
term by recursion. This has no cycle detection, so if aliases contains a loop
then this function will recurse infinitely.
Information regarding an FVarId definition.
- userName : Name
The user-provided name of the
FVarId - stx : Syntax
A (usually
.ident) syntax for the defined variable - opts : Options
The options set locally to this part of the syntax (used by
IgnoreFn) The list of all
FVarIds that are considered as being defined at this position. This can have more than one element if multiple variables are declared by the same syntax, such as thehinif h : c then ... else .... We only report an unused variable at this span if all variables inaliasesare unused.
Instances For
The main data structure used to collect information from the info tree regarding unused variables.
- constDecls : Std.HashSet String.Range
The set of all (ranges corresponding to) global definitions that are made in the syntax. For example in
mutual def foo := ... def bar := ... where baz := ... endthis would be the spans forfoo,bar, andbaz. Global definitions are always treated as used. (It would be nice to be able to detect unused global definitions but this requires more information than the linter framework can provide.) - fvarDefs : Std.HashMap String.Range FVarDefinition
The collection of all local declarations, organized by the span of the declaration. We collapse all declarations declared at the same position into a single record using
FVarDefinition.aliases. - fvarUses : Std.HashSet FVarId
The set of
FVarIds that are used directly. These may or may not be aliases. - fvarAliases : Std.HashMap FVarId FVarId
A mapping from alias to original FVarId. We don't guarantee that the value is not itself an alias, but we use
followAliaseswhen adding new elements to try to avoid long chains. - assignments : Array (PersistentHashMap MVarId Expr)
Collection of all
MetavarContexts following the execution of a tactic. We trawl these if needed to find additionalfvarUses.
Instances For
Collect information from the infoTrees into References.
See References for more information about the return value.
Equations
- Lean.Linter.UnusedVariables.collectReferences infoTrees cmdStxRange linterSets = (Lean.Linter.UnusedVariables.collectReferences.go✝ cmdStxRange linterSets infoTrees none).run false
Instances For
Reports unused variable warnings on each command. Use linter.unusedVariables to disable.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Returns true if this is a message produced by the unused variable linter. This is used to give these messages an additional "faded" style in the editor.
Equations
- msg.isUnusedVariableWarning = Lean.MessageData.hasTag (fun (x : Lean.Name) => x == Lean.Linter.linter.unusedVariables.name) msg