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