language/forward_reference.bend

bend-lint-rules@0.1.0.0 · 126 lines · 0 definitions

1# language/forward_reference: live code that uses a definition of its file defined below it.
2# Heuristics: Bend checks definitions in file order, and one below is still a bodiless claim, so live
3# code cannot use it: the check fails with "a filled definition". So two definitions cannot call each
4# other. Types and equations are dead code, so they may name any definition, and an @unsafe
5# definition (`def f?`) may use any. Bend's checker stops at its first error; this rule reports every
6# such use at once. A definition used as a type argument of a call is dead too, but the rule cannot
7# tell such an argument from a live one.
8import Base
9import bend-lint-api@0.1.0.0/linter/lint.bend as Lint
10import bend-lint-api@0.1.0.0/shared/lists.bend as Lists
11import bend-lint-api@0.1.0.0/shared/names.bend as Names
12
13# Types
14# =====
15
16# A definition of the file: where it is among the file's nodes, and its name as written.
17type Place is Data:
18  Place{at: U32, name: String}
19
20# What judging the uses in one definition reads: where each definition of the file is, by the name the check gives it;
21# and where the definition is, with its name.
22type User is Data:
23  User{places: Map<&2, Maybe<&2, Place>>, at: U32, name: String}
24
25# Functions
26# =========
27def message(+used: String, +user: String) -> String:
28  used
29    ++ " is defined below "
30    ++ user
31    ++ ", and Bend checks definitions in file order: live code can use only what is defined above it, so this use fails the check (\"a filled definition\"). Move "
32    ++ used
33    ++ " above "
34    ++ user
35    ++ ". If the two call each other, merge them into one definition with an argument that selects which to run: Bend does not allow mutual recursion. This is a false positive if the use is an argument for an erased parameter (`-x`): that is dead code."
36
37# Where each definition among `nodes`, the file's, is, from `at`, onto `out`.
38def places(
39  nodes: List<&2, Lint.Node>,
40  +at: U32,
41  out: Map<&2, Maybe<&2, Place>>
42) -> Map<&2, Maybe<&2, Place>>:
43  match nodes:
44    case Nil{}:
45      out
46    case Con{Lint.Node{Lint.Definition{t, u}, name, s, k, f}, rest}:
47      places(rest, (at + 1: U32), Map.set(&2, Maybe<&2, Place>, out, t, Some{Place{at, name}}))
48    case Con{node, rest}:
49      places(rest, (at + 1: U32), out)
50
51# The finding about a use of `used` from `user`, if `used` is below it. Whether the use is live is asked only then:
52# `&&` computes both sides.
53def below(
54  hit: Bool,
55  path: List<&2, Lint.Above>,
56  used: String,
57  user: String,
58  span: Maybe<&2, Lint.Span>
59) -> List<&2, Lint.Finding>:
60  match hit:
61    case True{}:
62      Lint.finding(Lint.Warning{}, Lint.is_live(path), span, message(used, user))
63    case False{}:
64      []
65
66def judged(
67  path: List<&2, Lint.Above>,
68  place: Maybe<&2, Place>,
69  user: User,
70  span: Maybe<&2, Lint.Span>
71) -> List<&2, Lint.Finding>:
72  match place user:
73    case Some{Place{p, used}} User{places, at, name}:
74      below(U32.is_gt(p, at), path, used, name, span)
75    case _ _:
76      []
77
78def User.places(user: User) -> Map<&2, Maybe<&2, Place>>:
79  match user:
80    case User{places, at, name}:
81      places
82
83def use(+user: User, +path: List<&2, Lint.Above>, +node: Lint.Node) -> List<&2, Lint.Finding>:
84  match node:
85    case Lint.Node{Lint.Reference{t}, n, span, k, f}:
86      judged(path, Names.find(Place, User.places(user), t), user, span)
87    case _:
88      []
89
90# The findings in the definitions among `nodes`, from `at`, onto `out`. An @unsafe definition may use any.
91def users(
92  nodes: List<&2, Lint.Node>,
93  +places: Map<&2, Maybe<&2, Place>>,
94  +at: U32,
95  out: List<&2, Lint.Finding>
96) -> List<&2, Lint.Finding>:
97  match nodes:
98    case Nil{}:
99      List.reverse(&2, Lint.Finding, out)
100    case Con{Lint.Node{Lint.Definition{t, False{}}, +name, s, k, f}, rest}:
101      users(
102        rest,
103        places,
104        (at + 1: U32),
105        Lists.onto(
106          &2,
107          Lint.Finding,
108          Lint.visit(
109            ~User,
110            ~Lint.Finding,
111            ~use,
112            User{places, at, name},
113            [Lint.Node{Lint.Definition{t, False{}}, name, s, k, f}]
114          ),
115          out
116        )
117      )
118    case Con{node, rest}:
119      users(rest, places, (at + 1: U32), out)
120
121def run(+here: Lint.Here) -> Lint.Ask(List<&2, Lint.Finding>):
122  +nodes = Lint.Here.nodes(here)
123  Lint.done(users(nodes, places(nodes, 0, Map.new(&2, Maybe<&2, Place>)), 0, []))
124
125def rule() -> Lint.Rule:
126  Lint.Rule{Lint.Needs{False{}, False{}}, [], Lint.OnFile{run}}