src/rules/suspicious/unused.bend
bolt@1.27.0.0 · 260 lines · 28 definitions
1
2
3
4
5
6
7
8
9
10
11import Base
12import bolt@1.27.0.0/src/src.bend as Src
13import bolt@1.27.0.0/src/lazy/lazy.bend as Lazy
14import bolt@1.27.0.0/src/finding.bend as F
15import bolt@1.27.0.0/src/syntax/bind.bend as Bind
16import bolt@1.27.0.0/src/syntax/tree.bend as Tree
17import bolt@1.27.0.0/src/syntax/lex.bend as Lex
18
19
20def lines(ts: List<&2, Lex.Tok>, acc: List<&2, U32>) -> List<&2, U32>:
21 match ts:
22 case Nil{}:
23 acc
24 case Con{Lex.Tok{k, t, l, c}, rest}:
25 l <> lines(rest, acc)
26
27
28def colon.kind(kk: Lex.TokKind) -> Bool:
29 match kk:
30 case Lex.TColon{}:
31 True{}
32 case other:
33 False{}
34
35
36def colon(nn: Tree.Node) -> Bool:
37 match nn:
38 case Tree.Leaf{Lex.Tok{k, _t, _l, _c}}:
39 colon.kind(k)
40 case other:
41 False{}
42
43
44
45def foreign.ends(kids: Tree.Node, last: Bool) -> Bool:
46 match kids:
47 case Tree.NCons{h, t}:
48 foreign.ends(t, colon(h))
49 case other:
50 last
51
52
53def foreign.first(sk: Tree.StmtKind) -> Bool:
54 match sk:
55 case Tree.SImport{}:
56 True{}
57 case other:
58 False{}
59
60
61
62
63def foreign.opens(body: Tree.Node, open: Bool) -> Bool:
64 match body:
65 case Tree.NCons{Tree.Stmt{sk, kids, _b}, rest}:
66 match open:
67 case True{}:
68 foreign.opens(rest, Bool.not(foreign.ends(kids, False{})))
69 case False{}:
70 foreign.first(sk)
71 case other:
72 False{}
73
74
75
76def foreign(root: Tree.Node) -> List<&2, U32>:
77 match root:
78 case Tree.NCons{Tree.Stmt{Tree.SDef{}, +kids, body}, rest}:
79 +more = foreign(rest)
80 +own = lines(Tree.leaves(kids), more)
81 Bool.pick(List<&2, U32>, foreign.opens(body, Bool.not(foreign.ends(kids, False{}))), own, more)
82 case Tree.NCons{other, rest}:
83 foreign(rest)
84 case other:
85 Nil{}
86
87
88def reportable(kk: Bind.BindKind, +note: String, +line: U32, +foreign_lines: List<&2, U32>) -> Bool:
89 match kk:
90 case Bind.KLocal{}:
91 True{}
92 case Bind.KParam{}:
93 Bool.and(Bool.not(String.starts_with(note, "-")), Bool.not(List.contains(~U32, ~U32.is_eq, foreign_lines, line)))
94 case other:
95 False{}
96
97
98def used(uses: List<&2, Bind.Use>, +line: U32, +col: U32) -> Bool:
99 match uses:
100 case Nil{}:
101 False{}
102 case Con{Bind.Use{n, l, c, Bind.TLocal{+tl, +tc}}, rest}:
103 Lazy.or_else(Bool.and(U32.is_eq(tl, line), U32.is_eq(tc, col)), _u => used(rest, line, col))
104 case Con{other, rest}:
105 used(rest, line, col)
106
107
108def what(kk: Bind.BindKind) -> String:
109 match kk:
110 case Bind.KParam{}:
111 "Parameter"
112 case other:
113 "Local"
114
115
116type Cell is Data:
117 Cell{line: U32, col: U32}
118
119
120
121
122
123type Hits is Data:
124 HNone{}
125 HLeaf{cells: List<&2, Cell>}
126 HNode{lo: Hits, hi: Hits}
127
128
129def depth() -> Nat:
130 16n
131
132
133def path(dd: Nat, +key: U32) -> List<&2, Bool>:
134 match dd:
135 case 0n:
136 Nil{}
137 case 1n+p:
138 U32.is_even(key) <> path(p, U32.shr(key))
139
140
141def hits.cells(hh: Hits) -> List<&2, Cell>:
142 match hh:
143 case HNone{}:
144 Nil{}
145 case HLeaf{cells}:
146 cells
147 case HNode{_lo, _hi}:
148 Nil{}
149
150
151def hits.lo(hh: Hits) -> Hits:
152 match hh:
153 case HNone{}:
154 HNone{}
155 case HLeaf{_cells}:
156 HNone{}
157 case HNode{lo, _hi}:
158 lo
159
160
161def hits.hi(hh: Hits) -> Hits:
162 match hh:
163 case HNone{}:
164 HNone{}
165 case HLeaf{_cells}:
166 HNone{}
167 case HNode{_lo, hi}:
168 hi
169
170
171def hits.near(bb: Bool, hh: Hits) -> Hits:
172 match bb:
173 case True{}:
174 hits.lo(hh)
175 case False{}:
176 hits.hi(hh)
177
178
179def hits.join(bb: Bool, sub: Hits, far: Hits) -> Hits:
180 match bb:
181 case True{}:
182 HNode{sub, far}
183 case False{}:
184 HNode{far, sub}
185
186
187def put(pp: List<&2, Bool>, +hh: Hits, +cell: Cell) -> Hits:
188 match pp:
189 case Nil{}:
190 HLeaf{cell <> hits.cells(hh)}
191 case Con{+bb, rest}:
192 hits.join(bb, put(rest, hits.near(bb, hh), cell), hits.near(Bool.not(bb), hh))
193
194
195def hits.any(cs: List<&2, Cell>, +line: U32, +col: U32) -> Bool:
196 match cs:
197 case Nil{}:
198 False{}
199 case Con{Cell{l, c}, rest}:
200 Lazy.or_else(Bool.and(U32.is_eq(l, line), U32.is_eq(c, col)), _u => hits.any(rest, line, col))
201
202
203def find(pp: List<&2, Bool>, hh: Hits, +line: U32, +col: U32) -> Bool:
204 match pp:
205 case Nil{}:
206 hits.any(hits.cells(hh), line, col)
207 case Con{bb, rest}:
208 find(rest, hits.near(bb, hh), line, col)
209
210
211def index.one(tg: Bind.Target, +hh: Hits) -> Hits:
212 match tg:
213 case Bind.TLocal{+tl, tc}:
214 put(path(depth(), tl), hh, Cell{tl, tc})
215 case Bind.TItem{_n}:
216 hh
217 case Bind.TQual{_a, _n}:
218 hh
219 case Bind.TFree{}:
220 hh
221
222
223def index(uses: List<&2, Bind.Use>) -> Hits:
224 match uses:
225 case Nil{}:
226 HNone{}
227 case Con{Bind.Use{_n, _l, _c, tg}, rest}:
228 index.one(tg, index(rest))
229
230
231
232def seen(hh: Hits, +uses: List<&2, Bind.Use>, +line: U32, +col: U32) -> Bool:
233 Lazy.or_else(find(path(depth(), line), hh, line, col), _u => used(uses, line, col))
234
235def check.go(
236 binds: List<&2, Bind.Bind>,
237 +hh: Hits,
238 +uses: List<&2, Bind.Use>,
239 +fl: List<&2, U32>,
240 +path: String
241) -> List<&2, F.Finding>:
242 match binds:
243 case Nil{}:
244 Nil{}
245 case Con{Bind.Bind{+name, +line, +col, +kind, note}, rest}:
246 +more = check.go(rest, hh, uses, fl, path)
247 +shown = reportable(kind, note, line, fl)
248 +hit = Bool.and(shown, Bool.not(String.starts_with(name, "_")))
249 Bool.pick(List<&2, F.Finding>, Lazy.and_then(hit, _u => Bool.not(seen(hh, uses, line, col))),
250 F.Finding{path, line, col, U32.from_nat(String.length(name)), "unused",
251 what(kind) ++ " " ++ name ++ " is never used."} <> more, more)
252
253def check.on(bb: Bind.Bound, fl: List<&2, U32>, path: String) -> List<&2, F.Finding>:
254 Bind.Bound{binds, +uses, scopes} = bb
255 check.go(binds, index(uses), uses, fl, path)
256
257
258def check(ss: Src.Src) -> List<&2, F.Finding>:
259 Src.Src{path, text, toks, tree, bound, items} = ss
260 check.on(bound, foreign(tree), path)