Skip to content
← Back

src/semantic/symbol_use_locations.ghul

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