Skip to content
← Back

src/semantic/dotnet/assemblies.ghul

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