lean4-htt/tests/lean/run/unusedVarDoMatch.lean
Sebastian Graf cdbed919ec
fix: preserve TermInfo for do-match discriminant variables (#12666)
This PR fixes spurious unused variable warnings for variables used in
non-atomic match discriminants in `do` notation. For example, in `match
Json.parse s >>= fromJson? with`, the variable `s` would be reported as
unused.

The root cause is that `expandNonAtomicDiscrs?` eagerly elaborates the
discriminant via `Term.elabTerm`, which creates TermInfo for variable
references. The result is then passed to `elabDoElem` for further
elaboration. When the match elaboration is postponed (e.g. because the
discriminant type contains an mvar from `fromJson?`), the result is a
postponed synthetic mvar. The `withTermInfoContext'` wrapper in
`elabDoElemFns` checks `isTacticOrPostponedHole?` on this result,
detects a postponed mvar, and replaces the info subtree with a `hole`
node — discarding all the TermInfo that was accumulated during
discriminant elaboration.

The fix applies `mkSaveInfoAnnotation` to the result, which prevents
`isTacticOrPostponedHole?` from recognizing it as a hole. This is the
same mechanism that `elabLetMVar` uses to preserve info trees when the
body is a metavariable.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 23:54:17 +00:00

44 lines
1.3 KiB
Text

module
import Init.Control.Do
import Lean.Data.Json
set_option linter.unusedVariables true
set_option backward.do.legacy false
-- Regression tests: variables used in non-atomic match discriminants in `do`
-- should not trigger unused variable warnings.
open Lean in
def test1 (s? : Option String) : IO (Option Nat) := do
let r ← s?.mapM fun s => do
let s ← pure (s ++ "")
match Json.parse s >>= fromJson? with
| .ok v => pure v
| .error e => throw <| IO.userError s!"bad: {e}"
return r
open Lean in
def test2 (s? : Option String) : IO (Option Nat) := do
let r ← s?.mapM fun s => do
let s ← pure (s ++ "")
match Json.parse s >>= fromJson? with
| .ok v => pure v
| .error _ => pure 0
return r
open Lean in
def test3 (env : IO (Option String)) : IO (Option Nat) := do
let some urlMapStr ← env | return none
match Json.parse urlMapStr >>= fromJson? with
| .ok urlMap => return urlMap
| .error e => throw <| IO.userError s!"invalid JSON: {e}"
open Lean in
def test4 (header? : Option String) : IO (Option Nat) := do
let header ← header?.mapM fun header => do
let header ← if header == "" then pure "default" else pure header
match Json.parse header >>= fromJson? with
| .ok header => pure header
| .error e => throw <| IO.userError s!"failed to parse: {e}"
return header