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
init(
23
type_details_lookup: TYPE_DETAILS_LOOKUP,
24
symbol_factory: SYMBOL_FACTORY
25
) is
26
_type_details_lookup = type_details_lookup;
27
_symbol_factory = symbol_factory;
28
29
_symbol_factory.set_symbol_table(self);
30
31
_symbol_store = SYMBOL_STORE();
32
_authoritative_names = Collections.SET[string]();
33
si
34
35
find_root_matches(matches: Collections.MutableMap[string, Symbols.Symbol]) is
36
let distinct_namespaces = Collections.SET[string]();
37
38
let owner = EMPTY_SCOPE("");
39
40
for ns in _type_details_lookup.find_all_root_namespaces() do
41
if !matches.contains_key(ns) then
42
matches.add(ns, COMPLETION_SYMBOL(owner, ns, Symbols.SymbolKind.NAMESPACE, Symbols.CompletionKind.MODULE));
43
fi
44
od
45
si
46
47
find_member_matches(namespace_name: string, matches: Collections.MutableMap[string, Symbols.Symbol]) is
48
let owner = EMPTY_SCOPE(namespace_name);
49
let distinct_namespaces = Collections.SET[string](_type_details_lookup.find_all_namespaces_under(namespace_name));
50
51
let type_details = _type_details_lookup.get_all_type_details_in_ghul_namespace(namespace_name);
52
53
if !type_details? then
54
return;
55
fi
56
57
for td in type_details do
58
let dotnet_type = td.dotnet_type;
59
let ghul_name = td.ghul_type_name;
60
let ghul_namespace = td.ghul_namespace;
61
62
if ghul_namespace =~ namespace_name then
63
if !matches.contains_key(ghul_name) then
64
let symbol_kind mut = Symbols.SymbolKind.UNDEFINED;
65
let completion_kind mut = Symbols.CompletionKind.UNDEFINED;
66
67
if dotnet_type.is_value_type then
68
symbol_kind = Symbols.SymbolKind.STRUCT;
69
completion_kind = Symbols.CompletionKind.STRUCT;
70
elif dotnet_type.is_interface then
71
symbol_kind = Symbols.SymbolKind.INTERFACE;
72
completion_kind = Symbols.CompletionKind.INTERFACE;
73
elif dotnet_type.is_class then
74
symbol_kind = Symbols.SymbolKind.CLASS;
75
completion_kind = Symbols.CompletionKind.CLASS;
76
fi
77
78
matches.add(ghul_name, COMPLETION_SYMBOL(owner, ghul_name, symbol_kind, completion_kind));
79
fi
80
elif ghul_namespace.starts_with("{namespace_name}.") then
81
let suffix = ghul_namespace.substring(namespace_name.length + 1);
82
let parts = suffix.split(['.']);
83
84
if parts.count >= 2 then
85
distinct_namespaces.add(parts[0]);
86
fi
87
fi
88
od
89
90
for ns in distinct_namespaces do
91
if !matches.contains_key(ns) then
92
matches.add(ns, COMPLETION_SYMBOL(owner, ns, Symbols.SymbolKind.NAMESPACE, Symbols.CompletionKind.MODULE));
93
fi
94
od
95
si
96
97
get_symbol(ghul_name: string) -> Symbols.Scoped? is
98
// Trust the by-name cache only once the name has been resolved
99
// authoritatively. A provisional single (cached by add_symbol
100
// while some type's ancestors load) is deliberately not trusted
101
// here: an arity-colliding name must fall through to the scan
102
// below so its TYPE_GROUP forms instead of the lone member
103
// shadowing it. The provisional entry stays in the store, so a
104
// recursive by-name lookup during the group's own materialisation
105
// still resolves (breaking reflected-graph cycles). The probe's
106
// bool matters: a cached null is a known no-result and must be
107
// returned as-is, not fall through to the scan (which would
108
// re-cache the no-result and throw on the duplicate key).
109
if _authoritative_names.contains(ghul_name) /\ _symbol_store.has_symbol(ghul_name) then
110
return _symbol_store.get_symbol(ghul_name);
111
fi
112
113
let type_name = TYPE_NAME(ghul_name);
114
115
// Single-pass scan: track the first matching TYPE_DETAILS
116
// without allocating; only build a LIST when a second
117
// match shows up (the rare multi-generic-count case). Keeps
118
// the common one-match path allocation-free, matching the
119
// pre-argument-count-overloading cost profile.
120
let search_list = _type_details_lookup.get_all_type_details_in_ghul_namespace(type_name.namespace_name);
121
let first: TYPE_DETAILS? mut = null;
122
let all: Collections.LIST[TYPE_DETAILS]? mut = null;
123
124
if search_list? then
125
for details in search_list do
126
if details.matches(type_name.namespace_name, type_name.name) then
127
if !first? then
128
first = details;
129
elif !all? then
130
all = Collections.LIST[TYPE_DETAILS]();
131
all.add(first);
132
all.add(details);
133
else
134
all.add(details);
135
fi
136
fi
137
od
138
fi
139
140
if all? then
141
// Mark authoritative before materialising: a recursive by-name
142
// lookup during the group build then trusts the provisional
143
// single already in the store rather than re-entering here.
144
_authoritative_names.add(ghul_name);
145
return materialize_type_group(ghul_name, type_name, all);
146
elif first? then
147
assert first.assembly_name? /\ first.assembly_name.length > 0 else " invalid assembly name: {first}";
148
149
_authoritative_names.add(ghul_name);
150
151
// Reuse the by-dotnet-type cache if this type was already
152
// materialised (e.g. while loading another type's ancestors)
153
// rather than creating a parallel symbol for the same .NET
154
// type — otherwise the two would fail to unify (`int` vs
155
// `Ghul.int`). Cache the confirmed single under the bare name.
156
let cached = _symbol_store.get_symbol(first.dotnet_type);
157
let single: Symbols.Scoped? = if cached? then cached else create_symbol(first) fi;
158
159
if single? then
160
_symbol_store.set_name_symbol(ghul_name, single);
161
fi
162
163
return single;
164
fi
165
166
_authoritative_names.add(ghul_name);
167
_symbol_store.cache_no_result(ghul_name);
168
return null;
169
si
170
171
// Search every already-materialised globals carrier in a
172
// namespace. Nothing is materialised here: this backs a lookup
173
// that runs on each missed name in the namespace, and the
174
// carrier a name does resolve to has been materialised by the
175
// caller before it gets here.
176
find_global_member(namespace_name: string, name: string) -> Symbols.Symbol? is
177
if !_type_details_lookup.has_globals_carrier(namespace_name) then
178
return null;
179
fi
180
181
let search_list = _type_details_lookup.get_all_type_details_in_ghul_namespace(namespace_name);
182
183
if !search_list? then
184
return null;
185
fi
186
187
for details in search_list do
188
if !details.is_globals_carrier then
189
continue;
190
fi
191
192
if let carrier: Symbols.Classy = _symbol_store.get_symbol(details.dotnet_type) then
193
let member = carrier.find_member(name);
194
195
if member? then
196
return member;
197
fi
198
fi
199
od
200
201
return null;
202
si
203
204
materialize_type_group(ghul_name: string, type_name: TYPE_NAME, all_details: Collections.LIST[TYPE_DETAILS]) -> Symbols.Scoped? is
205
// The string-keyed cache holds the group itself after all
206
// members materialize, so subsequent bare-name lookups skip
207
// the multi-materialize cost. Each member's by-dotnet-type
208
// cache entry still points to that member; only the bare
209
// ghul_name key gets replaced.
210
let owner = EMPTY_SCOPE(type_name.namespace_name);
211
let group = Symbols.TYPE_GROUP(Source.LOCATION.reflected, owner, type_name.name);
212
213
for details in all_details do
214
assert details.assembly_name? /\ details.assembly_name.length > 0 else " invalid assembly name: {details}";
215
216
// The .NET type may already have been materialized via
217
// `get_symbol(TYPE)` — reuse the cached Classy rather
218
// than creating a parallel one with a fresh EMPTY_SCOPE
219
// owner.
220
let cached = _symbol_store.get_symbol(details.dotnet_type);
221
let member: Symbols.Scoped? = if cached? then cached else create_symbol(details) fi;
222
223
if isa Symbols.Classy(member) then
224
group.add(member);
225
fi
226
od
227
228
if group.count == 0 then
229
return null;
230
fi
231
232
if group.count == 1 then
233
// Defensive: if only one of the materializations stuck,
234
// return the bare Classy and avoid IL-mangling it as if
235
// it had sibling generic-argument counts.
236
let only = group.classies[0];
237
only.has_argument_count_siblings = false;
238
_symbol_store.set_name_symbol(ghul_name, only);
239
return only;
240
fi
241
242
_symbol_store.set_name_symbol(ghul_name, group);
243
244
return group;
245
si
246
247
get_symbol(type: TYPE) -> Symbols.Scoped? is
248
let result mut = _symbol_store.get_symbol(type);
249
250
if result? then
251
return result;
252
fi
253
254
let type_details mut = _type_details_lookup.get_type_details_by_dotnet_type(type);
255
256
if !type_details? /\ _symbol_factory.has_variant_attribute(type) /\ type.base_type? then
257
// Cross-assembly variant: its `.NET` Namespace equals the
258
// parent union's full name and it isn't registered in
259
// the by-dotnet-type lookup (assemblies.ghul queues it
260
// for materialization-via-union instead). Trigger the
261
// parent union's creation here so `materialize_variants`
262
// populates the variant as a child of the union, then
263
// return that union-owned member symbol — falling
264
// through would route this through `create_class` and
265
// produce a detached `Symbols.VARIANT` with an
266
// `EMPTY_SCOPE` owner.
267
let parent_type mut = type.base_type!;
268
if parent_type.is_generic_type /\ !parent_type.is_generic_type_definition then
269
parent_type = parent_type.get_generic_type_definition();
270
fi
271
272
let union_symbol = get_symbol(parent_type);
273
274
if isa Symbols.Classy(union_symbol) then
275
let variant_member = union_symbol.find_member(type.name);
276
if isa Symbols.Scoped(variant_member) then
277
return variant_member;
278
fi
279
fi
280
fi
281
282
if !type_details? then
283
Std.error.write_line("warning: no type details for type {type} in assembly {type.assembly.get_name()}");
284
285
let asm_name = type.assembly.get_name();
286
let asm_version = asm_name.version;
287
288
// FIXME: nested type name bodge?
289
type_details =
290
TYPE_DETAILS(
291
type,
292
type.`namespace ?? "",
293
type.name,
294
null,
295
asm_name.name ?? "",
296
if asm_version? then (asm_version.to_string() ?? "0:0:0:0").replace('.', ':') else "0:0:0:0" fi
297
);
298
fi
299
300
assert type_details.assembly_name? /\ type_details.assembly_name.length > 0 else " invalid assembly name: {type_details}";
301
302
return create_symbol(type_details);
303
si
304
305
create_symbol(type_details: TYPE_DETAILS) -> Symbols.Scoped? is
306
let result = _symbol_factory.create_symbol(type_details);
307
308
if !result? then
309
@IF.debug() Std.error.write_line("not something we can handle yet: ignoring: {type}");
310
311
return null;
312
fi
313
314
let dotnet_type = type_details.dotnet_type;
315
316
_symbol_store.add_symbol(dotnet_type, "{type_details.ghul_namespace}.{type_details.ghul_type_name}", result);
317
318
// The symbol is registered in the store before its ancestors
319
// and members load so self-referential generics (bool's
320
// Comparable[bool], Equatable[bool], ...) resolve to it
321
// instead of recursing. The flip side: an exception escaping
322
// the population below leaves a permanently half-built symbol
323
// cached for the life of the process, and callers up the stack
324
// swallow the exception as an ordinary per-item failure. Log
325
// it here so a member-less reflected type is diagnosable.
326
try
327
_symbol_factory.add_ancestors(result, dotnet_type);
328
329
_symbol_factory.add_members(result, dotnet_type);
330
331
// When the union's symbol has just been created, also
332
// materialize each of its variants as a child member.
333
// Cross-assembly variants don't go through normal type
334
// lookup (their reflected `Namespace` is the union's
335
// full name and would clash with the union as a
336
// namespace) — assemblies.ghul stashed them keyed by
337
// the union's full name in TYPE_DETAILS_LOOKUP.
338
if let union_result: Symbols.UNION = result then
339
_symbol_factory.materialize_variants(union_result, dotnet_type);
340
fi
341
342
_symbol_factory.resolve_overrides(result);
343
catch ex: System.Exception
344
Std.error.write_line("warning: failed to fully materialize reflected type {dotnet_type}: {ex}");
345
Std.error.flush();
346
347
throw ex;
348
yrt
349
350
return result;
351
si
352
si
353
si