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}}