Appearance
| 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 |