1 # rule chars: a `match` with more than eight character-literal arms 2 # (`case '.':`). `Char` is `Chr{code: U32}`, so such an arm is a U32 literal 3 # inside a constructor pattern, and bend's C backend pays for each one out of 4 # all proportion to its size -- about 90 MB, and the cost compounds through 5 # every def downstream of the one holding them (eighteen arms cost 1.57 GB 6 # and 8.3 s here, 0.10 GB and 0.7 s once rewritten). It is the literals, not 7 # the arms: a match over eighteen constructors costs nothing measurable. 8 # 9 # Compare `Char.to_u32(c)` instead. Bind the fallback above the comparisons 10 # so it is a value, not a call, or bolt's own `eager` rule fires on it; and 11 # give the def the reusable quantity (`+c: Char`), since the code point and 12 # the fallback both consume it. Where the arms carry linear values, leave 13 # the match alone: a cascade would break linearity and do every branch's 14 # work. 15 # 16 # Only a case's first match column is read, and only an arm whose pattern 17 # opens with a character literal counts: `case 'x':` does, `case Con{'x', t}:` 18 # and a literal in a later column of `match a b:` do not. 19 import Base 20 import ../../src.bend as Src 21 import ../../finding.bend as F 22 import ../../syntax/lex.bend as Lex 23 import ../../syntax/tree.bend as Tree 24 import ../../lazy/lazy.bend as Lazy 25 import ../tokens.bend as T 26 27 # one when a case pattern opens with a character literal, else zero 28 def literal(kids: Tree.Node) -> U32: 29 match kids: 30 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, rest}: 31 1 32 case other: 33 0 34 35 # the character-literal arms among a match's statements 36 def tally(body: Tree.Node, +nn: U32) -> U32: 37 match body: 38 case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, b}, rest}: 39 +k = literal(T.pattern(kids)) 40 tally(rest, (nn + k : U32)) 41 case Tree.NCons{h, rest}: 42 tally(rest, nn) 43 case other: 44 nn 45 46 # a finding when the match holds more character literals than is cheap 47 def report(+nn: U32, +path: String, +ll: U32, +cc: U32) -> List<&2, F.Finding>: 48 Bool.pick(List<&2, F.Finding>, U32.is_gt(nn, 8), 49 [F.Finding{path, ll, cc, 5, "chars", "This match has " ++ U32.show(nn) 50 ++ " character-literal cases, over the limit of 8, which blows up compile time and memory; compare Char.to_u32(c) instead."}], 51 []) 52 53 # a statement: a match is tallied 54 def on_stmt(+kids: Tree.Node, +body: Tree.Node, +path: String) -> List<&2, F.Finding>: 55 +hit = T.keyword(kids, "match") 56 Lazy.stop(List<&2, F.Finding>, Bool.not(hit), [], 57 _u => report(tally(body, 0), path, Tree.line(kids), Tree.col(kids))) 58 59 # every statement, at any depth 60 def walk(nn: Tree.Node, +path: String) -> List<&2, F.Finding>: 61 match nn: 62 case Tree.NCons{Tree.Stmt{kind, kids, +body}, rest}: 63 List.concat(&2, F.Finding, [on_stmt(kids, body, path), walk(body, path), walk(rest, path)]) 64 case Tree.NCons{h, rest}: 65 walk(rest, path) 66 case other: 67 Nil{} 68 69 # the rule 70 def check(ss: Src.Src) -> List<&2, F.Finding>: 71 Src.Src{path, text, toks, tree, bound, items} = ss 72 walk(tree, path)