Appearance
| 1 | namespace Semantic.DotNet is | |
| 2 | use TYPE = System.Type | |
| 3 | ||
| 4 | use System.Reflection.Assembly | |
| 5 | use System.Reflection.ConstructorInfo | |
| 6 | use System.Reflection.MethodInfo | |
| 7 | use System.Reflection.FieldInfo | |
| 8 | use System.Reflection.PropertyInfo | |
| 9 | use System.Reflection.MemberInfo | |
| 10 | use System.Reflection.ParameterInfo | |
| 11 | ||
| 12 | use System.Reflection.BindingFlags | |
| 13 | use System.Reflection.MemberTypes | |
| 14 | ||
| 15 | use System.Reflection.GenericParameterAttributes | |
| 16 | ||
| 17 | use Collections.List | |
| 18 | use Collections.MutableList | |
| 19 | use Collections.LIST | |
| 20 | use Collections.SET | |
| 21 | use Collections.MAP | |
| 22 | ||
| 23 | use Ghul.Pipes | |
| 24 | ||
| 25 | use Types.Type | |
| 26 | ||
| 27 | use Logging | |
| 28 | ||
| 29 | // The reflected types these hold cannot be looked up until the | |
| 30 | // type source has started, so start does it and the constructor | |
| 31 | // only registers the callback. | |
| 32 | @suppress("field-definite-assignment") | |
| 33 | class SYMBOL_FACTORY( | |
| 34 | _namespaces: NAMESPACES, | |
| 35 | _type_name_map: TYPE_NAME_MAP, | |
| 36 | _type_mapper: TYPE_MAPPER, | |
| 37 | _type_source: TypeSource, | |
| 38 | _type_details_lookup: TYPE_DETAILS_LOOKUP, | |
| 39 | logger: Logging.Logger private | |
| 40 | ) is | |
| 41 | _assembly_names: MAP[Assembly,string] | |
| 42 | ||
| 43 | _symbol_table: SYMBOL_TABLE | |
| 44 | ||
| 45 | _void_type: TYPE | |
| 46 | _object_type: TYPE | |
| 47 | _value_type: TYPE | |
| 48 | _array_type: TYPE | |
| 49 | _multicast_delegate_type: TYPE | |
| 50 | _bool_type: TYPE | |
| 51 | _int_type: TYPE | |
| 52 | _equatable_open_type: TYPE | |
| 53 | _comparable_open_type: TYPE | |
| 54 | _tuple_types: List[TYPE] | |
| 55 | ||
| 56 | // The types whose `<>` / `=~` is an intrinsic lowering to | |
| 57 | // opcodes (`compare.order` and friends in the runtime's | |
| 58 | // intrinsics). Their reflected interface members are hidden | |
| 59 | // from binary-operator resolution so `a < b` keeps the opcode | |
| 60 | // and its semantics — on `single`, `CompareTo` is a total | |
| 61 | // order that sorts NaN below everything, where the opcode is | |
| 62 | // the IEEE comparison. All are sealed, so the hidden member | |
| 63 | // can have no sibling overloads to shadow. | |
| 64 | // | |
| 65 | // `string` has an innate `=~` but no innate order, so it is in | |
| 66 | // the equality set only, and ordering one reaches | |
| 67 | // `System.String.CompareTo`. That comparison is culture- | |
| 68 | // sensitive: `"a" < "B"` answers true under a typical current | |
| 69 | // culture and false under invariant globalization, so a string | |
| 70 | // ordering is not machine-independent the way a scalar's is. | |
| 71 | _innate_order_type_names: SET[string] | |
| 72 | _innate_equality_type_names: SET[string] | |
| 73 | ||
| 74 | // The scalar types whose arithmetic, bitwise and shift operators | |
| 75 | // are the intrinsics. Held as the reflected types themselves, so | |
| 76 | // membership is identity rather than a rendered name. | |
| 77 | _intrinsic_operator_types: SET[TYPE] | |
| 78 | ||
| 79 | _ambiguous_method_checker: AMBIGUOUS_METHOD_CHECKER | |
| 80 | ||
| 81 | init(..) is | |
| 82 | _assembly_names = MAP[Assembly,string]() | |
| 83 | _ambiguous_method_checker = AMBIGUOUS_METHOD_CHECKER(logger) | |
| 84 | ||
| 85 | _type_source.on_start(() -> void is start(); si) | |
| 86 | si | |
| 87 | ||
| 88 | start() is | |
| 89 | _void_type = _type_source.get_type("System.Void") | |
| 90 | _object_type = _type_source.get_type("System.Object") | |
| 91 | _value_type = _type_source.get_type("System.ValueType") | |
| 92 | _array_type = _type_source.get_type("System.Array") | |
| 93 | _multicast_delegate_type = _type_source.get_type("System.MulticastDelegate") | |
| 94 | _bool_type = _type_source.get_type("System.Boolean") | |
| 95 | _int_type = _type_source.get_type("System.Int32") | |
| 96 | _equatable_open_type = _type_source.get_type("System.IEquatable`1") | |
| 97 | _comparable_open_type = _type_source.get_type("System.IComparable`1") | |
| 98 | ||
| 99 | _innate_order_type_names = SET([ | |
| 100 | "System.Char", | |
| 101 | "System.SByte", "System.Byte", | |
| 102 | "System.Int16", "System.UInt16", | |
| 103 | "System.Int32", "System.UInt32", | |
| 104 | "System.Int64", "System.UInt64", | |
| 105 | "System.IntPtr", "System.UIntPtr", | |
| 106 | "System.Single", "System.Double", | |
| 107 | "System.Decimal" | |
| 108 | ]) | |
| 109 | ||
| 110 | _innate_equality_type_names = SET([ | |
| 111 | "System.String" | |
| 112 | ]) | |
| 113 | ||
| 114 | _intrinsic_operator_types = SET(_type_source.get_types([ | |
| 115 | "System.Char", | |
| 116 | "System.SByte", "System.Byte", | |
| 117 | "System.Int16", "System.UInt16", | |
| 118 | "System.Int32", "System.UInt32", | |
| 119 | "System.Int64", "System.UInt64", | |
| 120 | "System.IntPtr", "System.UIntPtr", | |
| 121 | "System.Single", "System.Double", | |
| 122 | "System.Decimal" | |
| 123 | ])) | |
| 124 | ||
| 125 | _tuple_types = _type_source.get_types([ | |
| 126 | "System.ValueTuple`1", | |
| 127 | "System.ValueTuple`2", | |
| 128 | "System.ValueTuple`3", | |
| 129 | "System.ValueTuple`4", | |
| 130 | "System.ValueTuple`5", | |
| 131 | "System.ValueTuple`6", | |
| 132 | "System.ValueTuple`7" | |
| 133 | ]) | |
| 134 | ||
| 135 | // TODO handle more than 7-tuple types by | |
| 136 | // nesting them | |
| 137 | si | |
| 138 | ||
| 139 | set_symbol_table(symbol_table: SYMBOL_TABLE) is | |
| 140 | _symbol_table = symbol_table | |
| 141 | si | |
| 142 | ||
| 143 | // A reference set that cannot supply everything a type's members name | |
| 144 | // is a fact about the command line, so it is reported rather than | |
| 145 | // thrown: the caller keeps the partly-populated symbol, and the error | |
| 146 | // stops the build before generate-il can meet whatever the missing | |
| 147 | // members left behind. | |
| 148 | report_materialization_failure(dotnet_type: TYPE, ex: System.Exception) is | |
| 149 | _logger.environment_error( | |
| 150 | Source.LOCATION.reflected, | |
| 151 | "cannot load {dotnet_type}: {without_loader_advice(ex.message)}") | |
| 152 | si | |
| 153 | ||
| 154 | // The reflection layer's own remediation - load the assembly with | |
| 155 | // `LoadFromAssemblyPath`, or supply a resolver - addresses whoever | |
| 156 | // called it, which here is the compiler rather than the person reading | |
| 157 | // the diagnostic. The sentence before it names the assembly, which is | |
| 158 | // what they need. Dropping the advice is cosmetic on both sides: an | |
| 159 | // unrecognised message is reported whole. | |
| 160 | without_loader_advice(message: string) -> string is | |
| 161 | let advice = message.index_of("Either explicitly load") | |
| 162 | ||
| 163 | if advice < 0 then | |
| 164 | return message | |
| 165 | fi | |
| 166 | ||
| 167 | return message.substring(0, advice).trim_end() | |
| 168 | si | |
| 169 | ||
| 170 | create_symbol(type_details: TYPE_DETAILS) -> Symbols.Scoped? is | |
| 171 | try | |
| 172 | let dotnet_type = type_details.dotnet_type | |
| 173 | ||
| 174 | if dotnet_type.is_class \/ dotnet_type.is_value_type \/ dotnet_type.is_interface \/ dotnet_type.is_enum then | |
| 175 | return create_class(type_details) | |
| 176 | fi | |
| 177 | catch ex: System.Exception | |
| 178 | debug_always("create symbol failed: {type_details} exception: {ex}") | |
| 179 | ||
| 180 | throw ex | |
| 181 | yrt | |
| 182 | ||
| 183 | return null | |
| 184 | si | |
| 185 | ||
| 186 | create_class(type_details: TYPE_DETAILS) -> Symbols.Scoped is | |
| 187 | let dotnet_type = type_details.dotnet_type | |
| 188 | let ghul_type_name = type_details.ghul_type_name | |
| 189 | let assembly_name = type_details.assembly_name! | |
| 190 | let il_name mut = type_details.il_name | |
| 191 | ||
| 192 | let owner = EMPTY_SCOPE(type_details.ghul_namespace) | |
| 193 | ||
| 194 | let arguments = LIST() | |
| 195 | let argument_variances = LIST() | |
| 196 | let argument_constraint_kinds = LIST() | |
| 197 | let argument_has_constructor_constraint = LIST() | |
| 198 | let argument_type_bounds = Collections.LIST[Collections.List[Semantic.Types.Type]]() | |
| 199 | ||
| 200 | if dotnet_type.is_generic_type /\ dotnet_type.contains_generic_parameters then | |
| 201 | let argument_types = dotnet_type.get_generic_arguments() | |
| 202 | ||
| 203 | for a in argument_types do | |
| 204 | if a.is_generic_type_parameter then | |
| 205 | arguments.add(a.name) | |
| 206 | argument_variances.add(_type_mapper.map_type_argument_variance(a)) | |
| 207 | argument_constraint_kinds.add(_type_mapper.generic_parameter_constraint_kind(a)) | |
| 208 | argument_has_constructor_constraint.add(_type_mapper.generic_parameter_has_constructor_constraint(a)) | |
| 209 | argument_type_bounds.add(type_bounds(a)) | |
| 210 | fi | |
| 211 | od | |
| 212 | fi | |
| 213 | ||
| 214 | let result: Symbols.Classy mut | |
| 215 | ||
| 216 | if dotnet_type.is_enum then | |
| 217 | let enum_struct = | |
| 218 | Symbols.ENUM_STRUCT(Source.LOCATION.reflected, Source.LOCATION.reflected, owner, ghul_type_name, owner) | |
| 219 | ||
| 220 | enum_struct.mark_overrides_resolved() | |
| 221 | ||
| 222 | enum_struct.underlying_type = _type_mapper.get_type(dotnet_type.get_enum_underlying_type()) | |
| 223 | ||
| 224 | Symbols.ENUM_ORDER_OPERATOR.register(enum_struct) | |
| 225 | Symbols.ENUM_EQUALITY_OPERATOR.register(enum_struct) | |
| 226 | ||
| 227 | if type_details.has_flags_attribute then | |
| 228 | Symbols.ENUM_AND_OPERATOR.register(enum_struct) | |
| 229 | Symbols.ENUM_OR_OPERATOR.register(enum_struct) | |
| 230 | Symbols.ENUM_XOR_OPERATOR.register(enum_struct) | |
| 231 | Symbols.ENUM_NOT_OPERATOR.register(enum_struct) | |
| 232 | fi | |
| 233 | ||
| 234 | result = enum_struct | |
| 235 | elif dotnet_type.is_class /\ has_union_attribute(dotnet_type) then | |
| 236 | result = Symbols.UNION(Source.LOCATION.reflected, Source.LOCATION.reflected, owner, ghul_type_name, arguments, owner) | |
| 237 | result.mark_overrides_resolved() | |
| 238 | elif dotnet_type.is_class /\ has_variant_attribute(dotnet_type) then | |
| 239 | result = Symbols.VARIANT(Source.LOCATION.reflected, Source.LOCATION.reflected, owner, ghul_type_name, arguments, owner) | |
| 240 | result.mark_overrides_resolved() | |
| 241 | elif dotnet_type.is_class then | |
| 242 | let result_class = Symbols.CLASS(Source.LOCATION.reflected, Source.LOCATION.reflected, owner, ghul_type_name, arguments, owner) | |
| 243 | ||
| 244 | if dotnet_type == _object_type then | |
| 245 | result_class.mark_is_object() | |
| 246 | elif dotnet_type == _value_type then | |
| 247 | result_class.mark_is_root_value_type() | |
| 248 | elif dotnet_type == _array_type then | |
| 249 | result_class.mark_is_root_array_type() | |
| 250 | fi | |
| 251 | ||
| 252 | if has_closed_attribute(dotnet_type) then | |
| 253 | result_class.mark_closed() | |
| 254 | fi | |
| 255 | ||
| 256 | if _inherits_multicast_delegate(dotnet_type) then | |
| 257 | result_class.mark_delegate() | |
| 258 | fi | |
| 259 | ||
| 260 | if dotnet_type.is_abstract then | |
| 261 | result_class.mark_abstract() | |
| 262 | fi | |
| 263 | ||
| 264 | result = result_class | |
| 265 | result.mark_overrides_resolved() | |
| 266 | elif dotnet_type.is_value_type then | |
| 267 | if dotnet_type == _void_type then | |
| 268 | result = Symbols.VOID_STRUCT(Source.LOCATION.reflected, Source.LOCATION.reflected, owner, ghul_type_name, arguments, owner) | |
| 269 | result.il_is_primitive_type = true | |
| 270 | else | |
| 271 | result = Symbols.STRUCT(Source.LOCATION.reflected, Source.LOCATION.reflected, owner, ghul_type_name, arguments, owner) | |
| 272 | result.il_is_primitive_type = dotnet_type.is_primitive \/ dotnet_type == _void_type | |
| 273 | fi | |
| 274 | ||
| 275 | result.mark_overrides_resolved() | |
| 276 | elif dotnet_type.is_interface then | |
| 277 | let result_trait = Symbols.TRAIT(Source.LOCATION.reflected, Source.LOCATION.reflected, owner, ghul_type_name, arguments, owner) | |
| 278 | ||
| 279 | if has_closed_attribute(dotnet_type) then | |
| 280 | result_trait.mark_closed() | |
| 281 | fi | |
| 282 | ||
| 283 | result = result_trait | |
| 284 | else | |
| 285 | throw System.Exception("unhandled reflected kind for {dotnet_type}") | |
| 286 | fi | |
| 287 | ||
| 288 | // The attribute check alone misses a type imported from an | |
| 289 | // assembly built before this change - ghul-runtime itself | |
| 290 | // included. A type name is never operator syntax, so a | |
| 291 | // leading `$` on a reflected type is an unambiguous legacy | |
| 292 | // signal, the same reasoning add_field's fallback uses. | |
| 293 | if | |
| 294 | type_details.is_globals_carrier \/ | |
| 295 | _has_compiler_generated_attribute(dotnet_type.get_custom_attributes_data()) \/ | |
| 296 | ghul_type_name.starts_with('$') | |
| 297 | then | |
| 298 | result.mark_synthesized() | |
| 299 | fi | |
| 300 | ||
| 301 | if argument_variances.count > 0 then | |
| 302 | result.argument_variances = argument_variances | |
| 303 | fi | |
| 304 | ||
| 305 | if argument_constraint_kinds.count > 0 then | |
| 306 | result.argument_constraint_kinds = argument_constraint_kinds | |
| 307 | fi | |
| 308 | ||
| 309 | if argument_has_constructor_constraint.count > 0 then | |
| 310 | result.argument_has_constructor_constraint = argument_has_constructor_constraint | |
| 311 | fi | |
| 312 | ||
| 313 | if argument_type_bounds.count > 0 /\ argument_type_bounds |> any(l => l.count > 0) then | |
| 314 | result.argument_type_bounds = argument_type_bounds | |
| 315 | fi | |
| 316 | ||
| 317 | result.il_assembly_name = assembly_name | |
| 318 | ||
| 319 | if let builder = type_details.async_builder_type then | |
| 320 | if let classy_result: Symbols.Classy = result then | |
| 321 | classy_result.async_builder = _type_mapper.get_type(builder) | |
| 322 | fi | |
| 323 | fi | |
| 324 | ||
| 325 | if !il_name? then | |
| 326 | il_name = get_il_name(dotnet_type) | |
| 327 | fi | |
| 328 | ||
| 329 | result.il_name_override = il_name | |
| 330 | result.dotnet_full_name = dotnet_type.full_name | |
| 331 | ||
| 332 | return result | |
| 333 | si | |
| 334 | ||
| 335 | add_ancestors(symbol: Symbols.Scoped, type: TYPE) is | |
| 336 | if !isa Symbols.Classy(symbol) then | |
| 337 | return | |
| 338 | fi | |
| 339 | ||
| 340 | let result = symbol | |
| 341 | ||
| 342 | if type.base_type? then | |
| 343 | result.add_ancestor(_type_mapper.get_type(type.base_type!)) | |
| 344 | elif type.is_interface then | |
| 345 | result.add_ancestor(_type_mapper.get_type(_type_source.get_type("System.Object"))) | |
| 346 | fi | |
| 347 | ||
| 348 | // A trait `type` implements directly can carry a reference-`?` | |
| 349 | // type argument that ordinary reflection has no way to | |
| 350 | // surface (see INTERFACE_NULLABILITY) — recover it from the | |
| 351 | // assembly's raw metadata and apply it to the mapped type | |
| 352 | // before it becomes an ancestor. Empty for a trait `type` | |
| 353 | // only inherits via a base class, or one with nothing | |
| 354 | // optional in its arguments; either way the mapped type is | |
| 355 | // used as-is. | |
| 356 | let nullable_by_key = _type_source.interface_nullability_bytes(type) | |
| 357 | ||
| 358 | for i in type.get_interfaces() do | |
| 359 | let mapped mut = _type_mapper.get_type(i) | |
| 360 | ||
| 361 | let bytes: LIST[int] mut | |
| 362 | ||
| 363 | if nullable_by_key.try_get_value(Semantic.DotNet.INTERFACE_NULLABILITY.key_for(i), bytes ref) then | |
| 364 | mapped = Semantic.DotNet.NULLABILITY.apply_bytes(mapped, bytes) ?? mapped | |
| 365 | fi | |
| 366 | ||
| 367 | result.add_ancestor(mapped) | |
| 368 | od | |
| 369 | si | |
| 370 | ||
| 371 | // Reflected interfaces are the one kind that isn't marked | |
| 372 | // overrides-resolved on creation, so this is where their | |
| 373 | // inherited members get pulled down. It has to run after | |
| 374 | // `add_members`: a .NET interface that redeclares an inherited | |
| 375 | // member covariantly (`new IDerived? BaseType { get; }`) only | |
| 376 | // has its own declaration to override with once that | |
| 377 | // declaration is in scope. Pull down first and the inherited | |
| 378 | // declaration wins, hiding the derived one and turning a | |
| 379 | // resolved redeclaration into an inheritance clash. | |
| 380 | resolve_overrides(symbol: Symbols.Scoped) is | |
| 381 | if !isa Symbols.Classy(symbol) then | |
| 382 | return | |
| 383 | fi | |
| 384 | ||
| 385 | let result = symbol | |
| 386 | ||
| 387 | IoC.CONTAINER.instance.symbol_table.enter_scope(result) | |
| 388 | result.pull_down_super_symbols() | |
| 389 | IoC.CONTAINER.instance.symbol_table.leave_scope(result) | |
| 390 | si | |
| 391 | ||
| 392 | mask_gpa(a: GenericParameterAttributes, b: GenericParameterAttributes) -> GenericParameterAttributes => | |
| 393 | cast GenericParameterAttributes(cast int(a) & cast int(b)) | |
| 394 | ||
| 395 | // Every ghūl-mapped .NET type bound on this parameter (`where T : | |
| 396 | // IA, IB`), empty when the parameter is unbounded or only carries | |
| 397 | // the redundant `System.ValueType` companion of a `struct` flag. | |
| 398 | type_bounds(t: TYPE) -> Collections.List[Semantic.Types.Type] is | |
| 399 | let result = Collections.LIST[Semantic.Types.Type]() | |
| 400 | let constraint_attributes = mask_gpa(t.generic_parameter_attributes, GenericParameterAttributes.SPECIAL_CONSTRAINT_MASK) | |
| 401 | let struct_constrained = | |
| 402 | constraint_attributes.has_flag(GenericParameterAttributes.NOT_NULLABLE_VALUE_TYPE_CONSTRAINT) | |
| 403 | ||
| 404 | for c in t.get_generic_parameter_constraints() do | |
| 405 | if !(struct_constrained /\ c.full_name? /\ c.full_name =~ "System.ValueType") then | |
| 406 | result.add(_type_mapper.get_type(c)) | |
| 407 | fi | |
| 408 | od | |
| 409 | ||
| 410 | return result | |
| 411 | si | |
| 412 | ||
| 413 | // Static members of the `$globals` synthetic class are global | |
| 414 | // functions / variables / properties in the source language. Detect | |
| 415 | // the host so the importer can produce GLOBAL_FUNCTION / | |
| 416 | // GLOBAL_VARIABLE / GLOBAL_PROPERTY symbols rather than the | |
| 417 | // static-on-a-class kinds. Their owner becomes the enclosing | |
| 418 | // NAMESPACE (mirroring source-side `declare_function`) so qualified | |
| 419 | // names skip the synthetic `$globals` segment and IL emission goes | |
| 420 | // through the existing `gen_globals_class_reference` path. | |
| 421 | _globals_owner_namespace(owner: Symbols.Symbol) -> Symbols.NAMESPACE? is | |
| 422 | if !(owner.name =~ "$globals") \/ !isa Symbols.Classy(owner) then | |
| 423 | return null | |
| 424 | fi | |
| 425 | ||
| 426 | let classy_owner = owner.owner | |
| 427 | ||
| 428 | if !classy_owner? then | |
| 429 | return null | |
| 430 | fi | |
| 431 | ||
| 432 | return _namespaces.try_find_namespace(".{classy_owner.name}") | |
| 433 | si | |
| 434 | ||
| 435 | add_members(result: Symbols.Scoped, type: TYPE) is | |
| 436 | let properties = MAP() | |
| 437 | let indexers = SET() | |
| 438 | let indexer_accessors = SET() | |
| 439 | let methods = LIST() | |
| 440 | ||
| 441 | for p in type.get_properties() do | |
| 442 | add_property(result, type, properties, indexers, indexer_accessors, p) | |
| 443 | od | |
| 444 | ||
| 445 | for t in type.get_members(cast BindingFlags(64 + 4 + 32 + 16 + 8)) do | |
| 446 | add_member(result, type, properties, indexers, indexer_accessors, methods, t) | |
| 447 | od | |
| 448 | ||
| 449 | if methods.count > 0 then | |
| 450 | _ambiguous_method_checker.check(result, methods) | |
| 451 | ||
| 452 | for m in methods do | |
| 453 | // An intrinsic operator is registered on the types of | |
| 454 | // its operands rather than on the carrier that declared | |
| 455 | // it. | |
| 456 | if let home: Symbols.Classy = m.function.owner /\ isa Symbols.INNATE_FUNCTION(m.function) then | |
| 457 | home.add_member(m.function) | |
| 458 | ||
| 459 | for other in Symbols.TYPE_HOMED_OPERATORS.homes(m.function) do | |
| 460 | if other != home then | |
| 461 | other.add_member(m.function) | |
| 462 | fi | |
| 463 | od | |
| 464 | else | |
| 465 | result.add_member(m.function) | |
| 466 | fi | |
| 467 | od | |
| 468 | fi | |
| 469 | ||
| 470 | inherit_default_trait_members(result, type) | |
| 471 | si | |
| 472 | ||
| 473 | // Copy any concrete (`is_default_trait_method`) method from | |
| 474 | // a trait the class implements into the class itself, unless | |
| 475 | // the class declares a method with the same signature. | |
| 476 | // | |
| 477 | // Why this is its own pass: `mark_overrides_resolved()` runs | |
| 478 | // on reflected concrete classes in `create_class` so the | |
| 479 | // full `SYMBOL_INHERITANCE_RESOLVER` skips them — its | |
| 480 | // override-clash and abstract-method checks would otherwise | |
| 481 | // fire on .NET-side diamonds (`IList` × `IReadOnlyList`, | |
| 482 | // `MemberInfo.Module`, …) that the assembly's IL has already | |
| 483 | // resolved via explicit interface implementation but that | |
| 484 | // ghul's reflection layer can't see. Pulling down just the | |
| 485 | // trait defaults keeps the assumption-of-trust for | |
| 486 | // overrides while still making `find_member("filter")` work | |
| 487 | // on a reflected class that inherits `filter` from `Pipe[T]`. | |
| 488 | inherit_default_trait_members(result: Symbols.Scoped, type: TYPE) is | |
| 489 | if !isa Symbols.Classy(result) then | |
| 490 | return | |
| 491 | fi | |
| 492 | ||
| 493 | let classy = result | |
| 494 | ||
| 495 | for i in 0..classy.ancestors.count do | |
| 496 | let scope = classy.get_ancestor(i)?.scope | |
| 497 | ||
| 498 | if !scope? then | |
| 499 | continue | |
| 500 | fi | |
| 501 | ||
| 502 | for member in scope.symbols do | |
| 503 | inherit_default_trait_member(classy, member) | |
| 504 | od | |
| 505 | od | |
| 506 | si | |
| 507 | ||
| 508 | inherit_default_trait_member(into: Symbols.Classy, member: Symbols.Symbol) is | |
| 509 | if isa Symbols.FUNCTION_GROUP(member) then | |
| 510 | let group = member | |
| 511 | ||
| 512 | for function in group.functions do | |
| 513 | if function.is_default_trait_method /\ !declares_override_of(into, function) then | |
| 514 | into.add_member(function) | |
| 515 | fi | |
| 516 | od | |
| 517 | elif isa Symbols.Function(member) then | |
| 518 | let function = member | |
| 519 | ||
| 520 | if function.is_default_trait_method /\ !declares_override_of(into, function) then | |
| 521 | into.add_member(function) | |
| 522 | fi | |
| 523 | elif let property: Symbols.Property = member then | |
| 524 | // A property with a default body is inherited the same way, | |
| 525 | // unless the class declares one of that name itself. | |
| 526 | if let getter = property.read_function /\ getter.is_default_trait_method /\ !into.find_direct(property.name)? then | |
| 527 | into.add_member(property) | |
| 528 | fi | |
| 529 | fi | |
| 530 | si | |
| 531 | ||
| 532 | // Whether the class itself declares a method with the default | |
| 533 | // method's signature: that method is the class's implementation | |
| 534 | // of it, so the default is not inherited beside it as a second | |
| 535 | // candidate for the same call. | |
| 536 | declares_override_of(into: Symbols.Classy, default_method: Symbols.Function) -> bool static is | |
| 537 | let existing = into.find_direct(default_method.name) | |
| 538 | ||
| 539 | if let group: Symbols.FUNCTION_GROUP = existing then | |
| 540 | return group.functions |> any(f => _overrides(into, f, default_method)) | |
| 541 | elif let single: Symbols.Function = existing then | |
| 542 | return _overrides(into, single, default_method) | |
| 543 | fi | |
| 544 | ||
| 545 | return false | |
| 546 | si | |
| 547 | ||
| 548 | _overrides(into: Symbols.Classy, candidate: Symbols.Function, default_method: Symbols.Function) -> bool static => | |
| 549 | candidate != default_method /\ | |
| 550 | candidate.owner == into /\ | |
| 551 | candidate.override_class =~ default_method.override_class | |
| 552 | ||
| 553 | add_member( | |
| 554 | owner: Symbols.Scoped, | |
| 555 | type: TYPE, | |
| 556 | properties: MAP[System.Reflection.MethodInfo,PROPERTY_DETAILS], | |
| 557 | indexers: SET[System.Reflection.MethodInfo], | |
| 558 | indexer_accessors: SET[System.Reflection.MethodInfo], | |
| 559 | methods: LIST[(function: Symbols.Function, method_info: MethodInfo)], | |
| 560 | member: MemberInfo | |
| 561 | ) -> Symbols.Symbol? | |
| 562 | is | |
| 563 | let member_type = member.member_type | |
| 564 | ||
| 565 | if member_type == MemberTypes.CONSTRUCTOR then | |
| 566 | add_constructor(owner, cast ConstructorInfo?(member)!) | |
| 567 | elif member_type == MemberTypes.FIELD then | |
| 568 | add_field(owner, type, cast FieldInfo?(member)!) | |
| 569 | elif member_type == MemberTypes.METHOD then | |
| 570 | add_method(owner, type, properties, indexers, indexer_accessors, methods, cast MethodInfo?(member)!) | |
| 571 | fi | |
| 572 | return null | |
| 573 | si | |
| 574 | ||
| 575 | add_constructor(owner: Symbols.Scoped, method: ConstructorInfo) is | |
| 576 | let result: Symbols.Function mut | |
| 577 | let location = Source.LOCATION.reflected | |
| 578 | ||
| 579 | if !method.is_public /\ !_is_session_member(method) then | |
| 580 | return | |
| 581 | fi | |
| 582 | ||
| 583 | if method.is_static then | |
| 584 | result = Symbols.STATIC_METHOD(location, location, owner, "init", owner) | |
| 585 | elif owner.is_value_type then | |
| 586 | result = Symbols.STRUCT_METHOD(location, location, owner, "init", owner) | |
| 587 | elif method.is_abstract then | |
| 588 | result = Symbols.ABSTRACT_METHOD(location, location, owner, "init", owner) | |
| 589 | else | |
| 590 | result = Symbols.INSTANCE_METHOD(location, location, owner, "init", owner) | |
| 591 | fi | |
| 592 | ||
| 593 | result.il_name_override = method.name | |
| 594 | ||
| 595 | let argument_names = LIST() | |
| 596 | let argument_types = LIST() | |
| 597 | let argument_defaults = Collections.LIST[string?]() | |
| 598 | let argument_reads = Collections.LIST[bool]() | |
| 599 | let argument_writes = Collections.LIST[bool]() | |
| 600 | let argument_is_pack = Collections.LIST[bool]() | |
| 601 | let argument_pack_depth = Collections.LIST[int]() | |
| 602 | let spread_argument_index mut = -1 | |
| 603 | let has_direction_override mut = false | |
| 604 | ||
| 605 | for a in method.get_parameters() do | |
| 606 | argument_names.add(a.name ?? "") | |
| 607 | let arg_attributes = a.get_custom_attributes_data() | |
| 608 | ||
| 609 | let pack_depth = _argument_pack_depth(arg_attributes) | |
| 610 | ||
| 611 | argument_is_pack.add(pack_depth >= 0) | |
| 612 | argument_pack_depth.add(if pack_depth >= 0 then pack_depth else 0 fi) | |
| 613 | ||
| 614 | if _has_attribute_named(arg_attributes, ARGUMENT_SPREAD_ATTRIBUTE_NAME) then | |
| 615 | spread_argument_index = argument_names.count - 1 | |
| 616 | fi | |
| 617 | ||
| 618 | argument_types.add( | |
| 619 | NULLABILITY.resolve( | |
| 620 | _with_tuple_element_names(_type_mapper.get_type(a.parameter_type), arg_attributes), | |
| 621 | arg_attributes, | |
| 622 | method, | |
| 623 | a.parameter_type | |
| 624 | )! | |
| 625 | ) | |
| 626 | argument_defaults.add(_default_for_parameter(a)) | |
| 627 | argument_reads.add(!(a.is_out /\ !a.is_in)) | |
| 628 | argument_writes.add(!(a.is_in /\ !a.is_out)) | |
| 629 | if a.is_out \/ a.is_in then | |
| 630 | has_direction_override = true | |
| 631 | fi | |
| 632 | od | |
| 633 | ||
| 634 | result.argument_is_pack = argument_is_pack | |
| 635 | result.argument_pack_depth = argument_pack_depth | |
| 636 | result.spread_argument_index = spread_argument_index | |
| 637 | ||
| 638 | let return_pack_depth = _pack_depth(method.get_custom_attributes_data(), RETURN_PACK_ATTRIBUTE_NAME) | |
| 639 | ||
| 640 | if return_pack_depth >= 0 then | |
| 641 | result.return_pack_depth = return_pack_depth | |
| 642 | fi | |
| 643 | result.argument_names = argument_names | |
| 644 | result.arguments = argument_types | |
| 645 | result.argument_defaults = argument_defaults | |
| 646 | ||
| 647 | if has_direction_override then | |
| 648 | result.set_argument_directions(argument_reads, argument_writes) | |
| 649 | fi | |
| 650 | ||
| 651 | result.return_type = _type_mapper.get_type(_void_type) | |
| 652 | result.mark_pack_type_parameters() | |
| 653 | ||
| 654 | // A public parameterless constructor — what a `new()` | |
| 655 | // type-parameter constraint requires of a type argument. | |
| 656 | if argument_types.count == 0 /\ isa Symbols.Classy(owner) then | |
| 657 | (cast Symbols.Classy(owner)).has_parameterless_constructor = true | |
| 658 | fi | |
| 659 | ||
| 660 | owner.add_member(result) | |
| 661 | si | |
| 662 | ||
| 663 | add_field(owner: Symbols.Scoped, type: TYPE, `field: FieldInfo) is | |
| 664 | if !`field.is_public /\ !_is_session_member(`field) then | |
| 665 | return | |
| 666 | fi | |
| 667 | ||
| 668 | if type.is_enum /\ !`field.is_special_name then | |
| 669 | let raw_value = `field.get_raw_constant_value() | |
| 670 | let enum_member = | |
| 671 | Symbols.ENUM_STRUCT_MEMBER( | |
| 672 | Source.LOCATION.reflected, | |
| 673 | cast Symbols.ENUM_STRUCT?(owner)!, | |
| 674 | _type_name_map.get_constant_name(owner.qualified_name, `field.name, false, `field.declaring_type ?? type), | |
| 675 | if raw_value? then raw_value.to_string() ?? "" else "" fi | |
| 676 | ) | |
| 677 | ||
| 678 | owner.add_member(enum_member) | |
| 679 | ||
| 680 | return | |
| 681 | fi | |
| 682 | ||
| 683 | let result: Symbols.Field mut | |
| 684 | let location = Source.LOCATION.reflected | |
| 685 | ||
| 686 | let field_name = `field.name | |
| 687 | ||
| 688 | let name: string? mut = null | |
| 689 | ||
| 690 | if field_name.length == 5 /\ `field.name.starts_with("Item") /\ type.is_generic_type then | |
| 691 | let unspecialized_type = type.get_generic_type_definition() | |
| 692 | ||
| 693 | let is_tuple mut = false | |
| 694 | ||
| 695 | for t in _tuple_types do | |
| 696 | if unspecialized_type == t then | |
| 697 | is_tuple = true | |
| 698 | break | |
| 699 | fi | |
| 700 | od | |
| 701 | ||
| 702 | if is_tuple then | |
| 703 | name = "{(cast int(field_name[field_name.length-1]) - 49)}" | |
| 704 | fi | |
| 705 | fi | |
| 706 | ||
| 707 | if !name? then | |
| 708 | name = _type_name_map.get_member_name(owner.qualified_name, `field.name, false, false, `field.declaring_type ?? type) | |
| 709 | fi | |
| 710 | ||
| 711 | // For inherited fields, the declaring symbol must point at the base | |
| 712 | // class, not the derived class — otherwise the IL fieldref says | |
| 713 | // "Foo::SubjectField" instead of "FooBase`2<...>::SubjectField" and | |
| 714 | // the runtime can't find the field. | |
| 715 | let declaring_symbol: Symbols.Symbol mut = owner | |
| 716 | let dt = `field.declaring_type | |
| 717 | if dt? /\ dt != type then | |
| 718 | declaring_symbol = _type_mapper.get_type(dt).symbol | |
| 719 | fi | |
| 720 | ||
| 721 | if `field.is_static then | |
| 722 | let globals_namespace = _globals_owner_namespace(owner) | |
| 723 | let constant = REFLECTED_CONSTANTS.value_of(`field) | |
| 724 | ||
| 725 | if globals_namespace? then | |
| 726 | let global = Symbols.GLOBAL_VARIABLE(location, globals_namespace, name) | |
| 727 | global.il_carrier = cast Symbols.Classy?(owner)! | |
| 728 | result = global | |
| 729 | elif isa ReflectedConstant.ABSENT(constant) then | |
| 730 | result = Symbols.CONSTANT_FIELD(location, declaring_symbol, name, null) | |
| 731 | elif let value: ReflectedConstant.VALUE = constant then | |
| 732 | result = Symbols.CONSTANT_FIELD(location, declaring_symbol, name, value.text) | |
| 733 | else | |
| 734 | result = Symbols.STATIC_FIELD(location, declaring_symbol, name) | |
| 735 | fi | |
| 736 | elif type.is_value_type then | |
| 737 | result = Symbols.STRUCT_FIELD(location, declaring_symbol, name) | |
| 738 | else | |
| 739 | result = Symbols.INSTANCE_FIELD(location, declaring_symbol, name) | |
| 740 | fi | |
| 741 | ||
| 742 | result.il_name_override = `field.name | |
| 743 | ||
| 744 | let field_attributes = `field.get_custom_attributes_data() | |
| 745 | ||
| 746 | // The attribute check alone misses a field imported from an | |
| 747 | // assembly built by a compiler that predates it. A field name | |
| 748 | // is never operator syntax - only a function can be named with | |
| 749 | // one - so a leading `$` on a field carries no ambiguity and is | |
| 750 | // a safe legacy fallback, the same shape as | |
| 751 | // GLOBALS_CARRIER.is_carrier's attribute-or-name check. | |
| 752 | if _has_compiler_generated_attribute(field_attributes) \/ name.starts_with('$') then | |
| 753 | result.mark_synthesized() | |
| 754 | fi | |
| 755 | ||
| 756 | result.set_type( | |
| 757 | NULLABILITY.resolve( | |
| 758 | _with_tuple_element_names(_type_mapper.get_type(`field.field_type), field_attributes), | |
| 759 | field_attributes, | |
| 760 | `field, | |
| 761 | `field.field_type | |
| 762 | )! | |
| 763 | ) | |
| 764 | ||
| 765 | // Same problem as add_method: inherited fields on a constructed | |
| 766 | // generic base have their declared type returned by reflection in | |
| 767 | // *substituted* form. Recover the open form via the open base type | |
| 768 | // and stash it as unspecialized_type; gen_reference prefers it. | |
| 769 | if dt? /\ dt != type /\ dt.is_generic_type /\ !dt.is_generic_type_definition then | |
| 770 | let open_type = dt.get_generic_type_definition() | |
| 771 | let open_field: FieldInfo? mut = null | |
| 772 | ||
| 773 | for m in open_type.get_members(cast BindingFlags(2 + 4 + 8 + 16 + 32)) do | |
| 774 | if m.member_type == MemberTypes.FIELD then | |
| 775 | let candidate = cast FieldInfo?(m)! | |
| 776 | if candidate.metadata_token == `field.metadata_token then | |
| 777 | open_field = candidate | |
| 778 | break | |
| 779 | fi | |
| 780 | fi | |
| 781 | od | |
| 782 | ||
| 783 | if open_field? then | |
| 784 | result.unspecialized_type = _type_mapper.get_type(open_field.field_type) | |
| 785 | fi | |
| 786 | fi | |
| 787 | ||
| 788 | owner.add_member(result) | |
| 789 | si | |
| 790 | ||
| 791 | // `family` and `famorassem` are deliberately absent: a subclass in | |
| 792 | // another assembly is entitled to a protected member. | |
| 793 | _is_reachable_from_other_assembly(method: MethodInfo) -> bool => | |
| 794 | !(method.is_private \/ method.is_assembly \/ method.is_family_and_assembly) \/ | |
| 795 | _is_session_member(method) | |
| 796 | ||
| 797 | // A member an earlier cell of the session declared, other than one | |
| 798 | // the compiler generated for it: the session is one assembly, so | |
| 799 | // its assembly-private members are as visible as they would be in | |
| 800 | // one file, and the ordinary access rules decide who may use them. | |
| 801 | _is_session_member(member: System.Reflection.MemberInfo) -> bool is | |
| 802 | let declaring = member.declaring_type | |
| 803 | ||
| 804 | return | |
| 805 | declaring? /\ !member.name.contains('$') /\ | |
| 806 | IoC.CONTAINER.instance.assemblies.can_reach_privates_of(declaring.assembly) | |
| 807 | si | |
| 808 | ||
| 809 | // The name of the type's indexer property, as named by | |
| 810 | // `DefaultMemberAttribute`. A type with no marker cannot have | |
| 811 | // an indexer, but the name an indexer would take is still the | |
| 812 | // right answer for the accessor-naming comparison. | |
| 813 | _default_member_name(type: TYPE) -> string is | |
| 814 | for attr in type.get_custom_attributes_data() do | |
| 815 | if attr.attribute_type.full_name =~ "System.Reflection.DefaultMemberAttribute" then | |
| 816 | for argument in attr.constructor_arguments do | |
| 817 | let name = cast string?(argument.value) | |
| 818 | ||
| 819 | if name? then | |
| 820 | return name | |
| 821 | fi | |
| 822 | od | |
| 823 | fi | |
| 824 | od | |
| 825 | ||
| 826 | return Symbols.INDEXER_NAMES.property_name | |
| 827 | si | |
| 828 | ||
| 829 | add_property( | |
| 830 | owner: Symbols.Scoped, | |
| 831 | type: TYPE, | |
| 832 | properties: MAP[System.Reflection.MethodInfo,PROPERTY_DETAILS], | |
| 833 | indexers: SET[System.Reflection.MethodInfo], | |
| 834 | indexer_accessors: SET[System.Reflection.MethodInfo], | |
| 835 | property: PropertyInfo | |
| 836 | ) is | |
| 837 | let index_parameters = property.get_index_parameters() | |
| 838 | ||
| 839 | if index_parameters.count > 0 then | |
| 840 | // An indexed property is *the* indexer when the declaring | |
| 841 | // type nominates it. A type can carry others that indexing | |
| 842 | // must not reach — but one already named `Item` was | |
| 843 | // reachable before the nomination was consulted, so it | |
| 844 | // stays reachable whether or not it is the nominated one. | |
| 845 | let is_indexer = | |
| 846 | property.name =~ _default_member_name(property.declaring_type ?? type) \/ | |
| 847 | property.name =~ Symbols.INDEXER_NAMES.property_name | |
| 848 | ||
| 849 | let getter = property.get_get_method() | |
| 850 | if getter? then | |
| 851 | indexers.add(getter) | |
| 852 | ||
| 853 | if is_indexer then | |
| 854 | indexer_accessors.add(getter) | |
| 855 | fi | |
| 856 | fi | |
| 857 | ||
| 858 | let setter = property.get_set_method() | |
| 859 | if setter? then | |
| 860 | indexers.add(setter) | |
| 861 | ||
| 862 | if is_indexer then | |
| 863 | indexer_accessors.add(setter) | |
| 864 | fi | |
| 865 | fi | |
| 866 | ||
| 867 | return | |
| 868 | fi | |
| 869 | ||
| 870 | if property.name =~ "Current" then | |
| 871 | if property.declaring_type == _type_source.get_type("System.Collections.IEnumerator") then | |
| 872 | return | |
| 873 | fi | |
| 874 | fi | |
| 875 | ||
| 876 | let result: Symbols.Property mut | |
| 877 | let name = | |
| 878 | _ghul_name_override(property) ?? | |
| 879 | _type_name_map.get_member_name(owner.qualified_name, property.name, false, property.is_special_name, property.declaring_type ?? type) | |
| 880 | ||
| 881 | let getter = property.get_method | |
| 882 | let setter = property.set_method | |
| 883 | ||
| 884 | if setter? /\ !getter? then | |
| 885 | return | |
| 886 | fi | |
| 887 | ||
| 888 | let is_static mut = false | |
| 889 | ||
| 890 | if getter? /\ getter.is_static then | |
| 891 | is_static = true | |
| 892 | elif setter? /\ setter.is_static then | |
| 893 | is_static = true | |
| 894 | fi | |
| 895 | ||
| 896 | if is_static then | |
| 897 | let globals_namespace = _globals_owner_namespace(owner) | |
| 898 | if globals_namespace? then | |
| 899 | result = Symbols.GLOBAL_PROPERTY(Source.LOCATION.reflected, Source.LOCATION.reflected, globals_namespace, name, setter?) | |
| 900 | else | |
| 901 | result = Symbols.STATIC_PROPERTY(Source.LOCATION.reflected, Source.LOCATION.reflected, owner, name, setter?, false) | |
| 902 | fi | |
| 903 | else | |
| 904 | result = Symbols.INSTANCE_PROPERTY(Source.LOCATION.reflected, Source.LOCATION.reflected, owner, name, setter?, false) | |
| 905 | fi | |
| 906 | ||
| 907 | result.set_type( | |
| 908 | _with_property_tuple_element_names( | |
| 909 | NULLABILITY.resolve( | |
| 910 | _type_mapper.get_type(property.property_type), | |
| 911 | property.get_custom_attributes_data(), | |
| 912 | property, | |
| 913 | property.property_type | |
| 914 | )!, | |
| 915 | property | |
| 916 | ) | |
| 917 | ) | |
| 918 | result.il_name_override = property.name | |
| 919 | ||
| 920 | // Not CompilerGeneratedAttribute here: the C# compiler puts it | |
| 921 | // on a record's public surface too (EqualityContract is | |
| 922 | // protected, but a member reachable through inheritance still | |
| 923 | // has to stay visible where it's reachable at all), so it | |
| 924 | // does not distinguish hidden from ordinary API the way it | |
| 925 | // does for a field or a type. is_special_name is the CLR's | |
| 926 | // own narrower signal for a property that is not ordinary | |
| 927 | // API. | |
| 928 | if property.is_special_name then | |
| 929 | result.mark_synthesized() | |
| 930 | fi | |
| 931 | ||
| 932 | owner.add_member(result) | |
| 933 | ||
| 934 | let property_details = PROPERTY_DETAILS(result, property.get_get_method(), property.get_set_method()) | |
| 935 | ||
| 936 | if getter? then | |
| 937 | properties[getter] = property_details | |
| 938 | fi | |
| 939 | ||
| 940 | if setter? then | |
| 941 | properties[setter] = property_details | |
| 942 | fi | |
| 943 | si | |
| 944 | ||
| 945 | add_method( | |
| 946 | owner: Symbols.Scoped, | |
| 947 | type: TYPE, | |
| 948 | properties: MAP[System.Reflection.MethodInfo,PROPERTY_DETAILS], | |
| 949 | indexers: SET[System.Reflection.MethodInfo], | |
| 950 | indexer_accessors: SET[System.Reflection.MethodInfo], | |
| 951 | methods: LIST[(function: Symbols.Function, method_info: MethodInfo)], | |
| 952 | method: MethodInfo | |
| 953 | ) is | |
| 954 | add_method(owner, type, properties, indexers, indexer_accessors, methods, method, null) | |
| 955 | si | |
| 956 | ||
| 957 | // `force_name`, when non-null, supplies the ghūl name instead of | |
| 958 | // deriving one from the method. Used to surface a single | |
| 959 | // reflected method under a second name: the derivation is | |
| 960 | // skipped so the alias cannot re-trigger the side effects some | |
| 961 | // of its branches carry. | |
| 962 | // | |
| 963 | // An alias of an abstract interface member is built as a | |
| 964 | // default trait method rather than an abstract one. Only the | |
| 965 | // primary name carries the obligation to implement, so a type | |
| 966 | // satisfies the interface by supplying that one member, while | |
| 967 | // both names remain callable. | |
| 968 | add_method( | |
| 969 | owner: Symbols.Scoped, | |
| 970 | type: TYPE, | |
| 971 | properties: MAP[System.Reflection.MethodInfo,PROPERTY_DETAILS], | |
| 972 | indexers: SET[System.Reflection.MethodInfo], | |
| 973 | indexer_accessors: SET[System.Reflection.MethodInfo], | |
| 974 | methods: LIST[(function: Symbols.Function, method_info: MethodInfo)], | |
| 975 | method: MethodInfo, | |
| 976 | force_name: string? | |
| 977 | ) is | |
| 978 | if !_is_reachable_from_other_assembly(method) then | |
| 979 | return | |
| 980 | fi | |
| 981 | ||
| 982 | let result: Symbols.Function mut | |
| 983 | let location = Source.LOCATION.reflected | |
| 984 | ||
| 985 | let name: string mut | |
| 986 | let hide_from_operator_resolution mut = false | |
| 987 | ||
| 988 | // Set when this method is declared under a name the compiler | |
| 989 | // itself picked ($get_iterator below) rather than one the | |
| 990 | // reflected metadata names it, so it never appears as a | |
| 991 | // second, ordinarily-callable member of the reflected type. | |
| 992 | let is_synthesized_name mut = false | |
| 993 | ||
| 994 | // Set when the method is also to be reached under its | |
| 995 | // ordinary de-camelled name, alongside the operator | |
| 996 | // spelling picked below. | |
| 997 | let alias_name: string? mut = null | |
| 998 | ||
| 999 | let property_details: PROPERTY_DETAILS mut | |
| 1000 | ||
| 1001 | if force_name? then | |
| 1002 | name = force_name | |
| 1003 | elif properties.try_get_value(method, property_details ref) then | |
| 1004 | name = | |
| 1005 | _ghul_name_override(method) ?? | |
| 1006 | _type_name_map.get_member_name(owner.qualified_name, method.name, method.is_private, true, method.declaring_type ?? type) | |
| 1007 | is_synthesized_name = true | |
| 1008 | elif indexer_accessors.contains(method) then | |
| 1009 | // Declared only under the indexer names, whatever | |
| 1010 | // metadata calls the accessor. An indexer is reached by | |
| 1011 | // indexing and by nothing else, so the metadata spelling | |
| 1012 | // is deliberately not surfaced as a second member. | |
| 1013 | name = Symbols.INDEXER_NAMES.accessor(method.name) | |
| 1014 | elif method.name =~ "Equals" /\ _is_self_relational_interface_target(type, method, _equatable_open_type, _bool_type) then | |
| 1015 | name = "=~" | |
| 1016 | alias_name = _plain_member_name(owner, type, indexers, method) | |
| 1017 | hide_from_operator_resolution = type.full_name? /\ _innate_equality_type_names.contains(type.full_name) | |
| 1018 | elif method.name =~ "CompareTo" /\ _is_self_relational_interface_target(type, method, _comparable_open_type, _int_type) then | |
| 1019 | name = "<>" | |
| 1020 | alias_name = _plain_member_name(owner, type, indexers, method) | |
| 1021 | hide_from_operator_resolution = type.full_name? /\ _innate_order_type_names.contains(type.full_name) | |
| 1022 | elif method.name =~ "get_Current" /\ method.declaring_type == _type_source.get_type("System.Collections.IEnumerator") then | |
| 1023 | return | |
| 1024 | elif method.name =~ "GetEnumerator" then | |
| 1025 | if method.declaring_type == _type_source.get_type("System.Collections.IEnumerable") then | |
| 1026 | return | |
| 1027 | fi | |
| 1028 | ||
| 1029 | name = "$get_iterator" | |
| 1030 | is_synthesized_name = true | |
| 1031 | ||
| 1032 | let iterator_property = Symbols.INSTANCE_PROPERTY(Source.LOCATION.reflected, Source.LOCATION.reflected, owner, "iterator", false, false) | |
| 1033 | iterator_property.set_type(_type_mapper.get_type(method.return_type)) | |
| 1034 | ||
| 1035 | owner.add_member(iterator_property) | |
| 1036 | ||
| 1037 | property_details = PROPERTY_DETAILS(iterator_property, method, null) | |
| 1038 | elif | |
| 1039 | method.is_static /\ | |
| 1040 | let mdt0 = method.declaring_type in mdt0? /\ | |
| 1041 | _generic_math_operator_name(method.name)? /\ | |
| 1042 | ( | |
| 1043 | (method.is_virtual /\ mdt0.is_interface) \/ | |
| 1044 | _declares_own_operators(mdt0) | |
| 1045 | ) | |
| 1046 | then | |
| 1047 | name = _generic_math_operator_name(method.name)! | |
| 1048 | else | |
| 1049 | name = _plain_member_name(owner, type, indexers, method) | |
| 1050 | ||
| 1051 | // A .NET special name - an operator, an event accessor - | |
| 1052 | // is reached through its dedicated syntax, never under | |
| 1053 | // any name, whatever assembly declared it. The flag on | |
| 1054 | // the row is the upstream fact; the $ spelling | |
| 1055 | // get_member_name derives from it is presentation. | |
| 1056 | // | |
| 1057 | // The test repeats the two carve-outs _plain_member_name | |
| 1058 | // applies, so the verdict and the name stay in step: an | |
| 1059 | // indexer accessor is named under its indexer names, and | |
| 1060 | // a ghūl name override replaces the derived spelling | |
| 1061 | // outright - either way there is no $ to hide. | |
| 1062 | if | |
| 1063 | method.is_special_name /\ | |
| 1064 | !indexers.contains(method) /\ | |
| 1065 | !_ghul_name_override(method)? | |
| 1066 | then | |
| 1067 | is_synthesized_name = true | |
| 1068 | fi | |
| 1069 | fi | |
| 1070 | ||
| 1071 | let declaring_symbol: Symbols.Symbol mut = owner | |
| 1072 | ||
| 1073 | let mdt = method.declaring_type | |
| 1074 | if mdt? /\ mdt != type then | |
| 1075 | declaring_symbol = _type_mapper.get_type(mdt).symbol | |
| 1076 | fi | |
| 1077 | ||
| 1078 | let intrinsic_operation = _intrinsic_operation(method.get_custom_attributes_data()) | |
| 1079 | ||
| 1080 | if intrinsic_operation? then | |
| 1081 | let globals_namespace = if method.is_static then _globals_owner_namespace(owner) else null fi | |
| 1082 | ||
| 1083 | if globals_namespace? then | |
| 1084 | // Innate calls lower to opcodes inline and never emit a | |
| 1085 | // methodref, so an innate function needs no il_assembly_name. | |
| 1086 | let home = _intrinsic_home(name, method) | |
| 1087 | ||
| 1088 | if home? then | |
| 1089 | result = Symbols.INNATE_FUNCTION(location, home, name, home, intrinsic_operation) | |
| 1090 | else | |
| 1091 | result = Symbols.INNATE_FUNCTION(location, globals_namespace, name, globals_namespace, intrinsic_operation) | |
| 1092 | fi | |
| 1093 | else | |
| 1094 | result = Symbols.INNATE_METHOD(location, declaring_symbol, name, owner, intrinsic_operation) | |
| 1095 | fi | |
| 1096 | elif method.is_static then | |
| 1097 | let globals_namespace = _globals_owner_namespace(owner) | |
| 1098 | if globals_namespace? then | |
| 1099 | let global = Symbols.GLOBAL_FUNCTION(location, location, globals_namespace, name, globals_namespace) | |
| 1100 | global.il_carrier = cast Symbols.Classy?(owner)! | |
| 1101 | result = global | |
| 1102 | else | |
| 1103 | let static_method = Symbols.STATIC_METHOD(location, location, declaring_symbol, name, owner) | |
| 1104 | ||
| 1105 | // A static virtual/abstract interface member - | |
| 1106 | // .NET's generic-math feature. Reachable through a | |
| 1107 | // bound type parameter, which needs the | |
| 1108 | // `constrained.` call shape; an ordinary reflected | |
| 1109 | // static method never is. | |
| 1110 | static_method.is_static_interface_virtual = method.is_virtual /\ mdt? /\ mdt.is_interface | |
| 1111 | ||
| 1112 | static_method.cannot_be_overridden = !static_method.is_static_interface_virtual | |
| 1113 | ||
| 1114 | result = static_method | |
| 1115 | fi | |
| 1116 | elif mdt? /\ mdt.is_value_type then | |
| 1117 | result = Symbols.STRUCT_METHOD(location, location, declaring_symbol, name, owner) | |
| 1118 | ||
| 1119 | result.cannot_be_overridden = true | |
| 1120 | elif method.is_abstract /\ !force_name? then | |
| 1121 | result = Symbols.ABSTRACT_METHOD(location, location, declaring_symbol, name, owner) | |
| 1122 | elif mdt? /\ mdt.is_interface then | |
| 1123 | result = Symbols.DEFAULT_TRAIT_METHOD(location, location, declaring_symbol, name, owner) | |
| 1124 | else | |
| 1125 | result = Symbols.INSTANCE_METHOD(location, location, declaring_symbol, name, owner) | |
| 1126 | ||
| 1127 | // A non-virtual or final CLR method binds to exactly | |
| 1128 | // one body, and so does any method of a sealed owner: | |
| 1129 | // no subclass exists to override it in. | |
| 1130 | result.cannot_be_overridden = | |
| 1131 | !method.is_virtual \/ method.is_final \/ (mdt? /\ mdt.is_sealed) | |
| 1132 | fi | |
| 1133 | ||
| 1134 | if method.is_generic_method then | |
| 1135 | result.is_generic = true | |
| 1136 | ||
| 1137 | let generic_argument_names = LIST() | |
| 1138 | let generic_arguments = LIST() | |
| 1139 | let generic_argument_constraint_kinds = Collections.LIST[Symbols.TypeParameterConstraintKind]() | |
| 1140 | let generic_argument_has_constructor_constraint = Collections.LIST[bool]() | |
| 1141 | let generic_argument_type_bounds = Collections.LIST[Collections.List[Semantic.Types.Type]]() | |
| 1142 | ||
| 1143 | let ga = method.get_generic_arguments() | |
| 1144 | ||
| 1145 | for p in ga do | |
| 1146 | generic_argument_names.add(p.name) | |
| 1147 | generic_arguments.add(_type_mapper.get_type(p)) | |
| 1148 | generic_argument_constraint_kinds.add(_type_mapper.generic_parameter_constraint_kind(p)) | |
| 1149 | generic_argument_has_constructor_constraint.add(_type_mapper.generic_parameter_has_constructor_constraint(p)) | |
| 1150 | generic_argument_type_bounds.add(type_bounds(p)) | |
| 1151 | od | |
| 1152 | ||
| 1153 | result.generic_argument_names = generic_argument_names | |
| 1154 | result.generic_arguments = generic_arguments | |
| 1155 | result.generic_argument_constraint_kinds = generic_argument_constraint_kinds | |
| 1156 | result.generic_argument_has_constructor_constraint = generic_argument_has_constructor_constraint | |
| 1157 | ||
| 1158 | if generic_argument_type_bounds.count > 0 /\ generic_argument_type_bounds |> any(l => l.count > 0) then | |
| 1159 | result.generic_argument_type_bounds = generic_argument_type_bounds | |
| 1160 | fi | |
| 1161 | fi | |
| 1162 | ||
| 1163 | result.il_name_override = method.name | |
| 1164 | result.is_hidden_from_operator_resolution = hide_from_operator_resolution | |
| 1165 | ||
| 1166 | // Not CompilerGeneratedAttribute on a foreign assembly: the C# | |
| 1167 | // compiler puts it on a record's ToString, Equals, GetHashCode, | |
| 1168 | // Deconstruct and equality operators too - ordinary callable | |
| 1169 | // API, not hidden members - so unlike a field or a type it does | |
| 1170 | // not distinguish the two there. On a ghūl-compiled assembly it | |
| 1171 | // is authoritative: the emitter marks what it synthesises - | |
| 1172 | // property accessors, lambda bodies compiled onto their | |
| 1173 | // enclosing type as $anon members, state-machine frame members, | |
| 1174 | // the Object.Equals bridge - and marks nothing a user wrote, | |
| 1175 | // since an authored member is either public and unmarked or | |
| 1176 | // invisible past the assembly boundary. Reading it recognises | |
| 1177 | // those shapes whatever name they travel under, and leaves an | |
| 1178 | // author's own `$` spelling alone. | |
| 1179 | if | |
| 1180 | is_synthesized_name \/ | |
| 1181 | ( | |
| 1182 | _type_name_map.is_ghul_compiled(method.declaring_type ?? type) /\ | |
| 1183 | _has_compiler_generated_attribute(method.get_custom_attributes_data()) | |
| 1184 | ) | |
| 1185 | then | |
| 1186 | result.mark_synthesized() | |
| 1187 | fi | |
| 1188 | ||
| 1189 | if method.is_family \/ method.is_family_or_assembly then | |
| 1190 | result.mark_imported_protected() | |
| 1191 | fi | |
| 1192 | ||
| 1193 | if _has_pure_attribute(method.get_custom_attributes_data()) then | |
| 1194 | // the marker carries the declared-pure contract: the | |
| 1195 | // function is trusted store-free, and local overrides | |
| 1196 | // are held to the pure-override contract here in the | |
| 1197 | // overriding assembly | |
| 1198 | result.mark_declared_pure() | |
| 1199 | fi | |
| 1200 | ||
| 1201 | if _has_stable_attribute(method.get_custom_attributes_data()) then | |
| 1202 | // the marker carries the declared-stable contract of | |
| 1203 | // the property this accessor reads: facts narrowed | |
| 1204 | // through it count as backed, and local overrides are | |
| 1205 | // held to the stable-override contract here in the | |
| 1206 | // overriding assembly | |
| 1207 | result.mark_declared_stable() | |
| 1208 | fi | |
| 1209 | ||
| 1210 | let argument_names = LIST() | |
| 1211 | let argument_types = LIST() | |
| 1212 | let argument_defaults = Collections.LIST[string?]() | |
| 1213 | let argument_reads = Collections.LIST[bool]() | |
| 1214 | let argument_writes = Collections.LIST[bool]() | |
| 1215 | let argument_is_pack = Collections.LIST[bool]() | |
| 1216 | let argument_pack_depth = Collections.LIST[int]() | |
| 1217 | let spread_argument_index mut = -1 | |
| 1218 | let has_direction_override mut = false | |
| 1219 | ||
| 1220 | for a in method.get_parameters() do | |
| 1221 | argument_names.add(a.name ?? "") | |
| 1222 | let arg_attributes = a.get_custom_attributes_data() | |
| 1223 | ||
| 1224 | let pack_depth = _argument_pack_depth(arg_attributes) | |
| 1225 | ||
| 1226 | argument_is_pack.add(pack_depth >= 0) | |
| 1227 | argument_pack_depth.add(if pack_depth >= 0 then pack_depth else 0 fi) | |
| 1228 | ||
| 1229 | if _has_attribute_named(arg_attributes, ARGUMENT_SPREAD_ATTRIBUTE_NAME) then | |
| 1230 | spread_argument_index = argument_names.count - 1 | |
| 1231 | fi | |
| 1232 | ||
| 1233 | argument_types.add( | |
| 1234 | _with_pure_function( | |
| 1235 | NULLABILITY.resolve( | |
| 1236 | _with_tuple_element_names(_type_mapper.get_type(a.parameter_type), arg_attributes), | |
| 1237 | arg_attributes, | |
| 1238 | method, | |
| 1239 | a.parameter_type | |
| 1240 | )!, | |
| 1241 | arg_attributes | |
| 1242 | ) | |
| 1243 | ) | |
| 1244 | argument_defaults.add(_default_for_parameter(a)) | |
| 1245 | argument_reads.add(!(a.is_out /\ !a.is_in)) | |
| 1246 | argument_writes.add(!(a.is_in /\ !a.is_out)) | |
| 1247 | if a.is_out \/ a.is_in then | |
| 1248 | has_direction_override = true | |
| 1249 | fi | |
| 1250 | od | |
| 1251 | ||
| 1252 | result.argument_is_pack = argument_is_pack | |
| 1253 | result.argument_pack_depth = argument_pack_depth | |
| 1254 | result.spread_argument_index = spread_argument_index | |
| 1255 | ||
| 1256 | let return_pack_depth = _pack_depth(method.get_custom_attributes_data(), RETURN_PACK_ATTRIBUTE_NAME) | |
| 1257 | ||
| 1258 | if return_pack_depth >= 0 then | |
| 1259 | result.return_pack_depth = return_pack_depth | |
| 1260 | fi | |
| 1261 | result.argument_names = argument_names | |
| 1262 | result.arguments = argument_types | |
| 1263 | result.argument_defaults = argument_defaults | |
| 1264 | ||
| 1265 | if has_direction_override then | |
| 1266 | result.set_argument_directions(argument_reads, argument_writes) | |
| 1267 | fi | |
| 1268 | ||
| 1269 | let return_attributes = method.return_parameter.get_custom_attributes_data() | |
| 1270 | result.return_type = | |
| 1271 | _with_pure_function( | |
| 1272 | NULLABILITY.resolve( | |
| 1273 | _with_tuple_element_names( | |
| 1274 | _type_mapper.get_type(method.return_type), | |
| 1275 | return_attributes | |
| 1276 | ), | |
| 1277 | return_attributes, | |
| 1278 | method, | |
| 1279 | method.return_type | |
| 1280 | )!, | |
| 1281 | return_attributes | |
| 1282 | ) | |
| 1283 | ||
| 1284 | result.mark_pack_type_parameters() | |
| 1285 | ||
| 1286 | // When inheriting a method from a constructed-generic base in another | |
| 1287 | // assembly, reflection returns parameter and return types in their | |
| 1288 | // *substituted* form (e.g. !0 surfaces as System.Object when the base | |
| 1289 | // is FooBase<object,...>). The CLR requires the methodref signature | |
| 1290 | // to use the open-generic form (!0, !!0). Recover the open form via | |
| 1291 | // the open base type and stash it as unspecialized_*; the IL emitter | |
| 1292 | // already prefers unspecialized_* when present. | |
| 1293 | let dt = method.declaring_type | |
| 1294 | if dt? /\ dt != type /\ dt.is_generic_type /\ !dt.is_generic_type_definition then | |
| 1295 | let open_type = dt.get_generic_type_definition() | |
| 1296 | let open_method: MethodInfo? mut = null | |
| 1297 | ||
| 1298 | for m in open_type.get_members(cast BindingFlags(2 + 4 + 8 + 16 + 32)) do | |
| 1299 | if m.member_type == MemberTypes.METHOD then | |
| 1300 | let candidate = cast MethodInfo?(m)! | |
| 1301 | if candidate.metadata_token == method.metadata_token then | |
| 1302 | open_method = candidate | |
| 1303 | break | |
| 1304 | fi | |
| 1305 | fi | |
| 1306 | od | |
| 1307 | ||
| 1308 | if open_method? then | |
| 1309 | let unspec_arg_types = LIST() | |
| 1310 | for a in open_method.get_parameters() do | |
| 1311 | unspec_arg_types.add(_type_mapper.get_type(a.parameter_type)) | |
| 1312 | od | |
| 1313 | result.unspecialized_arguments = unspec_arg_types | |
| 1314 | result.unspecialized_return_type = _type_mapper.get_type(open_method.return_type) | |
| 1315 | fi | |
| 1316 | fi | |
| 1317 | ||
| 1318 | // property_details is only set for a property accessor method: | |
| 1319 | // most of the branches above never touch it, so it reaches here | |
| 1320 | // unassigned on every other path. | |
| 1321 | @suppress("presence-test-non-optional", "definite-assignment") | |
| 1322 | if property_details? then | |
| 1323 | let accessor_method_name = method.name | |
| 1324 | ||
| 1325 | if method.equals(property_details.dotnet_get_method) then | |
| 1326 | property_details.ghul_property.read_function = result | |
| 1327 | property_details.ghul_property.read_function_il_name_override = method.name | |
| 1328 | elif method.equals(property_details.dotnet_set_method) then | |
| 1329 | property_details.ghul_property.assign_function = result | |
| 1330 | property_details.ghul_property.assign_function_il_name_override = method.name | |
| 1331 | fi | |
| 1332 | fi | |
| 1333 | ||
| 1334 | methods.add((result, method)) | |
| 1335 | ||
| 1336 | // `IEquatable[T].Equals` and `IComparable[T].CompareTo` are | |
| 1337 | // reached as the operators `=~` and `<>`, and also under the | |
| 1338 | // ordinary de-camelled name, so a call spelled `equals` or | |
| 1339 | // `compare_to` resolves as well. Both symbols carry the same | |
| 1340 | // `il_name_override`, so either spelling emits a call to the | |
| 1341 | // one underlying method. | |
| 1342 | if alias_name? then | |
| 1343 | add_method(owner, type, properties, indexers, indexer_accessors, methods, method, alias_name) | |
| 1344 | fi | |
| 1345 | si | |
| 1346 | ||
| 1347 | // The type an intrinsic operator is registered on: the type of its | |
| 1348 | // first operand, for the operators a type declares for itself. | |
| 1349 | // Null for every other intrinsic, which stays global. | |
| 1350 | _intrinsic_home(name: string, method: MethodInfo) -> Symbols.Classy? is | |
| 1351 | if !Symbols.TYPE_HOMED_OPERATORS.contains(name) then | |
| 1352 | return null | |
| 1353 | fi | |
| 1354 | ||
| 1355 | let parameters = method.get_parameters() | |
| 1356 | ||
| 1357 | if parameters.count == 0 then | |
| 1358 | return null | |
| 1359 | fi | |
| 1360 | ||
| 1361 | if let first: Semantic.Types.NAMED = _type_mapper.get_type(parameters[0].parameter_type) then | |
| 1362 | return cast Symbols.Classy?(first.symbol) | |
| 1363 | fi | |
| 1364 | ||
| 1365 | return null | |
| 1366 | si | |
| 1367 | ||
| 1368 | // Whether a type's public static operator methods import under | |
| 1369 | // their operator spelling. The scalar types do not: their | |
| 1370 | // operators are the intrinsics, and a reflected `op_Addition` on | |
| 1371 | // `System.Decimal` or `System.IntPtr` would be a second candidate | |
| 1372 | // for the same operands. | |
| 1373 | _declares_own_operators(type: TYPE) -> bool => | |
| 1374 | !type.is_interface /\ !_intrinsic_operator_types.contains(type) | |
| 1375 | ||
| 1376 | // The ghūl spelling of a CLR operator-method name, or null for a | |
| 1377 | // method that is not one. Consulted for a static virtual interface | |
| 1378 | // member and for a public static operator on an ordinary type; a | |
| 1379 | // scalar type's reflected operator keeps its mangled name, since | |
| 1380 | // the intrinsic is that type's operator. | |
| 1381 | // | |
| 1382 | // The arithmetic, bitwise and shift operators. The comparison and | |
| 1383 | // equality operators are deliberately absent: ghūl derives those | |
| 1384 | // from `<>` and `=~`, and a bound that supplies these operators | |
| 1385 | // supplies those too, so importing `op_LessThan` as `<` would | |
| 1386 | // give the same expression a second, unrelated route to a | |
| 1387 | // different member. | |
| 1388 | _generic_math_operator_name(dotnet_name: string) -> string? => | |
| 1389 | case dotnet_name | |
| 1390 | when "op_Addition" then "+" | |
| 1391 | when "op_Subtraction" then "-" | |
| 1392 | when "op_Multiply" then "*" | |
| 1393 | when "op_Division" then "/" | |
| 1394 | when "op_Modulus" then "%" | |
| 1395 | when "op_UnaryNegation" then "-" | |
| 1396 | when "op_BitwiseAnd" then "&" | |
| 1397 | when "op_BitwiseOr" then "|" | |
| 1398 | when "op_ExclusiveOr" then "^" | |
| 1399 | when "op_OnesComplement" then "\\" | |
| 1400 | when "op_LeftShift" then "<<" | |
| 1401 | when "op_RightShift" then ">>" | |
| 1402 | when "op_UnsignedRightShift" then ">>>" | |
| 1403 | else null | |
| 1404 | esac | |
| 1405 | ||
| 1406 | _plain_member_name( | |
| 1407 | owner: Symbols.Scoped, | |
| 1408 | type: TYPE, | |
| 1409 | indexers: SET[System.Reflection.MethodInfo], | |
| 1410 | method: MethodInfo | |
| 1411 | ) -> string => | |
| 1412 | _ghul_name_override(method) ?? | |
| 1413 | _type_name_map.get_member_name( | |
| 1414 | owner.qualified_name, | |
| 1415 | method.name, | |
| 1416 | method.is_private, | |
| 1417 | method.is_special_name /\ !indexers.contains(method), | |
| 1418 | method.declaring_type ?? type | |
| 1419 | ) | |
| 1420 | ||
| 1421 | get_assembly_name(type: TYPE) -> string is | |
| 1422 | let assembly = type.assembly | |
| 1423 | ||
| 1424 | let result: string mut | |
| 1425 | ||
| 1426 | if _assembly_names.try_get_value(assembly, result ref) then | |
| 1427 | return result | |
| 1428 | fi | |
| 1429 | ||
| 1430 | result = assembly.get_name().name ?? "" | |
| 1431 | ||
| 1432 | _assembly_names.add(assembly, result) | |
| 1433 | ||
| 1434 | return result | |
| 1435 | si | |
| 1436 | ||
| 1437 | // C# records TupleElementNamesAttribute on the property itself; | |
| 1438 | // a ghūl assembly built before the property carried it holds | |
| 1439 | // the names only on the getter's `.param [0]`, which reflects | |
| 1440 | // as the return parameter rather than as a method attribute. | |
| 1441 | // Every reflected property reaches here, so neither carrier is | |
| 1442 | // read until the type is known to be a tuple. | |
| 1443 | _with_property_tuple_element_names( | |
| 1444 | type: Types.Type, | |
| 1445 | property: PropertyInfo | |
| 1446 | ) -> Types.Type is | |
| 1447 | let names = | |
| 1448 | TUPLE_ELEMENT_NAMES.read(property.get_custom_attributes_data()) ?? | |
| 1449 | (if let getter = property.get_get_method() then | |
| 1450 | TUPLE_ELEMENT_NAMES.read(getter.return_parameter.get_custom_attributes_data()) | |
| 1451 | else | |
| 1452 | null | |
| 1453 | fi) | |
| 1454 | ||
| 1455 | if let found = names then | |
| 1456 | return TUPLE_ELEMENT_NAMES.apply(type, found) | |
| 1457 | fi | |
| 1458 | ||
| 1459 | return type | |
| 1460 | si | |
| 1461 | ||
| 1462 | // If `attributes` carry a TupleElementNamesAttribute, return | |
| 1463 | // `type` with the recovered names put back on every tuple in it, | |
| 1464 | // however deep; otherwise return `type` unchanged. The reflected | |
| 1465 | // ValueTuple type itself has no names. | |
| 1466 | _with_tuple_element_names( | |
| 1467 | type: Types.Type, | |
| 1468 | attributes: Collections.Iterable[System.Reflection.CustomAttributeData] | |
| 1469 | ) -> Types.Type is | |
| 1470 | let names = TUPLE_ELEMENT_NAMES.read(attributes) | |
| 1471 | ||
| 1472 | if !names? then | |
| 1473 | return type | |
| 1474 | fi | |
| 1475 | ||
| 1476 | return TUPLE_ELEMENT_NAMES.apply(type, names) | |
| 1477 | si | |
| 1478 | ||
| 1479 | // The name `GHUL_NAME_ATTRIBUTE` carries, when a member is | |
| 1480 | // marked with one — an override or interface implementation | |
| 1481 | // whose IL name the base or interface fixed, so the IL name | |
| 1482 | // itself cannot be de-camelled back to the ghūl name the way | |
| 1483 | // an ordinary member's can. Read in preference to any name | |
| 1484 | // computed from the IL name. | |
| 1485 | _ghul_name_override(member: MemberInfo) -> string? is | |
| 1486 | try | |
| 1487 | for attr in member.get_custom_attributes_data() do | |
| 1488 | if attr.attribute_type.full_name =~ "Ghul.Internal.GHUL_NAME_ATTRIBUTE" then | |
| 1489 | return cast string?(attr.constructor_arguments[0].value) | |
| 1490 | fi | |
| 1491 | od | |
| 1492 | catch ex: System.Exception | |
| 1493 | yrt | |
| 1494 | ||
| 1495 | return null | |
| 1496 | si | |
| 1497 | ||
| 1498 | has_union_attribute(type: TYPE) -> bool is | |
| 1499 | try | |
| 1500 | for attr in type.get_custom_attributes_data() do | |
| 1501 | let attr_type = attr.attribute_type | |
| 1502 | ||
| 1503 | if attr_type.full_name =~ "Ghul.Internal.UNION_ATTRIBUTE" then | |
| 1504 | return true | |
| 1505 | fi | |
| 1506 | od | |
| 1507 | catch ex: System.Exception | |
| 1508 | yrt | |
| 1509 | ||
| 1510 | return false | |
| 1511 | si | |
| 1512 | ||
| 1513 | has_variant_attribute(type: TYPE) -> bool is | |
| 1514 | try | |
| 1515 | for attr in type.get_custom_attributes_data() do | |
| 1516 | let attr_type = attr.attribute_type | |
| 1517 | ||
| 1518 | if attr_type.full_name =~ "Ghul.Internal.VARIANT_ATTRIBUTE" then | |
| 1519 | return true | |
| 1520 | fi | |
| 1521 | od | |
| 1522 | catch ex: System.Exception | |
| 1523 | yrt | |
| 1524 | ||
| 1525 | return false | |
| 1526 | si | |
| 1527 | ||
| 1528 | has_default_variant_attribute(type: TYPE) -> bool is | |
| 1529 | try | |
| 1530 | for attr in type.get_custom_attributes_data() do | |
| 1531 | let attr_type = attr.attribute_type | |
| 1532 | ||
| 1533 | if attr_type.full_name =~ "Ghul.Internal.DEFAULT_VARIANT_ATTRIBUTE" then | |
| 1534 | return true | |
| 1535 | fi | |
| 1536 | od | |
| 1537 | catch ex: System.Exception | |
| 1538 | yrt | |
| 1539 | ||
| 1540 | return false | |
| 1541 | si | |
| 1542 | ||
| 1543 | // Resolving an attribute's type reaches for the assembly that | |
| 1544 | // declares it, so a marker this compiler emits against a runtime | |
| 1545 | // too old to declare it throws rather than answering. Treated as | |
| 1546 | // absent: the slot then reads the way it did before the marker | |
| 1547 | // existed. | |
| 1548 | ARGUMENT_PACK_ATTRIBUTE_NAME: string static => | |
| 1549 | "Ghul.Internal.ARGUMENT_PACK_ATTRIBUTE" | |
| 1550 | ||
| 1551 | RETURN_PACK_ATTRIBUTE_NAME: string static => | |
| 1552 | "Ghul.Internal.RETURN_PACK_ATTRIBUTE" | |
| 1553 | ||
| 1554 | ARGUMENT_SPREAD_ATTRIBUTE_NAME: string static => | |
| 1555 | "Ghul.Internal.ARGUMENT_SPREAD_ATTRIBUTE" | |
| 1556 | ||
| 1557 | // How deep in the parameter's own type the argument-pack marker | |
| 1558 | // sits, and negative when the parameter carries no marker at | |
| 1559 | // all. The argumentless form of the attribute is depth zero - | |
| 1560 | // the parameter's own function type - which is what an assembly | |
| 1561 | // built before the marker could be written deeper records. | |
| 1562 | _argument_pack_depth(attributes: Collections.Iterable[System.Reflection.CustomAttributeData]) -> int => | |
| 1563 | _pack_depth(attributes, ARGUMENT_PACK_ATTRIBUTE_NAME) | |
| 1564 | ||
| 1565 | _pack_depth( | |
| 1566 | attributes: Collections.Iterable[System.Reflection.CustomAttributeData], | |
| 1567 | attribute_name: string | |
| 1568 | ) -> int is | |
| 1569 | try | |
| 1570 | for attr in attributes do | |
| 1571 | if attr.attribute_type.full_name =~ attribute_name then | |
| 1572 | for argument in attr.constructor_arguments do | |
| 1573 | if let depth = cast int?(argument.value) then | |
| 1574 | return depth | |
| 1575 | fi | |
| 1576 | od | |
| 1577 | ||
| 1578 | return 0 | |
| 1579 | fi | |
| 1580 | od | |
| 1581 | catch ex: System.Exception | |
| 1582 | yrt | |
| 1583 | ||
| 1584 | return -1 | |
| 1585 | si | |
| 1586 | ||
| 1587 | _has_attribute_named(attributes: Collections.Iterable[System.Reflection.CustomAttributeData], name: string) -> bool is | |
| 1588 | try | |
| 1589 | for attr in attributes do | |
| 1590 | if attr.attribute_type.full_name =~ name then | |
| 1591 | return true | |
| 1592 | fi | |
| 1593 | od | |
| 1594 | catch ex: System.Exception | |
| 1595 | yrt | |
| 1596 | ||
| 1597 | return false | |
| 1598 | si | |
| 1599 | ||
| 1600 | _has_pure_attribute(attributes: Collections.Iterable[System.Reflection.CustomAttributeData]) -> bool is | |
| 1601 | try | |
| 1602 | for attr in attributes do | |
| 1603 | let attr_type = attr.attribute_type | |
| 1604 | ||
| 1605 | if attr_type.full_name =~ "Ghul.Internal.PURE_ATTRIBUTE" then | |
| 1606 | return true | |
| 1607 | fi | |
| 1608 | od | |
| 1609 | catch ex: System.Exception | |
| 1610 | yrt | |
| 1611 | ||
| 1612 | return false | |
| 1613 | si | |
| 1614 | ||
| 1615 | _has_stable_attribute(attributes: Collections.Iterable[System.Reflection.CustomAttributeData]) -> bool is | |
| 1616 | try | |
| 1617 | for attr in attributes do | |
| 1618 | let attr_type = attr.attribute_type | |
| 1619 | ||
| 1620 | if attr_type.full_name =~ "Ghul.Internal.STABLE_ATTRIBUTE" then | |
| 1621 | return true | |
| 1622 | fi | |
| 1623 | od | |
| 1624 | catch ex: System.Exception | |
| 1625 | yrt | |
| 1626 | ||
| 1627 | return false | |
| 1628 | si | |
| 1629 | ||
| 1630 | // The BCL's own marker for a synthesised member or type — see | |
| 1631 | // SRM_STRUCTURE_WALK.COMPILER_GENERATED_ATTRIBUTE_NAME for the | |
| 1632 | // emission side. Reading it back means an importing compilation | |
| 1633 | // recognises a synthesised symbol without testing its name, | |
| 1634 | // whether the assembly it came from is ghūl-compiled or not — | |
| 1635 | // a C#-compiled record's synthesised members carry it too. | |
| 1636 | _has_compiler_generated_attribute(attributes: Collections.Iterable[System.Reflection.CustomAttributeData]) -> bool is | |
| 1637 | try | |
| 1638 | for attr in attributes do | |
| 1639 | let attr_type = attr.attribute_type | |
| 1640 | ||
| 1641 | if attr_type.full_name =~ "System.Runtime.CompilerServices.CompilerGeneratedAttribute" then | |
| 1642 | return true | |
| 1643 | fi | |
| 1644 | od | |
| 1645 | catch ex: System.Exception | |
| 1646 | yrt | |
| 1647 | ||
| 1648 | return false | |
| 1649 | si | |
| 1650 | ||
| 1651 | // True when `method` is `type`'s implementation of the | |
| 1652 | // self-instantiated generic interface named by `open_interface_name` | |
| 1653 | // (`System.IEquatable\`1` or `System.IComparable\`1`) — i.e. `type` | |
| 1654 | // implements `IEquatable[type]`/`IComparable[type]`, not some other | |
| 1655 | // instantiation, and `method` is that interface member rather than | |
| 1656 | // an unrelated same-named overload (`object.Equals`, the | |
| 1657 | // non-generic `IComparable.CompareTo(object)`). | |
| 1658 | // | |
| 1659 | // `Type.GetInterfaceMap` would answer this directly but throws | |
| 1660 | // `NotSupportedException` under the `MetadataLoadContext` this | |
| 1661 | // reflection runs in (import assemblies are loaded for metadata | |
| 1662 | // only, never executed), so this matches by signature instead: | |
| 1663 | // `IEquatable[T].Equals`/`IComparable[T].CompareTo` are always | |
| 1664 | // exactly `(T) -> bool`/`(T) -> int`, and no other member can | |
| 1665 | // satisfy that interface with a different parameter type. The one | |
| 1666 | // gap this leaves is a type that implements the interface | |
| 1667 | // *explicitly* (`bool IEquatable<Box>.Equals(Box other)`) — that | |
| 1668 | // member doesn't surface under its simple name in `GetMethods()` | |
| 1669 | // at all, with or without `GetInterfaceMap`, so it's an existing | |
| 1670 | // reflection-layer limitation rather than one this check adds. | |
| 1671 | _is_self_relational_interface_target(type: TYPE, method: MethodInfo, open_interface: TYPE, expected_return_type: TYPE) -> bool is | |
| 1672 | if method.return_type != expected_return_type then | |
| 1673 | return false | |
| 1674 | fi | |
| 1675 | ||
| 1676 | let parameters = method.get_parameters() | |
| 1677 | ||
| 1678 | if parameters.count != 1 then | |
| 1679 | return false | |
| 1680 | fi | |
| 1681 | ||
| 1682 | // `IEquatable[T].=~`/`IComparable[T].<>` on the trait's own | |
| 1683 | // declaration: `type` is the open interface itself (reflecting | |
| 1684 | // `Ghul.Equatable[T]`'s member list, not a concrete | |
| 1685 | // implementer), and the parameter is its own type parameter. | |
| 1686 | if type == open_interface then | |
| 1687 | return parameters[0].parameter_type == open_interface.get_generic_arguments()[0] | |
| 1688 | fi | |
| 1689 | ||
| 1690 | // An implementer: `type` closed-implements `IEquatable[type]` | |
| 1691 | // and the parameter is `type` itself. | |
| 1692 | let closed_interface = open_interface.make_generic_type([type]) | |
| 1693 | ||
| 1694 | return type.get_interfaces() |> any(i => i == closed_interface) /\ | |
| 1695 | parameters[0].parameter_type == type | |
| 1696 | si | |
| 1697 | ||
| 1698 | // The intrinsic operation name (e.g. "arithmetic.add") carried by | |
| 1699 | // a method's INTRINSIC_ATTRIBUTE, or null if it carries none. An | |
| 1700 | // imported method bearing the marker is reconstructed as an innate | |
| 1701 | // function whose calls lower to IL opcodes, matching a source- | |
| 1702 | // declared intrinsic. | |
| 1703 | _intrinsic_operation(attributes: Collections.Iterable[System.Reflection.CustomAttributeData]) -> string? is | |
| 1704 | try | |
| 1705 | for attr in attributes do | |
| 1706 | if attr.attribute_type.full_name =~ "Ghul.Internal.INTRINSIC_ATTRIBUTE" then | |
| 1707 | for argument in attr.constructor_arguments do | |
| 1708 | return cast string?(argument.value) | |
| 1709 | od | |
| 1710 | fi | |
| 1711 | od | |
| 1712 | catch ex: System.Exception | |
| 1713 | yrt | |
| 1714 | ||
| 1715 | return null | |
| 1716 | si | |
| 1717 | ||
| 1718 | // A parameter or return slot carrying the purity marker has a | |
| 1719 | // pure top-level function type: rebuild the mapped type in | |
| 1720 | // its pure shape. Slots whose mapped type is not a function | |
| 1721 | // type (or is the zero-argument void form, which has no pure | |
| 1722 | // shape) keep the plain type — conservative on both sides. | |
| 1723 | _with_pure_function(type: Types.Type, attributes: Collections.Iterable[System.Reflection.CustomAttributeData]) -> Types.Type is | |
| 1724 | if !type.is_function \/ !isa Types.GENERIC(type) \/ !_has_pure_attribute(attributes) then | |
| 1725 | return type | |
| 1726 | fi | |
| 1727 | ||
| 1728 | let lookup = IoC.CONTAINER.instance.innate_symbol_lookup | |
| 1729 | ||
| 1730 | let types = Collections.LIST[Types.Type]() | |
| 1731 | ||
| 1732 | for a in (cast Types.GENERIC(type)).arguments do | |
| 1733 | types.add(a) | |
| 1734 | od | |
| 1735 | ||
| 1736 | if type.is_action then | |
| 1737 | types.add(lookup.get_void_type()) | |
| 1738 | fi | |
| 1739 | ||
| 1740 | return lookup.get_function_type(types, true) | |
| 1741 | si | |
| 1742 | ||
| 1743 | // A delegate type, found by walking the base chain to | |
| 1744 | // System.MulticastDelegate and comparing reflected types by | |
| 1745 | // identity. What a delegate member does is whatever its target | |
| 1746 | // does, so nothing about its shape can vouch for it. | |
| 1747 | _inherits_multicast_delegate(type: TYPE) -> bool is | |
| 1748 | let current mut = type.base_type | |
| 1749 | ||
| 1750 | while current? do | |
| 1751 | if current == _multicast_delegate_type then | |
| 1752 | return true | |
| 1753 | fi | |
| 1754 | ||
| 1755 | current = current.base_type | |
| 1756 | od | |
| 1757 | ||
| 1758 | return false | |
| 1759 | si | |
| 1760 | ||
| 1761 | has_closed_attribute(type: TYPE) -> bool is | |
| 1762 | try | |
| 1763 | for attr in type.get_custom_attributes_data() do | |
| 1764 | let attr_type = attr.attribute_type | |
| 1765 | ||
| 1766 | if attr_type.full_name =~ "Ghul.Internal.CLOSED_ATTRIBUTE" then | |
| 1767 | return true | |
| 1768 | fi | |
| 1769 | od | |
| 1770 | catch ex: System.Exception | |
| 1771 | yrt | |
| 1772 | ||
| 1773 | return false | |
| 1774 | si | |
| 1775 | ||
| 1776 | // Cross-assembly variant materialization: when reflecting a | |
| 1777 | // union from another assembly, the variant classes are | |
| 1778 | // sibling top-level types whose `Namespace` equals the | |
| 1779 | // union's full name (assemblies.ghul: import_type queues | |
| 1780 | // them into TYPE_DETAILS_LOOKUP by union full name instead | |
| 1781 | // of registering them as top-level). When the union's | |
| 1782 | // symbol is created and its members populated, call this | |
| 1783 | // to also materialize the variants as members of the union. | |
| 1784 | materialize_variants(union_symbol: Symbols.UNION, union_type: TYPE) is | |
| 1785 | let union_full_name = union_type.full_name | |
| 1786 | ||
| 1787 | if !union_full_name? then | |
| 1788 | return | |
| 1789 | fi | |
| 1790 | ||
| 1791 | let variants = _type_details_lookup.get_variants_for_union(union_full_name) | |
| 1792 | ||
| 1793 | if !variants? then | |
| 1794 | return | |
| 1795 | fi | |
| 1796 | ||
| 1797 | for variant_type in variants do | |
| 1798 | materialize_variant(union_symbol, variant_type) | |
| 1799 | od | |
| 1800 | si | |
| 1801 | ||
| 1802 | materialize_variant(union_symbol: Symbols.UNION, variant_type: TYPE) is | |
| 1803 | let variant_name = variant_type.name | |
| 1804 | let assembly_name = get_assembly_name(variant_type) | |
| 1805 | ||
| 1806 | let arguments = LIST() | |
| 1807 | let argument_variances = LIST() | |
| 1808 | ||
| 1809 | if variant_type.is_generic_type /\ variant_type.contains_generic_parameters then | |
| 1810 | for a in variant_type.get_generic_arguments() do | |
| 1811 | if a.is_generic_type_parameter then | |
| 1812 | arguments.add(a.name) | |
| 1813 | argument_variances.add(_type_mapper.map_type_argument_variance(a)) | |
| 1814 | fi | |
| 1815 | od | |
| 1816 | fi | |
| 1817 | ||
| 1818 | let variant_symbol = | |
| 1819 | Symbols.VARIANT( | |
| 1820 | Source.LOCATION.reflected, | |
| 1821 | Source.LOCATION.reflected, | |
| 1822 | union_symbol, | |
| 1823 | variant_name, | |
| 1824 | arguments, | |
| 1825 | union_symbol | |
| 1826 | ) | |
| 1827 | ||
| 1828 | variant_symbol.mark_overrides_resolved() | |
| 1829 | ||
| 1830 | if argument_variances.count > 0 then | |
| 1831 | variant_symbol.argument_variances = argument_variances | |
| 1832 | fi | |
| 1833 | ||
| 1834 | variant_symbol.il_assembly_name = assembly_name | |
| 1835 | variant_symbol.il_name_override = get_il_name(variant_type) | |
| 1836 | variant_symbol.dotnet_full_name = variant_type.full_name | |
| 1837 | ||
| 1838 | // Register the variant in the lookup so subsequent | |
| 1839 | // dotnet-type-keyed lookups (`get_symbol(type)`) and | |
| 1840 | // resolution paths can find it. | |
| 1841 | let variant_td = | |
| 1842 | TYPE_DETAILS( | |
| 1843 | variant_type, | |
| 1844 | union_symbol.qualified_name, | |
| 1845 | variant_name, | |
| 1846 | variant_symbol.il_name_override, | |
| 1847 | assembly_name | |
| 1848 | ) | |
| 1849 | ||
| 1850 | union_symbol.add_member(variant_symbol) | |
| 1851 | ||
| 1852 | add_ancestors(variant_symbol, variant_type) | |
| 1853 | add_members(variant_symbol, variant_type) | |
| 1854 | resolve_overrides(variant_symbol) | |
| 1855 | ||
| 1856 | // Populate the variant's `_field_names` list from the | |
| 1857 | // .NET instance-field declaration order so positional | |
| 1858 | // destructuring (`let (v, r) = step`) works the same | |
| 1859 | // way it does for an intra-assembly variant. Source- | |
| 1860 | // defined variants get this filled by | |
| 1861 | // `declare_variable`; reflected variants need to do it | |
| 1862 | // explicitly here. | |
| 1863 | for `field in variant_type.get_fields() do | |
| 1864 | if !`field.is_static /\ `field.is_public then | |
| 1865 | variant_symbol.register_reflected_field_name(`field.name) | |
| 1866 | fi | |
| 1867 | od | |
| 1868 | ||
| 1869 | // Wire the parent union's `default_variant` pointer to | |
| 1870 | // the marked variant so cross-asm `u?` / `u!` lowers to | |
| 1871 | // isa/cast against this variant — matching what source- | |
| 1872 | // defined unions get from declare_members. | |
| 1873 | if has_default_variant_attribute(variant_type) then | |
| 1874 | variant_symbol.is_default = true | |
| 1875 | union_symbol.default_variant = variant_symbol | |
| 1876 | fi | |
| 1877 | si | |
| 1878 | ||
| 1879 | // The CLR-side full name, with the nested-type separator the | |
| 1880 | // metadata reader expects: reflection spells a nested type | |
| 1881 | // `Outer+Inner`, and a type reference names it `Outer/Inner`. | |
| 1882 | get_il_name(type: TYPE) -> string is | |
| 1883 | let full_name: string? = type.full_name | |
| 1884 | ||
| 1885 | assert full_name? else "get il name type has no full name: {type}" | |
| 1886 | ||
| 1887 | return full_name.replace("+", "/") | |
| 1888 | si | |
| 1889 | ||
| 1890 | // Reflect the .NET-declared default value of a parameter into | |
| 1891 | // the string form that `Function.argument_defaults` expects. | |
| 1892 | // - null ⇒ no default ⇒ parameter is not omittable. | |
| 1893 | // - "default" ⇒ omittable, fill with `IR.Values.DEFAULT` (the | |
| 1894 | // .NET parameter is a reference type with `= null`, or a | |
| 1895 | // `Nullable<T>` with no constant value). | |
| 1896 | // - any other string ⇒ a constant literal, formatted with | |
| 1897 | // invariant culture so the emission side can read it back | |
| 1898 | // independent of host locale. | |
| 1899 | // | |
| 1900 | // Conservative gate: only primitive numeric types, bool, char, | |
| 1901 | // string, and enums are stored. Anything else (decimal, | |
| 1902 | // struct, …) returns null so the parameter is treated as not | |
| 1903 | // omittable rather than silently filled with a zero default. | |
| 1904 | _default_for_parameter(a: ParameterInfo) -> string? is | |
| 1905 | if !a.has_default_value then | |
| 1906 | return null | |
| 1907 | fi | |
| 1908 | ||
| 1909 | let raw = a.raw_default_value | |
| 1910 | ||
| 1911 | // This channel does spell an absent value as a word, because | |
| 1912 | // ghūl's own `= _` reaches it that way and a parameter | |
| 1913 | // default is read back by the same conversion. | |
| 1914 | if !raw? then | |
| 1915 | return "default" | |
| 1916 | fi | |
| 1917 | ||
| 1918 | return REFLECTED_CONSTANTS.text_of(raw, a.parameter_type) | |
| 1919 | si | |
| 1920 | si | |
| 1921 | si |