src/rules/suspicious/hoist.bend
bolt@1.21.0.0 · 653 lines · 0 definitions
1# rule hoist: a fixed table built inside a def that calls itself, from inputs
2# that do not change, and then indexed. The build runs again on every step.
3# Build it once, outside the recursion. A build is a list or array literal,
4# `List.replicate` / `List.range` / `List.map` / `Array.new` / `Array.map`, or
5# a call to a def of this file whose body is itself one fixed table of more
6# than eight cells; a def whose body cannot be seen (another module's) is not
7# a build. A table of eight cells or fewer is left alone, and so is a build
8# whose arguments depend on the step. A name that is not a carried parameter
9# (a pattern binder, a value computed on the step) keeps the build; a case arm
10# that does not recurse is cold; laws and proofs do not run (a def with no
11# type at all fills a law: it is a proof). `List.get` itself stays with
12# `index`.
13import Base
14import bolt@1.21.0.0/src/src.bend as Src
15import bolt@1.21.0.0/src/finding.bend as F
16import bolt@1.21.0.0/src/syntax/lex.bend as Lex
17import bolt@1.21.0.0/src/syntax/tree.bend as Tree
18import bolt@1.21.0.0/src/rules/calls.bend as Calls
19import bolt@1.21.0.0/src/lazy/lazy.bend as Lazy
20import bolt@1.21.0.0/src/rules/suspicious/table.bend as Table
21
22# where an accessor reads its collection, when it is one
23type Slot is Data:
24 Slot{on: Bool, at: Nat}
25
26# a `name = expr` whose left side is one plain name
27type Got is Data:
28 Got{nm: String, expr: Tree.Node}
29
30# which argument a get or set indexes
31def slot_pick(+list: Bool, +str: Bool, +arr: Bool) -> Slot:
32 match list str arr:
33 case True{} b c:
34 Slot{True{}, 2n}
35 case False{} True{} c:
36 Slot{True{}, 0n}
37 case False{} False{} True{}:
38 Slot{True{}, 1n}
39 case False{} False{} False{}:
40 Slot{False{}, 0n}
41
42# List.get / List.set read argument 2, String.get argument 0, Array.get / Array.set argument 1
43def slot_of(+tt: String) -> Slot:
44 slot_pick(
45 Bool.or(String.eq(tt, "List.get"), String.eq(tt, "List.set")),
46 String.eq(tt, "String.get"),
47 Bool.or(String.eq(tt, "Array.get"), String.eq(tt, "Array.set")))
48
49# the self-calls' argument lists
50def pushes(nn: Tree.Node, +self: String) -> List<&2, List<&2, Tree.Node>>:
51 match nn:
52 case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}:
53 +me = Bool.and(String.eq(o, "("), String.eq(t, self))
54 +more = List.concat(&2, List<&2, Tree.Node>, [pushes(kids, self), pushes(rest, self)])
55 Bool.pick(List<&2, List<&2, Tree.Node>>, me, Calls.args(kids) <> more, more)
56 case Tree.NCons{Tree.Group{open, +kids, close}, rest}:
57 List.concat(&2, List<&2, Tree.Node>, [pushes(kids, self), pushes(rest, self)])
58 case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}:
59 List.concat(&2, List<&2, Tree.Node>, [pushes(kids, self), pushes(body, self), pushes(rest, self)])
60 case Tree.NCons{h, rest}:
61 pushes(rest, self)
62 case other:
63 Nil{}
64
65# is the chain exactly this name?
66def lone_eq(nn: Tree.Node, +name: String) -> Bool:
67 match nn:
68 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, Tree.NNil{}}:
69 String.eq(t, name)
70 case other:
71 False{}
72
73# every self-call passes this same name at this argument
74def all_same(cs: List<&2, List<&2, Tree.Node>>, +ii: Nat, +name: String) -> Bool:
75 match cs:
76 case Nil{}:
77 True{}
78 case Con{as, rest}:
79 +here = lone_eq(Calls.arg(as, ii), name)
80 +more = all_same(rest, ii, name)
81 Bool.and(here, more)
82
83# parameters every self-call passes unchanged, when there is a self-call
84def carried.go(
85 ps: List<&2, Tree.Node>,
86 +cs: List<&2, List<&2, Tree.Node>>,
87 +ii: Nat,
88 +acc: List<&2, String>
89) -> List<&2, String>:
90 match ps:
91 case Nil{}:
92 List.reverse(&2, String, acc)
93 case Con{p, rest}:
94 +nm = Calls.param_name(p)
95 +keep = Bool.and(Bool.not(String.is_empty(nm)), all_same(cs, ii, nm))
96 carried.go(rest, cs, (ii + 1n : Nat), Bool.pick(List<&2, String>, keep, nm <> acc, acc))
97
98# parameters passed through unchanged; empty when nothing recurses
99def carried_of(cs: List<&2, List<&2, Tree.Node>>, +sig: Tree.Node) -> List<&2, String>:
100 match cs:
101 case Nil{}:
102 Nil{}
103 case Con{h, t}:
104 carried.go(Calls.params(sig), h <> t, 0n, [])
105
106# parameters passed through unchanged; empty when nothing recurses
107def carried(sig: Tree.Node, body: Tree.Node, +self: String) -> List<&2, String>:
108 carried_of(pushes(body, self), sig)
109
110# a plain name is closed only when it is a carried parameter; a type or a dotted name is
111def name_ok(kk: Lex.TokKind, +tt: String, +carried: List<&2, String>) -> Bool:
112 match kk:
113 case Lex.TName{}:
114 List.contains(~String, ~String.eq, carried, tt)
115 case other:
116 True{}
117
118# a token after an operator: a template argument is not a value, anything else is
119def leaf_ok(+skip: Bool, tok: Lex.Tok, +carried: List<&2, String>) -> Bool:
120 match skip:
121 case True{}:
122 True{}
123 case False{}:
124 Lex.Tok{+k, +t, l, c} = tok
125 name_ok(k, t, carried)
126
127# an application is closed in its arguments; a bracketed name is a value
128def value_ok(+app: Bool, kk: Lex.TokKind, +tt: String, +carried: List<&2, String>) -> Bool:
129 match app:
130 case True{}:
131 True{}
132 case False{}:
133 name_ok(kk, tt, carried)
134
135# every value name under the node is a carried parameter (callees and templates are not values)
136def closed(nn: Tree.Node, +carried: List<&2, String>) -> Bool:
137 match nn:
138 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, +op, l, c}}, Tree.NCons{Tree.Leaf{+tok}, tail}}:
139 +ok = leaf_ok(String.eq(op, "~"), tok, carried)
140 +aft = closed(tail, carried)
141 Bool.and(ok, aft)
142 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, op, l, c}}, rest}:
143 closed(rest, carried)
144 case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}:
145 +ok = value_ok(String.eq(o, "("), k, t, carried)
146 +inn = closed(kids, carried)
147 +aft = closed(rest, carried)
148 Bool.and(ok, Bool.and(inn, aft))
149 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, rest}:
150 +ok = List.contains(~String, ~String.eq, carried, t)
151 +aft = closed(rest, carried)
152 Bool.and(ok, aft)
153 case Tree.NCons{Tree.Leaf{tok}, rest}:
154 closed(rest, carried)
155 case Tree.NCons{Tree.Group{open, +kids, close}, rest}:
156 +inn = closed(kids, carried)
157 +aft = closed(rest, carried)
158 Bool.and(inn, aft)
159 case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}:
160 +inn = closed(kids, carried)
161 +bod = closed(body, carried)
162 +aft = closed(rest, carried)
163 Bool.and(inn, Bool.and(bod, aft))
164 case Tree.NCons{h, rest}:
165 closed(rest, carried)
166 case Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}:
167 List.contains(~String, ~String.eq, carried, t)
168 case other:
169 True{}
170
171# a top-level token of this text, not one nested in a group
172def top_mark(nn: Tree.Node, +want: String) -> Bool:
173 match nn:
174 case Tree.NCons{Tree.Group{open, kids, close}, rest}:
175 top_mark(rest, want)
176 case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, rest}:
177 +more = top_mark(rest, want)
178 Bool.or(String.eq(t, want), more)
179 case Tree.NCons{h, rest}:
180 top_mark(rest, want)
181 case other:
182 False{}
183
184# `[v : T*n]` or `[v : T^d]`: an array, not a list
185def is_array(+kids: Tree.Node) -> Bool:
186 +col = top_mark(kids, ":")
187 +star = top_mark(kids, "*")
188 +hat = top_mark(kids, "^")
189 Bool.and(col, Bool.or(star, hat))
190
191# how many top-level copies of this token
192def top_count(nn: Tree.Node, +want: String, +got: Nat) -> Nat:
193 match nn:
194 case Tree.NCons{Tree.Group{open, kids, close}, rest}:
195 top_count(rest, want, got)
196 case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, rest}:
197 +next = Bool.pick(Nat, String.eq(t, want), (got + 1n : Nat), got)
198 top_count(rest, want, next)
199 case Tree.NCons{h, rest}:
200 top_count(rest, want, got)
201 case other:
202 got
203
204# a number literal of eight or fewer
205def small_num(+tt: String) -> Bool:
206 List.contains(~String, ~String.eq,
207 ["0", "1", "2", "3", "4", "5", "6", "7", "8", "0n", "1n", "2n", "3n", "4n", "5n", "6n", "7n", "8n"], tt)
208
209# the size after `*` is a literal of eight or fewer, or the depth after `^` (2^d slots) three or fewer
210def small_size(nn: Tree.Node) -> Bool:
211 match nn:
212 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, +op, l, c}},
213 Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, +t, l2, c2}}, rest}}:
214 +here = Bool.or(Bool.and(String.eq(op, "*"), small_num(t)),
215 Bool.and(String.eq(op, "^"), List.contains(~String, ~String.eq, ["0", "1", "2", "3", "0n", "1n", "2n", "3n"], t)))
216 +more = small_size(rest)
217 Bool.or(here, more)
218 case Tree.NCons{Tree.Group{open, kids, close}, rest}:
219 small_size(rest)
220 case Tree.NCons{h, rest}:
221 small_size(rest)
222 case other:
223 False{}
224
225# more than eight cells: nine or more list elements, or an array whose size is not that small
226def wide_shape(+arr: Bool, +kids: Tree.Node) -> Bool:
227 match arr:
228 case True{}:
229 Bool.not(small_size(kids))
230 case False{}:
231 Nat.is_gt(top_count(kids, ",", 0n), 7n)
232
233# constructors that build a fixed table rather than walk one
234def allowed(+tt: String) -> Bool:
235 List.contains(~String, ~String.eq,
236 ["List.replicate", "List.range", "List.map", "Array.new", "Array.map"], tt)
237
238# a Base constructor on the allow list, or a def of this file whose body is
239# itself a wide table (`wides`); a def whose body cannot be seen is not one
240def builder(kk: Lex.TokKind, +tt: String, +self: String, +wides: List<&2, String>) -> Bool:
241 match kk:
242 case Lex.TName{}:
243 Bool.and(Bool.not(String.eq(tt, self)), List.contains(~String, ~String.eq, wides, tt))
244 case Lex.TDotted{}:
245 Bool.or(allowed(tt), List.contains(~String, ~String.eq, wides, tt))
246 case other:
247 False{}
248
249# a bracket group that is a closed list or array of more than eight cells
250def group_yes(+arr: Bool, +kids: Tree.Node, +carried: List<&2, String>) -> Bool:
251 +shape = Bool.or(top_mark(kids, ","), arr)
252 +ok = closed(kids, carried)
253 +wide = wide_shape(arr, kids)
254 Bool.and(shape, Bool.and(ok, wide))
255
256# a bracket group that is a closed list or array
257def table_group(+brack: Bool, +kids: Tree.Node, +carried: List<&2, String>) -> Bool:
258 match brack:
259 case False{}:
260 False{}
261 case True{}:
262 group_yes(is_array(kids), kids, carried)
263
264# the chain is exactly one number; its text, or empty
265def num_text(nn: Tree.Node) -> String:
266 match nn:
267 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, +t, l, c}}, Tree.NNil{}}:
268 t
269 case other:
270 ""
271
272# a count is wide when it is not a literal of eight or fewer (a name may be large)
273def count_wide(aa: Tree.Node) -> Bool:
274 Bool.not(small_num(num_text(aa)))
275
276# the last argument
277def last_node(as: List<&2, Tree.Node>) -> Tree.Node:
278 match as:
279 case Nil{}:
280 Tree.NNil{}
281 case Con{h, Nil{}}:
282 h
283 case Con{h, rest}:
284 last_node(rest)
285
286# `List.replicate` / `Array.new` take argument 2; anything else is wide unless it is `List.range`
287def call_arm(rep: Bool, +as: List<&2, Tree.Node>, +end: Tree.Node, +range: Bool) -> Bool:
288 match rep:
289 case True{}:
290 count_wide(Calls.arg(as, 2n))
291 case False{}:
292 Bool.pick(Bool, range, count_wide(end), True{})
293
294# `List.replicate` / `Array.new` / `List.range` are wide unless their count is a small literal
295def call_wide(+tt: String, kids: Tree.Node) -> Bool:
296 +as = Calls.args(kids)
297 call_arm(Bool.or(String.eq(tt, "List.replicate"), String.eq(tt, "Array.new")), as, last_node(as), String.eq(tt, "List.range"))
298
299# a call that builds a table from closed arguments, and is not a small literal count
300def table_built(+ok: Bool, +wide: Bool, kids: Tree.Node, +carried: List<&2, String>) -> Bool:
301 match ok wide:
302 case True{} True{}:
303 closed(kids, carried)
304 case True{} False{}:
305 False{}
306 case False{} b:
307 False{}
308
309# a call that builds a table from closed arguments
310def table_call(
311 +app: Bool,
312 kk: Lex.TokKind,
313 +tt: String,
314 +kids: Tree.Node,
315 +self: String,
316 +wides: List<&2, String>,
317 +carried: List<&2, String>
318) -> Bool:
319 match app:
320 case False{}:
321 False{}
322 case True{}:
323 table_built(builder(kk, tt, self, wides), call_wide(tt, kids), kids, carried)
324
325# a fixed table of more than eight cells, whatever its cells read
326def wide_of(nn: Tree.Node) -> Bool:
327 match nn:
328 case Tree.NCons{Tree.Group{Lex.Tok{k, +o, l, c}, +kids, close}, Tree.NNil{}}:
329 wide_shape(is_array(kids), kids)
330 case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}},
331 Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, Tree.NNil{}}}:
332 call_wide(t, kids)
333 case other:
334 False{}
335
336# a def whose body is one wide fixed table (`Table.built`, and more than eight cells)
337def wide_one(body: Tree.Node, +name: String, +acc: List<&2, String>) -> List<&2, String>:
338 match body:
339 case Tree.NCons{Tree.Stmt{kind, +kids, inn}, Tree.NNil{}}:
340 Bool.pick(List<&2, String>, Bool.and(Table.built(kids), wide_of(kids)), name <> acc, acc)
341 case other:
342 acc
343
344# defs of this file whose body is one wide fixed table
345def wide_defs(ds: List<&2, Calls.Def>, acc: List<&2, String>) -> List<&2, String>:
346 match ds:
347 case Nil{}:
348 acc
349 case Con{Calls.Def{+name, sig, +body}, rest}:
350 wide_defs(rest, wide_one(body, name, acc))
351
352# a list, an array, or a builder call, closed over carried parameters
353def is_table(nn: Tree.Node, +self: String, +wides: List<&2, String>, +carried: List<&2, String>) -> Bool:
354 match nn:
355 case Tree.NCons{Tree.Group{Lex.Tok{k, +o, l, c}, +kids, close}, Tree.NNil{}}:
356 table_group(String.eq(o, "["), kids, carried)
357 case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, l, c}},
358 Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, Tree.NNil{}}}:
359 table_call(String.eq(o, "("), k, t, kids, self, wides, carried)
360 case other:
361 False{}
362
363# drop the right-hand side
364def skip_expr(nn: Tree.Node) -> Maybe<&2, Got>:
365 match nn:
366 case other:
367 None{}
368
369# the chain after `=`, when the left side is one plain name and nothing else
370def got_bad(+bad: Bool, +nm: String, expr: Tree.Node) -> Maybe<&2, Got>:
371 match bad:
372 case True{}:
373 skip_expr(expr)
374 case False{}:
375 Some{Got{nm, expr}}
376
377# the chain after `=`, when exactly one plain name stands before it
378def got_at(+seen: Bool, +bad: Bool, +nm: String, expr: Tree.Node) -> Maybe<&2, Got>:
379 match seen:
380 case False{}:
381 skip_expr(expr)
382 case True{}:
383 got_bad(bad, nm, expr)
384
385# a second name, a constructor or a group on the left is not a plain binder
386def eat(nn: Tree.Node, +seen: Bool, +bad: Bool, +nm: String) -> Maybe<&2, Got>:
387 match nn:
388 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, rest}:
389 got_at(seen, bad, nm, rest)
390 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, rest}:
391 eat(rest, True{}, Bool.or(bad, seen), t)
392 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, rest}:
393 eat(rest, seen, True{}, nm)
394 case Tree.NCons{Tree.Group{open, kids, close}, rest}:
395 eat(rest, seen, True{}, nm)
396 case Tree.NCons{h, rest}:
397 eat(rest, seen, bad, nm)
398 case other:
399 None{}
400
401# a finding on the table expression
402def cite(+nn: Tree.Node, +path: String) -> List<&2, F.Finding>:
403 [F.Finding{path, Tree.line(nn), Tree.col(nn), U32.from_nat(String.length(Tree.text(nn))), "hoist",
404 "This table is rebuilt on every step from inputs that do not change; build it once, outside the recursion."}]
405
406# a name, and nothing after it
407def wrapped_inside(nn: Tree.Node, +binder: String) -> Bool:
408 match nn:
409 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, Tree.NNil{}}:
410 String.eq(t, binder)
411 case other:
412 False{}
413
414# inside one pair of parentheses
415def wrapped_open(+paren: Bool, kids: Tree.Node, +binder: String) -> Bool:
416 match paren:
417 case True{}:
418 wrapped_inside(kids, binder)
419 case False{}:
420 False{}
421
422# the chain is exactly this name, or that name in parentheses
423def wrapped(nn: Tree.Node, +binder: String) -> Bool:
424 match nn:
425 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, Tree.NNil{}}:
426 String.eq(t, binder)
427 case Tree.NCons{Tree.Group{Lex.Tok{k, +o, l, c}, +kids, close}, Tree.NNil{}}:
428 wrapped_open(String.eq(o, "("), kids, binder)
429 case other:
430 False{}
431
432# the collection argument is this name
433def uses_arg(ss: Slot, +as: List<&2, Tree.Node>, +binder: String) -> Bool:
434 Slot{on, at} = ss
435 match on:
436 case False{}:
437 False{}
438 case True{}:
439 wrapped(Calls.arg(as, at), binder)
440
441# `name[` is an index; a call is an index when its collection argument is the name
442def uses_here_call(+call: Bool, +tt: String, kids: Tree.Node, +binder: String) -> Bool:
443 match call:
444 case False{}:
445 False{}
446 case True{}:
447 uses_arg(slot_of(tt), Calls.args(kids), binder)
448
449# `name[` is an index; a call is an index when its collection argument is the name
450def uses_here(+brack: Bool, +call: Bool, +tt: String, kids: Tree.Node, +binder: String) -> Bool:
451 match brack:
452 case True{}:
453 String.eq(tt, binder)
454 case False{}:
455 uses_here_call(call, tt, kids, binder)
456
457# the name is indexed later: `name[`, or the collection of a get or set
458def uses_name(nn: Tree.Node, +binder: String) -> Bool:
459 match nn:
460 case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}:
461 +here = uses_here(String.eq(o, "["), String.eq(o, "("), t, kids, binder)
462 +inn = uses_name(kids, binder)
463 +aft = uses_name(rest, binder)
464 Bool.or(here, Bool.or(inn, aft))
465 case Tree.NCons{Tree.Group{open, +kids, close}, rest}:
466 +inn = uses_name(kids, binder)
467 +aft = uses_name(rest, binder)
468 Bool.or(inn, aft)
469 case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}:
470 +inn = uses_name(kids, binder)
471 +bod = uses_name(body, binder)
472 +aft = uses_name(rest, binder)
473 Bool.or(inn, Bool.or(bod, aft))
474 case Tree.NCons{h, rest}:
475 uses_name(rest, binder)
476 case other:
477 False{}
478
479# indexed in the let's body or in a following statement
480def uses_or(body: Tree.Node, rest: Tree.Node, +binder: String) -> Bool:
481 +inn = uses_name(body, binder)
482 +aft = uses_name(rest, binder)
483 Bool.or(inn, aft)
484
485# a finding when the table is also indexed
486def let_used(+used: Bool, +expr: Tree.Node, +path: String) -> List<&2, F.Finding>:
487 match used:
488 case True{}:
489 cite(expr, path)
490 case False{}:
491 Nil{}
492
493# a finding when the right-hand side is a closed table that is indexed
494def let_use(+tab: Bool, +used: Bool, +expr: Tree.Node, +path: String) -> List<&2, F.Finding>:
495 match tab:
496 case False{}:
497 Nil{}
498 case True{}:
499 let_used(used, expr, path)
500
501# a plain let of a table
502def let_of(
503 mm: Maybe<&2, Got>,
504 +body: Tree.Node,
505 +rest: Tree.Node,
506 +self: String,
507 +wides: List<&2, String>,
508 +carried: List<&2, String>,
509 +path: String
510) -> List<&2, F.Finding>:
511 match mm:
512 case None{}:
513 Nil{}
514 case Some{Got{+nm, +expr}}:
515 let_use(is_table(expr, self, wides, carried), uses_or(body, rest, nm), expr, path)
516
517# a hot plain let of a table that is indexed afterwards
518def let_hit(
519 +hot: Bool,
520 kids: Tree.Node,
521 +body: Tree.Node,
522 +rest: Tree.Node,
523 +self: String,
524 +wides: List<&2, String>,
525 +carried: List<&2, String>,
526 +path: String
527) -> List<&2, F.Finding>:
528 match hot:
529 case False{}:
530 Nil{}
531 case True{}:
532 let_of(eat(kids, False{}, False{}, ""), body, rest, self, wides, carried, path)
533
534# a finding when this argument is itself a closed table
535def one_yes(+yes: Bool, +aa: Tree.Node, +path: String) -> List<&2, F.Finding>:
536 match yes:
537 case True{}:
538 cite(aa, path)
539 case False{}:
540 Nil{}
541
542# the collection argument, when this call indexes one
543def arg_table(
544 ss: Slot,
545 +as: List<&2, Tree.Node>,
546 +self: String,
547 +wides: List<&2, String>,
548 +carried: List<&2, String>,
549 +path: String
550) -> List<&2, F.Finding>:
551 Slot{on, at} = ss
552 match on:
553 case False{}:
554 Nil{}
555 case True{}:
556 +aa = Calls.arg(as, at)
557 one_yes(is_table(aa, self, wides, carried), aa, path)
558
559# an inline table passed straight to a get or set, when the group is a call
560def call_open(
561 +open: Bool,
562 +tt: String,
563 kids: Tree.Node,
564 +self: String,
565 +wides: List<&2, String>,
566 +carried: List<&2, String>,
567 +path: String
568) -> List<&2, F.Finding>:
569 match open:
570 case False{}:
571 Nil{}
572 case True{}:
573 arg_table(slot_of(tt), Calls.args(kids), self, wides, carried, path)
574
575# an inline table passed straight to a get or set
576def call_table(
577 +hot: Bool,
578 +open: Bool,
579 +tt: String,
580 kids: Tree.Node,
581 +self: String,
582 +wides: List<&2, String>,
583 +carried: List<&2, String>,
584 +path: String
585) -> List<&2, F.Finding>:
586 match hot:
587 case False{}:
588 Nil{}
589 case True{}:
590 call_open(open, tt, kids, self, wides, carried, path)
591
592# a `=>` cools the rest of its chain: a lambda's body runs when it is called, not per step
593def warm(+hot: Bool, kk: Lex.TokKind) -> Bool:
594 match kk:
595 case Lex.TLam{}:
596 False{}
597 case other:
598 hot
599
600# tables built in a hot region
601def walk(
602 nn: Tree.Node,
603 +self: String,
604 +wides: List<&2, String>,
605 +carried: List<&2, String>,
606 +path: String,
607 +hot: Bool
608) -> List<&2, F.Finding>:
609 match nn:
610 case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}:
611 +heat = warm(hot, k)
612 +own = call_table(heat, String.eq(o, "("), t, kids, self, wides, carried, path)
613 +inn = walk(kids, self, wides, carried, path, heat)
614 +aft = walk(rest, self, wides, carried, path, heat)
615 List.concat(&2, F.Finding, [own, inn, aft])
616 case Tree.NCons{Tree.Leaf{Lex.Tok{k, t, l, c}}, rest}:
617 walk(rest, self, wides, carried, path, warm(hot, k))
618 case Tree.NCons{Tree.Group{open, +kids, close}, rest}:
619 List.concat(&2, F.Finding,
620 [walk(kids, self, wides, carried, path, hot), walk(rest, self, wides, carried, path, hot)])
621 case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, +body}, rest}:
622 +arm = Bool.and(hot, Calls.calls(body, self))
623 List.concat(&2, F.Finding, [walk(kids, self, wides, carried, path, False{}),
624 walk(body, self, wides, carried, path, arm), walk(rest, self, wides, carried, path, hot)])
625 case Tree.NCons{Tree.Stmt{kind, +kids, +body}, +rest}:
626 +own = let_hit(hot, kids, body, rest, self, wides, carried, path)
627 List.concat(&2, F.Finding, [own, walk(kids, self, wides, carried, path, hot),
628 walk(body, self, wides, carried, path, hot), walk(rest, self, wides, carried, path, hot)])
629 case Tree.NCons{h, rest}:
630 walk(rest, self, wides, carried, path, hot)
631 case other:
632 Nil{}
633
634def check.go(
635 ds: List<&2, Calls.Def>,
636 +wide: List<&2, String>,
637 +path: String,
638 acc: List<&2, List<&2, F.Finding>>
639) -> List<&2, F.Finding>:
640 match ds:
641 case Nil{}:
642 List.concat(&2, F.Finding, List.reverse(&2, List<&2, F.Finding>, acc))
643 case Con{Calls.Def{+name, +sig, +body}, rest}:
644 check.go(rest, wide, path,
645 Lazy.stop(List<&2, F.Finding>,
646 Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), [],
647 _u => walk(body, name, wide, carried(sig, body, name), path, True{})) <> acc)
648
649# the rule
650def check(ss: Src.Src) -> List<&2, F.Finding>:
651 Src.Src{path, text, toks, tree, bound, items} = ss
652 +ds = Calls.defs(tree)
653 check.go(ds, wide_defs(ds, []), path, [])