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