src/rules/correctness/twice.bend

bolt@1.23.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.23.0.0/src/src.bend as Src
12import bolt@1.23.0.0/src/finding.bend as F
13import bolt@1.23.0.0/src/syntax/lex.bend as Lex
14import bolt@1.23.0.0/src/syntax/tree.bend as Tree
15import bolt@1.23.0.0/src/syntax/bind.bend as Bind
16import bolt@1.23.0.0/src/lazy/lazy.bend as Lazy
17import bolt@1.23.0.0/src/rules/tokens.bend as T
18import bolt@1.23.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)