This PR ensures that `withSetOptionIn` does not modify the infotrees or error on malformed option values, and thus avoids panics in linters that traverse the infotrees with `visitM`. `withSetOptionIn` is only intended to be used in linters, and thus should provide the linter action with the infotrees produced during elaboration without modification. However, `withSetOptionIn` had not only been modifying the infotrees by elaborating the option, but had been producing context-free info nodes in doing so. These caused uses of `visitM` and related functions to panic in typical linters. Likewise, `withSetOptionIn` also should not cause the linter to error if somehow it consumes a malformed option value, but instead should fail silently; the error should (only) be logged during the original elaboration. We give an optional flag `(addInfo := true)` to `Elab.elabSetOption` which controls whether info is added to the infotrees. To clarify that `withSetOptionIn` is used only in linters, we move it into `Lean.Linter.Basic`. To avoid import cycles, we move the current contents of `Lean.Linter.Basic` back to `Lean.Linter.Init`. We also publicly import both `Lean.Linter.Init` and `Lean.Elab.Command` into `Lean.Linter.Basic`, meaning that going forward, linter files need only import `Lean.Linter.Basic` to access the standard linter API. This was brought up on Zulip [here](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/bug.3A.20withSetOptionIn.20creates.20context-free.20info.20nodes/with/556905420), and discussed briefly in office hours. --------- Co-authored-by: Wojciech Rozowski <wojciech@lean-fro.org>
134 lines
3.7 KiB
Text
134 lines
3.7 KiB
Text
import Lean
|
|
|
|
/-!
|
|
# `withSetOptionIn`
|
|
-/
|
|
|
|
open Lean Elab Command
|
|
|
|
section recurse
|
|
|
|
/-!
|
|
This test checks that `withSetOptionIn` recurses into the command syntax (`stx[2]`) in
|
|
`set_option ... in <cmd>`.
|
|
|
|
Prior to #3806, `withSetOptionIn` erroneously recursed into the syntax `in` (`stx[1]`).
|
|
-/
|
|
|
|
/-- Trace `foo` when `set_option trace.debug true`. -/
|
|
elab "#trace_debug_foo" : command => do trace[debug] "foo"
|
|
|
|
/-- Elab `cmd` using `withSetOptionIn`. -/
|
|
elab "#test " cmd:command : command => withSetOptionIn elabCommand cmd
|
|
|
|
/-! ## Controls
|
|
|
|
Ensure that `#trace_debug_foo` works as expected.
|
|
-/
|
|
|
|
#guard_msgs in
|
|
#trace_debug_foo
|
|
|
|
/-- trace: [debug] foo -/
|
|
#guard_msgs(trace) in
|
|
set_option trace.debug true in #trace_debug_foo
|
|
|
|
/-! ## Test
|
|
|
|
Should trace `[debug] foo`, and not log the error "unexpected command 'in'".
|
|
-/
|
|
|
|
/-- trace: [debug] foo -/
|
|
#guard_msgs(trace) in
|
|
#test set_option trace.debug true in #trace_debug_foo
|
|
|
|
end recurse
|
|
|
|
section infotree
|
|
|
|
/-!
|
|
These tests check that `withSetOptionIn` does not modify the infotrees (#11313).
|
|
|
|
Modifying the infotrees in `withSetOptionIn` in linters created context-free info nodes, which
|
|
caused `visitM` and related means of searching the infotrees, such as `collectNodesBottomUp`, to
|
|
panic.
|
|
|
|
We also check that we do not error inside linters due to malformed options.
|
|
-/
|
|
|
|
/-- The total size of an infotree plus `acc`. -/
|
|
partial def sizeAux (acc : Nat) : InfoTree → Nat
|
|
| .node _ ch => ch.foldl (init := acc + 1) sizeAux
|
|
| .context _ t => sizeAux (acc + 1) t
|
|
| .hole _ => acc + 1
|
|
|
|
/-- Check that the infotrees within `withSetOptionIn` all have a context and match the size of the
|
|
infotrees before `withSetOptionIn`. Noisy for confirmation when `pp.all` is true via
|
|
`set_option ... in`. -/
|
|
def checkInfoTrees : Linter where run cmd := do
|
|
-- Get initial infotree size
|
|
let initInfoTreeSize := (← getInfoTrees).foldl (init := 0) sizeAux
|
|
if getPPAll (← getOptions) then throwError "`pp.all` should not be set ambiently for testing."
|
|
withSetOptionIn (fun cmd => do
|
|
if getPPAll (← getOptions) then logInfo m!"Test linter is functioning."
|
|
-- Ensure we're checking something.
|
|
if (← getInfoTrees).isEmpty then throwError "No infotrees."
|
|
-- Ensure every infotree has context
|
|
unless (← getInfoTrees).all (· matches .context ..) do
|
|
logError "Context-free infotree!"
|
|
-- Ensure the number of nodes is the same
|
|
unless initInfoTreeSize = (← getInfoTrees).foldl (init := 0) sizeAux do
|
|
logError "Wrong size!") cmd
|
|
|
|
run_cmd do addLinter checkInfoTrees
|
|
|
|
-- This errored with both "Context-free infotree!" and "Wrong size!" prior to #11313.
|
|
/--
|
|
info: Test linter is functioning.
|
|
-/
|
|
#guard_msgs in
|
|
set_option pp.all true in
|
|
example : True := trivial
|
|
|
|
end infotree
|
|
|
|
section malformedOption
|
|
|
|
/-! A malformed `set_option` should only produce an error at the command level;
|
|
linters should ignore the bad option and not fail. -/
|
|
|
|
-- Control: ensure `linter.indexVariables` is present and uses `withSetOptionIn`
|
|
|
|
set_option linter.indexVariables false
|
|
|
|
/--
|
|
warning: Forbidden variable appearing as an index: use `i`, `j`, or `k`: x
|
|
|
|
Note: This linter can be disabled with `set_option linter.indexVariables false`
|
|
-/
|
|
#guard_msgs in
|
|
set_option linter.indexVariables true in
|
|
example (a : List Bool) (x : Nat) := a[x]?
|
|
|
|
-- No error from `linter.indexVariables` on unknown options
|
|
/--
|
|
error: Unknown option `unknown.option`
|
|
-/
|
|
#guard_msgs in
|
|
set_option unknown.option true in
|
|
example := trivial
|
|
|
|
-- No error from `linter.indexVariables` on bad option values
|
|
/--
|
|
error: set_option value type mismatch: The value
|
|
3
|
|
has type
|
|
Nat
|
|
but the option `linter.all` expects a value of type
|
|
Bool
|
|
-/
|
|
#guard_msgs in
|
|
set_option linter.all 3 in
|
|
example := trivial
|
|
|
|
end malformedOption
|