Skip to content
← Back

src/syntax/process/definition_virtuality.ghul

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