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

Commit 2e76f36

Browse files
fpvandoornmergify[bot]
authored andcommitted
feat(tactic/sanity_check): add #sanity_check command (#1318)
* create a file sanity_check Currently it contains a tactic that detects unused arguments in declarations In the future I want to add other cleaning tactics * fix last tactic * update comment * checkpoint * checkpoint * rewrite sanity_check * update sanity_check * move results to appropriate files * move some declarations Some declarations in tactic.core made more sense in meta.expr tactic.core now imports string.defs (which adds very little) add documentation * add entry to docs/tactic.md * fix errors * some extra documentation * add test * add doc to meta.expr
1 parent 397c016 commit 2e76f36

File tree

6 files changed

+353
-60
lines changed

6 files changed

+353
-60
lines changed

docs/tactics.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,3 +1053,9 @@ localized "attribute [simp] le_refl" in le
10531053
### rotate
10541054

10551055
`rotate` moves the first goal to the back. `rotate n` will do this `n` times.
1056+
1057+
### sanity_check
1058+
1059+
The `#sanity_check` command checks for common mistakes in the current file or in all of mathlib, respectively.
1060+
1061+
Currently this will check for unused arguments in declarations and whether a declaration is incorrectly marked as a def/lemma.

src/data/string/defs.lean

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@ list.as_string ∘ f ∘ string.to_list
1111
def split_on (c : char) (s : string) : list string :=
1212
(s.to_list.split_on c).map list.as_string
1313

14+
/-- Tests whether the first string is a prefix of the second string -/
1415
def is_prefix_of (x y : string) : bool :=
1516
x.to_list.is_prefix_of y.to_list
1617

1718
def is_suffix_of (x y : string) : bool :=
1819
x.to_list.is_suffix_of y.to_list
1920

21+
/-- Removes the first `n` elements from the string `s` -/
22+
def popn (s : string) (n : nat) : string :=
23+
(s.mk_iterator.nextn n).next_to_string
24+
2025
end string

src/meta/expr.lean

Lines changed: 140 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,100 @@
22
Copyright (c) 2019 Robert Y. Lewis. All rights reserved.
33
Released under Apache 2.0 license as described in the file LICENSE.
44
Authors: Mario Carneiro, Simon Hudon, Scott Morrison, Keeley Hoek, Robert Y. Lewis
5+
-/
6+
7+
/-!
8+
# Additional operations on expr and related types
9+
10+
This file defines basic operations on the types expr, name, declaration, level, environment.
511
6-
Additional non-tactic operations on expr and related types
12+
This file is mostly for non-tactics. Tactics should generally be placed in `tactic.core`.
13+
14+
## Tags
15+
expr, name, declaration, level, environment, meta, metaprogramming, tactic
716
-/
817

918
namespace name
10-
19+
/-- If `nm` is a simple name (having only one string component) starting with `_`, then `deinternalize_field nm` removes the underscore. Otherwise, it does nothing. -/
1120
meta def deinternalize_field : name → name
12-
| (name.mk_string s name.anonymous) :=
21+
| (mk_string s name.anonymous) :=
1322
let i := s.mk_iterator in
1423
if i.curr = '_' then i.next.next_to_string else s
1524
| n := n
1625

26+
/-- `get_nth_prefix nm n` removes the last `n` components from `nm` -/
1727
meta def get_nth_prefix : name → ℕ → name
1828
| nm 0 := nm
1929
| nm (n + 1) := get_nth_prefix nm.get_prefix n
2030

31+
/-- Auxilliary definition for `pop_nth_prefix` -/
2132
private meta def pop_nth_prefix_aux : name → ℕ → name × ℕ
2233
| anonymous n := (anonymous, 1)
2334
| nm n := let (pfx, height) := pop_nth_prefix_aux nm.get_prefix n in
2435
if height ≤ n then (anonymous, height + 1)
2536
else (nm.update_prefix pfx, height + 1)
2637

27-
-- Pops the top `n` prefixes from the given name.
38+
/-- Pops the top `n` prefixes from the given name. -/
2839
meta def pop_nth_prefix (nm : name) (n : ℕ) : name :=
2940
prod.fst $ pop_nth_prefix_aux nm n
3041

42+
/-- Pop the prefix of a name -/
3143
meta def pop_prefix (n : name) : name :=
3244
pop_nth_prefix n 1
3345

46+
/-- Auxilliary definition for `from_components` -/
3447
private def from_components_aux : name → list string → name
3548
| n [] := n
3649
| n (s :: rest) := from_components_aux (name.mk_string s n) rest
3750

51+
/-- Build a name from components. For example `from_components ["foo","bar"]` becomes
52+
``` `foo.bar``` -/
3853
def from_components : list string → name :=
3954
from_components_aux name.anonymous
4055

41-
-- `name`s can contain numeral pieces, which are not legal names
42-
-- when typed/passed directly to the parser. We turn an arbitrary
43-
-- name into a legal identifier name.
56+
/-- `name`s can contain numeral pieces, which are not legal names
57+
when typed/passed directly to the parser. We turn an arbitrary
58+
name into a legal identifier name by turning the numbers to strings. -/
4459
meta def sanitize_name : name → name
4560
| name.anonymous := name.anonymous
4661
| (name.mk_string s p) := name.mk_string s $ sanitize_name p
4762
| (name.mk_numeral s p) := name.mk_string sformat!"n{s}" $ sanitize_name p
4863

64+
/-- Append a string to the last component of a name -/
4965
def append_suffix : name → string → name
5066
| (mk_string s n) s' := mk_string (s ++ s') n
5167
| n _ := n
5268

69+
/-- The first component of a name, turning a number to a string -/
70+
meta def head : name → string
71+
| (mk_string s anonymous) := s
72+
| (mk_string s p) := head p
73+
| (mk_numeral n p) := head p
74+
| anonymous := "[anonymous]"
75+
76+
/-- Tests whether the first component of a name is `"_private"` -/
77+
meta def is_private (n : name) : bool :=
78+
n.head = "_private"
79+
80+
/-- Get the last component of a name, and convert it to a string. -/
81+
meta def last : name → string
82+
| (mk_string s _) := s
83+
| (mk_numeral n _) := repr n
84+
| anonymous := "[anonymous]"
85+
86+
/-- Returns the number of characters used to print all the string components of a name,
87+
including periods between name segments. Ignores numerical parts of a name. -/
88+
meta def length : name → ℕ
89+
| (mk_string s anonymous) := s.length
90+
| (mk_string s p) := s.length + 1 + p.length
91+
| (mk_numeral n p) := p.length
92+
| anonymous := "[anonymous]".length
93+
5394
end name
5495

5596
namespace level
5697

98+
/-- Tests whether a universe level is non-zero for all assignments of its variables -/
5799
meta def nonzero : level → bool
58100
| (succ _) := tt
59101
| (max l₁ l₂) := l₁.nonzero || l₂.nonzero
@@ -65,48 +107,56 @@ end level
65107
namespace expr
66108
open tactic
67109

110+
/-- Turns an expression into a positive natural number, assuming it is only built up from
111+
`has_one.one`, `bit0` and `bit1`. -/
68112
protected meta def to_pos_nat : expr → option ℕ
69113
| `(has_one.one _) := some 1
70114
| `(bit0 %%e) := bit0 <$> e.to_pos_nat
71115
| `(bit1 %%e) := bit1 <$> e.to_pos_nat
72116
| _ := none
73117

118+
/-- Turns an expression into a natural number, assuming it is only built up from
119+
`has_one.one`, `bit0`, `bit1` and `has_zero.zero`. -/
74120
protected meta def to_nat : expr → option ℕ
75121
| `(has_zero.zero _) := some 0
76122
| e := e.to_pos_nat
77123

124+
/-- Turns an expression into a integer, assuming it is only built up from
125+
`has_one.one`, `bit0`, `bit1`, `has_zero.zero` and a optionally a single `has_neg.neg` as head. -/
78126
protected meta def to_int : expr → option ℤ
79127
| `(has_neg.neg %%e) := do n ← e.to_nat, some (-n)
80128
| e := coe <$> e.to_nat
81129

82-
meta def is_meta_var : expr → bool
130+
/-- Tests whether an expression is a meta-variable. -/
131+
meta def is_mvar : expr → bool
83132
| (mvar _ _ _) := tt
84-
| e := ff
133+
| _ := ff
85134

135+
/-- Tests whether an expression is a sort. -/
86136
meta def is_sort : expr → bool
87137
| (sort _) := tt
88138
| e := ff
89139

140+
/-- Returns a list of all local constants in an expression (without duplicates). -/
90141
meta def list_local_consts (e : expr) : list expr :=
91142
e.fold [] (λ e' _ es, if e'.is_local_constant then insert e' es else es)
92143

144+
/-- Returns a name_set of all constants in an expression. -/
93145
meta def list_constant (e : expr) : name_set :=
94146
e.fold mk_name_set (λ e' _ es, if e'.is_constant then es.insert e'.const_name else es)
95147

148+
/-- Returns a list of all meta-variables in an expression (without duplicates). -/
96149
meta def list_meta_vars (e : expr) : list expr :=
97-
e.fold [] (λ e' _ es, if e'.is_meta_var then insert e' es else es)
150+
e.fold [] (λ e' _ es, if e'.is_mvar then insert e' es else es)
98151

152+
/-- Returns a name_set of all constants in an expression starting with a certain prefix. -/
99153
meta def list_names_with_prefix (pre : name) (e : expr) : name_set :=
100154
e.fold mk_name_set $ λ e' _ l,
101155
match e' with
102156
| expr.const n _ := if n.get_prefix = pre then l.insert n else l
103157
| _ := l
104158
end
105159

106-
meta def is_mvar : expr → bool
107-
| (mvar _ _ _) := tt
108-
| _ := ff
109-
110160
/--
111161
is_num_eq n1 n2 returns true if n1 and n2 are both numerals with the same numeral structure,
112162
ignoring differences in type and type class arguments.
@@ -120,35 +170,57 @@ meta def is_num_eq : expr → expr → bool
120170
| `(%%a/%%a') `(%%b/%%b') := a.is_num_eq b
121171
| _ _ := ff
122172

173+
/-- Simplifies the expression `t` with the specified options.
174+
The result is `(new_e, pr)` with the new expression `new_e` and a proof
175+
`pr : e = new_e`. -/
123176
meta def simp (t : expr)
124177
(cfg : simp_config := {}) (discharger : tactic unit := failed)
125178
(no_defaults := ff) (attr_names : list name := []) (hs : list simp_arg_type := []) :
126179
tactic (expr × expr) :=
127180
do (s, to_unfold) ← mk_simp_set no_defaults attr_names hs,
128181
simplify s to_unfold t cfg `eq discharger
129182

183+
/-- Definitionally simplifies the expression `t` with the specified options.
184+
The result is the simplified expression. -/
130185
meta def dsimp (t : expr)
131186
(cfg : dsimp_config := {})
132187
(no_defaults := ff) (attr_names : list name := []) (hs : list simp_arg_type := []) :
133188
tactic expr :=
134189
do (s, to_unfold) ← mk_simp_set no_defaults attr_names hs,
135190
s.dsimplify to_unfold t cfg
136191

137-
end expr
192+
/-- Auxilliary definition for `expr.pi_arity` -/
193+
meta def pi_arity_aux : ℕ → expr → ℕ
194+
| n (pi _ _ _ b) := pi_arity_aux (n + 1) b
195+
| n e := n
196+
197+
/-- The arity of a pi-type. Does not perform any reduction of the expression.
198+
In one application this was ~30 times quicker than `tactic.get_pi_arity`. -/
199+
meta def pi_arity : expr → ℕ :=
200+
pi_arity_aux 0
138201

202+
end expr
139203

140204
namespace environment
141205

206+
/-- Tests whether a name is declared in the current file. Fixes an error in `in_current_file`
207+
which returns `tt` for the four names `quot, quot.mk, quot.lift, quot.ind` -/
142208
meta def in_current_file' (env : environment) (n : name) : bool :=
143209
env.in_current_file n && (n ∉ [``quot, ``quot.mk, ``quot.lift, ``quot.ind])
144210

211+
/-- Tests whether `n` is an inductive type with one constructor without indices.
212+
If so, returns the number of paramaters and the name of the constructor.
213+
Otherwise, returns `none`. -/
145214
meta def is_structure_like (env : environment) (n : name) : option (nat × name) :=
146215
do guardb (env.is_inductive n),
147216
d ← (env.get n).to_option,
148217
[intro] ← pure (env.constructors_of n) | none,
149218
guard (env.inductive_num_indices n = 0),
150219
some (env.inductive_num_params n, intro)
151220

221+
/-- Tests whether `n` is a structure.
222+
It will first test whether `n` is structure-like and then test that the first projection is
223+
defined in the environment and is a projection. -/
152224
meta def is_structure (env : environment) (n : name) : bool :=
153225
option.is_some $ do
154226
(nparams, intro) ← env.is_structure_like n,
@@ -158,4 +230,57 @@ option.is_some $ do
158230
(some di.type) | none,
159231
env.is_projection (n ++ x.deinternalize_field)
160232

233+
/-- For all declarations `d` where `f d = some x` this adds `x` to the returned list. -/
234+
meta def decl_filter_map {α : Type} (e : environment) (f : declaration → option α) : list α :=
235+
e.fold [] $ λ d l, match f d with
236+
| some r := r :: l
237+
| none := l
238+
end
239+
240+
/-- Maps `f` to all declarations in the environment. -/
241+
meta def decl_map {α : Type} (e : environment) (f : declaration → α) : list α :=
242+
e.decl_filter_map $ λ d, some (f d)
243+
244+
/-- Lists all declarations in the environment -/
245+
meta def get_decls (e : environment) : list declaration :=
246+
e.decl_map id
247+
248+
/-- Lists all trusted (non-meta) declarations in the environment -/
249+
meta def get_trusted_decls (e : environment) : list declaration :=
250+
e.decl_filter_map (λ d, if d.is_trusted then some d else none)
251+
252+
/-- Lists the name of all declarations in the environment -/
253+
meta def get_decl_names (e : environment) : list name :=
254+
e.decl_map declaration.to_name
255+
256+
/-- Fold a monad over all declarations in the environment. -/
257+
meta def mfold {α : Type} {m : TypeType} [monad m] (e : environment) (x : α)
258+
(fn : declaration → α → m α) : m α :=
259+
e.fold (return x) (λ d t, t >>= fn d)
260+
161261
end environment
262+
263+
namespace declaration
264+
265+
open tactic
266+
/-- Checks whether the declaration is declared in the current file.
267+
This is a simple wrapper around `environment.in_current_file'` -/
268+
meta def in_current_file (d : declaration) : tactic bool :=
269+
do e ← get_env, return $ e.in_current_file' d.to_name
270+
271+
/-- Checks whether a declaration is a theorem -/
272+
meta def is_theorem : declaration → bool
273+
| (thm _ _ _ _) := tt
274+
| _ := ff
275+
276+
/-- Checks whether a declaration is a constant -/
277+
meta def is_constant : declaration → bool
278+
| (cnst _ _ _ _) := tt
279+
| _ := ff
280+
281+
/-- Checks whether a declaration is a axiom -/
282+
meta def is_axiom : declaration → bool
283+
| (ax _ _ _) := tt
284+
| _ := ff
285+
286+
end declaration

src/tactic/core.lean

Lines changed: 20 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Copyright (c) 2018 Mario Carneiro. All rights reserved.
33
Released under Apache 2.0 license as described in the file LICENSE.
44
Authors: Mario Carneiro, Simon Hudon, Scott Morrison, Keeley Hoek
55
-/
6-
import data.dlist.basic category.basic meta.expr meta.rb_map tactic.cache
6+
import data.dlist.basic category.basic meta.expr meta.rb_map data.string.defs
77

88
namespace expr
99
open tactic
@@ -83,50 +83,6 @@ meta def emit_code_here : string → lean.parser unit
8383

8484
end lean.parser
8585

86-
namespace name
87-
88-
meta def head : name → string
89-
| (mk_string s anonymous) := s
90-
| (mk_string s p) := head p
91-
| (mk_numeral n p) := head p
92-
| anonymous := "[anonymous]"
93-
94-
meta def is_private (n : name) : bool :=
95-
n.head = "_private"
96-
97-
meta def last : name → string
98-
| (mk_string s _) := s
99-
| (mk_numeral n _) := repr n
100-
| anonymous := "[anonymous]"
101-
102-
meta def length : name → ℕ
103-
| (mk_string s anonymous) := s.length
104-
| (mk_string s p) := s.length + 1 + p.length
105-
| (mk_numeral n p) := p.length
106-
| anonymous := "[anonymous]".length
107-
108-
end name
109-
110-
namespace environment
111-
meta def decl_filter_map {α : Type} (e : environment) (f : declaration → option α) : list α :=
112-
e.fold [] $ λ d l, match f d with
113-
| some r := r :: l
114-
| none := l
115-
end
116-
117-
meta def decl_map {α : Type} (e : environment) (f : declaration → α) : list α :=
118-
e.decl_filter_map $ λ d, some (f d)
119-
120-
meta def get_decls (e : environment) : list declaration :=
121-
e.decl_map id
122-
123-
meta def get_trusted_decls (e : environment) : list declaration :=
124-
e.decl_filter_map (λ d, if d.is_trusted then some d else none)
125-
126-
meta def get_decl_names (e : environment) : list name :=
127-
e.decl_map declaration.to_name
128-
end environment
129-
13086
namespace format
13187

13288
meta def intercalate (x : format) : list format → format :=
@@ -1153,5 +1109,24 @@ meta def trace_macro (_ : parse $ tk "trace!") (s : string) : parser pexpr :=
11531109
do e ← pformat_macro () s,
11541110
pure ``((%%e : pformat) >>= trace)
11551111

1112+
/-- A hackish way to get the `src` directory of mathlib. -/
1113+
meta def get_mathlib_dir : tactic string :=
1114+
do e ← get_env,
1115+
s ← e.decl_olean `tactic.reset_instance_cache,
1116+
return $ s.popn_back 17
1117+
1118+
/-- Checks whether `ml` is a prefix of the file where `n` is declared.
1119+
If you want to run `is_in_mathlib` many times, you should use this tactic instead,
1120+
since it is expensive to execute get_mathlib_dir many times. -/
1121+
meta def is_in_mathlib_aux (ml : string) (n : name) : tactic bool :=
1122+
do e ← get_env, return $ ml.is_prefix_of $ (e.decl_olean n).get_or_else ""
1123+
1124+
/-- Checks whether a declaration with the given name is declared in mathlib.
1125+
If you want to run this tactic many times, you should use `is_in_mathlib_aux` instead,
1126+
since it is expensive to execute get_mathlib_dir many times. -/
1127+
meta def is_in_mathlib (n : name) : tactic bool :=
1128+
do ml ← get_mathlib_dir, is_in_mathlib_aux ml n
1129+
1130+
11561131
end tactic
11571132
open tactic

0 commit comments

Comments
 (0)