Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use Logging | |
| 3 | use Source | |
| 4 | ||
| 5 | use Semantic.Types.Type | |
| 6 | ||
| 7 | use System.Text.StringBuilder | |
| 8 | ||
| 9 | // Resolves an attribute pragma — a pragma whose name is not a built-in | |
| 10 | // compiler pragma — to a .NET attribute type and constructor, and | |
| 11 | // attaches the resolved CUSTOM_ATTRIBUTE to the target symbol, from | |
| 12 | // which the emitter encodes the attribute's value blob. | |
| 13 | // | |
| 14 | // The `format_*` half renders the arguments as assembler source. The | |
| 15 | // rendering itself is unused (see CUSTOM_ATTRIBUTE), but an argument | |
| 16 | // shape it cannot render makes it answer null, and that is what | |
| 17 | // rejects the attribute — so it is the argument validation, spelled | |
| 18 | // as a renderer. | |
| 19 | class ATTRIBUTE_RESOLVER is | |
| 20 | _logger: Logger | |
| 21 | _innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup | |
| 22 | _overload_resolver: Semantic.OVERLOAD_RESOLVER | |
| 23 | _visitor: ScopedVisitor | |
| 24 | ||
| 25 | init( | |
| 26 | logger: Logger, | |
| 27 | innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup, | |
| 28 | overload_resolver: Semantic.OVERLOAD_RESOLVER, | |
| 29 | visitor: ScopedVisitor | |
| 30 | ) is | |
| 31 | super.init() | |
| 32 | ||
| 33 | _logger = logger | |
| 34 | _innate_symbol_lookup = innate_symbol_lookup | |
| 35 | _overload_resolver = overload_resolver | |
| 36 | _visitor = visitor | |
| 37 | si | |
| 38 | ||
| 39 | // A built-in pragma is handled by the compiler itself; anything | |
| 40 | // else is taken to name a .NET attribute. | |
| 41 | is_built_in(name: string) -> bool => | |
| 42 | name.starts_with("IL.") \/ | |
| 43 | name.starts_with("IF.") \/ | |
| 44 | name =~ "precedence" \/ | |
| 45 | name =~ "entry" \/ | |
| 46 | name =~ "suppress" \/ | |
| 47 | name =~ "equality" | |
| 48 | ||
| 49 | resolve(pragma: Trees.Pragmas.PRAGMA, target: Semantic.Symbols.Symbol?) is | |
| 50 | let name = pragma.name.to_string() | |
| 51 | ||
| 52 | // The test pragmas emitted MSTest 3 attributes against an | |
| 53 | // assembly name MSTest 4 no longer ships, so the tests they | |
| 54 | // marked were silently not discovered. The attributes are | |
| 55 | // written as attributes now instead. The name is rejected | |
| 56 | // whatever the pragma was written against, so a use inside a | |
| 57 | // body — which resolves against no target — reports too. | |
| 58 | if name =~ "test" \/ name =~ "test_class" \/ name =~ "test_method" then | |
| 59 | _logger.error( | |
| 60 | pragma.name.location, | |
| 61 | "{name} is not supported", | |
| 62 | pragma.name.location, | |
| 63 | "help: use the test framework's attributes, as in @TestClass() on the class and @TestMethod() on the method") | |
| 64 | return | |
| 65 | fi | |
| 66 | ||
| 67 | if !target? then | |
| 68 | return | |
| 69 | fi | |
| 70 | ||
| 71 | if is_built_in(name) then | |
| 72 | return | |
| 73 | fi | |
| 74 | ||
| 75 | let attribute_type = resolve_attribute_type(pragma.name) | |
| 76 | ||
| 77 | if !attribute_type? then | |
| 78 | _logger.error(pragma.name.location, "unknown attribute {name}") | |
| 79 | return | |
| 80 | fi | |
| 81 | ||
| 82 | let positional = gather_positional_arguments(pragma) | |
| 83 | ||
| 84 | let constructor = resolve_constructor(pragma, attribute_type, positional) | |
| 85 | ||
| 86 | if !constructor? then | |
| 87 | return | |
| 88 | fi | |
| 89 | ||
| 90 | let completed = complete_positional_arguments(constructor, positional) | |
| 91 | ||
| 92 | let named = resolve_named_arguments(attribute_type, pragma) | |
| 93 | ||
| 94 | if !named? then | |
| 95 | return | |
| 96 | fi | |
| 97 | ||
| 98 | if !format_arguments(pragma, constructor, completed, named)? then | |
| 99 | return | |
| 100 | fi | |
| 101 | ||
| 102 | // `DllImport` is not carried as a custom attribute: it is a | |
| 103 | // pseudo-attribute, whose whole content becomes flags on the | |
| 104 | // method row and a row in the ImplMap table. | |
| 105 | if attribute_type == _dll_import_attribute() then | |
| 106 | _resolve_pinvoke(pragma, target, completed, named) | |
| 107 | return | |
| 108 | fi | |
| 109 | ||
| 110 | if !target.custom_attributes? then | |
| 111 | target.custom_attributes = Collections.LIST[Semantic.CUSTOM_ATTRIBUTE]() | |
| 112 | fi | |
| 113 | ||
| 114 | let attributes = target.custom_attributes! | |
| 115 | ||
| 116 | for i in 0..attributes.count do | |
| 117 | if attributes[i].pragma == pragma then | |
| 118 | attributes.remove_at(i) | |
| 119 | break | |
| 120 | fi | |
| 121 | od | |
| 122 | ||
| 123 | attributes.add( | |
| 124 | Semantic.CUSTOM_ATTRIBUTE(constructor, completed, named, pragma)) | |
| 125 | si | |
| 126 | ||
| 127 | // Every type in the signature has to be one the runtime can | |
| 128 | // carry into the library. Reported at the method, naming the | |
| 129 | // type that cannot cross. | |
| 130 | _check_pinvoke_signature( | |
| 131 | pragma: Trees.Pragmas.PRAGMA, | |
| 132 | function: Semantic.Symbols.Function | |
| 133 | ) -> bool is | |
| 134 | let signature = Semantic.PINVOKE_SIGNATURE(_innate_symbol_lookup) | |
| 135 | ||
| 136 | let names = function.argument_names | |
| 137 | ||
| 138 | for i in 0..function.arguments.count do | |
| 139 | if !signature.can_take(function.arguments[i]) then | |
| 140 | _logger.error( | |
| 141 | pragma.name.location, | |
| 142 | "{names[i]} is a {function.arguments[i]}, which cannot be passed to a shared library") | |
| 143 | ||
| 144 | return false | |
| 145 | fi | |
| 146 | od | |
| 147 | ||
| 148 | if !signature.can_return(function.return_type) then | |
| 149 | _logger.error( | |
| 150 | pragma.name.location, | |
| 151 | "{function.return_type} cannot be returned from a shared library") | |
| 152 | ||
| 153 | return false | |
| 154 | fi | |
| 155 | ||
| 156 | return true | |
| 157 | si | |
| 158 | ||
| 159 | // The attribute type itself, resolved once through the symbol | |
| 160 | // table and compared by identity: a type is recognised by which | |
| 161 | // symbol it is rather than by what it renders as, so a user type | |
| 162 | // that happens to qualify the same way is not taken for this one | |
| 163 | // and a change to how a type is spelled cannot quietly stop it | |
| 164 | // being recognised. | |
| 165 | _dll_import: Semantic.Symbols.Symbol? | |
| 166 | _dll_import_resolved: bool | |
| 167 | ||
| 168 | _dll_import_attribute() -> Semantic.Symbols.Symbol? is | |
| 169 | if _dll_import_resolved then | |
| 170 | return _dll_import | |
| 171 | fi | |
| 172 | ||
| 173 | _dll_import_resolved = true | |
| 174 | ||
| 175 | let system = _visitor.find("System") | |
| 176 | ||
| 177 | if !system? then | |
| 178 | return null | |
| 179 | fi | |
| 180 | ||
| 181 | let runtime = system.find_member("Runtime") | |
| 182 | ||
| 183 | if !runtime? then | |
| 184 | return null | |
| 185 | fi | |
| 186 | ||
| 187 | let interop = runtime.find_member("InteropServices") | |
| 188 | ||
| 189 | if !interop? then | |
| 190 | return null | |
| 191 | fi | |
| 192 | ||
| 193 | _dll_import = interop.find_member("DllImportAttribute") | |
| 194 | ||
| 195 | return _dll_import | |
| 196 | si | |
| 197 | ||
| 198 | // Reads a `DllImport` into the import the method is emitted | |
| 199 | // from, and reports the declarations it cannot be written on. | |
| 200 | // Reported here rather than at emission, so a program that | |
| 201 | // cannot be called into says so before any IL is written. | |
| 202 | _resolve_pinvoke( | |
| 203 | pragma: Trees.Pragmas.PRAGMA, | |
| 204 | target: Semantic.Symbols.Symbol, | |
| 205 | positional: Collections.List[IR.Values.Value], | |
| 206 | named: Collections.List[Semantic.NAMED_ATTRIBUTE_ARGUMENT] | |
| 207 | ) is | |
| 208 | let function = cast Semantic.Symbols.Function?(target) | |
| 209 | ||
| 210 | if !function? then | |
| 211 | _logger.error(pragma.name.location, "only a method can carry DllImport") | |
| 212 | return | |
| 213 | fi | |
| 214 | ||
| 215 | if function.is_instance then | |
| 216 | _logger.error(pragma.name.location, "a method carrying DllImport must be static") | |
| 217 | return | |
| 218 | fi | |
| 219 | ||
| 220 | let module = _string_argument(positional, 0) | |
| 221 | ||
| 222 | if !module? \/ module.length == 0 then | |
| 223 | _logger.error(pragma.name.location, "the module to call into is missing from this DllImport") | |
| 224 | return | |
| 225 | fi | |
| 226 | ||
| 227 | if !function.is_declared_without_body then | |
| 228 | _logger.error( | |
| 229 | pragma.name.location, | |
| 230 | "a method carrying DllImport is the call into the library, so it takes no body of its own") | |
| 231 | return | |
| 232 | fi | |
| 233 | ||
| 234 | if !_check_pinvoke_signature(pragma, function) then | |
| 235 | return | |
| 236 | fi | |
| 237 | ||
| 238 | let entry_point = _named_string(named, "EntryPoint") ?? function.name | |
| 239 | ||
| 240 | function.pinvoke = | |
| 241 | Semantic.PINVOKE_IMPORT(module, entry_point, _import_attributes(named)) | |
| 242 | si | |
| 243 | ||
| 244 | // The flags the runtime marshals the call by, read off the | |
| 245 | // attribute's named arguments. What is not written takes the | |
| 246 | // platform's default, which is what leaving the bit clear says. | |
| 247 | _import_attributes( | |
| 248 | named: Collections.List[Semantic.NAMED_ATTRIBUTE_ARGUMENT] | |
| 249 | ) -> System.Reflection.MethodImportAttributes is | |
| 250 | let flags mut = 0 | |
| 251 | ||
| 252 | if _named_number(named, "SetLastError") == 1 then | |
| 253 | flags = flags | cast int(System.Reflection.MethodImportAttributes.SET_LAST_ERROR) | |
| 254 | fi | |
| 255 | ||
| 256 | if _named_number(named, "ExactSpelling") == 1 then | |
| 257 | flags = flags | cast int(System.Reflection.MethodImportAttributes.EXACT_SPELLING) | |
| 258 | fi | |
| 259 | ||
| 260 | case _named_number(named, "CharSet") | |
| 261 | when 2 then | |
| 262 | flags = flags | cast int(System.Reflection.MethodImportAttributes.CHAR_SET_ANSI) | |
| 263 | when 3 then | |
| 264 | flags = flags | cast int(System.Reflection.MethodImportAttributes.CHAR_SET_UNICODE) | |
| 265 | when 4 then | |
| 266 | flags = flags | cast int(System.Reflection.MethodImportAttributes.CHAR_SET_AUTO) | |
| 267 | else | |
| 268 | esac | |
| 269 | ||
| 270 | case _named_number(named, "CallingConvention") | |
| 271 | when 1 then | |
| 272 | flags = flags | cast int(System.Reflection.MethodImportAttributes.CALLING_CONVENTION_WIN_API) | |
| 273 | when 2 then | |
| 274 | flags = flags | cast int(System.Reflection.MethodImportAttributes.CALLING_CONVENTION_C_DECL) | |
| 275 | when 3 then | |
| 276 | flags = flags | cast int(System.Reflection.MethodImportAttributes.CALLING_CONVENTION_STD_CALL) | |
| 277 | when 4 then | |
| 278 | flags = flags | cast int(System.Reflection.MethodImportAttributes.CALLING_CONVENTION_THIS_CALL) | |
| 279 | when 5 then | |
| 280 | flags = flags | cast int(System.Reflection.MethodImportAttributes.CALLING_CONVENTION_FAST_CALL) | |
| 281 | else | |
| 282 | esac | |
| 283 | ||
| 284 | cast System.Reflection.MethodImportAttributes(flags) | |
| 285 | si | |
| 286 | ||
| 287 | _string_argument(values: Collections.List[IR.Values.Value], index: int) -> string? is | |
| 288 | if index >= values.count then | |
| 289 | return null | |
| 290 | fi | |
| 291 | ||
| 292 | if let literal: IR.Values.Literal.STRING = values[index] then | |
| 293 | return literal.value | |
| 294 | fi | |
| 295 | ||
| 296 | null | |
| 297 | si | |
| 298 | ||
| 299 | _named_string( | |
| 300 | named: Collections.List[Semantic.NAMED_ATTRIBUTE_ARGUMENT], | |
| 301 | il_name: string | |
| 302 | ) -> string? is | |
| 303 | for argument in named do | |
| 304 | if argument.il_name =~ il_name then | |
| 305 | if let literal: IR.Values.Literal.STRING = argument.value then | |
| 306 | return literal.value | |
| 307 | fi | |
| 308 | fi | |
| 309 | od | |
| 310 | ||
| 311 | null | |
| 312 | si | |
| 313 | ||
| 314 | // A named argument written as a number - an enum member, or a | |
| 315 | // bool - or -1 when the attribute did not write one. | |
| 316 | _named_number( | |
| 317 | named: Collections.List[Semantic.NAMED_ATTRIBUTE_ARGUMENT], | |
| 318 | il_name: string | |
| 319 | ) -> int is | |
| 320 | for argument in named do | |
| 321 | if argument.il_name =~ il_name then | |
| 322 | if let literal: IR.Values.Literal.NUMBER = argument.value then | |
| 323 | let rendered = literal.rendered | |
| 324 | let value mut = 0 | |
| 325 | ||
| 326 | if int.try_parse(rendered, value ref) then | |
| 327 | return value | |
| 328 | fi | |
| 329 | fi | |
| 330 | fi | |
| 331 | od | |
| 332 | ||
| 333 | -1 | |
| 334 | si | |
| 335 | ||
| 336 | // The constructor's parameters filled out from what the call | |
| 337 | // site supplied, so that both back ends encode the same | |
| 338 | // arguments in the same order rather than each deciding what an | |
| 339 | // omitted trailing parameter meant. | |
| 340 | // | |
| 341 | // A parameter beyond what was supplied is only reachable through | |
| 342 | // resolve_constructor_with_omitted_optionals, which has already | |
| 343 | // established that every one of them carries a usable literal | |
| 344 | // default. | |
| 345 | complete_positional_arguments( | |
| 346 | constructor: Semantic.Symbols.Function, | |
| 347 | positional: Collections.List[IR.Values.Value] | |
| 348 | ) -> Collections.List[IR.Values.Value] is | |
| 349 | let parameters = constructor.arguments | |
| 350 | let completed = Collections.LIST[IR.Values.Value]() | |
| 351 | ||
| 352 | for i in 0..parameters.count do | |
| 353 | if i < positional.count then | |
| 354 | completed.add(positional[i]) | |
| 355 | else | |
| 356 | completed.add( | |
| 357 | DEFAULT_ARGUMENT_VALUES.build( | |
| 358 | constructor.argument_defaults[i], | |
| 359 | parameters[i], | |
| 360 | _innate_symbol_lookup | |
| 361 | )) | |
| 362 | fi | |
| 363 | od | |
| 364 | ||
| 365 | return completed | |
| 366 | si | |
| 367 | ||
| 368 | // Every `name = value` argument resolved against the attribute's | |
| 369 | // own members, or null once one of them has been reported as | |
| 370 | // naming nothing, naming something that is neither a field nor a | |
| 371 | // property, or having no usable type. | |
| 372 | resolve_named_arguments( | |
| 373 | attribute_type: Semantic.Symbols.Classy, | |
| 374 | pragma: Trees.Pragmas.PRAGMA | |
| 375 | ) -> Collections.List[Semantic.NAMED_ATTRIBUTE_ARGUMENT]? is | |
| 376 | let resolved = Collections.LIST[Semantic.NAMED_ATTRIBUTE_ARGUMENT]() | |
| 377 | ||
| 378 | let named = pragma.named_arguments | |
| 379 | ||
| 380 | if !named? then | |
| 381 | return resolved | |
| 382 | fi | |
| 383 | ||
| 384 | for named_argument in named do | |
| 385 | let name = named_argument.name.to_string() | |
| 386 | ||
| 387 | let member = attribute_type.find_member(name) | |
| 388 | ||
| 389 | if !member? then | |
| 390 | _logger.error( | |
| 391 | named_argument.name.location, | |
| 392 | "attribute {attribute_type.name} has no field or property {name}") | |
| 393 | ||
| 394 | return null | |
| 395 | fi | |
| 396 | ||
| 397 | let is_field = member.symbol_kind == Semantic.Symbols.SymbolKind.FIELD | |
| 398 | ||
| 399 | if | |
| 400 | !is_field /\ | |
| 401 | member.symbol_kind != Semantic.Symbols.SymbolKind.PROPERTY | |
| 402 | then | |
| 403 | _logger.error( | |
| 404 | named_argument.name.location, | |
| 405 | "{name} is not a field or property of attribute {attribute_type.name}") | |
| 406 | ||
| 407 | return null | |
| 408 | fi | |
| 409 | ||
| 410 | let member_type = member.type | |
| 411 | ||
| 412 | if !member_type? then | |
| 413 | _logger.error( | |
| 414 | named_argument.name.location, | |
| 415 | "attribute argument {name} has an unsupported type") | |
| 416 | ||
| 417 | return null | |
| 418 | fi | |
| 419 | ||
| 420 | resolved.add( | |
| 421 | Semantic.NAMED_ATTRIBUTE_ARGUMENT( | |
| 422 | is_field, | |
| 423 | member.il_name_override ?? name, | |
| 424 | member_type, | |
| 425 | named_argument.value.value, | |
| 426 | named_argument.value.location)) | |
| 427 | od | |
| 428 | ||
| 429 | return resolved | |
| 430 | si | |
| 431 | ||
| 432 | // Resolve the pragma name to a .NET attribute class, accepting the | |
| 433 | // `Foo` short form for a `FooAttribute` — whether `Foo` is written | |
| 434 | // bare or fully qualified (`A.B.Foo` resolves `A.B.FooAttribute`). | |
| 435 | resolve_attribute_type(name: Trees.Identifiers.Identifier) -> Semantic.Symbols.Classy? is | |
| 436 | let direct = as_classy(_visitor.try_find(name)) | |
| 437 | ||
| 438 | if direct? then | |
| 439 | return direct | |
| 440 | fi | |
| 441 | ||
| 442 | let suffixed_name = "{name.name}Attribute" | |
| 443 | ||
| 444 | if name.is_qualified then | |
| 445 | let qualifier = _visitor.try_find(name.qualifier!) | |
| 446 | ||
| 447 | if !qualifier? then | |
| 448 | return null | |
| 449 | fi | |
| 450 | ||
| 451 | return as_classy(qualifier.find_member(suffixed_name)) | |
| 452 | fi | |
| 453 | ||
| 454 | return as_classy(_visitor.find(suffixed_name)) | |
| 455 | si | |
| 456 | ||
| 457 | as_classy(symbol: Semantic.Symbols.Symbol?) -> Semantic.Symbols.Classy? is | |
| 458 | if !symbol? then | |
| 459 | return null | |
| 460 | fi | |
| 461 | ||
| 462 | // A name shared across generic arities resolves to a TYPE_GROUP. | |
| 463 | // Applying an attribute supplies no type arguments, so the | |
| 464 | // non-generic member is the intended one. | |
| 465 | if let group: Semantic.Symbols.TYPE_GROUP = symbol then | |
| 466 | return group.find_by_generic_arguments_count(0) | |
| 467 | fi | |
| 468 | ||
| 469 | return cast Semantic.Symbols.Classy?(symbol) | |
| 470 | si | |
| 471 | ||
| 472 | gather_positional_arguments(pragma: Trees.Pragmas.PRAGMA) -> Collections.LIST[IR.Values.Value] is | |
| 473 | let result = Collections.LIST[IR.Values.Value]() | |
| 474 | ||
| 475 | for argument in pragma.arguments.expressions do | |
| 476 | if argument.value? then | |
| 477 | result.add(argument.value) | |
| 478 | fi | |
| 479 | od | |
| 480 | ||
| 481 | return result | |
| 482 | si | |
| 483 | ||
| 484 | resolve_constructor( | |
| 485 | pragma: Trees.Pragmas.PRAGMA, | |
| 486 | attribute_type: Semantic.Symbols.Classy, | |
| 487 | positional: Collections.List[IR.Values.Value] | |
| 488 | ) -> Semantic.Symbols.Function? is | |
| 489 | let init_group = cast Semantic.Symbols.FUNCTION_GROUP?(attribute_type.find_member("init")) | |
| 490 | ||
| 491 | if !init_group? then | |
| 492 | _logger.error(pragma.location, "attribute {attribute_type.name} has no usable constructor") | |
| 493 | return null | |
| 494 | fi | |
| 495 | ||
| 496 | let argument_types = Collections.LIST[Type]() | |
| 497 | ||
| 498 | for argument in positional do | |
| 499 | if argument.type? then | |
| 500 | argument_types.add(argument.type) | |
| 501 | else | |
| 502 | argument_types.add(Semantic.Types.ERROR()) | |
| 503 | fi | |
| 504 | od | |
| 505 | ||
| 506 | _logger.speculate() | |
| 507 | ||
| 508 | let overload_result = _overload_resolver.resolve(pragma.location, init_group, argument_types, false, true, true) | |
| 509 | ||
| 510 | if overload_result? then | |
| 511 | _logger.commit() | |
| 512 | return overload_result.function | |
| 513 | fi | |
| 514 | ||
| 515 | let use retry_site = RETRY_SITE_STATS.enter("attributes.constructor_fallback", RetrySiteKind.ALTERNATIVE) | |
| 516 | let with_empty_arrays = resolve_constructor_with_empty_arrays(init_group, positional, argument_types) | |
| 517 | ||
| 518 | if with_empty_arrays? then | |
| 519 | _logger.roll_back() | |
| 520 | return with_empty_arrays | |
| 521 | fi | |
| 522 | ||
| 523 | let with_omitted_optionals = resolve_constructor_with_omitted_optionals(init_group, argument_types) | |
| 524 | ||
| 525 | if with_omitted_optionals? then | |
| 526 | _logger.roll_back() | |
| 527 | return with_omitted_optionals | |
| 528 | fi | |
| 529 | ||
| 530 | _logger.commit() | |
| 531 | _logger.error(pragma.location, "no constructor of attribute {attribute_type.name} matches the supplied arguments") | |
| 532 | return null | |
| 533 | si | |
| 534 | ||
| 535 | // An empty array literal argument carries no element type of its | |
| 536 | // own, so it falls back to object[] and fails to match a more | |
| 537 | // specific array parameter. An empty array is compatible with any | |
| 538 | // array type, so when the direct resolution fails, retry accepting | |
| 539 | // each empty-array argument against any array parameter of a | |
| 540 | // uniquely matching constructor. The empty array is then formatted | |
| 541 | // with the parameter's element type in format_argument. | |
| 542 | resolve_constructor_with_empty_arrays( | |
| 543 | init_group: Semantic.Symbols.FUNCTION_GROUP, | |
| 544 | positional: Collections.List[IR.Values.Value], | |
| 545 | argument_types: Collections.List[Type] | |
| 546 | ) -> Semantic.Symbols.Function? is | |
| 547 | let have_empty_array mut = false | |
| 548 | ||
| 549 | for argument in positional do | |
| 550 | if is_empty_array(argument) then | |
| 551 | have_empty_array = true | |
| 552 | fi | |
| 553 | od | |
| 554 | ||
| 555 | if !have_empty_array then | |
| 556 | return null | |
| 557 | fi | |
| 558 | ||
| 559 | let match: Semantic.Symbols.Function? mut = null | |
| 560 | ||
| 561 | for candidate in init_group.functions do | |
| 562 | if candidate.arguments.count != positional.count then | |
| 563 | continue | |
| 564 | fi | |
| 565 | ||
| 566 | let ok mut = true | |
| 567 | ||
| 568 | for i in 0..positional.count do | |
| 569 | let parameter = candidate.arguments[i] | |
| 570 | ||
| 571 | if is_empty_array(positional[i]) then | |
| 572 | if !parameter.get_element_type()? then | |
| 573 | ok = false | |
| 574 | fi | |
| 575 | elif !parameter.is_assignable_from(argument_types[i]) then | |
| 576 | ok = false | |
| 577 | fi | |
| 578 | od | |
| 579 | ||
| 580 | if ok then | |
| 581 | if match? then | |
| 582 | return null | |
| 583 | fi | |
| 584 | ||
| 585 | match = candidate | |
| 586 | fi | |
| 587 | od | |
| 588 | ||
| 589 | return match | |
| 590 | si | |
| 591 | ||
| 592 | // ghūl cannot omit an optional parameter in an ordinary call, | |
| 593 | // so a constructor called with fewer positional arguments | |
| 594 | // than its arity — e.g. an attribute whose .NET constructor | |
| 595 | // carries `CallerFilePath`/`CallerLineNumber` defaults — | |
| 596 | // would otherwise need every trailing default spelled out at | |
| 597 | // the call site. Retry accepting the supplied prefix against | |
| 598 | // a uniquely matching constructor whose unsupplied trailing | |
| 599 | // parameters all declare a default value; format_arguments | |
| 600 | // fills those defaults in. | |
| 601 | // | |
| 602 | // Only a concrete literal default qualifies — the "default" | |
| 603 | // sentinel (a ghūl source `= _` parameter, or a reflected | |
| 604 | // parameter whose CLR default is itself `default(T)`) is | |
| 605 | // excluded, since format_argument has no ilasm literal to | |
| 606 | // render it as for an arbitrary parameter type. | |
| 607 | resolve_constructor_with_omitted_optionals( | |
| 608 | init_group: Semantic.Symbols.FUNCTION_GROUP, | |
| 609 | argument_types: Collections.List[Type] | |
| 610 | ) -> Semantic.Symbols.Function? is | |
| 611 | let match: Semantic.Symbols.Function? mut = null | |
| 612 | ||
| 613 | for candidate in init_group.functions do | |
| 614 | if candidate.arguments.count <= argument_types.count then | |
| 615 | continue | |
| 616 | fi | |
| 617 | ||
| 618 | let ok mut = true | |
| 619 | ||
| 620 | for i in 0..argument_types.count do | |
| 621 | if !candidate.arguments[i].is_assignable_from(argument_types[i]) then | |
| 622 | ok = false | |
| 623 | fi | |
| 624 | od | |
| 625 | ||
| 626 | if ok then | |
| 627 | for i in argument_types.count..candidate.arguments.count do | |
| 628 | if | |
| 629 | i >= candidate.argument_defaults.count \/ | |
| 630 | !candidate.argument_defaults[i]? \/ | |
| 631 | candidate.argument_defaults[i] =~ "default" | |
| 632 | then | |
| 633 | ok = false | |
| 634 | fi | |
| 635 | od | |
| 636 | fi | |
| 637 | ||
| 638 | if ok then | |
| 639 | if match? then | |
| 640 | return null | |
| 641 | fi | |
| 642 | ||
| 643 | match = candidate | |
| 644 | fi | |
| 645 | od | |
| 646 | ||
| 647 | return match | |
| 648 | si | |
| 649 | ||
| 650 | is_empty_array(value: IR.Values.Value) -> bool is | |
| 651 | if let sequence: IR.Values.SEQUENCE = value then | |
| 652 | return sequence.values.count == 0 | |
| 653 | fi | |
| 654 | ||
| 655 | return false | |
| 656 | si | |
| 657 | ||
| 658 | // The ilasm textual custom-attribute body: `( 01 00 00 00 )` when | |
| 659 | // there are no arguments at all, otherwise `{ <fixed>... <named>... }`. | |
| 660 | // Returns null if any argument is not a compile-time constant. | |
| 661 | format_arguments( | |
| 662 | pragma: Trees.Pragmas.PRAGMA, | |
| 663 | constructor: Semantic.Symbols.Function, | |
| 664 | positional: Collections.List[IR.Values.Value], | |
| 665 | named: Collections.List[Semantic.NAMED_ATTRIBUTE_ARGUMENT] | |
| 666 | ) -> string? is | |
| 667 | let have_named = named.count > 0 | |
| 668 | ||
| 669 | if constructor.arguments.count == 0 /\ !have_named then | |
| 670 | return "( 01 00 00 00 )" | |
| 671 | fi | |
| 672 | ||
| 673 | let parameter_types = constructor.arguments | |
| 674 | ||
| 675 | let buffer = StringBuilder() | |
| 676 | ||
| 677 | buffer.append("{{") | |
| 678 | ||
| 679 | for i in 0..parameter_types.count do | |
| 680 | let formatted = format_argument(parameter_types[i], positional[i]) | |
| 681 | ||
| 682 | if !formatted? then | |
| 683 | _logger.error(pragma.location, "attribute argument {i + 1} is not a compile-time constant") | |
| 684 | return null | |
| 685 | fi | |
| 686 | ||
| 687 | buffer.append(" ").append(formatted) | |
| 688 | od | |
| 689 | ||
| 690 | if have_named then | |
| 691 | for named_argument in named do | |
| 692 | let formatted = format_named_argument(named_argument) | |
| 693 | ||
| 694 | if !formatted? then | |
| 695 | return null | |
| 696 | fi | |
| 697 | ||
| 698 | buffer.append(" ").append(formatted) | |
| 699 | od | |
| 700 | fi | |
| 701 | ||
| 702 | buffer.append(" }}") | |
| 703 | ||
| 704 | return buffer.to_string() | |
| 705 | si | |
| 706 | ||
| 707 | // One `name = value` argument in the ilasm textual form — | |
| 708 | // `property <type> 'Name' = <value>` or `field <type> 'Name' = ...`. | |
| 709 | // Returns null (after logging) if the member's type or the value | |
| 710 | // cannot be rendered. | |
| 711 | format_named_argument( | |
| 712 | named_argument: Semantic.NAMED_ATTRIBUTE_ARGUMENT | |
| 713 | ) -> string? is | |
| 714 | let name = named_argument.il_name | |
| 715 | ||
| 716 | let keyword = if named_argument.is_field then "field" else "property" fi | |
| 717 | ||
| 718 | let type_token = named_argument_type_token(named_argument.member_type) | |
| 719 | ||
| 720 | if !type_token? then | |
| 721 | _logger.error(named_argument.location, "attribute argument {name} has an unsupported type") | |
| 722 | return null | |
| 723 | fi | |
| 724 | ||
| 725 | let formatted_value = | |
| 726 | format_argument(named_argument.member_type, named_argument.value) | |
| 727 | ||
| 728 | if !formatted_value? then | |
| 729 | _logger.error(named_argument.location, "attribute argument {name} is not a compile-time constant") | |
| 730 | return null | |
| 731 | fi | |
| 732 | ||
| 733 | return "{keyword} {type_token} '{name}' = {formatted_value}" | |
| 734 | si | |
| 735 | ||
| 736 | // One positional or named-value argument in the ilasm textual | |
| 737 | // form, or null if `value` is not a supported compile-time | |
| 738 | // constant — a string, a number (optionally negated), a bool, an | |
| 739 | // enum member, `null`, or an array of those. | |
| 740 | format_argument(parameter_type: Type?, value: IR.Values.Value?) -> string? is | |
| 741 | if !value? then | |
| 742 | return null | |
| 743 | fi | |
| 744 | ||
| 745 | if parameter_type? /\ parameter_type.matches(_innate_symbol_lookup.get_object_type()) then | |
| 746 | let inner = format_typed_value(value) | |
| 747 | ||
| 748 | if inner? then | |
| 749 | return "object({inner})" | |
| 750 | fi | |
| 751 | ||
| 752 | return null | |
| 753 | fi | |
| 754 | ||
| 755 | if isa IR.Values.NULL(value) then | |
| 756 | if parameter_type? /\ parameter_type.matches(_innate_symbol_lookup.get_string_type()) then | |
| 757 | return "string(nullref)" | |
| 758 | fi | |
| 759 | ||
| 760 | return null | |
| 761 | fi | |
| 762 | ||
| 763 | if let string_value: IR.Values.Literal.STRING = value then | |
| 764 | return "string({quote(string_value.value)})" | |
| 765 | fi | |
| 766 | ||
| 767 | let number_text = try_get_number_text(value) | |
| 768 | ||
| 769 | if number_text? then | |
| 770 | let keyword = serialization_keyword(parameter_type) | |
| 771 | ||
| 772 | if keyword =~ "bool" then | |
| 773 | return "bool({number_text_to_bool(number_text)})" | |
| 774 | fi | |
| 775 | ||
| 776 | return "{keyword}({number_text})" | |
| 777 | fi | |
| 778 | ||
| 779 | if let sequence: IR.Values.SEQUENCE = value then | |
| 780 | // An empty array literal carried no elements to infer from | |
| 781 | // and fell back to object[]; take its element type from the | |
| 782 | // parameter instead. | |
| 783 | if sequence.values.count == 0 /\ parameter_type? then | |
| 784 | let element_type = parameter_type.get_element_type() | |
| 785 | ||
| 786 | if element_type? then | |
| 787 | let element_token = named_argument_type_token(element_type) | |
| 788 | ||
| 789 | if element_token? then | |
| 790 | return "{element_token}[0]()" | |
| 791 | fi | |
| 792 | fi | |
| 793 | fi | |
| 794 | ||
| 795 | return format_array(sequence) | |
| 796 | fi | |
| 797 | ||
| 798 | if let typeof_value: IR.Values.TYPEOF = value then | |
| 799 | // A typeof argument can name any type; only a class/struct/ | |
| 800 | // enum or an array of one serializes to a blob name, so a | |
| 801 | // typeof of anything else is rejected here rather than | |
| 802 | // reaching the encoder as a name it cannot serialize. | |
| 803 | if _typeof_arg_is_serializable(typeof_value.typeof_type) then | |
| 804 | return "type" | |
| 805 | fi | |
| 806 | ||
| 807 | return null | |
| 808 | fi | |
| 809 | ||
| 810 | return null | |
| 811 | si | |
| 812 | ||
| 813 | // The ilasm `<element>[N](e0 e1 ...)` form for an array argument. | |
| 814 | format_array(sequence: IR.Values.SEQUENCE) -> string? is | |
| 815 | let element_token = named_argument_type_token(sequence.element_type) | |
| 816 | ||
| 817 | if !element_token? then | |
| 818 | return null | |
| 819 | fi | |
| 820 | ||
| 821 | let buffer = StringBuilder() | |
| 822 | ||
| 823 | buffer | |
| 824 | .append(element_token) | |
| 825 | .append("[") | |
| 826 | .append(sequence.values.count) | |
| 827 | .append("](") | |
| 828 | ||
| 829 | for i in 0..sequence.values.count do | |
| 830 | if i > 0 then | |
| 831 | buffer.append(" ") | |
| 832 | fi | |
| 833 | ||
| 834 | let element = format_array_element(sequence.element_type, sequence.values[i]) | |
| 835 | ||
| 836 | if !element? then | |
| 837 | return null | |
| 838 | fi | |
| 839 | ||
| 840 | buffer.append(element) | |
| 841 | od | |
| 842 | ||
| 843 | buffer.append(")") | |
| 844 | ||
| 845 | return buffer.to_string() | |
| 846 | si | |
| 847 | ||
| 848 | // A single array element — the bare form, without the | |
| 849 | // `keyword(...)` wrapper that a scalar argument carries. An | |
| 850 | // element of an `object[]` is itself self-describing, so for | |
| 851 | // an `object`-typed element type each element is emitted in its | |
| 852 | // value-typed form (`int32(5)`, `string('x')`, …) rather than | |
| 853 | // bare — that is what `format_typed_value` produces. | |
| 854 | format_array_element(element_type: Type?, value: IR.Values.Value) -> string? is | |
| 855 | if element_type? /\ element_type.matches(_innate_symbol_lookup.get_object_type()) then | |
| 856 | return format_typed_value(value) | |
| 857 | fi | |
| 858 | ||
| 859 | if let string_value: IR.Values.Literal.STRING = value then | |
| 860 | return quote(string_value.value) | |
| 861 | fi | |
| 862 | ||
| 863 | let number_text = try_get_number_text(value) | |
| 864 | ||
| 865 | if number_text? then | |
| 866 | if serialization_keyword(element_type) =~ "bool" then | |
| 867 | return number_text_to_bool(number_text) | |
| 868 | fi | |
| 869 | ||
| 870 | return number_text | |
| 871 | fi | |
| 872 | ||
| 873 | return null | |
| 874 | si | |
| 875 | ||
| 876 | // The ilasm typed self-describing form for a value, used as the | |
| 877 | // inner of an `object(...)` wrapper and as each element of an | |
| 878 | // `object[]` array. Keys off the value's own type rather than | |
| 879 | // any enclosing slot type, because that is exactly the | |
| 880 | // information that has to be re-asserted inline when the slot's | |
| 881 | // declared type is `System.Object`. | |
| 882 | format_typed_value(value: IR.Values.Value) -> string? is | |
| 883 | if isa IR.Values.NULL(value) then | |
| 884 | // No top-level `nullref` form inside ilasm's `object(...)`; | |
| 885 | // a null reference is encoded as a null string (the form | |
| 886 | // C# emits for `[Foo((object)null)]`). | |
| 887 | return "string(nullref)" | |
| 888 | fi | |
| 889 | ||
| 890 | if let string_value: IR.Values.Literal.STRING = value then | |
| 891 | return "string({quote(string_value.value)})" | |
| 892 | fi | |
| 893 | ||
| 894 | let number_text = try_get_number_text(value) | |
| 895 | ||
| 896 | if number_text? then | |
| 897 | if value.type? then | |
| 898 | let primitive = try_primitive_keyword(value.type) | |
| 899 | ||
| 900 | if primitive? then | |
| 901 | if primitive =~ "bool" then | |
| 902 | return "bool({number_text_to_bool(number_text)})" | |
| 903 | fi | |
| 904 | ||
| 905 | return "{primitive}({number_text})" | |
| 906 | fi | |
| 907 | ||
| 908 | // An enum-typed value inside an `object` slot would | |
| 909 | // require encoding the enum's type-name SerString | |
| 910 | // into the blob, and ilasm's `{...}` grammar has no | |
| 911 | // production for `enum` inside `object(...)` or as | |
| 912 | // an object-array element — the whole `.custom` | |
| 913 | // would have to be emitted as raw bytes. Reject for | |
| 914 | // now; the caller surfaces "not a compile-time | |
| 915 | // constant". | |
| 916 | fi | |
| 917 | ||
| 918 | return null | |
| 919 | fi | |
| 920 | ||
| 921 | if let typeof_value: IR.Values.TYPEOF = value then | |
| 922 | // A typeof argument can name any type; only a class/struct/ | |
| 923 | // enum or an array of one serializes to a blob name, so a | |
| 924 | // typeof of anything else is rejected here rather than | |
| 925 | // reaching the encoder as a name it cannot serialize. | |
| 926 | if _typeof_arg_is_serializable(typeof_value.typeof_type) then | |
| 927 | return "type" | |
| 928 | fi | |
| 929 | ||
| 930 | return null | |
| 931 | fi | |
| 932 | ||
| 933 | if let sequence: IR.Values.SEQUENCE = value then | |
| 934 | return format_array(sequence) | |
| 935 | fi | |
| 936 | ||
| 937 | return null | |
| 938 | si | |
| 939 | ||
| 940 | // The numeric text of a NUMBER literal, or of a unary minus | |
| 941 | // applied to one — `-5` parses as a `-` innate over `5`, not as a | |
| 942 | // negative literal — or null if `value` is not a numeric constant. | |
| 943 | try_get_number_text(value: IR.Values.Value) -> string? is | |
| 944 | if let number: IR.Values.Literal.NUMBER = value then | |
| 945 | return number.rendered | |
| 946 | fi | |
| 947 | ||
| 948 | if let innate_call: IR.Values.Call.INNATE = value then | |
| 949 | if | |
| 950 | innate_call.function.name =~ "-" /\ | |
| 951 | innate_call.arguments.count == 1 | |
| 952 | then | |
| 953 | if let number: IR.Values.Literal.NUMBER = innate_call.arguments[0] then | |
| 954 | return "-{number.rendered}" | |
| 955 | fi | |
| 956 | fi | |
| 957 | fi | |
| 958 | ||
| 959 | return null | |
| 960 | si | |
| 961 | ||
| 962 | number_text_to_bool(number_text: string) -> string => | |
| 963 | if number_text =~ "0" then "false" else "true" fi | |
| 964 | ||
| 965 | // The ilasm type token for a named-argument member, an array | |
| 966 | // member, or an array element — a primitive keyword, `string`, | |
| 967 | // `enum <name>`, or `<element>[]`; null if the type cannot be | |
| 968 | // encoded into a custom-attribute blob from a textual literal. | |
| 969 | named_argument_type_token(type: Type?) -> string? is | |
| 970 | if !type? then | |
| 971 | return null | |
| 972 | fi | |
| 973 | ||
| 974 | if type.matches(_innate_symbol_lookup.get_string_type()) then | |
| 975 | return "string" | |
| 976 | fi | |
| 977 | ||
| 978 | if type.matches(_innate_symbol_lookup.get_type_type()) then | |
| 979 | return "type" | |
| 980 | fi | |
| 981 | ||
| 982 | if type.matches(_innate_symbol_lookup.get_object_type()) then | |
| 983 | return "object" | |
| 984 | fi | |
| 985 | ||
| 986 | let primitive = try_primitive_keyword(type) | |
| 987 | ||
| 988 | if primitive? then | |
| 989 | return primitive | |
| 990 | fi | |
| 991 | ||
| 992 | if let array: Semantic.Types.ARRAY = type then | |
| 993 | let element_token = named_argument_type_token(array.arguments[0]) | |
| 994 | ||
| 995 | if element_token? then | |
| 996 | return "{element_token}[]" | |
| 997 | fi | |
| 998 | ||
| 999 | return null | |
| 1000 | fi | |
| 1001 | ||
| 1002 | if type.symbol.symbol_kind == Semantic.Symbols.SymbolKind.ENUM then | |
| 1003 | if let classy: Semantic.Symbols.Classy = type.symbol then | |
| 1004 | if classy.il_assembly_name? then | |
| 1005 | return "enum [{classy.il_assembly_name}]{classy.qualified_name}" | |
| 1006 | fi | |
| 1007 | ||
| 1008 | return "enum {classy.qualified_name}" | |
| 1009 | fi | |
| 1010 | fi | |
| 1011 | ||
| 1012 | return null | |
| 1013 | si | |
| 1014 | ||
| 1015 | // Whether a typeof argument's type can be serialized into an | |
| 1016 | // attribute blob. The answer is the serializer's own - the same | |
| 1017 | // one the encoder uses - so what is accepted here is exactly | |
| 1018 | // what the blob can spell, and a typeof of anything else (a | |
| 1019 | // pointer, a type parameter) is rejected as a diagnostic rather | |
| 1020 | // than reaching the encoder. | |
| 1021 | _typeof_arg_is_serializable(type: Type?) -> bool is | |
| 1022 | if !type? then | |
| 1023 | return false | |
| 1024 | fi | |
| 1025 | ||
| 1026 | return Semantic.DotNet.REFLECTION_TYPE_NAME.try_serialize(type)? | |
| 1027 | si | |
| 1028 | ||
| 1029 | // The serialization keyword for a primitive value type, or null | |
| 1030 | // for anything that is not a primitive. The keyword itself | |
| 1031 | // reaches only discarded text; what matters is the null/non-null | |
| 1032 | // answer, which gates whether the attribute argument is accepted. | |
| 1033 | // Classification is by type identity, matching the binary blob | |
| 1034 | // encoder, not by rendering the type and string-matching. | |
| 1035 | try_primitive_keyword(type: Type?) -> string? is | |
| 1036 | if !type? then | |
| 1037 | return null | |
| 1038 | fi | |
| 1039 | ||
| 1040 | let t = type | |
| 1041 | ||
| 1042 | if t.matches(_innate_symbol_lookup.get_bool_type()) then return "bool"; fi | |
| 1043 | if t.matches(_innate_symbol_lookup.get_char_type()) then return "char"; fi | |
| 1044 | if t.matches(_innate_symbol_lookup.get_byte_type()) then return "int8"; fi | |
| 1045 | if t.matches(_innate_symbol_lookup.get_ubyte_type()) then return "uint8"; fi | |
| 1046 | if t.matches(_innate_symbol_lookup.get_short_type()) then return "int16"; fi | |
| 1047 | if t.matches(_innate_symbol_lookup.get_ushort_type()) then return "uint16"; fi | |
| 1048 | if t.matches(_innate_symbol_lookup.get_int_type()) then return "int32"; fi | |
| 1049 | if t.matches(_innate_symbol_lookup.get_uint_type()) then return "uint32"; fi | |
| 1050 | if t.matches(_innate_symbol_lookup.get_long_type()) then return "int64"; fi | |
| 1051 | if t.matches(_innate_symbol_lookup.get_ulong_type()) then return "uint64"; fi | |
| 1052 | if t.matches(_innate_symbol_lookup.get_single_type()) then return "float32"; fi | |
| 1053 | if t.matches(_innate_symbol_lookup.get_double_type()) then return "float64"; fi | |
| 1054 | ||
| 1055 | return null | |
| 1056 | si | |
| 1057 | ||
| 1058 | // The ilasm serialization keyword for a numeric or bool value: | |
| 1059 | // the parameter type's primitive keyword, or `int32` for an enum, | |
| 1060 | // whose fixed-argument form encodes the underlying value. | |
| 1061 | serialization_keyword(parameter_type: Type?) -> string is | |
| 1062 | let primitive = try_primitive_keyword(parameter_type) | |
| 1063 | ||
| 1064 | if primitive? then | |
| 1065 | return primitive | |
| 1066 | fi | |
| 1067 | ||
| 1068 | return "int32" | |
| 1069 | si | |
| 1070 | ||
| 1071 | quote(value: string) -> string => | |
| 1072 | Semantic.DotNet.IL_ATTRIBUTE_ARGUMENTS.quote(value) | |
| 1073 | si | |
| 1074 | si |