Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use Source.LOCATION | |
| 3 | use Trees | |
| 4 | use Ghul.Disposable | |
| 5 | ||
| 6 | use Logging.Logger | |
| 7 | ||
| 8 | use Semantic.Symbols.Classy | |
| 9 | use Semantic.Symbols.Function | |
| 10 | use Semantic.Symbols.Symbol | |
| 11 | ||
| 12 | // Walks the AST after resolve-overrides and announces, as an | |
| 13 | // editor-only inlay at each declared name, what the compiler knows | |
| 14 | // about that declaration's place in the dispatch hierarchy. ghūl | |
| 15 | // writes none of it: there is no `virtual`, `override`, `sealed` or | |
| 16 | // `final` keyword, and `abstract` is never written on a method, so a | |
| 17 | // reader otherwise has to reconstruct the answer from the rest of | |
| 18 | // the assembly. | |
| 19 | // | |
| 20 | // The glyphs, in the order they are emitted: | |
| 21 | // | |
| 22 | // ◆ abstract — no body, so an implementor has to supply one | |
| 23 | // ▲ overrides a base member | |
| 24 | // ▼ overridden by a more derived member | |
| 25 | // ○ an override could exist outside this assembly | |
| 26 | // | |
| 27 | // A declaration that earns none of them gets no inlay at all. That | |
| 28 | // is the common case — a member that neither overrides nor is | |
| 29 | // overridden and cannot be — and marking it would put ghost text on | |
| 30 | // nearly every line, so absence carries it. | |
| 31 | // | |
| 32 | // A type's header already states its base type, `abstract` and | |
| 33 | // `open`, so only what the header does not say is announced: `▼` | |
| 34 | // when something in the assembly extends or implements it, and `◆` | |
| 35 | // when it is abstract by inference from a body-less method rather | |
| 36 | // than by the modifier. Only a class or a trait can earn either: a | |
| 37 | // struct is extended by nothing, and a union's extenders are its | |
| 38 | // variants, declared in its own body a line below. | |
| 39 | // | |
| 40 | // Both accessors of a property are declared at the property's own | |
| 41 | // name, so a readable-and-assignable property produces two carriers | |
| 42 | // at one column; DEFINITION_VIRTUALITY_MERGER folds them into one | |
| 43 | // hint whose hover answers for each half separately. | |
| 44 | // | |
| 45 | // Emission is gated on `Logger.want_hint_for`, which is false | |
| 46 | // outside analysis mode and for a file the client has not opened, | |
| 47 | // so a batch build walks the tree and records nothing. | |
| 48 | class DEFINITION_VIRTUALITY: Visitor is | |
| 49 | // Base slug. An accessor carrier appends its half so the merger | |
| 50 | // can tell a get/set pair from two unrelated carriers that | |
| 51 | // happen to share a column. | |
| 52 | CODE: string static => "definition-virtuality" | |
| 53 | ||
| 54 | ABSTRACT: string static => "◆" | |
| 55 | OVERRIDES: string static => "▲" | |
| 56 | OVERRIDDEN: string static => "▼" | |
| 57 | OPEN: string static => "○" | |
| 58 | ||
| 59 | _logger: Logger | |
| 60 | _symbol_table: Semantic.SYMBOL_TABLE | |
| 61 | ||
| 62 | init(logger: Logger, symbol_table: Semantic.SYMBOL_TABLE) is | |
| 63 | super.init() | |
| 64 | ||
| 65 | _logger = logger | |
| 66 | _symbol_table = symbol_table | |
| 67 | si | |
| 68 | ||
| 69 | apply(source_file: Compiler.SOURCE_FILE) is | |
| 70 | if !_logger.want_hint_for(source_file.definition.location) then | |
| 71 | return | |
| 72 | fi | |
| 73 | ||
| 74 | // A file can be walked more than once in one state - the | |
| 75 | // on-demand expression walk re-runs this pass for a file the | |
| 76 | // full build left closed - so drop what a previous run | |
| 77 | // recorded for it rather than stacking a second copy. | |
| 78 | _logger.clear_inlays(source_file.file_name, CODE) | |
| 79 | ||
| 80 | source_file.definition.walk(self) | |
| 81 | si | |
| 82 | ||
| 83 | visit(function: Definitions.FUNCTION) is | |
| 84 | let name = function.name | |
| 85 | ||
| 86 | if !name? then | |
| 87 | return | |
| 88 | fi | |
| 89 | ||
| 90 | let symbol = _function_for(function) | |
| 91 | ||
| 92 | if !symbol? then | |
| 93 | return | |
| 94 | fi | |
| 95 | ||
| 96 | // A constructor is reached by construction rather than by | |
| 97 | // dispatch, so it has no virtuality to report. | |
| 98 | if symbol.is_constructor then | |
| 99 | return | |
| 100 | fi | |
| 101 | ||
| 102 | let half = _accessor_half(function) | |
| 103 | ||
| 104 | // A member the compiler wrote — a union's `=~`, its | |
| 105 | // `get_hash_code`, the `object.equals` bridge — is announced | |
| 106 | // at the declaration it was synthesised from, where it reads | |
| 107 | // as a claim about code the user can see. Property and | |
| 108 | // indexer accessors are synthesised too but stand for a | |
| 109 | // member that was written, so they stay. | |
| 110 | if (function.is_synthesized \/ symbol.is_internal) /\ !half? then | |
| 111 | return | |
| 112 | fi | |
| 113 | ||
| 114 | // A body-less method of a ghūl class is not declared as an | |
| 115 | // abstract symbol — the class is marked implicitly abstract | |
| 116 | // and the method gets a throwing body — so the symbol alone | |
| 117 | // would answer only for a trait member or an import. | |
| 118 | let body = function.body | |
| 119 | let is_abstract = symbol.is_abstract \/ !body? \/ body.is_null | |
| 120 | // Names in the hover read relative to the declaring type, | |
| 121 | // the scope a reader looking at this declaration is in. | |
| 122 | let scope = cast Semantic.Scope?(symbol.owner) | |
| 123 | ||
| 124 | let overridees = _described(symbol.overridees, scope) | |
| 125 | let overriders = _described(symbol.overriders, scope) | |
| 126 | let is_open = symbol.is_openly_dispatchable /\ !_owner_is_union(symbol) | |
| 127 | ||
| 128 | if !is_abstract /\ overridees.count == 0 /\ overriders.count == 0 /\ !is_open then | |
| 129 | return | |
| 130 | fi | |
| 131 | ||
| 132 | let prefix = if half? then "{half} " else "" fi | |
| 133 | ||
| 134 | let glyphs = System.Text.StringBuilder() | |
| 135 | let notes = Collections.LIST[string]() | |
| 136 | ||
| 137 | if is_abstract then | |
| 138 | glyphs.append(ABSTRACT) | |
| 139 | notes.add("{prefix}{ABSTRACT} abstract") | |
| 140 | fi | |
| 141 | ||
| 142 | if overridees.count > 0 then | |
| 143 | glyphs.append(OVERRIDES) | |
| 144 | notes.add("{prefix}{OVERRIDES} overrides {_join(overridees)}") | |
| 145 | fi | |
| 146 | ||
| 147 | if overriders.count > 0 then | |
| 148 | glyphs.append(OVERRIDDEN) | |
| 149 | notes.add("{prefix}{OVERRIDDEN} overridden by {_join(overriders)}") | |
| 150 | fi | |
| 151 | ||
| 152 | if is_open then | |
| 153 | glyphs.append(OPEN) | |
| 154 | notes.add("{prefix}{OPEN} can be overridden outside this assembly") | |
| 155 | fi | |
| 156 | ||
| 157 | let code = if half? then "{CODE}-{half}" else CODE fi | |
| 158 | ||
| 159 | _logger.inlay(name.location, code, "{glyphs}", "{_subject(function)}\n\n{_bullets(notes)}") | |
| 160 | si | |
| 161 | ||
| 162 | visit(`class: Definitions.CLASS) is | |
| 163 | _announce_type(`class, `class.name) | |
| 164 | si | |
| 165 | ||
| 166 | visit(`trait: Definitions.TRAIT) is | |
| 167 | _announce_type(`trait, `trait.name) | |
| 168 | si | |
| 169 | ||
| 170 | _announce_type(node: Trees.Node, name: Trees.Identifiers.Identifier?) is | |
| 171 | if !name? then | |
| 172 | return | |
| 173 | fi | |
| 174 | ||
| 175 | let symbol = _classy_for(node) | |
| 176 | ||
| 177 | if !symbol? then | |
| 178 | return | |
| 179 | fi | |
| 180 | ||
| 181 | let implementors = _described(symbol.implementors, symbol.enclosing_scope) | |
| 182 | let is_implicitly_abstract = symbol.is_implicitly_abstract | |
| 183 | ||
| 184 | if !is_implicitly_abstract /\ implementors.count == 0 then | |
| 185 | return | |
| 186 | fi | |
| 187 | ||
| 188 | let glyphs = System.Text.StringBuilder() | |
| 189 | let notes = Collections.LIST[string]() | |
| 190 | ||
| 191 | if is_implicitly_abstract then | |
| 192 | glyphs.append(ABSTRACT) | |
| 193 | notes.add("{ABSTRACT} abstract — inferred from a body-less method, not written") | |
| 194 | fi | |
| 195 | ||
| 196 | if implementors.count > 0 then | |
| 197 | glyphs.append(OVERRIDDEN) | |
| 198 | ||
| 199 | let verb = if symbol.is_trait then "implemented by" else "extended by" fi | |
| 200 | ||
| 201 | notes.add("{OVERRIDDEN} {verb} {_join(implementors)}") | |
| 202 | fi | |
| 203 | ||
| 204 | _logger.inlay(name.location, CODE, "{glyphs}", "{name.name}\n\n{_bullets(notes)}") | |
| 205 | si | |
| 206 | ||
| 207 | // What this declaration is called in the hover's first line. An | |
| 208 | // accessor is named for the property or indexer it serves rather | |
| 209 | // than for the `$get_` / `get_` symbol the rewriter gave it. | |
| 210 | _subject(function: Definitions.FUNCTION) -> string is | |
| 211 | let property = function.for_property | |
| 212 | ||
| 213 | if property? /\ property.name? then | |
| 214 | return property.name.name | |
| 215 | fi | |
| 216 | ||
| 217 | if function.for_indexer? then | |
| 218 | return "[]" | |
| 219 | fi | |
| 220 | ||
| 221 | return function.name!.name | |
| 222 | si | |
| 223 | ||
| 224 | // "get" / "set" for a property or indexer accessor, null for a | |
| 225 | // method — which is also what tells the merger whether a carrier | |
| 226 | // is one half of a pair. | |
| 227 | _accessor_half(function: Definitions.FUNCTION) -> string? is | |
| 228 | if !function.for_property? /\ !function.for_indexer? then | |
| 229 | return null | |
| 230 | fi | |
| 231 | ||
| 232 | return if function.is_assign_accessor then "set" else "get" fi | |
| 233 | si | |
| 234 | ||
| 235 | _bullets(notes: Collections.Iterable[string]) -> string is | |
| 236 | let body = System.Text.StringBuilder() | |
| 237 | ||
| 238 | for n in notes do | |
| 239 | if body.length > 0 then | |
| 240 | body.append("\n") | |
| 241 | fi | |
| 242 | ||
| 243 | body.append("- ").append(n) | |
| 244 | od | |
| 245 | ||
| 246 | return "{body}" | |
| 247 | si | |
| 248 | ||
| 249 | _join(names: Collections.Iterable[string]) -> string is | |
| 250 | let joined = System.Text.StringBuilder() | |
| 251 | ||
| 252 | for n in names do | |
| 253 | if joined.length > 0 then | |
| 254 | joined.append(", ") | |
| 255 | fi | |
| 256 | ||
| 257 | joined.append("`").append(n).append("`") | |
| 258 | od | |
| 259 | ||
| 260 | return "{joined}" | |
| 261 | si | |
| 262 | ||
| 263 | // The related symbols, each rendered as its declaration head by | |
| 264 | // the same renderer the hover uses. Two entries describing one | |
| 265 | // declaration — a specialization and the generic it came from — | |
| 266 | // collapse to a single line, decided on the declaration each | |
| 267 | // entry specialized from rather than on how the two render: | |
| 268 | // distinct declarations can render alike, and dropping one of | |
| 269 | // those would answer "who overrides this?" with a list shorter | |
| 270 | // than the truth. | |
| 271 | // | |
| 272 | // Names render relative to `scope`, so a name that is | |
| 273 | // unambiguous where the reader is looking stays short and one | |
| 274 | // that is not carries the least prefix that distinguishes it. | |
| 275 | _described(symbols: Collections.Iterable[Symbol]?, scope: Semantic.Scope?) -> Collections.LIST[string] is | |
| 276 | let described = Collections.LIST[string]() | |
| 277 | ||
| 278 | if !symbols? then | |
| 279 | return described | |
| 280 | fi | |
| 281 | ||
| 282 | let seen = Collections.LIST[Symbol]() | |
| 283 | ||
| 284 | let use render_scope = IoC.CONTAINER.instance.name_display.with_scope(scope) | |
| 285 | ||
| 286 | for s in symbols do | |
| 287 | let declaration = s.root_specialized_from | |
| 288 | ||
| 289 | if seen.contains(declaration) then | |
| 290 | continue | |
| 291 | fi | |
| 292 | ||
| 293 | seen.add(declaration) | |
| 294 | described.add(_describe(s)) | |
| 295 | od | |
| 296 | ||
| 297 | return described | |
| 298 | si | |
| 299 | ||
| 300 | // A member is named by its whole declaration head, because that | |
| 301 | // is what tells two overloads apart — the case where a bare name | |
| 302 | // would answer "who overrides this?" with one entry standing for | |
| 303 | // two. A type is named by its name alone: its declaration head | |
| 304 | // adds only the kind, which the surrounding sentence already | |
| 305 | // says, and drops the namespace that distinguishes two types | |
| 306 | // sharing a short name. | |
| 307 | _describe(symbol: Symbol) -> string => | |
| 308 | if symbol.is_type then | |
| 309 | IoC.CONTAINER.instance.name_display.name_for(symbol) | |
| 310 | else | |
| 311 | "{symbol}" | |
| 312 | fi | |
| 313 | ||
| 314 | // A union's variants are its whole set of subtypes and are | |
| 315 | // declared in its own body, so nothing outside the assembly can | |
| 316 | // add one however the openness default reads. | |
| 317 | _owner_is_union(symbol: Symbol) -> bool is | |
| 318 | let owner = symbol.owner | |
| 319 | ||
| 320 | if !owner? \/ !isa Classy(owner) then | |
| 321 | return false | |
| 322 | fi | |
| 323 | ||
| 324 | let classy = cast Classy(owner) | |
| 325 | ||
| 326 | return classy.is_union \/ classy.is_variant | |
| 327 | si | |
| 328 | ||
| 329 | _function_for(node: Trees.Node) -> Function? is | |
| 330 | let scope = _symbol_table.scope_for(node) | |
| 331 | ||
| 332 | if !scope? then | |
| 333 | return null | |
| 334 | fi | |
| 335 | ||
| 336 | return cast Function?(scope.underlying_scope) | |
| 337 | si | |
| 338 | ||
| 339 | _classy_for(node: Trees.Node) -> Classy? is | |
| 340 | let scope = _symbol_table.scope_for(node) | |
| 341 | ||
| 342 | if !scope? then | |
| 343 | return null | |
| 344 | fi | |
| 345 | ||
| 346 | return cast Classy?(scope.underlying_scope) | |
| 347 | si | |
| 348 | si | |
| 349 | si |