src/rules/correctness/twice.bend
bolt@1.11.0.0 · 89 lines · 0 definitions
1# rule twice: a list pattern in a def that calls itself, opening with the
2# same literal twice (`case 10 <> 10 <> _:`, `case Con{'a', Con{'a', t}}:`).
3# The checker hangs on it (pi-bend BEND-018, b6a83a52; bend 2.0.16 still
4# does). Only the opening pair was seen to hang: `10 <> 11 <> 10`,
5# `_ <> 10 <> 10` and `10 <> 11 <> 11` check at once, and so does the same
6# pattern in a def that does not recurse. Match one element per step, with a
7# state. A def recurses when its body calls it, `name(..)`: a parameter or a
8# value named like the def is not a call. Only a case's first match column is
9# read: a list pattern in a later column of `match a b:` is not.
10import Base
11import bolt@1.11.0.0/src/src.bend as Src
12import bolt@1.11.0.0/src/finding.bend as F
13import bolt@1.11.0.0/src/syntax/lex.bend as Lex
14import bolt@1.11.0.0/src/syntax/tree.bend as Tree
15import bolt@1.11.0.0/src/syntax/bind.bend as Bind
16import bolt@1.11.0.0/src/lazy/lazy.bend as Lazy
17import bolt@1.11.0.0/src/rules/tokens.bend as T
18import bolt@1.11.0.0/src/rules/calls.bend as Calls
19
20# the literal a list pattern opens with: `x <> ..` or `Con{x, ..}`
21def head(pat: Tree.Node) -> Maybe<&2, Lex.Tok>:
22 match pat:
23 case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, l, c}}, Tree.NCons{Tree.Leaf{Lex.Tok{k2, +o, l2, c2}}, rest}}:
24 Bool.pick(Maybe<&2, Lex.Tok>, Bool.and(T.is_lit(k), String.eq(o, "<>")), Some{Lex.Tok{k, t, l, c}}, None{})
25 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TUpper{}, +t, l, c}},
26 Tree.NCons{Tree.Group{open, Tree.NCons{Tree.Leaf{Lex.Tok{+k, +x, l2, c2}}, kids}, close}, rest}}:
27 Bool.pick(Maybe<&2, Lex.Tok>, Bool.and(String.eq(t, "Con"), T.is_lit(k)), Some{Lex.Tok{k, x, l2, c2}}, None{})
28 case other:
29 None{}
30
31# the rest of a list pattern after its first element
32def tail(pat: Tree.Node) -> Tree.Node:
33 match pat:
34 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}},
35 Tree.NCons{Tree.Group{open, Tree.NCons{x, Tree.NCons{comma, kids}}, close}, rest}}:
36 kids
37 case Tree.NCons{x, Tree.NCons{op, rest}}:
38 rest
39 case other:
40 Tree.NNil{}
41
42# a finding at the second literal when it repeats the first
43def same(aa: Maybe<&2, Lex.Tok>, bb: Maybe<&2, Lex.Tok>, +path: String) -> List<&2, F.Finding>:
44 match aa bb:
45 case Some{Lex.Tok{k, +t, l, c}} Some{Lex.Tok{k2, +t2, l2, c2}}:
46 Bool.pick(List<&2, F.Finding>, String.eq(t, t2),
47 [F.Finding{path, l2, c2, T.width(t2), "twice",
48 "This literal appears twice in one list pattern, which makes the checker hang; match one element per step."}],
49 [])
50 case a2 b2:
51 []
52
53# a case statement's pattern, checked
54def arm(+pat: Tree.Node, +path: String) -> List<&2, F.Finding>:
55 same(head(pat), head(tail(pat)), path)
56
57# every case statement under a recursive def, at any depth
58def walk(nn: Tree.Node, +path: String) -> List<&2, F.Finding>:
59 match nn:
60 case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, body}, rest}:
61 List.concat(&2, F.Finding, [arm(T.pattern(kids), path), walk(body, path), walk(rest, path)])
62 case Tree.NCons{Tree.Stmt{kind, kids, body}, rest}:
63 List.append(&2, F.Finding, walk(body, path), walk(rest, path))
64 case Tree.NCons{h, rest}:
65 walk(rest, path)
66 case other:
67 Nil{}
68
69# a def's body, when it calls itself
70def check.def(mm: Maybe<&2, String>, +body: Tree.Node, path: String) -> List<&2, F.Finding>:
71 match mm:
72 case None{}:
73 Nil{}
74 case Some{+name}:
75 Lazy.stop(List<&2, F.Finding>, Bool.not(Calls.calls(body, name)), [], _u => walk(body, path))
76
77def check.go(root: Tree.Node, +path: String) -> List<&2, F.Finding>:
78 match root:
79 case Tree.NCons{Tree.Stmt{Tree.SDef{}, +kids, body}, rest}:
80 List.append(&2, F.Finding, check.def(Bind.declared(kids), body, path), check.go(rest, path))
81 case Tree.NCons{h, rest}:
82 check.go(rest, path)
83 case other:
84 Nil{}
85
86# the rule
87def check(ss: Src.Src) -> List<&2, F.Finding>:
88 Src.Src{path, text, toks, tree, bound, items} = ss
89 check.go(tree, path)