Appearance
| 1 | namespace Semantic is | |
| 2 | use IO.Std; | |
| 3 | ||
| 4 | use Collections; | |
| 5 | ||
| 6 | use Pair = Collections.KeyValuePair; | |
| 7 | ||
| 8 | use Ghul.Pipes; | |
| 9 | ||
| 10 | use Source; | |
| 11 | ||
| 12 | trait SymbolUseListener is | |
| 13 | add_symbol_use(location: LOCATION, symbol: Symbols.Symbol); | |
| 14 | si | |
| 15 | ||
| 16 | class SYMBOL_USE_LOCATIONS: SymbolUseListener is | |
| 17 | _symbol_use_map: LOCATION_MAP[Symbols.Symbol]; | |
| 18 | _hover_info_map: LOCATION_MAP[HOVER_USE]; | |
| 19 | _symbol_reference_map: Collections.MAP[Symbols.Symbol,Collections.SET[LOCATION]]; | |
| 20 | ||
| 21 | init() is | |
| 22 | clear(); | |
| 23 | si | |
| 24 | ||
| 25 | dump_counts() is | |
| 26 | _symbol_use_map.dump_counts(); | |
| 27 | Std.error.write_line("symbol reference map: {_symbol_reference_map.count}"); | |
| 28 | si | |
| 29 | ||
| 30 | clear() is | |
| 31 | _symbol_use_map = LOCATION_MAP[Symbols.Symbol](); | |
| 32 | _hover_info_map = LOCATION_MAP[HOVER_USE](); | |
| 33 | _symbol_reference_map = Collections.MAP[Symbols.Symbol,Collections.SET[LOCATION]](65521); | |
| 34 | _reference_frames = Collections.LIST[Collections.LIST[Pair[Symbols.Symbol,LOCATION]]](); | |
| 35 | si | |
| 36 | ||
| 37 | // Speculation frames over everything this store records: hover | |
| 38 | // uses, symbol uses and reference-set entries. The iterative | |
| 39 | // inference re-walks (a lambda body walked again once its | |
| 40 | // parameter types settle, and the whole-body retry loop around | |
| 41 | // it) roll their diagnostics back before each re-walk; without | |
| 42 | // the same discipline here, every discarded walk leaves its | |
| 43 | // recorded uses behind — a hover, definition target or | |
| 44 | // reference resolved against not-yet-settled types sits at the | |
| 45 | // same span as the settled walk's record and can win the | |
| 46 | // tie-break. Callers open a frame wherever they speculate the | |
| 47 | // logger AND are guaranteed to re-walk everything recorded | |
| 48 | // since, so a rolled-back frame's information is always | |
| 49 | // re-recorded by the walk whose compilation stands. | |
| 50 | _reference_frames: Collections.LIST[Collections.LIST[Pair[Symbols.Symbol,LOCATION]]]; | |
| 51 | ||
| 52 | speculate() is | |
| 53 | _symbol_use_map.speculate(); | |
| 54 | _hover_info_map.speculate(); | |
| 55 | _reference_frames.add(Collections.LIST[Pair[Symbols.Symbol,LOCATION]]()); | |
| 56 | si | |
| 57 | ||
| 58 | roll_back() is | |
| 59 | assert _reference_frames.count > 0 else "roll_back with no open uses speculation frame"; | |
| 60 | ||
| 61 | _symbol_use_map.roll_back(); | |
| 62 | _hover_info_map.roll_back(); | |
| 63 | ||
| 64 | let frame = _reference_frames[_reference_frames.count - 1]; | |
| 65 | _reference_frames.remove_at(_reference_frames.count - 1); | |
| 66 | ||
| 67 | let i mut = frame.count - 1; | |
| 68 | ||
| 69 | while i >= 0 do | |
| 70 | let entry = frame[i]; | |
| 71 | let references: Collections.SET[LOCATION] mut; | |
| 72 | ||
| 73 | if _symbol_reference_map.try_get_value(entry.key, references ref) then | |
| 74 | references.remove(entry.value); | |
| 75 | fi | |
| 76 | ||
| 77 | i = i - 1; | |
| 78 | od | |
| 79 | si | |
| 80 | ||
| 81 | commit() is | |
| 82 | assert _reference_frames.count > 0 else "commit with no open uses speculation frame"; | |
| 83 | ||
| 84 | _symbol_use_map.commit(); | |
| 85 | _hover_info_map.commit(); | |
| 86 | ||
| 87 | let frame = _reference_frames[_reference_frames.count - 1]; | |
| 88 | _reference_frames.remove_at(_reference_frames.count - 1); | |
| 89 | ||
| 90 | if _reference_frames.count > 0 then | |
| 91 | _reference_frames[_reference_frames.count - 1].add_range(frame); | |
| 92 | fi | |
| 93 | si | |
| 94 | ||
| 95 | mark() -> int => _reference_frames.count; | |
| 96 | ||
| 97 | release(mark: int) is | |
| 98 | while _reference_frames.count > mark do | |
| 99 | roll_back(); | |
| 100 | od | |
| 101 | si | |
| 102 | ||
| 103 | // Exception-recovery guard: `let use` one of these around a | |
| 104 | // walk that opens speculation frames, so frames left open by a | |
| 105 | // throw are rolled back instead of desynchronising the stack. | |
| 106 | // On the normal path all frames are already closed and dispose | |
| 107 | // is a no-op. | |
| 108 | mark_then_release() -> USES_MARK_THEN_RELEASE => | |
| 109 | USES_MARK_THEN_RELEASE(self); | |
| 110 | ||
| 111 | add_symbol_use(location: LOCATION, symbol: Symbols.Symbol) is | |
| 112 | _add_use(location, symbol, null, null); | |
| 113 | si | |
| 114 | ||
| 115 | // Reconcile one file's recorded uses after an interface-preserving | |
| 116 | // incremental EDIT, before the body re-walk re-records its bodies. | |
| 117 | // | |
| 118 | // The edited file's entries split three ways: | |
| 119 | // - inside a re-walked body — discarded here; the re-walk | |
| 120 | // re-records them in current coordinates; | |
| 121 | // - at a retained interface node — not re-walked, so moved here | |
| 122 | // to that node's post-edit location (`correspondence`); | |
| 123 | // - other files — untouched. | |
| 124 | // | |
| 125 | // Without this the re-walk's records pile up on top of the stale | |
| 126 | // ones (duplicate, wrong-line find-references) and the retained | |
| 127 | // interface keeps pre-edit line numbers. | |
| 128 | refresh_edited_file( | |
| 129 | file_name: string, | |
| 130 | correspondence: Source.LOCATION_CORRESPONDENCE, | |
| 131 | body_spans: Source.BODY_SPANS | |
| 132 | ) is | |
| 133 | // _symbol_use_map and _symbol_reference_map hold the same set | |
| 134 | // of (location, symbol) facts — rebuild both from the use | |
| 135 | // map's entries. | |
| 136 | let use_entries = _symbol_use_map.file_entries(file_name); | |
| 137 | ||
| 138 | _symbol_use_map.remove_file(file_name); | |
| 139 | ||
| 140 | for entry in use_entries do | |
| 141 | let location = entry.location; | |
| 142 | let symbol = entry.value; | |
| 143 | ||
| 144 | let references = _get_references_or_empty(symbol); | |
| 145 | ||
| 146 | references.remove(location); | |
| 147 | ||
| 148 | let reconciled = correspondence.translate(location); | |
| 149 | ||
| 150 | if reconciled? then | |
| 151 | // A retained interface node — move the entry to its | |
| 152 | // post-edit location. When the entry is the symbol's | |
| 153 | // own definition site, move the retained symbol with | |
| 154 | // it — both its name location and its declaration span | |
| 155 | // (a separate field on functions / classes / traits / | |
| 156 | // properties). The match fails on a later pass (the | |
| 157 | // symbol now holds `reconciled`), so this is idempotent. | |
| 158 | if location =~ symbol.location then | |
| 159 | let new_span = correspondence.translate(symbol.span); | |
| 160 | ||
| 161 | symbol.set_location(reconciled); | |
| 162 | ||
| 163 | if new_span? then | |
| 164 | symbol.set_span(new_span); | |
| 165 | fi | |
| 166 | fi | |
| 167 | ||
| 168 | _symbol_use_map.put(reconciled, symbol); | |
| 169 | ||
| 170 | references.add(reconciled); | |
| 171 | elif !body_spans.contains(location.start) then | |
| 172 | // Neither a reconciled interface node nor inside a | |
| 173 | // re-walked body — keep it unchanged rather than drop | |
| 174 | // it (dropping would silently lose the reference). | |
| 175 | _symbol_use_map.put(location, symbol); | |
| 176 | ||
| 177 | references.add(location); | |
| 178 | fi | |
| 179 | // else: inside a re-walked body — dropped; the re-walk | |
| 180 | // re-records it in current coordinates. | |
| 181 | od | |
| 182 | ||
| 183 | let hover_entries = _hover_info_map.file_entries(file_name); | |
| 184 | ||
| 185 | _hover_info_map.remove_file(file_name); | |
| 186 | ||
| 187 | for entry in hover_entries do | |
| 188 | let reconciled = correspondence.translate(entry.location); | |
| 189 | ||
| 190 | if reconciled? then | |
| 191 | _hover_info_map.put(reconciled, entry.value); | |
| 192 | elif !body_spans.contains(entry.location.start) then | |
| 193 | _hover_info_map.put(entry.location, entry.value); | |
| 194 | fi | |
| 195 | od | |
| 196 | si | |
| 197 | ||
| 198 | // A symbol use that also carries the use-site AST node, so | |
| 199 | // HOVER can read the type observed at this occurrence off | |
| 200 | // the node instead of the symbol — see HOVER_USE.description. | |
| 201 | add_variable_use(location: LOCATION, symbol: Symbols.Symbol, value: IR.Values.Value) is | |
| 202 | _add_use(location, symbol, value, null); | |
| 203 | si | |
| 204 | ||
| 205 | // A symbol use whose observed type is supplied directly — | |
| 206 | // the assignment-target case, where the type this occurrence | |
| 207 | // should report (the state the assignment leaves behind) is | |
| 208 | // not the type of any single IR node. | |
| 209 | add_variable_use(location: LOCATION, symbol: Symbols.Symbol, observed_type: Types.Type) is | |
| 210 | _add_use(location, symbol, null, observed_type); | |
| 211 | si | |
| 212 | ||
| 213 | // Replace any existing hover entry at exactly this location | |
| 214 | // with a narrowed one — used by visit_member when path | |
| 215 | // narrowing wraps the receiver in a NARROW_VIEW / NARROW_PROJECT | |
| 216 | // after an earlier `add_symbol_use` has already recorded the | |
| 217 | // symbol without a value. Without the replace, both entries | |
| 218 | // sit at the same location and the tie-break picks the older | |
| 219 | // (unnarrowed) one. | |
| 220 | replace_with_variable_use(location: LOCATION, symbol: Symbols.Symbol?, value: IR.Values.Value) is | |
| 221 | if | |
| 222 | _suppress_depth > 0 \/ | |
| 223 | !symbol? \/ | |
| 224 | location.is_internal \/ | |
| 225 | location.is_reflected \/ | |
| 226 | symbol.is_internal | |
| 227 | then | |
| 228 | return; | |
| 229 | fi | |
| 230 | ||
| 231 | _hover_info_map.put_replacing(location, HOVER_USE(symbol, value, null, IoC.CONTAINER.instance.symbol_table.current_scope)); | |
| 232 | ||
| 233 | let root = symbol.root_specialized_from; | |
| 234 | ||
| 235 | _symbol_use_map.put(location, root); | |
| 236 | _add_symbol_reference(location, root); | |
| 237 | si | |
| 238 | ||
| 239 | // Speculative walks (the assignment left-type probe) | |
| 240 | // compile an expression purely to read a type off it; the | |
| 241 | // uses they record would duplicate — and, recorded first at | |
| 242 | // an assignment target, out-rank — the ones the real walk | |
| 243 | // records. A depth so nested probes compose. | |
| 244 | _suppress_depth: int; | |
| 245 | ||
| 246 | begin_suppress() is | |
| 247 | _suppress_depth = _suppress_depth + 1; | |
| 248 | si | |
| 249 | ||
| 250 | end_suppress() is | |
| 251 | _suppress_depth = _suppress_depth - 1; | |
| 252 | si | |
| 253 | ||
| 254 | _add_use(location: LOCATION, symbol: Symbols.Symbol? mut, value: IR.Values.Value?, observed_type: Types.Type?) is | |
| 255 | if | |
| 256 | _suppress_depth > 0 \/ | |
| 257 | !symbol? \/ | |
| 258 | location.is_internal \/ | |
| 259 | location.is_reflected \/ | |
| 260 | symbol.is_internal | |
| 261 | then | |
| 262 | return; | |
| 263 | fi | |
| 264 | ||
| 265 | // A symbol's own declaration renders relative to its enclosing | |
| 266 | // namespace, so a member keeps its type qualifier (COLOR.RED) | |
| 267 | // rather than resolving bare against the type it is declared in. | |
| 268 | let symbol_table = IoC.CONTAINER.instance.symbol_table; | |
| 269 | let render_scope = | |
| 270 | if location =~ symbol.location then | |
| 271 | symbol_table.current_namespace_scope | |
| 272 | else | |
| 273 | symbol_table.current_scope | |
| 274 | fi; | |
| 275 | ||
| 276 | _hover_info_map.put(location, HOVER_USE(symbol, value, observed_type, render_scope)); | |
| 277 | ||
| 278 | symbol = symbol.root_specialized_from; | |
| 279 | ||
| 280 | _symbol_use_map.put(location, symbol); | |
| 281 | _add_symbol_reference(location, symbol); | |
| 282 | si | |
| 283 | ||
| 284 | // Ranks symbol uses that share a source range; the higher rank | |
| 285 | // wins a hover / go-to-definition / semantic-token tie. A symbol | |
| 286 | // whose own definition site *is* this range ranks lowest: the | |
| 287 | // if-let leaf-name shorthand declares its synthesised local on | |
| 288 | // the scrutinee's member token (`if let x.y.z?` defines `z` at | |
| 289 | // the `.z` access), so a use recorded there should describe the | |
| 290 | // member the value came from, not the local derived from it. A | |
| 291 | // resolved Function outranks an ordinary use so a call target | |
| 292 | // still wins over a co-recorded overload-group or type name. | |
| 293 | _hover_priority(location: LOCATION, symbol: Symbols.Symbol?) -> int is | |
| 294 | if !symbol? then | |
| 295 | return 0; | |
| 296 | fi | |
| 297 | ||
| 298 | if symbol.location =~ location then | |
| 299 | return 0; | |
| 300 | fi | |
| 301 | ||
| 302 | if isa Symbols.Function(symbol) then | |
| 303 | return 2; | |
| 304 | fi | |
| 305 | ||
| 306 | return 1; | |
| 307 | si | |
| 308 | ||
| 309 | find_hover_use(file_name: string, line: int, column: int) -> HOVER_USE? is | |
| 310 | let matches = _hover_info_map.find_all(file_name, line, column); | |
| 311 | ||
| 312 | // find_all returns null when nothing matches | |
| 313 | @suppress("presence-test-non-optional") | |
| 314 | if !matches? \/ matches.count == 0 then | |
| 315 | return null; | |
| 316 | elif matches.count == 1 then | |
| 317 | return matches[0].value; | |
| 318 | fi | |
| 319 | ||
| 320 | let shortest_length mut = 1_000_000_000; | |
| 321 | let best_match: HOVER_USE? mut = null; | |
| 322 | let best_priority mut = -1; | |
| 323 | ||
| 324 | for m in matches do | |
| 325 | let length = m.location.length; | |
| 326 | ||
| 327 | if length < shortest_length then | |
| 328 | best_match = m.value; | |
| 329 | shortest_length = length; | |
| 330 | best_priority = _hover_priority(m.location, m.value.symbol); | |
| 331 | elif length == shortest_length then | |
| 332 | let priority = _hover_priority(m.location, m.value.symbol); | |
| 333 | ||
| 334 | if priority > best_priority then | |
| 335 | best_match = m.value; | |
| 336 | best_priority = priority; | |
| 337 | fi | |
| 338 | fi | |
| 339 | od | |
| 340 | ||
| 341 | return best_match; | |
| 342 | si | |
| 343 | ||
| 344 | // Every HOVER_USE recorded for one file, one per source range — | |
| 345 | // powering #HOVERMAP#, a whole-file hover dump the ghul.dev | |
| 346 | // example pipeline consumes offline instead of probing position | |
| 347 | // by position with #HOVER#. Where several uses share a range (an | |
| 348 | // overload group and the resolved member both record the | |
| 349 | // call-target identifier) the resolved Function is preferred, | |
| 350 | // mirroring find_hover_use so #HOVERMAP# and #HOVER# agree. | |
| 351 | hover_uses_in_file(file_name: string) -> List[LOCATION_SEARCH_RESULT[HOVER_USE]] is | |
| 352 | let best = Collections.MAP[LOCATION, HOVER_USE](); | |
| 353 | ||
| 354 | for entry in _hover_info_map.file_entries(file_name) do | |
| 355 | let current: HOVER_USE mut; | |
| 356 | ||
| 357 | if !best.try_get_value(entry.location, current ref) then | |
| 358 | best[entry.location] = entry.value; | |
| 359 | elif | |
| 360 | _hover_priority(entry.location, entry.value.symbol) > | |
| 361 | _hover_priority(entry.location, current.symbol) | |
| 362 | then | |
| 363 | best[entry.location] = entry.value; | |
| 364 | fi | |
| 365 | od | |
| 366 | ||
| 367 | let result = LIST[LOCATION_SEARCH_RESULT[HOVER_USE]](); | |
| 368 | ||
| 369 | for kv in best do | |
| 370 | result.add(LOCATION_SEARCH_RESULT[HOVER_USE](kv.key, kv.value)); | |
| 371 | od | |
| 372 | ||
| 373 | return result; | |
| 374 | si | |
| 375 | ||
| 376 | find_definition_from_use(file_name: string, line: int, column: int) -> Symbols.Symbol? => | |
| 377 | let matches = _symbol_use_map.find_all(file_name, line, column) in | |
| 378 | find_best_match(matches); | |
| 379 | ||
| 380 | find_best_match(matches: Collections.List[LOCATION_SEARCH_RESULT[Symbols.Symbol]]?) -> Symbols.Symbol? => | |
| 381 | if !matches? \/ matches.count == 0 then | |
| 382 | null; | |
| 383 | elif matches.count == 1 then | |
| 384 | matches[0].value; | |
| 385 | else | |
| 386 | let shortest_length mut = 1_000_000_000; | |
| 387 | let best_match: Symbols.Symbol? mut = null; | |
| 388 | let best_priority mut = -1; | |
| 389 | ||
| 390 | for m in matches do | |
| 391 | let location = m.location; | |
| 392 | let symbol = m.value; | |
| 393 | let length = location.length; | |
| 394 | ||
| 395 | if length < shortest_length then | |
| 396 | best_match = symbol; | |
| 397 | shortest_length = length; | |
| 398 | best_priority = _hover_priority(location, symbol); | |
| 399 | elif length == shortest_length then | |
| 400 | let priority = _hover_priority(location, symbol); | |
| 401 | ||
| 402 | if priority > best_priority then | |
| 403 | best_match = symbol; | |
| 404 | best_priority = priority; | |
| 405 | fi | |
| 406 | fi | |
| 407 | od | |
| 408 | ||
| 409 | best_match; | |
| 410 | fi; | |
| 411 | ||
| 412 | // - include definitions only | |
| 413 | // - don't include the definition location of the seached symbol itself unless no other matches found | |
| 414 | // - for methods and properties, include all definitions that override the searched symbol | |
| 415 | // - for classes and traits, include all definitions that inherit from the searched symbol | |
| 416 | // - for other symbols, return only the searched symbol | |
| 417 | find_declarations_of_symbol(symbol: Symbols.Symbol mut) -> Collections.Iterable[LOCATION] is | |
| 418 | symbol = symbol.root_specialized_from; | |
| 419 | ||
| 420 | let results = Collections.SET[Symbols.Symbol](); | |
| 421 | ||
| 422 | if symbol.is_classy then | |
| 423 | _get_super_class(symbol, results); | |
| 424 | else | |
| 425 | _get_overridees(symbol, results); | |
| 426 | fi | |
| 427 | ||
| 428 | if results.count == 0 then | |
| 429 | results.add(symbol); | |
| 430 | fi | |
| 431 | ||
| 432 | return results |> map(s => s.location); | |
| 433 | si | |
| 434 | ||
| 435 | // - include definitions only | |
| 436 | // - include the definition location of the seached symbol itself | |
| 437 | // - for methods and properties, include all definitions that override the searched symbol | |
| 438 | // - for classes and traits, include all definitions that inherit from the searched symbol | |
| 439 | // - for other symbols, return only the searched symbol | |
| 440 | find_implementations_of_symbol(symbol: Symbols.Symbol mut) -> Collections.Iterable[LOCATION] is | |
| 441 | symbol = symbol.root_specialized_from; | |
| 442 | ||
| 443 | let results = Collections.SET[Symbols.Symbol](); | |
| 444 | ||
| 445 | if symbol.is_classy then | |
| 446 | _get_all_implementing_symbols(symbol, results); | |
| 447 | else | |
| 448 | _get_all_overriding_symbols(symbol, results); | |
| 449 | fi | |
| 450 | ||
| 451 | return results |>map(s => s.location); | |
| 452 | si | |
| 453 | ||
| 454 | // - include uses but not definitions | |
| 455 | // - for methods and properties, search up the inheritance tree to find the root symbols that are overridden | |
| 456 | // - include references to all symbols that override the root overridee symbols | |
| 457 | // - for classes, traits and other symbols, include only references to the searched symbol | |
| 458 | // Uses bound to exactly this symbol (normalized to its | |
| 459 | // unspecialized root), without the override family | |
| 460 | // find-references folds in. The incremental interface edit's | |
| 461 | // teardown guard wants precisely the sites bound to the outgoing | |
| 462 | // symbol: a call bound to an overridden base member stays valid | |
| 463 | // when an override of it is removed. | |
| 464 | direct_references_to(symbol: Symbols.Symbol) -> Collections.Iterable[LOCATION] => | |
| 465 | _get_references_or_empty(symbol.root_specialized_from); | |
| 466 | ||
| 467 | find_references_to_symbol(symbol: Symbols.Symbol mut) -> Collections.Iterable[LOCATION] is | |
| 468 | symbol = symbol.root_specialized_from; | |
| 469 | ||
| 470 | let definitions = Collections.SET[Symbols.Symbol](); | |
| 471 | ||
| 472 | _collect_override_family(symbol, definitions); | |
| 473 | ||
| 474 | let results = Collections.SET[LOCATION](); | |
| 475 | ||
| 476 | _get_use_locations_for_symbols(definitions, results, false); | |
| 477 | ||
| 478 | return results; | |
| 479 | si | |
| 480 | ||
| 481 | // - include definitions and references | |
| 482 | // - include references to the seached symbol itself | |
| 483 | // - for methods and properties, search up the inheritance tree to find the root symbols that are overridden | |
| 484 | // - include references to all symbols that override the root overridee symbols | |
| 485 | // - for classes, traits and other symbols, include only references to the searched symbol | |
| 486 | find_references_to_symbol_for_rename(symbol: Symbols.Symbol mut) -> Collections.Iterable[LOCATION] is | |
| 487 | symbol = symbol.root_specialized_from; | |
| 488 | ||
| 489 | // A construction site records both the constructor and the | |
| 490 | // type it constructs at the same span, and the constructor | |
| 491 | // wins the best-match tie. Renaming a type from one of its | |
| 492 | // construction sites should rename the type, so redirect a | |
| 493 | // constructor to its owning type rather than refusing. | |
| 494 | if symbol.is_constructor /\ isa Symbols.Symbol(symbol.owner) then | |
| 495 | symbol = (cast Symbols.Symbol?(symbol.owner)!).root_specialized_from; | |
| 496 | fi | |
| 497 | ||
| 498 | if symbol.is_constructor then | |
| 499 | return Collections.SET[LOCATION](); | |
| 500 | fi | |
| 501 | ||
| 502 | let definitions = Collections.SET[Symbols.Symbol](); | |
| 503 | ||
| 504 | _collect_override_family(symbol, definitions); | |
| 505 | ||
| 506 | let results = Collections.SET[LOCATION](); | |
| 507 | ||
| 508 | _get_use_locations_for_symbols(definitions, results, true); | |
| 509 | ||
| 510 | results.add(symbol.location); | |
| 511 | ||
| 512 | return results; | |
| 513 | si | |
| 514 | ||
| 515 | // Every method/property symbol whose uses share a single rename or | |
| 516 | // find-references identity with `symbol`: the symbol itself, every | |
| 517 | // root overridee reachable up the inheritance chain, and every | |
| 518 | // symbol overriding those roots. For a classy or non-overridable | |
| 519 | // symbol the family is just the symbol itself. | |
| 520 | _collect_override_family(symbol: Symbols.Symbol, definitions: Collections.SET[Symbols.Symbol]) is | |
| 521 | if symbol.is_classy then | |
| 522 | definitions.add(symbol); | |
| 523 | return; | |
| 524 | fi | |
| 525 | ||
| 526 | let root_overridees = Collections.SET[Symbols.Symbol](); | |
| 527 | ||
| 528 | _get_root_overridees(symbol, root_overridees); | |
| 529 | ||
| 530 | for overridee in root_overridees do | |
| 531 | _get_all_overriding_symbols(overridee, definitions); | |
| 532 | od | |
| 533 | si | |
| 534 | ||
| 535 | _add_symbol_reference(location: LOCATION, symbol: Symbols.Symbol mut) is | |
| 536 | symbol = symbol.root_specialized_from; | |
| 537 | ||
| 538 | let refs = _get_references_set(symbol); | |
| 539 | ||
| 540 | // Re-walks of the same body (inference retries) legitimately | |
| 541 | // re-record the same symbol at the same location; the set | |
| 542 | // membership check keeps the entry single and the journal | |
| 543 | // records only genuine additions so roll_back removes | |
| 544 | // exactly what this frame added. | |
| 545 | if !refs.contains(location) then | |
| 546 | refs.add(location); | |
| 547 | ||
| 548 | if _reference_frames.count > 0 then | |
| 549 | _reference_frames[_reference_frames.count - 1].add(Pair[Symbols.Symbol,LOCATION](symbol, location)); | |
| 550 | fi | |
| 551 | fi | |
| 552 | si | |
| 553 | ||
| 554 | _get_use_locations_for_symbols( | |
| 555 | symbols: Collections.Iterable[Symbols.Symbol], | |
| 556 | into: Collections.SET[LOCATION], | |
| 557 | include_definitions: bool | |
| 558 | ) is | |
| 559 | for d in symbols do | |
| 560 | if include_definitions then | |
| 561 | into.add(d.location); | |
| 562 | fi | |
| 563 | ||
| 564 | let references = _get_references_or_empty(d); | |
| 565 | ||
| 566 | for reference in references do | |
| 567 | if include_definitions \/ reference !~ d.location then | |
| 568 | into.add(reference); | |
| 569 | fi | |
| 570 | od | |
| 571 | od | |
| 572 | si | |
| 573 | ||
| 574 | _get_root_overridees(symbol: Symbols.Symbol, results: Collections.SET[Symbols.Symbol]) is | |
| 575 | if results.contains(symbol) then | |
| 576 | return; | |
| 577 | fi | |
| 578 | ||
| 579 | let overridees = symbol.overridees; | |
| 580 | ||
| 581 | if !overridees? \/ overridees |> count() == 0 \/ (overridees |> find(o => o.is_reflected)).has_value then | |
| 582 | results.add(symbol); | |
| 583 | return; | |
| 584 | fi | |
| 585 | ||
| 586 | for overridee in overridees do | |
| 587 | _get_root_overridees(overridee, results); | |
| 588 | od | |
| 589 | si | |
| 590 | ||
| 591 | _get_overridees(symbol: Symbols.Symbol, results: Collections.SET[Symbols.Symbol]) is | |
| 592 | let overridees = symbol.overridees; | |
| 593 | ||
| 594 | if !overridees? then | |
| 595 | return; | |
| 596 | fi | |
| 597 | ||
| 598 | for overridee in overridees |> filter(overridee => !overridee.is_internal /\ !overridee.is_reflected) do | |
| 599 | results.add(overridee); | |
| 600 | od | |
| 601 | si | |
| 602 | ||
| 603 | _get_super_class(symbol: Symbols.Symbol, results: Collections.SET[Symbols.Symbol]) is | |
| 604 | let ancestors = symbol.ancestors; | |
| 605 | ||
| 606 | if ancestors.count == 0 then | |
| 607 | return; | |
| 608 | fi | |
| 609 | ||
| 610 | let result = ancestors[0].symbol; | |
| 611 | ||
| 612 | if !result.is_internal /\ !result.is_reflected then | |
| 613 | results.add(result); | |
| 614 | fi | |
| 615 | si | |
| 616 | ||
| 617 | // walk down the tree adding overriding methods | |
| 618 | _get_all_overriding_symbols(symbol: Symbols.Symbol, results: Collections.SET[Symbols.Symbol]) is | |
| 619 | if results.contains(symbol) then | |
| 620 | return; | |
| 621 | fi | |
| 622 | ||
| 623 | results.add(symbol); | |
| 624 | ||
| 625 | let overriders = symbol.overriders; | |
| 626 | ||
| 627 | if !overriders? \/ overriders |> count() == 0 then | |
| 628 | return; | |
| 629 | fi | |
| 630 | ||
| 631 | for overrider in overriders do | |
| 632 | _get_all_overriding_symbols(overrider, results); | |
| 633 | od | |
| 634 | si | |
| 635 | ||
| 636 | // walk down the tree adding implementing classes | |
| 637 | _get_all_implementing_symbols(symbol: Symbols.Symbol, results: Collections.SET[Symbols.Symbol]) is | |
| 638 | if results.contains(symbol) then | |
| 639 | return; | |
| 640 | fi | |
| 641 | ||
| 642 | results.add(symbol); | |
| 643 | ||
| 644 | let implementors = symbol.implementors; | |
| 645 | ||
| 646 | if !implementors? \/ implementors |> count() == 0 then | |
| 647 | return; | |
| 648 | fi | |
| 649 | ||
| 650 | for implementor in implementors do | |
| 651 | _get_all_implementing_symbols(implementor, results); | |
| 652 | od | |
| 653 | si | |
| 654 | ||
| 655 | _get_symbol_references_for_rename(symbol: Symbols.Symbol) -> Collections.SET[LOCATION] is | |
| 656 | let all_definitions = Collections.SET[Symbols.Symbol](); | |
| 657 | ||
| 658 | let root_overridees = Collections.SET[Symbols.Symbol](); | |
| 659 | ||
| 660 | _get_root_overridees(symbol, root_overridees); | |
| 661 | ||
| 662 | for root_overridee in root_overridees do | |
| 663 | _get_all_overriding_symbols(root_overridee, all_definitions); | |
| 664 | od | |
| 665 | ||
| 666 | let results = Collections.SET[LOCATION](); | |
| 667 | ||
| 668 | for d in all_definitions do | |
| 669 | let references = _get_references_or_empty(d); | |
| 670 | ||
| 671 | for reference in references do | |
| 672 | results.add(reference); | |
| 673 | od | |
| 674 | od | |
| 675 | ||
| 676 | return results; | |
| 677 | si | |
| 678 | ||
| 679 | _get_references_or_empty(symbol: Symbols.Symbol) -> Collections.SET[LOCATION] is | |
| 680 | let results: Collections.SET[LOCATION] mut; | |
| 681 | ||
| 682 | if _symbol_reference_map.try_get_value(symbol, results ref) then | |
| 683 | return results; | |
| 684 | fi | |
| 685 | ||
| 686 | return Collections.SET[LOCATION](); | |
| 687 | si | |
| 688 | ||
| 689 | _get_references_set(symbol: Symbols.Symbol mut) -> Collections.SET[LOCATION] is | |
| 690 | // FIXME: is this correct in all cases? | |
| 691 | symbol = symbol.root_specialized_from; | |
| 692 | let results: Collections.SET[LOCATION] mut; | |
| 693 | ||
| 694 | if !_symbol_reference_map.try_get_value(symbol, results ref) then | |
| 695 | results = Collections.SET[LOCATION](); | |
| 696 | _symbol_reference_map[symbol] = results; | |
| 697 | fi | |
| 698 | ||
| 699 | return results; | |
| 700 | si | |
| 701 | si | |
| 702 | ||
| 703 | struct USES_MARK_THEN_RELEASE: Disposable is | |
| 704 | _uses: SYMBOL_USE_LOCATIONS; | |
| 705 | _mark: int; | |
| 706 | ||
| 707 | init(uses: SYMBOL_USE_LOCATIONS) is | |
| 708 | _uses = uses; | |
| 709 | _mark = uses.mark(); | |
| 710 | si | |
| 711 | ||
| 712 | dispose() is | |
| 713 | _uses.release(_mark); | |
| 714 | si | |
| 715 | si | |
| 716 | ||
| 717 | // One reversible mutation of a LOCATION_MAP line-list, journalled | |
| 718 | // while a speculation frame is open so the frame can be undone. | |
| 719 | // APPENDED needs no payload: undo happens in strict reverse order, | |
| 720 | // so the appended entry is still the list's tail when its turn | |
| 721 | // comes. REMOVED re-inserts the entry at its original index, which | |
| 722 | // is valid at undo time for the same reason. | |
| 723 | union LocationMapOp[T] is | |
| 724 | APPENDED(list: Collections.LIST[Pair[LOCATION,T]]); | |
| 725 | REMOVED(list: Collections.LIST[Pair[LOCATION,T]], entry: Pair[LOCATION,T], index: int); | |
| 726 | si | |
| 727 | ||
| 728 | class LOCATION_MAP[T] is | |
| 729 | _file_name_to_file: Collections.MAP[string, Collections.MAP[int, Collections.LIST[Pair[LOCATION,T]]]]; | |
| 730 | ||
| 731 | // Speculation frames. While at least one frame is open, every | |
| 732 | // mutation journals a LocationMapOp into the innermost frame; | |
| 733 | // roll_back undoes the frame's ops in reverse, commit folds them | |
| 734 | // into the enclosing frame (so an outer roll_back also undoes | |
| 735 | // inner committed work), and at the bottom of the stack commit | |
| 736 | // makes the entries permanent. With no frame open, mutations | |
| 737 | // are permanent immediately and cost nothing extra. | |
| 738 | _frames: Collections.LIST[Collections.LIST[LocationMapOp[T]]]; | |
| 739 | ||
| 740 | init() is | |
| 741 | _file_name_to_file = Collections.MAP[string, Collections.MAP[int, Collections.LIST[Pair[LOCATION,T]]]](); | |
| 742 | _frames = Collections.LIST[Collections.LIST[LocationMapOp[T]]](); | |
| 743 | si | |
| 744 | ||
| 745 | speculate() is | |
| 746 | _frames.add(Collections.LIST[LocationMapOp[T]]()); | |
| 747 | si | |
| 748 | ||
| 749 | roll_back() is | |
| 750 | assert _frames.count > 0 else "roll_back with no open speculation frame"; | |
| 751 | ||
| 752 | let frame = _frames[_frames.count - 1]; | |
| 753 | _frames.remove_at(_frames.count - 1); | |
| 754 | ||
| 755 | let i mut = frame.count - 1; | |
| 756 | ||
| 757 | while i >= 0 do | |
| 758 | let op = frame[i]; | |
| 759 | ||
| 760 | if let appended: LocationMapOp.APPENDED[T] = op then | |
| 761 | appended.list.remove_at(appended.list.count - 1); | |
| 762 | elif let removed: LocationMapOp.REMOVED[T] = op then | |
| 763 | removed.list.insert(removed.index, removed.entry); | |
| 764 | fi | |
| 765 | ||
| 766 | i = i - 1; | |
| 767 | od | |
| 768 | si | |
| 769 | ||
| 770 | commit() is | |
| 771 | assert _frames.count > 0 else "commit with no open speculation frame"; | |
| 772 | ||
| 773 | let frame = _frames[_frames.count - 1]; | |
| 774 | _frames.remove_at(_frames.count - 1); | |
| 775 | ||
| 776 | if _frames.count > 0 then | |
| 777 | _frames[_frames.count - 1].add_range(frame); | |
| 778 | fi | |
| 779 | si | |
| 780 | ||
| 781 | mark() -> int => _frames.count; | |
| 782 | ||
| 783 | release(mark: int) is | |
| 784 | while _frames.count > mark do | |
| 785 | roll_back(); | |
| 786 | od | |
| 787 | si | |
| 788 | ||
| 789 | _journal(op: LocationMapOp[T]) is | |
| 790 | if _frames.count > 0 then | |
| 791 | _frames[_frames.count - 1].add(op); | |
| 792 | fi | |
| 793 | si | |
| 794 | ||
| 795 | dump_counts() is | |
| 796 | Std.error.write_line("file name to file map: {_file_name_to_file.count}"); | |
| 797 | si | |
| 798 | ||
| 799 | put(location: LOCATION, value: T) is | |
| 800 | let existing = _get_file(location.file_name); | |
| 801 | ||
| 802 | let file = | |
| 803 | if existing? then | |
| 804 | existing | |
| 805 | else | |
| 806 | let created = Collections.MAP[int, Collections.LIST[Pair[LOCATION,T]]](); | |
| 807 | _file_name_to_file[location.file_name] = created; | |
| 808 | created; | |
| 809 | fi; | |
| 810 | ||
| 811 | let start_line = location.start_line; | |
| 812 | let end_line = location.end_line; | |
| 813 | ||
| 814 | let list: Collections.LIST[Pair[LOCATION,T]] mut; | |
| 815 | ||
| 816 | for line in start_line::end_line do | |
| 817 | if file.contains_key(line) then | |
| 818 | list = file[line]; | |
| 819 | else | |
| 820 | list = Collections.LIST[Pair[LOCATION,T]](); | |
| 821 | file[line] = list; | |
| 822 | fi | |
| 823 | list.add(Pair[LOCATION,T](location,value)); | |
| 824 | _journal(LocationMapOp.APPENDED[T](list)); | |
| 825 | od | |
| 826 | si | |
| 827 | ||
| 828 | // Like `put`, but first drops every existing entry whose | |
| 829 | // location matches exactly — the caller is stamping fresh | |
| 830 | // information (a flow-narrowed observed type on a member | |
| 831 | // access that was earlier recorded as a plain symbol use) | |
| 832 | // and wants to replace, not accumulate. | |
| 833 | put_replacing(location: LOCATION, value: T) is | |
| 834 | let existing = _get_file(location.file_name); | |
| 835 | if existing? then | |
| 836 | let start_line = location.start_line; | |
| 837 | let end_line = location.end_line; | |
| 838 | for line in start_line::end_line do | |
| 839 | if existing.contains_key(line) then | |
| 840 | let list = existing[line]; | |
| 841 | let i mut = list.count - 1; | |
| 842 | while i >= 0 do | |
| 843 | if list[i].key =~ location then | |
| 844 | _journal(LocationMapOp.REMOVED[T](list, list[i], i)); | |
| 845 | list.remove_at(i); | |
| 846 | fi | |
| 847 | i = i - 1; | |
| 848 | od | |
| 849 | fi | |
| 850 | od | |
| 851 | fi | |
| 852 | ||
| 853 | put(location, value); | |
| 854 | si | |
| 855 | ||
| 856 | find_all(file_name: string, line: int, column: int) -> List[LOCATION_SEARCH_RESULT[T]]? is | |
| 857 | let file = _get_file(file_name); | |
| 858 | ||
| 859 | if !file? \/ !file.contains_key(line) then | |
| 860 | return null; | |
| 861 | fi | |
| 862 | ||
| 863 | let list = file[line]; | |
| 864 | ||
| 865 | if list == null then | |
| 866 | return null; | |
| 867 | fi | |
| 868 | ||
| 869 | let line_column = LOCATION.pair(line, column); | |
| 870 | ||
| 871 | let result = LIST[LOCATION_SEARCH_RESULT[T]](); | |
| 872 | ||
| 873 | for p in list do | |
| 874 | if p.key.contains(line_column) then | |
| 875 | result.add(LOCATION_SEARCH_RESULT[T](p.key, p.value)); | |
| 876 | fi | |
| 877 | od | |
| 878 | ||
| 879 | return result; | |
| 880 | si | |
| 881 | ||
| 882 | // Every stored entry for one file. `put` records a multi-line | |
| 883 | // location once per line it spans; each is yielded once, on its | |
| 884 | // own start line. Several entries at the same location (e.g. an | |
| 885 | // overload group and the resolved member) are all kept — the | |
| 886 | // caller chooses between them. | |
| 887 | file_entries(file_name: string) -> List[LOCATION_SEARCH_RESULT[T]] is | |
| 888 | let result = LIST[LOCATION_SEARCH_RESULT[T]](); | |
| 889 | ||
| 890 | let file = _get_file(file_name); | |
| 891 | ||
| 892 | if !file? then | |
| 893 | return result; | |
| 894 | fi | |
| 895 | ||
| 896 | for line_entry in file do | |
| 897 | let line = line_entry.key; | |
| 898 | ||
| 899 | for p in line_entry.value do | |
| 900 | if p.key.start_line == line then | |
| 901 | result.add(LOCATION_SEARCH_RESULT[T](p.key, p.value)); | |
| 902 | fi | |
| 903 | od | |
| 904 | od | |
| 905 | ||
| 906 | return result; | |
| 907 | si | |
| 908 | ||
| 909 | _get_file(file_name: string) -> Collections.MAP[int, Collections.LIST[Pair[LOCATION,T]]]? => | |
| 910 | if _file_name_to_file.contains_key(file_name) then | |
| 911 | _file_name_to_file[file_name] | |
| 912 | else | |
| 913 | null | |
| 914 | fi; | |
| 915 | ||
| 916 | // Drop every entry for one file. The incremental body re-walk | |
| 917 | // rebuilds the edited file's entries; see | |
| 918 | // SYMBOL_USE_LOCATIONS.refresh_edited_file. | |
| 919 | remove_file(file_name: string) is | |
| 920 | _file_name_to_file.remove(file_name); | |
| 921 | si | |
| 922 | si | |
| 923 | ||
| 924 | struct LOCATION_SEARCH_RESULT[T] is | |
| 925 | location: LOCATION; | |
| 926 | value: T; | |
| 927 | ||
| 928 | init(location: LOCATION, value: T) is | |
| 929 | self.location = location; | |
| 930 | self.value = value; | |
| 931 | si | |
| 932 | si | |
| 933 | ||
| 934 | // What HOVER knows about one symbol occurrence: the symbol | |
| 935 | // itself, plus — for variable uses — the use-site AST node. | |
| 936 | class HOVER_USE is | |
| 937 | symbol: Symbols.Symbol public; | |
| 938 | value: IR.Values.Value? public; | |
| 939 | observed_type: Types.Type? public; | |
| 940 | ||
| 941 | // The scope enclosing the use, captured when it was recorded, so | |
| 942 | // the hover renders names relative to where the reader's cursor | |
| 943 | // is rather than fully qualified. | |
| 944 | scope: Scope? public; | |
| 945 | ||
| 946 | init(symbol: Symbols.Symbol, value: IR.Values.Value?, observed_type: Types.Type?, scope: Scope?) is | |
| 947 | self.symbol = symbol; | |
| 948 | self.value = value; | |
| 949 | self.observed_type = observed_type; | |
| 950 | self.scope = scope; | |
| 951 | si | |
| 952 | ||
| 953 | // For a variable use, the resolved narrowed type — from the | |
| 954 | // recorded observed type or the use-site value — provided it's | |
| 955 | // fully settled. Flow-sensitive narrowing mutates a Variable's | |
| 956 | // `type` field during the compile walk and restores it after, | |
| 957 | // so by hover-request time `type` is the declared shape; the | |
| 958 | // observed type reaches us via the recorder. Node types can | |
| 959 | // freeze a partly-inferred form (`LIST[***]`), so we only | |
| 960 | // trust settled ones; the symbol's own type is a better fall | |
| 961 | // back for unsettled cases. | |
| 962 | observed_type_for_narrowing() -> Types.Type? is | |
| 963 | // Read the occurrence value into a local: presence | |
| 964 | // narrowing holds across the member accesses below for a | |
| 965 | // local, not for the `value` property, whose getter call | |
| 966 | // cannot carry a narrow. | |
| 967 | let occurrence = value; | |
| 968 | ||
| 969 | let observed = | |
| 970 | if observed_type? then | |
| 971 | observed_type | |
| 972 | elif occurrence? /\ occurrence.type? then | |
| 973 | occurrence.type | |
| 974 | else | |
| 975 | null | |
| 976 | fi; | |
| 977 | ||
| 978 | if !observed? \/ !observed.is_settled then | |
| 979 | return null; | |
| 980 | fi | |
| 981 | ||
| 982 | return observed; | |
| 983 | si | |
| 984 | ||
| 985 | // Build a describe-context that carries this occurrence's | |
| 986 | // observed (narrowed) type keyed by the collapsed symbol — | |
| 987 | // the shape both HOVER_USE's own kind_label / description | |
| 988 | // accessors and `Analysis.SIGNATURE_DOC` want. | |
| 989 | context() -> Symbols.DESCRIBE_CONTEXT is | |
| 990 | let observed = observed_type_for_narrowing(); | |
| 991 | if !observed? then | |
| 992 | return Symbols.DESCRIBE_CONTEXT.instance; | |
| 993 | fi | |
| 994 | let map = Collections.MAP[Symbols.Symbol, Types.Type](); | |
| 995 | map[symbol.collapse_group_if_single_member()] = observed; | |
| 996 | return Symbols.DESCRIBE_CONTEXT.with_observed_types(map); | |
| 997 | si | |
| 998 | ||
| 999 | // Single-line hover text with the classifier appended as a trailing | |
| 1000 | // `// kind` comment. The wire's own `signature` field is rendered | |
| 1001 | // separately against a column budget by `Analysis.SIGNATURE_DOC`. | |
| 1002 | description: string is | |
| 1003 | let s = symbol.collapse_group_if_single_member(); | |
| 1004 | let ctx = context(); | |
| 1005 | let sig = _render_in_scope(s, ctx); | |
| 1006 | let kind = s.describe_kind(ctx); | |
| 1007 | if kind? then | |
| 1008 | return "{sig} // {kind}"; | |
| 1009 | fi | |
| 1010 | return sig; | |
| 1011 | si | |
| 1012 | ||
| 1013 | // Render the symbol's signature with names shortened relative to | |
| 1014 | // the use's own scope, restoring the previous render scope even if | |
| 1015 | // rendering throws. | |
| 1016 | _render_in_scope(s: Symbols.Symbol, ctx: Symbols.DESCRIBE_CONTEXT) -> string is | |
| 1017 | let use render_scope = IoC.CONTAINER.instance.name_display.with_scope(scope); | |
| 1018 | ||
| 1019 | return Symbols.TEXT_RENDERER(ctx).render(s.describe(ctx)); | |
| 1020 | si | |
| 1021 | ||
| 1022 | // Human-readable classifier (`instance method`, `local variable`, | |
| 1023 | // `class`, `variant`, …) or null when the symbol has none — | |
| 1024 | // namespaces and labels. | |
| 1025 | kind_label: string? is | |
| 1026 | let s = symbol.collapse_group_if_single_member(); | |
| 1027 | return s.describe_kind(context()); | |
| 1028 | si | |
| 1029 | si | |
| 1030 | si |