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. 10 import Base 11 import ../../src.bend as Src 12 import ../../finding.bend as F 13 import ../../syntax/lex.bend as Lex 14 import ../../syntax/tree.bend as Tree 15 import ../../syntax/bind.bend as Bind 16 import ../../lazy/lazy.bend as Lazy 17 import ../tokens.bend as T 18 import ../calls.bend as Calls 19 20 # the literal a list pattern opens with: `x <> ..` or `Con{x, ..}` 21 def 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 32 def 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 43 def 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 54 def 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 58 def 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 70 def 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 77 def 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 87 def check(ss: Src.Src) -> List<&2, F.Finding>: 88 Src.Src{path, text, toks, tree, bound, items} = ss 89 check.go(tree, path)