Skip to content
This repository was archived by the owner on Jul 24, 2024. It is now read-only.

Commit d3b8622

Browse files
authored
fix(tactic/lint): simp_nf: do not ignore errors (#2266)
This PR fixes some bugs in the `simp_nf` linter. Previously it ignored all errors (from failing tactics). I've changed this so that errors from linters are handled centrally and reported as linter warnings. The `simp_is_conditional` function was also broken. As usual, new linters find new issues: 1. Apparently Lean sometimes throws away simp lemmas. leanprover-community/lean#163 2. Some types define `has_coe` but have an incorrect `has_coe_to_fun`, causing the simplifier to loop `⇑f a = ⇑↑f a = ⇑f a`. See the new library note:
1 parent 654533f commit d3b8622

File tree

13 files changed

+117
-44
lines changed

13 files changed

+117
-44
lines changed

src/algebra/field.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,9 @@ section
234234
variables {β : Type*} [division_ring α] [division_ring β]
235235
variables (f : α → β) [is_ring_hom f] {x y : α}
236236

237-
@[simp] lemma map_ne_zero : f x ≠ 0 ↔ x ≠ 0 := (of f).map_ne_zero
237+
lemma map_ne_zero : f x ≠ 0 ↔ x ≠ 0 := (of f).map_ne_zero
238238

239-
@[simp] lemma map_eq_zero : f x = 0 ↔ x = 0 := (of f).map_eq_zero
239+
lemma map_eq_zero : f x = 0 ↔ x = 0 := (of f).map_eq_zero
240240

241241
lemma map_inv : f x⁻¹ = (f x)⁻¹ := (of f).map_inv
242242

src/category_theory/monoidal/functorial.lean

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,12 @@ class lax_monoidal (F : C → D) [functorial.{v₁ v₂} F] :=
7373

7474
restate_axiom lax_monoidal.μ_natural'
7575
attribute [simp] lax_monoidal.μ_natural
76+
7677
restate_axiom lax_monoidal.left_unitality'
77-
attribute [simp] lax_monoidal.left_unitality
7878
restate_axiom lax_monoidal.right_unitality'
79-
attribute [simp] lax_monoidal.right_unitality
79+
-- The unitality axioms cannot be used as simp lemmas because they require
80+
-- higher-order matching to figure out the `F` and `X` from `F X`.
81+
8082
restate_axiom lax_monoidal.associativity'
8183
attribute [simp] lax_monoidal.associativity
8284

src/data/indicator_function.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ variables [has_zero β] {s t : set α} {f g : α → β} {a : α}
3838
@[reducible]
3939
def indicator (s : set α) (f : α → β) : α → β := λ x, if x ∈ s then f x else 0
4040

41-
@[simp] lemma indicator_apply (s : set α) (f : α → β) (a : α) :
41+
lemma indicator_apply (s : set α) (f : α → β) (a : α) :
4242
indicator s f a = if a ∈ s then f a else 0 := rfl
4343

4444
@[simp] lemma indicator_of_mem (h : a ∈ s) (f : α → β) : indicator s f a = f a := if_pos h

src/data/set/basic.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ not_nonempty_iff_eq_empty.2 rfl
287287
lemma eq_empty_or_nonempty (s : set α) : s = ∅ ∨ s.nonempty :=
288288
classical.by_cases or.inr (λ h, or.inl $ not_nonempty_iff_eq_empty.1 h)
289289

290-
@[simp] theorem ne_empty_iff_nonempty : s ≠ ∅ ↔ s.nonempty :=
290+
theorem ne_empty_iff_nonempty : s ≠ ∅ ↔ s.nonempty :=
291291
(not_congr not_nonempty_iff_eq_empty.symm).trans classical.not_not
292292

293293
theorem subset_eq_empty {s t : set α} (h : t ⊆ s) (e : s = ∅) : t = ∅ :=

src/linear_algebra/basic.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1358,7 +1358,7 @@ variables [module R M] [module R M₂] [module R M₃]
13581358
include R
13591359

13601360
instance : has_coe (M ≃ₗ[R] M₂) (M →ₗ[R] M₂) := ⟨to_linear_map⟩
1361-
1361+
-- see Note [function coercion]
13621362
instance : has_coe_to_fun (M ≃ₗ[R] M₂) := ⟨_, λ f, f.to_fun⟩
13631363

13641364
@[simp] theorem coe_apply (e : M ≃ₗ[R] M₂) (b : M) : (e : M →ₗ[R] M₂) b = e b := rfl

src/logic/basic.lean

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,33 @@ theorem coe_sort_coe_trans
6666
{α β γ} [has_coe α β] [has_coe_t_aux β γ] [has_coe_to_sort γ]
6767
(x : α) : @coe_sort α _ x = @coe_sort β _ x := rfl
6868

69+
/--
70+
Many structures such as bundled morphisms coerce to functions so that you can
71+
transparently apply them to arguments. For example, if `e : α ≃ β` and `a : α`
72+
then you can write `e a` and this is elaborated as `⇑e a`. This type of
73+
coercion is implemented using the `has_coe_to_fun`type class. There is one
74+
important consideration:
75+
76+
If a type coerces to another type which in turn coerces to a function,
77+
then it **must** implement `has_coe_to_fun` directly:
78+
```lean
79+
structure sparkling_equiv (α β) extends α ≃ β
80+
81+
-- if we add a `has_coe` instance,
82+
instance {α β} : has_coe (sparkling_equiv α β) (α ≃ β) :=
83+
⟨sparkling_equiv.to_equiv⟩
84+
85+
-- then a `has_coe_to_fun` instance **must** be added as well:
86+
instance {α β} : has_coe_to_fun (sparkling_equiv α β) :=
87+
⟨λ _, α → β, λ f, f.to_equiv.to_fun⟩
88+
```
89+
90+
(Rationale: if we do not declare the direct coercion, then `⇑e a` is not in
91+
simp-normal form. The lemma `coe_fn_coe_base` will unfold it to `⇑↑e a`. This
92+
often causes loops in the simplifier.)
93+
-/
94+
library_note "function coercion"
95+
6996
@[simp] theorem coe_sort_coe_base
7097
{α β} [has_coe α β] [has_coe_to_sort β]
7198
(x : α) : @coe_sort α _ x = @coe_sort β _ x := rfl

src/order/order_iso.lean

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,10 @@ def to_order_embedding (f : r ≃o s) : r ≼o s :=
206206
⟨f.to_equiv.to_embedding, f.ord⟩
207207

208208
instance : has_coe (r ≃o s) (r ≼o s) := ⟨to_order_embedding⟩
209+
-- see Note [function coercion]
210+
instance : has_coe_to_fun (r ≃o s) := ⟨λ _, α → β, λ f, f⟩
209211

210-
theorem coe_coe_fn (f : r ≃o s) : ((f : r ≼o s) : α → β) = f := rfl
212+
@[simp] lemma coe_coe_fn (f : r ≃o s) : ((f : r ≼o s) : α → β) = f := rfl
211213
@[simp] lemma to_equiv_to_fun (f : r ≃o s) (x : α) : f.to_equiv.to_fun x = f x := rfl
212214

213215
theorem ord' : ∀ (f : r ≃o s) {a b}, r a b ↔ s (f a) (f b)

src/set_theory/ordinal.lean

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,6 @@ def of_iso (f : r ≃o s) : r ≼i s :=
5858
rcases f.2 _ _ h with ⟨a', rfl⟩, exact ⟨a', rfl⟩
5959
end
6060

61-
@[simp] theorem of_iso_apply (f : r ≃o s) (x : α) : (f : r ≼o s) x = f x := rfl
62-
6361
@[simp] theorem refl_apply (x : α) : initial_seg.refl r x = x := rfl
6462

6563
@[simp] theorem trans_apply (f : r ≼i s) (g : s ≼i t) (a : α) : (f.trans g) a = g (f a) := rfl
@@ -195,9 +193,7 @@ def lt_equiv {r : α → α → Prop} {s : β → β → Prop} {t : γ → γ
195193
⟨@order_embedding.trans _ _ _ r s t f g, g f.top,
196194
begin
197195
intro x,
198-
rw [←g.right_inv x],
199-
simp only [order_iso.to_equiv_to_fun, coe_fn_coe_base, order_embedding.trans_apply],
200-
rw [←order_iso.ord'' g, f.down', exists_congr],
196+
rw [← g.right_inv x, order_iso.to_equiv_to_fun, ← order_iso.ord' g, f.down', exists_congr],
201197
intro y, exact ⟨congr_arg g, λ h, g.to_equiv.bijective.1 h⟩
202198
end
203199

src/tactic/core.lean

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,6 +1437,18 @@ meta def finally {β} (tac : tactic α) (finalizer : tactic β) : tactic α :=
14371437
| (result.exception msg p s') := (finalizer >> result.exception msg p) s'
14381438
end
14391439

1440+
/-- `decorate_error add_msg tac` prepends `add_msg` to an exception produced by `tac` -/
1441+
meta def decorate_error (add_msg : string) (tac : tactic α) : tactic α | s :=
1442+
match tac s with
1443+
| result.exception msg p s :=
1444+
let msg (_ : unit) : format := match msg with
1445+
| some msg := add_msg ++ format.line ++ msg ()
1446+
| none := add_msg
1447+
end in
1448+
result.exception msg p s
1449+
| ok := ok
1450+
end
1451+
14401452
/-- Applies tactic `t`. If it succeeds, revert the state, and return the value. If it fails,
14411453
returns the error message. -/
14421454
meta def retrieve_or_report_error {α : Type u} (t : tactic α) : tactic (α ⊕ string) :=

src/tactic/lint.lean

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -532,33 +532,50 @@ end
532532
private meta def simp_lhs (ty : expr): tactic expr :=
533533
prod.fst <$> simp_lhs_rhs ty
534534

535-
/-- `simp_is_conditional ty` returns true iff the simp lemma with type `ty` is conditional. -/
536-
private meta def simp_is_conditional : expr → tactic bool | ty := do
535+
/--
536+
`simp_is_conditional_core ty` returns `none` if `ty` is a conditional simp
537+
lemma, and `some lhs` otherwise.
538+
-/
539+
private meta def simp_is_conditional_core : expr → tactic (option expr) | ty := do
537540
ty ← whnf ty transparency.semireducible,
538541
match ty with
539-
| `(¬ %%lhs) := pure ff
540-
| `(%%lhs = _) := pure ff
541-
| `(%%lhs ↔ _) := pure ff
542-
| (expr.pi n bi a b) :=
543-
if bi ≠ binder_info.inst_implicit ∧ ¬ b.has_var then
544-
pure tt
545-
else do
546-
l ← mk_local' n bi a,
547-
simp_is_conditional (b.instantiate_var l)
548-
| ty := pure ff
542+
| `(¬ %%lhs) := pure lhs
543+
| `(%%lhs = _) := pure lhs
544+
| `(%%lhs ↔ _) := pure lhs
545+
| (expr.pi n bi a b) := do
546+
l ← mk_local' n bi a,
547+
some lhs ← simp_is_conditional_core (b.instantiate_var l) | pure none,
548+
if bi ≠ binder_info.inst_implicit ∧
549+
¬ (lhs.abstract_local l.local_uniq_name).has_var then
550+
pure none
551+
else
552+
pure lhs
553+
| ty := pure ty
549554
end
550555

556+
/--
557+
`simp_is_conditional ty` returns true iff the simp lemma with type `ty` is conditional.
558+
-/
559+
private meta def simp_is_conditional (ty : expr) : tactic bool :=
560+
option.is_none <$> simp_is_conditional_core ty
561+
551562
private meta def heuristic_simp_lemma_extraction (prf : expr) : tactic (list name) :=
552563
prf.list_constant.to_list.mfilter is_simp_lemma
553564

565+
/-- Checks whether two expressions are equal for the simplifier. That is,
566+
they are reducibly-definitional equal, and they have the same head symbol. -/
567+
meta def is_simp_eq (a b : expr) : tactic bool :=
568+
if a.get_app_fn.const_name ≠ b.get_app_fn.const_name then pure ff else
569+
succeeds $ is_def_eq a b transparency.reducible
570+
554571
/-- Reports declarations that are simp lemmas whose left-hand side is not in simp-normal form. -/
555572
meta def simp_nf_linter (timeout := 200000) (d : declaration) : tactic (option string) := do
556573
tt ← is_simp_lemma d.to_name | pure none,
557574
-- Sometimes, a definition is tagged @[simp] to add the equational lemmas to the simp set.
558575
-- In this case, ignore the declaration if it is not a valid simp lemma by itself.
559576
tt ← is_valid_simp_lemma_cnst d.to_name | pure none,
560-
(λ tac, tactic.try_for timeout tac <|> pure (some "timeout")) $ -- last resort
561-
(λ tac : tactic _, tac <|> pure none) $ -- tc resolution depth
577+
[] ← get_eqn_lemmas_for ff d.to_name | pure none,
578+
try_for timeout $
562579
retrieve $ do
563580
reset_instance_cache,
564581
g ← mk_meta_var d.type,
@@ -567,14 +584,16 @@ intros,
567584
(lhs, rhs) ← target >>= simp_lhs_rhs,
568585
sls ← simp_lemmas.mk_default,
569586
let sls' := sls.erase [d.to_name],
570-
-- TODO: should we do something special about rfl-lemmas?
571-
(lhs', prf1) ← simplify sls [] lhs {fail_if_unchanged := ff},
587+
(lhs', prf1) ← decorate_error "simplify fails on left-hand side:" $
588+
simplify sls [] lhs {fail_if_unchanged := ff},
572589
prf1_lems ← heuristic_simp_lemma_extraction prf1,
573590
if d.to_name ∈ prf1_lems then pure none else do
574-
(rhs', prf2) ← simplify sls [] rhs {fail_if_unchanged := ff},
575-
lhs'_eq_rhs' ← succeeds (is_def_eq lhs' rhs' transparency.reducible),
576-
lhs_in_nf ← succeeds (is_def_eq lhs' lhs transparency.reducible),
577-
if lhs'_eq_rhs' ∧ lhs'.get_app_fn.const_name = rhs'.get_app_fn.const_name then do
591+
is_cond ← simp_is_conditional d.type,
592+
(rhs', prf2) ← decorate_error "simplify fails on right-hand side:" $
593+
simplify sls [] rhs {fail_if_unchanged := ff},
594+
lhs'_eq_rhs' ← is_simp_eq lhs' rhs',
595+
lhs_in_nf ← is_simp_eq lhs' lhs,
596+
if lhs'_eq_rhs' then do
578597
used_lemmas ← heuristic_simp_lemma_extraction (prf1 prf2),
579598
pure $ pure $ "simp can prove this:\n"
580599
++ " by simp only " ++ to_string used_lemmas ++ "\n"
@@ -589,6 +608,8 @@ else if ¬ lhs_in_nf then do
589608
++ "to" ++ lhs'.group.indent 2 ++ format.line
590609
++ "using " ++ (to_fmt prf1_lems).group.indent 2 ++ format.line
591610
++ "Try to change the left-hand side to the simplified term!\n"
611+
else if ¬ is_cond ∧ lhs = lhs' then do
612+
pure "Left-hand side does not simplify.\nYou need to debug this yourself using `set_option trace.simplify.rewrite true`"
592613
else
593614
pure none
594615

@@ -705,8 +726,17 @@ checks.mmap $ λ ⟨linter_name, linter⟩, do
705726
let test_decls := if linter.auto_decls then all_decls else non_auto_decls,
706727
results ← test_decls.mfoldl (λ (results : rb_map name string) decl, do
707728
tt ← should_be_linted linter_name decl.to_name | pure results,
708-
some linter_warning ← linter.test decl | pure results,
709-
pure $ results.insert decl.to_name linter_warning) mk_rb_map,
729+
s ← read,
730+
let linter_warning : option string :=
731+
match linter.test decl s with
732+
| result.success w _ := w
733+
| result.exception msg _ _ :=
734+
some $ "LINTER FAILED:\n" ++ msg.elim "(no message)" (λ msg, to_string $ msg ())
735+
end,
736+
match linter_warning with
737+
| some w := pure $ results.insert decl.to_name w
738+
| none := pure results
739+
end) mk_rb_map,
710740
pure (linter_name, linter, results)
711741

712742
/-- Sorts a map with declaration keys as names by line number. -/

0 commit comments

Comments
 (0)