src/rules/laws/closed.bend

bolt@1.26.0.0 · 55 lines · 0 definitions

1# rule closed: a law in a LAWS.bend with no `for`/`exs` binder. A law with no
2# binder holds for the one input it names, which makes it a unit test the
3# checker runs, not a guarantee, and that is so for an equality (`{lhs == rhs
4# : T}`, including `IO(T)`) as much as for anything else. Nothing exempts a
5# closed law: quantify it, or delete it. Laws outside LAWS.bend are not held
6# to it.
7import Base
8import bolt@1.26.0.0/src/paths.bend as Paths
9import bolt@1.26.0.0/src/role.bend as Role
10import bolt@1.26.0.0/src/src.bend as Src
11import bolt@1.26.0.0/src/finding.bend as F
12import bolt@1.26.0.0/src/syntax/tree.bend as Tree
13import bolt@1.26.0.0/src/lazy/lazy.bend as Lazy
14import bolt@1.26.0.0/src/syntax/bind.bend as Bind
15
16# does a law's body bind anything (a `for` or `exs` line)?
17def binds(body: Tree.Node) -> Bool:
18  match body:
19    case Tree.NCons{Tree.Stmt{Tree.SFor{}, kids, b}, rest}:
20      True{}
21    case Tree.NCons{h, rest}:
22      binds(rest)
23    case other:
24      False{}
25
26# a law's name, `?` when it has none
27def name.of(mm: Maybe<&2, String>) -> String:
28  match mm:
29    case None{}:
30      "?"
31    case Some{n}:
32      n
33
34def check.go(root: Tree.Node, +path: String) -> List<&2, F.Finding>:
35  match root:
36    case Tree.NCons{Tree.Stmt{Tree.SLaw{}, +kids, +body}, rest}:
37      +more = check.go(rest, path)
38      Lazy.stop(List<&2, F.Finding>, binds(body), more,
39        _u => F.Finding{path, Tree.line(kids), Tree.col(kids), 0, "closed",
40          "Law " ++ name.of(Bind.declared(kids))
41            ++ " has no `for` or `exs` binder, so it checks a single case; quantify it or delete it."} <> more)
42    case Tree.NCons{h, rest}:
43      check.go(rest, path)
44    case other:
45      Nil{}
46
47# the rule
48def check(ss: Src.Src) -> List<&2, F.Finding>:
49  Src.Src{+path, text, toks, tree, bound, items} = ss
50  Lazy.stop(List<&2, F.Finding>, Bool.not(Paths.is_laws(path)), [], _u => check.go(tree, path))
51
52# the rule, told the file's role (src/role.bend): only a law file is checked
53def check.role(rr: Role.Role, ss: Src.Src) -> List<&2, F.Finding>:
54  Src.Src{path, text, toks, tree, bound, items} = ss
55  Lazy.stop(List<&2, F.Finding>, Bool.not(Role.is_laws(rr)), [], _u => check.go(tree, path))