Appearance
| 1 | namespace Semantic.DotNet is | |
| 2 | use TYPE = System.Type; | |
| 3 | ||
| 4 | use System.Reflection.PathAssemblyResolver; | |
| 5 | use System.Reflection.MetadataLoadContext; | |
| 6 | use System.Reflection.MetadataAssemblyResolver; | |
| 7 | use System.Reflection.Assembly; | |
| 8 | ||
| 9 | use IO.Std; | |
| 10 | ||
| 11 | use Collections.MAP; | |
| 12 | use Collections.SET; | |
| 13 | use Collections.LIST; | |
| 14 | use Collections.Iterable; | |
| 15 | ||
| 16 | use Ghul.Pipes; | |
| 17 | ||
| 18 | use Logging.TIMERS; | |
| 19 | ||
| 20 | class ASSEMBLIES( | |
| 21 | _timers: TIMERS, | |
| 22 | _flags: Compiler.GLOBAL_BUILD_FLAGS, | |
| 23 | _paths: Driver.PATH_CONFIG, | |
| 24 | _ghul_symbol_table: Semantic.SYMBOL_TABLE, | |
| 25 | _namespaces: NAMESPACES, | |
| 26 | _type_name_map: TYPE_NAME_MAP, | |
| 27 | _type_details_lookup: TYPE_DETAILS_LOOKUP | |
| 28 | ): TypeSource is | |
| 29 | _callbacks: LIST[() -> void]; | |
| 30 | ||
| 31 | _metadata_load_context: MetadataLoadContext; | |
| 32 | ||
| 33 | _assemblies_by_name: MAP[string,Assembly]; | |
| 34 | _assemblies_by_ghul_namespace: MAP[string,SET[Assembly]]; | |
| 35 | ||
| 36 | _is_started: bool; | |
| 37 | _default_imports_are_needed: bool; | |
| 38 | ||
| 39 | blocked_assemblies: SET[string]; | |
| 40 | ||
| 41 | all_assemblies: Iterable[Assembly] => _assemblies_by_name.values; | |
| 42 | ||
| 43 | init(..) is | |
| 44 | _default_imports_are_needed = true; | |
| 45 | ||
| 46 | _callbacks = LIST(); | |
| 47 | ||
| 48 | _assemblies_by_name = MAP(); | |
| 49 | _assemblies_by_ghul_namespace = MAP[string,SET[Assembly]](); | |
| 50 | ||
| 51 | blocked_assemblies = SET(); | |
| 52 | block_all_unsupported_assemblies(); | |
| 53 | si | |
| 54 | ||
| 55 | on_start(callback: () -> void) is | |
| 56 | _callbacks.add(callback); | |
| 57 | si | |
| 58 | ||
| 59 | get_type(type_name: string) -> TYPE => | |
| 60 | get_type("System.Runtime", type_name); | |
| 61 | ||
| 62 | get_type(assembly_name: string mut, type_name: string) -> TYPE is | |
| 63 | if assembly_name == null then | |
| 64 | assembly_name = "System.Runtime"; | |
| 65 | fi | |
| 66 | ||
| 67 | let result: TYPE? = _assemblies_by_name[assembly_name].get_type(type_name); | |
| 68 | ||
| 69 | assert result? else "couldn't find type {type_name} in {assembly_name}"; | |
| 70 | ||
| 71 | return result; | |
| 72 | si | |
| 73 | ||
| 74 | get_types(type_names: Iterable[string]) -> Collections.List[TYPE] => | |
| 75 | type_names |> | |
| 76 | map(name => get_type(name)) |> | |
| 77 | collect(); | |
| 78 | ||
| 79 | get_types(assembly_and_type_names: Iterable[(assembly_name: string, type_name: string)]) -> Collections.List[TYPE] => | |
| 80 | assembly_and_type_names |> | |
| 81 | map(names => get_type(names.assembly_name, names.type_name)) |> | |
| 82 | collect(); | |
| 83 | ||
| 84 | import(assembly_names: Iterable[string]? mut) is | |
| 85 | if _is_started then | |
| 86 | return; | |
| 87 | fi | |
| 88 | ||
| 89 | _is_started = true; | |
| 90 | ||
| 91 | if !assembly_names? \/ assembly_names |> count() == 0 then | |
| 92 | let sdk_path = _paths.sdk_reference_path; | |
| 93 | ||
| 94 | if !sdk_path? then | |
| 95 | throw System.Exception("no reference assemblies supplied: pass -a <assembly> or --sdk-ref-path <directory>"); | |
| 96 | fi | |
| 97 | ||
| 98 | let default_imports = | |
| 99 | ["System.Console", "System.Runtime", "System.IO.FileSystem", "System.Collections", "System.Threading"] |> | |
| 100 | map(name => IO.Path.combine(sdk_path, "{name}.dll")) |> | |
| 101 | collect_list(); | |
| 102 | ||
| 103 | if !_flags.exclude_runtime_symbols then | |
| 104 | default_imports.add("{_paths.install_folder}ghul-runtime.dll"); | |
| 105 | fi | |
| 106 | ||
| 107 | assembly_names = default_imports; | |
| 108 | fi | |
| 109 | ||
| 110 | let path_resolver = PathAssemblyResolver(assembly_names); | |
| 111 | ||
| 112 | _metadata_load_context = MetadataLoadContext(path_resolver, null); | |
| 113 | ||
| 114 | let to_process = LIST[(assembly: Assembly, name: string, version: string)](); | |
| 115 | ||
| 116 | for path in assembly_names do | |
| 117 | let name = IO.Path.get_file_name_without_extension(path) ?? ""; | |
| 118 | ||
| 119 | if _assemblies_by_name.contains_key(name) \/ blocked_assemblies.contains(name) then | |
| 120 | continue; | |
| 121 | fi | |
| 122 | ||
| 123 | // load_from_assembly_path memory-maps the file, holding it open and | |
| 124 | // write-locked for the lifetime of the MetadataLoadContext; that blocks | |
| 125 | // rebuilds of project-reference outputs from an analysis-mode session. | |
| 126 | let assembly = _metadata_load_context.load_from_byte_array(IO.File.read_all_bytes(path)); | |
| 127 | ||
| 128 | let assembly_version = assembly.get_name().version; | |
| 129 | let version = | |
| 130 | if assembly_version? then | |
| 131 | (assembly_version.to_string() ?? "0:0:0:0").replace('.', ':') | |
| 132 | else | |
| 133 | "0:0:0:0" | |
| 134 | fi; | |
| 135 | ||
| 136 | to_process.add((assembly, name, version)); | |
| 137 | ||
| 138 | _assemblies_by_name.add(name, assembly); | |
| 139 | od | |
| 140 | ||
| 141 | _type_name_map.start(self); | |
| 142 | ||
| 143 | for c in _callbacks do | |
| 144 | c(); | |
| 145 | od | |
| 146 | ||
| 147 | for a in to_process do | |
| 148 | import(a.assembly, a.name, a.version); | |
| 149 | od | |
| 150 | si | |
| 151 | ||
| 152 | import(assembly: Assembly, assembly_name: string?, assembly_version: string) is | |
| 153 | assert assembly_name? /\ assembly_name.length > 0; | |
| 154 | ||
| 155 | let types_file = "{_paths.get_library_location(IO.Path.combine("dotnet", "refs"))}{assembly_name}.types"; | |
| 156 | ||
| 157 | let any_succeeded mut = false; | |
| 158 | let exported_ex: System.Exception? mut = null; | |
| 159 | let forwarded_ex: System.Exception? mut = null; | |
| 160 | ||
| 161 | try | |
| 162 | for type in assembly.get_types() do | |
| 163 | import_type(type, assembly_name, assembly_version); | |
| 164 | ||
| 165 | any_succeeded = true; | |
| 166 | od | |
| 167 | catch ex: System.Exception | |
| 168 | exported_ex = exported_ex; | |
| 169 | yrt | |
| 170 | ||
| 171 | try | |
| 172 | for type in assembly.get_forwarded_types() do | |
| 173 | import_type(type, assembly_name, assembly_version); | |
| 174 | ||
| 175 | any_succeeded = true; | |
| 176 | od | |
| 177 | catch ex: System.Exception | |
| 178 | forwarded_ex = ex; | |
| 179 | yrt | |
| 180 | ||
| 181 | if !any_succeeded then | |
| 182 | Std.error.write_line("warning: couldn't enumerate any types in {assembly}"); | |
| 183 | fi | |
| 184 | si | |
| 185 | ||
| 186 | // Nested `family` and `famorassem` types stay reachable, matching the | |
| 187 | // carve-out the method import makes for protected members. An enclosing | |
| 188 | // type that is out of reach puts everything nested inside it out of | |
| 189 | // reach too. | |
| 190 | _is_externally_reachable(type: TYPE) -> bool is | |
| 191 | let current: TYPE? mut = type; | |
| 192 | ||
| 193 | while current? do | |
| 194 | if current.is_nested then | |
| 195 | if !(current.is_nested_public \/ current.is_nested_family \/ current.is_nested_fam_o_r_assem) then | |
| 196 | return false; | |
| 197 | fi | |
| 198 | elif !current.is_public then | |
| 199 | return false; | |
| 200 | fi | |
| 201 | ||
| 202 | current = current.declaring_type; | |
| 203 | od | |
| 204 | ||
| 205 | return true; | |
| 206 | si | |
| 207 | ||
| 208 | import_type(type: TYPE, assembly_name: string?, assembly_version: string) is | |
| 209 | assert assembly_name? else "attempting to import type {type} with no assembly name supplied"; | |
| 210 | ||
| 211 | if !_is_externally_reachable(type) then | |
| 212 | return; | |
| 213 | fi | |
| 214 | ||
| 215 | try | |
| 216 | let existing = _type_name_map.get_type_details(type); | |
| 217 | ||
| 218 | if existing? then | |
| 219 | existing.merge_assembly_reference(type, assembly_name, assembly_version); | |
| 220 | ||
| 221 | return; | |
| 222 | elif !type.`namespace? \/ !type.full_name? then | |
| 223 | return; | |
| 224 | fi | |
| 225 | ||
| 226 | // A ghūl `union` emits each variant as a sibling class | |
| 227 | // whose .NET `Namespace` is the union's full name. Left | |
| 228 | // alone, the reflection loader would register that | |
| 229 | // string as an actual namespace and clash with the | |
| 230 | // union class itself. The compiler tags variants with | |
| 231 | // `[Ghul.Internal.VARIANT_ATTRIBUTE]`; queue them under | |
| 232 | // the union's full name so symbol_factory can | |
| 233 | // materialize each one as a member of the union when | |
| 234 | // the union's symbol is created. | |
| 235 | if has_attribute(type, "Ghul.Internal.VARIANT_ATTRIBUTE") /\ type.`namespace? then | |
| 236 | _type_details_lookup.register_variant(type.`namespace!, type); | |
| 237 | return; | |
| 238 | fi | |
| 239 | ||
| 240 | let namespace_details = _type_name_map.get_namespace_details(type); | |
| 241 | let namespace_name: string mut; | |
| 242 | ||
| 243 | if namespace_details? then | |
| 244 | namespace_name = namespace_details.ghul_name; | |
| 245 | else | |
| 246 | namespace_name = type.`namespace!; | |
| 247 | fi | |
| 248 | ||
| 249 | // The magic types are declared in `Ghul.Internal` by the | |
| 250 | // assembly that provides them, so that assembly can declare | |
| 251 | // them and still be compiled by a compiler that reflects | |
| 252 | // them: the declaration and the registration it produces | |
| 253 | // never occupy the same namespace. The language expects to | |
| 254 | // find them in `Ghul`. | |
| 255 | let is_globals_carrier = | |
| 256 | GLOBALS_CARRIER.is_carrier(type.name, has_attribute(type, GLOBALS_CARRIER.attribute_name)); | |
| 257 | ||
| 258 | if namespace_name =~ "Ghul.Internal" /\ (is_globals_carrier \/ has_attribute(type, "Ghul.Internal.INTRINSIC_ATTRIBUTE")) then | |
| 259 | namespace_name = "Ghul"; | |
| 260 | fi | |
| 261 | ||
| 262 | // Build the ghūl-visible identifier by stripping the `\`N` | |
| 263 | // generic-argument-count suffix that .NET reflection exposes | |
| 264 | // for generic-type definitions (`Foo<T>` arrives as `Foo\`1`). | |
| 265 | // For a nested type the suffix sits on each enclosing segment | |
| 266 | // (`Dictionary\`2+ValueCollection`), so the strip is applied | |
| 267 | // per `+`-separated segment before the segments are joined with | |
| 268 | // `_`. Only the ghūl-visible identifier is cleaned up; IL | |
| 269 | // emission and reflection load still see the .NET full_name | |
| 270 | // with the suffixes. | |
| 271 | let type_name mut = _strip_arity_suffix(type.name); | |
| 272 | ||
| 273 | if type.is_nested then | |
| 274 | let parts = type.full_name!.split(['.']); | |
| 275 | let leaf = parts[parts.count-1]; | |
| 276 | ||
| 277 | let buffer = System.Text.StringBuilder(); | |
| 278 | let seen_any mut = false; | |
| 279 | ||
| 280 | for segment in leaf.split(['+']) do | |
| 281 | if seen_any then | |
| 282 | buffer.append('_'); | |
| 283 | fi | |
| 284 | ||
| 285 | buffer.append(_strip_arity_suffix(segment)); | |
| 286 | seen_any = true; | |
| 287 | od | |
| 288 | ||
| 289 | type_name = buffer.to_string(); | |
| 290 | fi | |
| 291 | ||
| 292 | let type_details = TYPE_DETAILS(type, namespace_name, type_name, null, assembly_name, assembly_version); | |
| 293 | ||
| 294 | type_details.is_globals_carrier = is_globals_carrier; | |
| 295 | ||
| 296 | assert type_details.assembly_name? /\ type_details.assembly_name.length > 0 else "invalid assmbly name before add type {type_details}"; | |
| 297 | ||
| 298 | _type_details_lookup.add_type(type_details); | |
| 299 | catch ex: System.Exception | |
| 300 | Std.error.write_line("failed to import: {type} exception: {ex.to_string().replace('\n', ' ')}"); | |
| 301 | yrt | |
| 302 | si | |
| 303 | ||
| 304 | has_attribute(type: TYPE, attribute_name: string) -> bool is | |
| 305 | try | |
| 306 | for attr in type.get_custom_attributes_data() do | |
| 307 | let attr_type = attr.attribute_type; | |
| 308 | ||
| 309 | if attr_type.full_name =~ attribute_name then | |
| 310 | return true; | |
| 311 | fi | |
| 312 | od | |
| 313 | catch ex: System.Exception | |
| 314 | // Some reflected types fail at GetCustomAttributesData() | |
| 315 | // (e.g. with missing referenced assemblies). Treating | |
| 316 | // the absence as an absent marker is safe — the worst | |
| 317 | // case is the unmarked behaviour kicking in for that type. | |
| 318 | yrt | |
| 319 | ||
| 320 | return false; | |
| 321 | si | |
| 322 | ||
| 323 | _strip_arity_suffix(name: string) -> string static is | |
| 324 | let backtick = name.last_index_of('`'); | |
| 325 | ||
| 326 | if backtick <= 0 \/ backtick >= name.length - 1 then | |
| 327 | return name; | |
| 328 | fi | |
| 329 | ||
| 330 | for i in (backtick + 1)..name.length do | |
| 331 | let c = name.get_chars(i); | |
| 332 | ||
| 333 | if c < '0' \/ c > '9' then | |
| 334 | return name; | |
| 335 | fi | |
| 336 | od | |
| 337 | ||
| 338 | return name.substring(0, backtick); | |
| 339 | si | |
| 340 | ||
| 341 | block_all_unsupported_assemblies() is | |
| 342 | blocked_assemblies.add("netstandard"); | |
| 343 | blocked_assemblies.add("mscorlib"); | |
| 344 | blocked_assemblies.add("WindowsBase"); | |
| 345 | blocked_assemblies.add("System.Configuration"); | |
| 346 | blocked_assemblies.add("System.Core"); | |
| 347 | blocked_assemblies.add("System.Data"); | |
| 348 | blocked_assemblies.add("System"); | |
| 349 | blocked_assemblies.add("System.Drawing"); | |
| 350 | blocked_assemblies.add("System.Net"); | |
| 351 | blocked_assemblies.add("System.Private.CoreLib"); | |
| 352 | blocked_assemblies.add("System.Security"); | |
| 353 | blocked_assemblies.add("System.ServiceModel.Web"); | |
| 354 | blocked_assemblies.add("System.ServiceProcess"); | |
| 355 | blocked_assemblies.add("System.Transactions"); | |
| 356 | blocked_assemblies.add("System.Configuration.ConfigurationManager"); | |
| 357 | blocked_assemblies.add("System.Runtime.Serialization"); | |
| 358 | si | |
| 359 | si | |
| 360 | si |