src/rules/suspicious/thunk.bend
bolt@1.27.0.0 · 173 lines · 13 definitions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30import Base
31import bolt@1.27.0.0/src/src.bend as Src
32import bolt@1.27.0.0/src/finding.bend as F
33import bolt@1.27.0.0/src/syntax/lex.bend as Lex
34import bolt@1.27.0.0/src/syntax/tree.bend as Tree
35import bolt@1.27.0.0/src/rules/calls.bend as Calls
36import bolt@1.27.0.0/src/lazy/lazy.bend as Lazy
37
38
39
40def reads(nn: Tree.Node, +pp: String) -> Bool:
41 match nn:
42 case Tree.Leaf{Lex.Tok{k, +t, _l, _c}}:
43 Bool.and(Lex.is_name(k), Bool.or(String.eq(t, pp), String.starts_with(t, pp ++ ".")))
44 case Tree.Group{_o, kids, _cl}:
45 reads(kids, pp)
46 case Tree.Stmt{_k, kids, body}:
47 +a = reads(kids, pp)
48 +b = reads(body, pp)
49 Bool.or(a, b)
50 case Tree.NCons{h, t}:
51 +a = reads(h, pp)
52 +b = reads(t, pp)
53 Bool.or(a, b)
54 case Tree.NNil{}:
55 False{}
56
57
58def ends(nn: Tree.Node) -> Bool:
59 match nn:
60 case Tree.NCons{Tree.Leaf{Lex.Tok{k, _t, _l, _c}}, _rest}:
61 Lex.is_comma(k)
62 case Tree.NCons{_h, _rest}:
63 False{}
64 case _other:
65 True{}
66
67
68
69def hit(+lead: Bool, +arrow: Bool, +own: Bool, +call: Bool, +end: Bool) -> Bool:
70 Bool.and(lead, Bool.and(arrow, Bool.and(own, Bool.and(call, end))))
71
72
73def cite(+ll: U32, +cc: U32, +name: String, +path: String) -> F.Finding:
74 F.Finding{path, ll, cc, U32.from_nat(String.length(name)), "thunk",
75 name ++ " calls itself inside a thunk, which allocates a closure and leaves the loop on every step; carry the test as a Bool into the next call (`go(rest, k, test(h))`) and match on it first, so the search stays a loop."}
76
77
78
79def site.group(
80 kp: Lex.TokKind,
81 +pp: String,
82 +aa: String,
83 +nm: String,
84 +ll: U32,
85 +cc: U32,
86 r3: Tree.Node,
87 +name: String,
88 +path: String
89) -> List<&2, F.Finding>:
90 match r3:
91 case Tree.NCons{Tree.Group{Lex.Tok{_ko, +o, _lo, _co}, kids, _cl}, after}:
92 +lead = Bool.and(Lex.is_name(kp), Bool.not(reads(kids, pp)))
93 Bool.pick(List<&2, F.Finding>,
94 hit(lead, String.eq(aa, "=>"), String.eq(nm, name), String.eq(o, "("), ends(after)),
95 [cite(ll, cc, name, path)], [])
96 case _other:
97 []
98
99
100def site.name(
101 kp: Lex.TokKind,
102 +pp: String,
103 +aa: String,
104 r2: Tree.Node,
105 +name: String,
106 +path: String
107) -> List<&2, F.Finding>:
108 match r2:
109 case Tree.NCons{Tree.Leaf{Lex.Tok{_kn, +n, +l, +c}}, r3}:
110 site.group(kp, pp, aa, n, l, c, r3, name, path)
111 case _other:
112 []
113
114
115def site.lam(kp: Lex.TokKind, +pp: String, rest: Tree.Node, +name: String, +path: String) -> List<&2, F.Finding>:
116 match rest:
117 case Tree.NCons{Tree.Leaf{Lex.Tok{_ka, +a, _la, _ca}}, r2}:
118 site.name(kp, pp, a, r2, name, path)
119 case _other:
120 []
121
122
123def site(hh: Tree.Node, rest: Tree.Node, +name: String, +path: String) -> List<&2, F.Finding>:
124 match hh:
125 case Tree.Leaf{Lex.Tok{kp, +p, _l, _c}}:
126 site.lam(kp, p, rest, name, path)
127 case _other:
128 []
129
130
131
132def lams(nn: Tree.Node, +seen: Bool) -> Nat:
133 match nn:
134 case Tree.NCons{Tree.Leaf{Lex.Tok{k, t, _l, _c}}, rest}:
135 +comma = Lex.is_comma(k)
136 Nat.add(Bool.pick(Nat, Bool.and(comma, seen), 1n, 0n),
137 lams(rest, Bool.and(Bool.not(comma), Bool.or(seen, String.eq(t, "=>")))))
138 case Tree.NCons{_h, rest}:
139 lams(rest, seen)
140 case _other:
141 Bool.pick(Nat, seen, 1n, 0n)
142
143
144def only(+oo: String, +kids: Tree.Node) -> Bool:
145 Bool.and(String.eq(oo, "("), Nat.is_eq(lams(kids, False{}), 1n))
146
147
148
149def walk(nn: Tree.Node, +lone: Bool, +name: String, +path: String) -> List<&2, F.Finding>:
150 match nn:
151 case Tree.NCons{+h, +rest}:
152 +here = site(h, rest, name, path)
153 List.concat(&2, F.Finding, [Bool.pick(List<&2, F.Finding>, lone, here, []),
154 walk(h, False{}, name, path), walk(rest, lone, name, path)])
155 case Tree.Group{Lex.Tok{_k, +o, _l, _c}, +kids, _cl}:
156 walk(kids, only(o, kids), name, path)
157 case Tree.Stmt{_k, kids, body}:
158 List.concat(&2, F.Finding, [walk(kids, False{}, name, path), walk(body, False{}, name, path)])
159 case _other:
160 Nil{}
161
162def check.go(ds: List<&2, Calls.Def>, +path: String, acc: List<&2, List<&2, F.Finding>>) -> List<&2, F.Finding>:
163 match ds:
164 case Nil{}:
165 List.concat(&2, F.Finding, List.reverse(&2, List<&2, F.Finding>, acc))
166 case Con{Calls.Def{+name, +sig, body}, rest}:
167 check.go(rest, path,
168 Lazy.stop(List<&2, F.Finding>, Calls.exempt(path, sig), [], _u => walk(body, False{}, name, path)) <> acc)
169
170
171def check(ss: Src.Src) -> List<&2, F.Finding>:
172 Src.Src{path, text, toks, tree, bound, items} = ss
173 check.go(Calls.defs(tree), path, [])