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. 8 import Base 9 import bend-lint-api@0.2.2.0/linter/lint.bend as Lint 10 import bend-lint-api@0.2.2.0/shared/lists.bend as Lists 11 import bend-lint-api@0.2.2.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. 17 type 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. 22 type User is Data: 23 User{places: Map<&2, Maybe<&2, Place>>, at: U32, name: String} 24 25 # Functions 26 # ========= 27 def 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`. 38 def 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. 53 def 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 66 def 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 78 def User.places(user: User) -> Map<&2, Maybe<&2, Place>>: 79 match user: 80 case User{places, at, name}: 81 places 82 83 def 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. 91 def 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 121 def 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 125 def rule() -> Lint.Rule: 126 Lint.Rule{Lint.Needs{False{}, False{}}, [], Lint.OnFile{run}}