Skip to content
← Back

src/semantic/dotnet/symbol_table.ghul

1
namespace Semantic.DotNet is
2
use TYPE = System.Type
3
use IO.Std
4
5
class SYMBOL_TABLE is
6
_type_details_lookup: TYPE_DETAILS_LOOKUP
7
_symbol_factory: SYMBOL_FACTORY
8
9
_symbol_store: SYMBOL_STORE
10
11
// ghul names whose by-name cache entry is the authoritative result
12
// (a TYPE_GROUP, a confirmed single with no sibling arities, or a
13
// no-result). Distinguishes those from the provisional single that
14
// `SYMBOL_STORE.add_symbol` caches while a type materialises (needed
15
// to break cycles in the reflected type graph): a provisional entry
16
// must be re-resolved through the arity scan so an arity-colliding
17
// name (`Ghul.Comparable` / `Comparable[T]`) yields its group
18
// rather than shadowing it. Populated once per name and never
19
// cleared, so resolution is stable across analysis-mode compile cycles.
20
_authoritative_names: Collections.SET[string]
21
22
// A namespace's globals can arrive on several carriers - one per
23
// contributing assembly, and the runtime's staging namespace
24
// re-homed into `Ghul.Intrinsics` adds a second from one assembly. Overloads of one name split
25
// across carriers form one group, held here so the merged group has
26
// one identity across lookups; keyed on the qualified name that was
27
// looked up, which is the name and not a rendering of the symbol.
28
_merged_globals: Collections.MAP[string, Symbols.Symbol]
29
30
init(
31
type_details_lookup: TYPE_DETAILS_LOOKUP,
32
symbol_factory: SYMBOL_FACTORY
33
) is
34
_type_details_lookup = type_details_lookup
35
_symbol_factory = symbol_factory
36
37
_symbol_factory.set_symbol_table(self)
38
39
_symbol_store = SYMBOL_STORE()
40
_authoritative_names = Collections.SET[string]()
41
_merged_globals = Collections.MAP[string, Symbols.Symbol]()
42
si
43
44
find_root_matches(matches: Collections.MutableMap[string, Symbols.Symbol]) is
45
let distinct_namespaces = Collections.SET[string]()
46
47
let owner = EMPTY_SCOPE("")
48
49
for ns in _type_details_lookup.find_all_root_namespaces() do
50
if !matches.contains_key(ns) then
51
matches.add(ns, COMPLETION_SYMBOL(owner, ns, Symbols.SymbolKind.NAMESPACE, Symbols.CompletionKind.MODULE))
52
fi
53
od
54
si
55
56
find_member_matches(namespace_name: string, matches: Collections.MutableMap[string, Symbols.Symbol]) is
57
if HIDDEN_SYMBOLS.is_hidden_namespace(namespace_name) then
58
return
59
fi
60
61
let owner = EMPTY_SCOPE(namespace_name)
62
let distinct_namespaces = Collections.SET[string](_type_details_lookup.find_all_namespaces_under(namespace_name))
63
64
let type_details = _type_details_lookup.get_all_type_details_in_ghul_namespace(namespace_name)
65
66
if !type_details? then
67
return
68
fi
69
70
for td in type_details do
71
// A globals carrier hosts a namespace's globals in IL, and
72
// import re-homes those members onto the namespace itself,
73
// so the carrier is never user-spellable. The same goes for
74
// any type the declaring compiler synthesised - a generator
75
// or async state machine's frame, a closure class - which
76
// its CompilerGeneratedAttribute names. Both stay resolvable
77
// by name; neither is offered as something to spell.
78
if td.is_globals_carrier \/ td.is_compiler_generated then
79
continue
80
fi
81
82
let dotnet_type = td.dotnet_type
83
let ghul_name = td.ghul_type_name
84
let ghul_namespace = td.ghul_namespace
85
86
if ghul_namespace =~ namespace_name then
87
if !matches.contains_key(ghul_name) then
88
let symbol_kind mut = Symbols.SymbolKind.UNDEFINED
89
let completion_kind mut = Symbols.CompletionKind.UNDEFINED
90
91
if dotnet_type.is_value_type then
92
symbol_kind = Symbols.SymbolKind.STRUCT
93
completion_kind = Symbols.CompletionKind.STRUCT
94
elif dotnet_type.is_interface then
95
symbol_kind = Symbols.SymbolKind.INTERFACE
96
completion_kind = Symbols.CompletionKind.INTERFACE
97
elif dotnet_type.is_class then
98
symbol_kind = Symbols.SymbolKind.CLASS
99
completion_kind = Symbols.CompletionKind.CLASS
100
fi
101
102
matches.add(ghul_name, COMPLETION_SYMBOL(owner, ghul_name, symbol_kind, completion_kind))
103
fi
104
elif ghul_namespace.starts_with("{namespace_name}.") then
105
let suffix = ghul_namespace.substring(namespace_name.length + 1)
106
let parts = suffix.split(['.'])
107
108
if parts.count >= 2 then
109
distinct_namespaces.add(parts[0])
110
fi
111
fi
112
od
113
114
for ns in distinct_namespaces do
115
if HIDDEN_SYMBOLS.is_hidden_namespace("{namespace_name}.{ns}") then
116
continue
117
fi
118
119
if !matches.contains_key(ns) then
120
matches.add(ns, COMPLETION_SYMBOL(owner, ns, Symbols.SymbolKind.NAMESPACE, Symbols.CompletionKind.MODULE))
121
fi
122
od
123
si
124
125
get_symbol(ghul_name: string) -> Symbols.Scoped? is
126
// Trust the by-name cache only once the name has been resolved
127
// authoritatively. A provisional single (cached by add_symbol
128
// while some type's ancestors load) is deliberately not trusted
129
// here: an arity-colliding name must fall through to the scan
130
// below so its TYPE_GROUP forms instead of the lone member
131
// shadowing it. The provisional entry stays in the store, so a
132
// recursive by-name lookup during the group's own materialisation
133
// still resolves (breaking reflected-graph cycles). The probe's
134
// bool matters: a cached null is a known no-result and must be
135
// returned as-is, not fall through to the scan (which would
136
// re-cache the no-result and throw on the duplicate key).
137
if _authoritative_names.contains(ghul_name) /\ _symbol_store.has_symbol(ghul_name) then
138
return _symbol_store.get_symbol(ghul_name)
139
fi
140
141
let type_name = TYPE_NAME(ghul_name)
142
143
// Single-pass scan: track the first matching TYPE_DETAILS
144
// without allocating; only build a LIST when a second
145
// match shows up (the rare multi-generic-count case). Keeps
146
// the common one-match path allocation-free, matching the
147
// pre-argument-count-overloading cost profile.
148
let search_list = _type_details_lookup.get_all_type_details_in_ghul_namespace(type_name.namespace_name)
149
let first: TYPE_DETAILS? mut = null
150
let all: Collections.LIST[TYPE_DETAILS]? mut = null
151
152
if search_list? then
153
for details in search_list do
154
if details.matches(type_name.namespace_name, type_name.name) then
155
if !first? then
156
first = details
157
elif !all? then
158
all = Collections.LIST[TYPE_DETAILS]()
159
all.add(first)
160
all.add(details)
161
else
162
all.add(details)
163
fi
164
fi
165
od
166
fi
167
168
if all? then
169
// Mark authoritative before materialising: a recursive by-name
170
// lookup during the group build then trusts the provisional
171
// single already in the store rather than re-entering here.
172
_authoritative_names.add(ghul_name)
173
return materialize_type_group(ghul_name, type_name, all)
174
elif first? then
175
assert first.assembly_name? /\ first.assembly_name.length > 0 else " invalid assembly name: {first}"
176
177
_authoritative_names.add(ghul_name)
178
179
// Reuse the by-dotnet-type cache if this type was already
180
// materialised (e.g. while loading another type's ancestors)
181
// rather than creating a parallel symbol for the same .NET
182
// type — otherwise the two would fail to unify (`int` vs
183
// `Ghul.int`). Cache the confirmed single under the bare name.
184
let cached = _symbol_store.get_symbol(first.dotnet_type)
185
let single: Symbols.Scoped? = if cached? then cached else create_symbol(first) fi
186
187
if single? then
188
_symbol_store.set_name_symbol(ghul_name, single)
189
fi
190
191
return single
192
fi
193
194
_authoritative_names.add(ghul_name)
195
_symbol_store.cache_no_result(ghul_name)
196
return null
197
si
198
199
// A lookup that found nothing is remembered, and so is the merged
200
// answer for a global's name. Both go when an assembly is imported
201
// into a reference set already in use, since either can change.
202
forget_no_results() is
203
for name in _symbol_store.forget_no_results() do
204
_authoritative_names.remove(name)
205
od
206
207
_merged_globals.clear()
208
si
209
210
// Search every already-materialised globals carrier in a
211
// namespace. Nothing is materialised here: this backs a lookup
212
// that runs on each missed name in the namespace, and the
213
// carrier a name does resolve to has been materialised by the
214
// caller before it gets here.
215
//
216
// Functions of the name found on more than one carrier merge into
217
// one group, so overloads a re-homed staging namespace contributes,
218
// or in two assemblies contributing to one namespace, resolve
219
// together as they do when declared in one compilation. Any other
220
// kind of symbol is taken from the first carrier that has it.
221
find_global_member(namespace_name: string, name: string) -> Symbols.Symbol? is
222
if !_type_details_lookup.has_globals_carrier(namespace_name) then
223
return null
224
fi
225
226
let key = "{namespace_name}.{name}"
227
228
if _merged_globals.contains_key(key) then
229
return _merged_globals[key]
230
fi
231
232
let search_list = _type_details_lookup.get_all_type_details_in_ghul_namespace(namespace_name)
233
234
if !search_list? then
235
return null
236
fi
237
238
let found = Collections.LIST[Symbols.Symbol]()
239
let all_materialised mut = true
240
241
for details in search_list do
242
if !details.is_globals_carrier then
243
continue
244
fi
245
246
if let carrier: Symbols.Classy = _symbol_store.get_symbol(details.dotnet_type) then
247
let member = carrier.find_member(name)
248
249
if member? then
250
found.add(member)
251
fi
252
else
253
all_materialised = false
254
fi
255
od
256
257
if found.count == 0 then
258
return null
259
fi
260
261
let result = GLOBAL_OVERLOAD_MERGE.merge(found)
262
263
// A carrier still to materialise could hold another overload,
264
// so the merged group is only fixed once every carrier has
265
// been seen.
266
if all_materialised /\ result != found[0] then
267
_merged_globals[key] = result
268
fi
269
270
return result
271
si
272
273
materialize_type_group(ghul_name: string, type_name: TYPE_NAME, all_details: Collections.LIST[TYPE_DETAILS]) -> Symbols.Scoped? is
274
// The string-keyed cache holds the group itself after all
275
// members materialize, so subsequent bare-name lookups skip
276
// the multi-materialize cost. Each member's by-dotnet-type
277
// cache entry still points to that member; only the bare
278
// ghul_name key gets replaced.
279
let owner = EMPTY_SCOPE(type_name.namespace_name)
280
let group = Symbols.TYPE_GROUP(Source.LOCATION.reflected, owner, type_name.name)
281
282
for details in all_details do
283
assert details.assembly_name? /\ details.assembly_name.length > 0 else " invalid assembly name: {details}"
284
285
// The .NET type may already have been materialized via
286
// `get_symbol(TYPE)` — reuse the cached Classy rather
287
// than creating a parallel one with a fresh EMPTY_SCOPE
288
// owner.
289
let cached = _symbol_store.get_symbol(details.dotnet_type)
290
let member: Symbols.Scoped? = if cached? then cached else create_symbol(details) fi
291
292
if isa Symbols.Classy(member) then
293
group.add(member)
294
fi
295
od
296
297
if group.count == 0 then
298
return null
299
fi
300
301
if group.count == 1 then
302
// Defensive: if only one of the materializations stuck,
303
// return the bare Classy and avoid IL-mangling it as if
304
// it had sibling generic-argument counts.
305
let only = group.classies[0]
306
only.has_argument_count_siblings = false
307
_symbol_store.set_name_symbol(ghul_name, only)
308
return only
309
fi
310
311
_symbol_store.set_name_symbol(ghul_name, group)
312
313
return group
314
si
315
316
get_symbol(type: TYPE) -> Symbols.Scoped? is
317
let result mut = _symbol_store.get_symbol(type)
318
319
if result? then
320
return result
321
fi
322
323
let type_details mut = _type_details_lookup.get_type_details_by_dotnet_type(type)
324
325
if !type_details? /\ _symbol_factory.has_variant_attribute(type) /\ type.base_type? then
326
// Cross-assembly variant: its `.NET` Namespace equals the
327
// parent union's full name and it isn't registered in
328
// the by-dotnet-type lookup (assemblies.ghul queues it
329
// for materialization-via-union instead). Trigger the
330
// parent union's creation here so `materialize_variants`
331
// populates the variant as a child of the union, then
332
// return that union-owned member symbol — falling
333
// through would route this through `create_class` and
334
// produce a detached `Symbols.VARIANT` with an
335
// `EMPTY_SCOPE` owner.
336
let parent_type mut = type.base_type!
337
if parent_type.is_generic_type /\ !parent_type.is_generic_type_definition then
338
parent_type = parent_type.get_generic_type_definition()
339
fi
340
341
let union_symbol = get_symbol(parent_type)
342
343
if isa Symbols.Classy(union_symbol) then
344
let variant_member = union_symbol.find_member(type.name)
345
if isa Symbols.Scoped(variant_member) then
346
return variant_member
347
fi
348
fi
349
fi
350
351
if !type_details? then
352
Std.error.write_line("warning: no type details for type {type} in assembly {type.assembly.get_name()}")
353
354
let asm_name = type.assembly.get_name()
355
356
// FIXME: nested type name bodge?
357
type_details =
358
TYPE_DETAILS(
359
type,
360
type.`namespace ?? "",
361
type.name,
362
null,
363
asm_name.name ?? ""
364
)
365
fi
366
367
assert type_details.assembly_name? /\ type_details.assembly_name.length > 0 else " invalid assembly name: {type_details}"
368
369
return create_symbol(type_details)
370
si
371
372
create_symbol(type_details: TYPE_DETAILS) -> Symbols.Scoped? is
373
let result = _symbol_factory.create_symbol(type_details)
374
375
if !result? then
376
@IF.debug() Std.error.write_line("not something we can handle yet: ignoring: {type}")
377
378
return null
379
fi
380
381
let dotnet_type = type_details.dotnet_type
382
383
_symbol_store.add_symbol(dotnet_type, "{type_details.ghul_namespace}.{type_details.ghul_type_name}", result)
384
385
// The symbol is registered in the store before its ancestors
386
// and members load so self-referential generics (bool's
387
// Comparable[bool], Equatable[bool], ...) resolve to it
388
// instead of recursing. The flip side: a failure in the
389
// population below leaves a permanently half-built symbol
390
// cached for the life of the process. Rethrowing does not undo
391
// that, and this runs on the lazy lookup path as well as the
392
// eager import one, so the throw surfaced from whatever member
393
// access touched the type first — as an internal error naming
394
// neither the type nor the assembly it needed.
395
try
396
_symbol_factory.add_ancestors(result, dotnet_type)
397
398
_symbol_factory.add_members(result, dotnet_type)
399
400
// When the union's symbol has just been created, also
401
// materialize each of its variants as a child member.
402
// Cross-assembly variants don't go through normal type
403
// lookup (their reflected `Namespace` is the union's
404
// full name and would clash with the union as a
405
// namespace) — assemblies.ghul stashed them keyed by
406
// the union's full name in TYPE_DETAILS_LOOKUP.
407
if let union_result: Symbols.UNION = result then
408
_symbol_factory.materialize_variants(union_result, dotnet_type)
409
fi
410
411
_symbol_factory.resolve_overrides(result)
412
catch ex: System.Exception
413
_symbol_factory.report_materialization_failure(dotnet_type, ex)
414
yrt
415
416
return result
417
si
418
si
419
si