Skip to content

Commit 7438e1b

Browse files
committed
Merge remote-tracking branch 'origin/joachim/simp-underLambda' into joachim/issue5667
2 parents 0d64516 + a11f74d commit 7438e1b

File tree

5 files changed

+178
-4
lines changed

5 files changed

+178
-4
lines changed

src/Init/MetaTypes.lean

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ structure Config where
114114
`let x := v; e` simplifies to `e` when `x` does not occur in `e`.
115115
-/
116116
zetaUnused : Bool := true
117+
/--
118+
When `underLambda` (default: `true`) is `false`, then `simp` will not rewrite under lambdas
119+
(so in particular not in the arms of a `match` expressoin), and not in the cases of an
120+
`if-then-else.`
121+
-/
122+
underLambda : Bool := true
117123
deriving Inhabited, BEq
118124

119125
end DSimp
@@ -224,7 +230,7 @@ structure Config where
224230
-/
225231
zetaDelta : Bool := false
226232
/--
227-
When `index` (default : `true`) is `false`, `simp` will only use the root symbol
233+
When `index` (default: `true`) is `false`, `simp` will only use the root symbol
228234
to find candidate `simp` theorems. It approximates Lean 3 `simp` behavior.
229235
-/
230236
index : Bool := true
@@ -238,6 +244,12 @@ structure Config where
238244
`let x := v; e` simplifies to `e` when `x` does not occur in `e`.
239245
-/
240246
zetaUnused : Bool := true
247+
/--
248+
When `underLambda` (default: `true`) is `false`, then `simp` will not rewrite under lambdas
249+
(so in particular not in the arms of a `match` expressoin), and not in the cases of an
250+
`if-then-else.`
251+
-/
252+
underLambda : Bool := true
241253
deriving Inhabited, BEq
242254

243255
-- Configuration object for `simp_all`

src/Lean/Meta/Tactic/Simp/Main.lean

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,9 @@ def simpProj (e : Expr) : SimpM Result := do
296296
def simpConst (e : Expr) : SimpM Result :=
297297
return { expr := (← reduce e) }
298298

299-
def simpLambda (e : Expr) : SimpM Result :=
299+
def simpLambda (e : Expr) : SimpM Result := do
300+
unless (← getConfig).underLambda do
301+
return { expr := e }
300302
withParent e <| lambdaTelescopeDSimp e fun xs e => withNewLemmas xs do
301303
let r ← simp e
302304
let eNew ← mkLambdaFVars xs r.expr
@@ -456,13 +458,32 @@ Auxiliary `dsimproc` for not visiting `Char` literal subterms.
456458
-/
457459
private def doNotVisitCharLit : DSimproc := doNotVisit isCharLit ``Char.ofNat
458460

461+
/-- The dsimp implementation of `underLambda := false` -/
462+
private def doNotVisitLambda : DSimproc := fun e => do
463+
if e.isLambda then
464+
return .done e
465+
if e.isApp && e.isAppOf ``ite then
466+
-- dsimp is using `Meta.transform`, which we cannot tell to only
467+
-- traverse into some subterms.
468+
-- So recurse into `dsimp` here. Unfortunately sets up a fresh transform cache.
469+
e.withApp fun f args => do
470+
let args' ← args.mapIdxM fun i a =>
471+
if i < 3 || i > 4 then
472+
dsimp a
473+
else
474+
pure a
475+
return .done (mkAppN f args')
476+
else
477+
return .continue
478+
459479
@[export lean_dsimp]
460480
private partial def dsimpImpl (e : Expr) : SimpM Expr := do
461481
let cfg ← getConfig
462482
unless cfg.dsimp do
463483
return e
464484
let m ← getMethods
465485
let pre := m.dpre >> doNotVisitOfNat >> doNotVisitOfScientific >> doNotVisitCharLit
486+
let pre := if cfg.underLambda then pre else doNotVisitLambda >> pre
466487
let post := m.dpost >> dsimpReduce
467488
withInDSimp do
468489
transform (usedLetOnly := cfg.zeta || cfg.zetaUnused) e (pre := pre) (post := post)
@@ -490,7 +511,10 @@ def congrDefault (e : Expr) : SimpM Result := do
490511

491512
/-- Process the given congruence theorem hypothesis. Return true if it made "progress". -/
492513
def processCongrHypothesis (h : Expr) : SimpM Bool := do
514+
trace[Debug.Meta.Tactic.simp.congr] "Processing {h} of type {←inferType h}"
493515
forallTelescopeReducing (← inferType h) fun xs hType => withNewLemmas xs do
516+
unless (← getConfig).underLambda || xs.isEmpty do
517+
return false
494518
let lhs ← instantiateMVars hType.appFn!.appArg!
495519
let r ← simp lhs
496520
let rhs := hType.appArg!
@@ -546,6 +570,7 @@ def trySimpCongrTheorem? (c : SimpCongrTheorem) (e : Expr) : SimpM (Option Resul
546570
let x := xs[i]!
547571
try
548572
if (← processCongrHypothesis x) then
573+
trace[Debug.Meta.Tactic.simp.congr] "modified!"
549574
modified := true
550575
catch _ =>
551576
trace[Meta.Tactic.simp.congr] "processCongrHypothesis {c.theoremName} failed {← inferType x}"

src/Lean/Meta/Tactic/Simp/Types.lean

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -586,11 +586,15 @@ def congrArgs (r : Result) (args : Array Expr) : SimpM Result := do
586586
else
587587
let cfg ← getConfig
588588
let infos := (← getFunInfoNArgs r.expr args.size).paramInfo
589+
let noIteArms := !cfg.underLambda && r.expr.isConstOf ``ite
589590
let mut r := r
590591
let mut i := 0
591592
for arg in args do
592-
if h : i < infos.size then
593-
trace[Debug.Meta.Tactic.simp] "app [{i}] {infos.size} {arg} hasFwdDeps: {infos[i].hasFwdDeps}"
593+
if noIteArms && (i == 3 || i == 4) then
594+
-- Do not traverse then/else arguments when underLambda := false
595+
r ← mkCongrFun r arg
596+
else if h : i < infos.size then
597+
trace[Debug.Meta.Tactic.simp] "app [{i+1}/{infos.size}] {arg} hasFwdDeps: {infos[i].hasFwdDeps}"
594598
let info := infos[i]
595599
if cfg.ground && info.isInstImplicit then
596600
-- We don't visit instance implicit arguments when we are reducing ground terms.
@@ -645,6 +649,7 @@ def tryAutoCongrTheorem? (e : Expr) : SimpM (Option Result) := do
645649
let args := e.getAppArgs
646650
let infos := (← getFunInfoNArgs f args.size).paramInfo
647651
let config ← getConfig
652+
let noIteArms := !config.underLambda && f.isConstOf ``ite
648653
let mut simplified := false
649654
let mut hasProof := false
650655
let mut hasCast := false
@@ -659,6 +664,11 @@ def tryAutoCongrTheorem? (e : Expr) : SimpM (Option Result) := do
659664
argsNew := argsNew.push arg
660665
i := i + 1
661666
continue
667+
if noIteArms && (i = 3 || i = 4) then
668+
-- Do not visit arms of an ite when `underLambda := false`
669+
argsNew := argsNew.push arg
670+
i := i + 1
671+
continue
662672
match kind with
663673
| CongrArgKind.fixed =>
664674
let argNew ← dsimp arg
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import Lean
2+
/-!
3+
Tests for `simp -underlambda`
4+
5+
(Not using #check_simp because it does not support simp arguments)
6+
-/
7+
8+
opaque P : {α : Type} → α → Prop
9+
10+
-- Like sorry, but no warning
11+
axiom abort {α : Prop} : α
12+
macro "abort" : tactic => `(tactic |exact abort)
13+
14+
/-- info: ⊢ P fun x => x -/
15+
#guard_msgs in
16+
example : P (fun (x : Nat) => id x) := by
17+
simp; trace_state; abort
18+
19+
/-- info: ⊢ P fun x => id x -/
20+
#guard_msgs in
21+
example : P (id (fun (x : Nat) => id x)) :=
22+
by simp -underLambda; trace_state; abort
23+
24+
/--
25+
info: o : Option Nat
26+
⊢ P
27+
(match o with
28+
| some val => id true
29+
| none => id false)
30+
-/
31+
#guard_msgs in
32+
example (o : Option Nat) : P (id (match id o with | some _ => id true | none => id false)) := by
33+
simp -underLambda; trace_state; abort
34+
35+
/--
36+
info: b : Bool
37+
⊢ P (if b = true then id 5 else id 6)
38+
-/
39+
#guard_msgs in
40+
example (b : Bool) : P (id (if id b then id 5 else id 6)) := by
41+
simp -underLambda; trace_state; abort
42+
43+
/--
44+
info: b : Bool
45+
⊢ P (if b = true then id 5 else id 6)
46+
-/
47+
#guard_msgs in
48+
example (b : Bool) : P (id (if id b then id 5 else id 6)) := by
49+
dsimp -underLambda; trace_state; abort
50+
51+
/--
52+
info: b : Bool
53+
g : {b : Bool} → b = true → Nat
54+
⊢ P (if h : b = true then id (g h) else id 7)
55+
-/
56+
#guard_msgs in
57+
set_option pp.proofs true in
58+
example (b : Bool) (g : {b : Bool} → b = true → Nat) :
59+
P (id (if h : b then id (g h) else id 7)) := by
60+
simp -underLambda; trace_state; abort
61+
62+
63+
/--
64+
info: xs : List Nat
65+
⊢ P (List.map (fun x => 0 + x) xs)
66+
-/
67+
#guard_msgs in
68+
example (xs : List Nat) :
69+
P (id (xs.map (fun x => 0 + x))) := by
70+
simp -underLambda; trace_state; abort
71+
72+
-- The following rewrite works because
73+
-- List.map_id_fun
74+
-- applies definitionally
75+
76+
/--
77+
info: xs : List Nat
78+
⊢ P (id xs)
79+
-/
80+
#guard_msgs in
81+
example (xs : List Nat) :
82+
P (xs.map (fun x => id x)) := by
83+
simp -underLambda only [List.map_id_fun]; trace_state; abort
84+
85+
86+
attribute [congr] List.map_congr_left
87+
88+
/--
89+
info: xs : List Nat
90+
⊢ P (List.map (fun x => 0 + x) xs)
91+
-/
92+
#guard_msgs in
93+
example (xs : List Nat) :
94+
P (id (xs.map (fun x => 0 + x))) := by
95+
simp -underLambda; trace_state; abort
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/-!
2+
This uses `simp -underLambda` to perform a calculation that involves well-founded
3+
recursion and is thus hard to do with `decide`. It needs the `-underLambda` flag
4+
as otherwise the well-founded function's equation lemmas cause it to loop.
5+
-/
6+
7+
def find (P : Nat → Prop) [DecidablePred P] (i : Nat) (h : ∃ n, i ≤ n ∧ P n) : Nat :=
8+
if hi : P i then
9+
i
10+
else
11+
find P (i+1) (by
12+
obtain ⟨w, hle, h⟩ := h
13+
have : w ≠ i := fun heq => by cases heq; contradiction
14+
exact ⟨w, by omega, h⟩;
15+
)
16+
decreasing_by sorry
17+
18+
-- Without -underLambda, the following example will unroll the `find` equation
19+
-- very very often
20+
21+
/--
22+
error: tactic 'fail' failed
23+
P : Nat → Bool
24+
h0 : ∀ (i : Nat), i < 10 → ¬P i = true
25+
h1 : P 100 = true
26+
⊢ (if hi : P 10 = true then 10 else find (fun n => P n = true) (10 + 1) ⋯) = 100
27+
-/
28+
#guard_msgs in
29+
example (P : Nat → Bool) (h0 : ∀ i, i < 10 → ¬ P i) (h1 : P 100) :
30+
find (fun n => P n) 0100, by omega, h1⟩ = 100 := by
31+
simp -underLambda [find, *]
32+
fail

0 commit comments

Comments
 (0)