lean4-htt/tests/elab/sym_eta_mwe.lean
Leonardo de Moura 1af697a44b
fix: eta-reduce patterns containing loose pattern variables (#13448)
This PR fixes a regression in `Sym.simp` where rewrite rules whose LHS
contains a lambda over a pattern variable (e.g. `∃ x, a = x`) failed to
match targets with semantically equivalent structure.

`Sym.etaReduceAux` previously refused any eta-reduction whenever the
body had loose bound variables, but patterns produced by stripping outer
foralls always carry such loose bvars. The eta-reduction therefore
skipped patterns while still firing on the target, producing mismatched
discrimination tree keys and no match.

The fix narrows the check to loose bvars in the range `[0, n)` (those
that would actually refer to the peeled binders) and lowers any
remaining loose bvars by `n` so that pattern-variable references stay
consistent in the reduced expression. The discrimination tree now
classifies patterns like `exists_eq_True : (∃ x, a = x) = True` with
their full structure rather than falling back to `.other`.

Includes a regression test (`sym_simp_1.lean`) and Sebastian Graf's MWE
(`sym_eta_mwe.lean`).

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

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 20:49:21 +00:00

55 lines
2.2 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
MWE: `Sym.DiscrTree.getMatch` misses an entry whose `Pattern.match?` succeeds.
We insert patterns for `Spec.forIn_iterM_id` and `Spec.IterM.forIn_map` into a
`Sym.DiscrTree`. Looking up `forIn (IterM.map ...) ...` via `getMatch` returns only
`forIn_iterM_id`, but brute-force `Pattern.match?` also finds `IterM.forIn_map`.
-/
import Lean
import Std
open Lean Meta Sym Std Do
/-- Extract the program from a `@[spec]` theorem `∀ ..., Triple prog P Q`. -/
private def mkProgPattern (declName : Name) : MetaM Pattern := do
let ci ← getConstInfo declName
let expr ← mkExpectedTypeHint (← mkConstWithLevelParams declName) ci.type
Prod.fst <$> (mkPatternFromExprWithKey expr ci.levelParams fun type => do
let type ← whnfR type
let_expr Triple _m _ps _inst _α prog _P _Q := type
| throwError "not a Triple: {indentExpr type}"
return (prog, ())).run' {}
/-- A `forIn` on a mapped iterator — the expression we want to look up. -/
noncomputable def lookupTerm : Id Unit :=
forIn ((#[0].iterM Id).map id) () fun _ _ => pure (.yield ())
/--
info: getMatch: [Std.Do.Spec.forIn_iterM_id, Std.Do.Spec.IterM.forIn_map]
pattern.match?: [Std.Do.Spec.IterM.forIn_map, Std.Do.Spec.forIn_iterM_id]
---
info: GOOD: getMatch and brute-force agree
-/
#guard_msgs in
#eval show MetaM Unit from do
let patMap ← mkProgPattern ``Spec.IterM.forIn_map
let patGeneric ← mkProgPattern ``Spec.forIn_iterM_id
let mut tree : DiscrTree Name := .empty
tree := insertPattern tree patMap ``Spec.IterM.forIn_map
tree := insertPattern tree patGeneric ``Spec.forIn_iterM_id
let e ← instantiateMVars (← getConstInfo ``lookupTerm).value?.get!
let e ← SymM.run (shareCommon e)
let treeResults := Sym.getMatch tree e
let mut bruteResults : Array Name := #[]
for (name, pat) in #[(``Spec.IterM.forIn_map, patMap), (``Spec.forIn_iterM_id, patGeneric)] do
if let some _ ← SymM.run (pat.match? e) then
bruteResults := bruteResults.push name
logInfo m!"getMatch: {treeResults}\npattern.match?: {bruteResults}"
if treeResults.size < bruteResults.size then
logInfo "BUG: getMatch missed entries that pattern.match? finds"
else
logInfo "GOOD: getMatch and brute-force agree"