Skip to content
← Back

src/semantic/symbol_use_locations.ghul

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