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".
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. If we 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
An IgnoreFunction receives:
- a
Syntax.identfor the unused variable - a
Syntax.Stackwith the location of this piece of syntax in the command - The
Optionsset 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
Visit a PersistentHashMap.Node, collecting all fvars in it into fvarUses
Visit a PersistentHashMap.Entry, collecting all fvars in it into fvarUses
Visit an Expr, collecting all fvars in it into fvarUses
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 : Lake.Name
The user-provided name of the
FVarId - stx : Lean.Syntax
A (usually
.ident) syntax for the defined variable - opts : Lean.Options
The options set locally to this part of the syntax (used by
IgnoreFn) - aliases : Array Lean.FVarId
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 : Lean.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.) 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 : Lean.HashSet Lean.FVarId
The set of
FVarIds that are used directly. These may or may not be aliases. - fvarAliases : Lean.HashMap Lean.FVarId Lean.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 (Lean.PersistentHashMap Lean.MVarId Lean.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
- One or more equations did not get rendered due to their size.
Instances For
Since declarations attach the declaration info to the declId,
we skip that to get to the .ident if possible.
Equations
- Lean.Linter.UnusedVariables.collectReferences.skipDeclIdIfPresent stx = if stx.isOfKind `Lean.Parser.Command.declId = true then stx[0] else stx
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 : Lake.Name) => x == Lean.Linter.linter.unusedVariables.name) msg