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, formats | |
| 11 | // its arguments into the ilasm textual custom-attribute form, and | |
| 12 | // attaches the resolved CUSTOM_ATTRIBUTE to the target symbol so the | |
| 13 | // IL generator can emit a `.custom` directive. | |
| 14 | class ATTRIBUTE_RESOLVER is | |
| 15 | _logger: Logger; | |
| 16 | _innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup; | |
| 17 | _overload_resolver: Semantic.OVERLOAD_RESOLVER; | |
| 18 | _visitor: ScopedVisitor; | |
| 19 | ||
| 20 | init( | |
| 21 | logger: Logger, | |
| 22 | innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup, | |
| 23 | overload_resolver: Semantic.OVERLOAD_RESOLVER, | |
| 24 | visitor: ScopedVisitor | |
| 25 | ) is | |
| 26 | super.init(); | |
| 27 | ||
| 28 | _logger = logger; | |
| 29 | _innate_symbol_lookup = innate_symbol_lookup; | |
| 30 | _overload_resolver = overload_resolver; | |
| 31 | _visitor = visitor; | |
| 32 | si | |
| 33 | ||
| 34 | // A built-in pragma is handled by the compiler itself; anything | |
| 35 | // else is taken to name a .NET attribute. | |
| 36 | is_built_in(name: string) -> bool => | |
| 37 | name.starts_with("IL.") \/ | |
| 38 | name.starts_with("IF.") \/ | |
| 39 | name =~ "precedence" \/ | |
| 40 | name =~ "test" \/ | |
| 41 | name =~ "test_class" \/ | |
| 42 | name =~ "test_method" \/ | |
| 43 | name =~ "entry" \/ | |
| 44 | name =~ "suppress"; | |
| 45 | ||
| 46 | resolve(pragma: Trees.Pragmas.PRAGMA, target: Semantic.Symbols.Symbol?) is | |
| 47 | if !target? then | |
| 48 | return; | |
| 49 | fi | |
| 50 | ||
| 51 | let name = pragma.name.to_string(); | |
| 52 | ||
| 53 | if is_built_in(name) then | |
| 54 | return; | |
| 55 | fi | |
| 56 | ||
| 57 | let attribute_type = resolve_attribute_type(pragma.name); | |
| 58 | ||
| 59 | if !attribute_type? then | |
| 60 | _logger.error(pragma.name.location, "unknown attribute {name}"); | |
| 61 | return; | |
| 62 | fi | |
| 63 | ||
| 64 | let positional = gather_positional_arguments(pragma); | |
| 65 | ||
| 66 | let constructor = resolve_constructor(pragma, attribute_type, positional); | |
| 67 | ||
| 68 | if !constructor? then | |
| 69 | return; | |
| 70 | fi | |
| 71 | ||
| 72 | let arguments_text = format_arguments(pragma, attribute_type, constructor, positional); | |
| 73 | ||
| 74 | if !arguments_text? then | |
| 75 | return; | |
| 76 | fi | |
| 77 | ||
| 78 | if !target.custom_attributes? then | |
| 79 | target.custom_attributes = Collections.LIST[Semantic.CUSTOM_ATTRIBUTE](); | |
| 80 | fi | |
| 81 | ||
| 82 | target.custom_attributes!.add(Semantic.CUSTOM_ATTRIBUTE(constructor, arguments_text)); | |
| 83 | si | |
| 84 | ||
| 85 | // Resolve the pragma name to a .NET attribute class, accepting the | |
| 86 | // `Foo` short form for a `FooAttribute` — whether `Foo` is written | |
| 87 | // bare or fully qualified (`A.B.Foo` resolves `A.B.FooAttribute`). | |
| 88 | resolve_attribute_type(name: Trees.Identifiers.Identifier) -> Semantic.Symbols.Classy? is | |
| 89 | let direct = as_classy(_visitor.try_find(name)); | |
| 90 | ||
| 91 | if direct? then | |
| 92 | return direct; | |
| 93 | fi | |
| 94 | ||
| 95 | let suffixed_name = "{name.name}Attribute"; | |
| 96 | ||
| 97 | if name.is_qualified then | |
| 98 | let qualifier = _visitor.try_find(name.qualifier!); | |
| 99 | ||
| 100 | if !qualifier? then | |
| 101 | return null; | |
| 102 | fi | |
| 103 | ||
| 104 | return as_classy(qualifier.find_member(suffixed_name)); | |
| 105 | fi | |
| 106 | ||
| 107 | return as_classy(_visitor.find(suffixed_name)); | |
| 108 | si | |
| 109 | ||
| 110 | as_classy(symbol: Semantic.Symbols.Symbol?) -> Semantic.Symbols.Classy? is | |
| 111 | if !symbol? then | |
| 112 | return null; | |
| 113 | fi | |
| 114 | ||
| 115 | // A name shared across generic arities resolves to a TYPE_GROUP. | |
| 116 | // Applying an attribute supplies no type arguments, so the | |
| 117 | // non-generic member is the intended one. | |
| 118 | if let group: Semantic.Symbols.TYPE_GROUP = symbol then | |
| 119 | return group.find_by_generic_arguments_count(0); | |
| 120 | fi | |
| 121 | ||
| 122 | return cast Semantic.Symbols.Classy?(symbol); | |
| 123 | si | |
| 124 | ||
| 125 | gather_positional_arguments(pragma: Trees.Pragmas.PRAGMA) -> Collections.LIST[IR.Values.Value] is | |
| 126 | let result = Collections.LIST[IR.Values.Value](); | |
| 127 | ||
| 128 | for argument in pragma.arguments.expressions do | |
| 129 | if argument.value? then | |
| 130 | result.add(argument.value); | |
| 131 | fi | |
| 132 | od | |
| 133 | ||
| 134 | return result; | |
| 135 | si | |
| 136 | ||
| 137 | resolve_constructor( | |
| 138 | pragma: Trees.Pragmas.PRAGMA, | |
| 139 | attribute_type: Semantic.Symbols.Classy, | |
| 140 | positional: Collections.List[IR.Values.Value] | |
| 141 | ) -> Semantic.Symbols.Function? is | |
| 142 | let init_group = cast Semantic.Symbols.FUNCTION_GROUP?(attribute_type.find_member("init")); | |
| 143 | ||
| 144 | if !init_group? then | |
| 145 | _logger.error(pragma.location, "attribute {attribute_type.name} has no usable constructor"); | |
| 146 | return null; | |
| 147 | fi | |
| 148 | ||
| 149 | let argument_types = Collections.LIST[Type](); | |
| 150 | ||
| 151 | for argument in positional do | |
| 152 | if argument.type? then | |
| 153 | argument_types.add(argument.type!); | |
| 154 | else | |
| 155 | argument_types.add(Semantic.Types.ERROR()); | |
| 156 | fi | |
| 157 | od | |
| 158 | ||
| 159 | _logger.speculate(); | |
| 160 | ||
| 161 | let overload_result = _overload_resolver.resolve(pragma.location, init_group, argument_types, false, true, true); | |
| 162 | ||
| 163 | if overload_result? then | |
| 164 | _logger.commit(); | |
| 165 | return overload_result.function; | |
| 166 | fi | |
| 167 | ||
| 168 | let with_empty_arrays = resolve_constructor_with_empty_arrays(init_group, positional, argument_types); | |
| 169 | ||
| 170 | if with_empty_arrays? then | |
| 171 | _logger.roll_back(); | |
| 172 | return with_empty_arrays; | |
| 173 | fi | |
| 174 | ||
| 175 | let with_omitted_optionals = resolve_constructor_with_omitted_optionals(init_group, argument_types); | |
| 176 | ||
| 177 | if with_omitted_optionals? then | |
| 178 | _logger.roll_back(); | |
| 179 | return with_omitted_optionals; | |
| 180 | fi | |
| 181 | ||
| 182 | _logger.commit(); | |
| 183 | _logger.error(pragma.location, "no constructor of attribute {attribute_type.name} matches the supplied arguments"); | |
| 184 | return null; | |
| 185 | si | |
| 186 | ||
| 187 | // An empty array literal argument carries no element type of its | |
| 188 | // own, so it falls back to object[] and fails to match a more | |
| 189 | // specific array parameter. An empty array is compatible with any | |
| 190 | // array type, so when the direct resolution fails, retry accepting | |
| 191 | // each empty-array argument against any array parameter of a | |
| 192 | // uniquely matching constructor. The empty array is then formatted | |
| 193 | // with the parameter's element type in format_argument. | |
| 194 | resolve_constructor_with_empty_arrays( | |
| 195 | init_group: Semantic.Symbols.FUNCTION_GROUP, | |
| 196 | positional: Collections.List[IR.Values.Value], | |
| 197 | argument_types: Collections.List[Type] | |
| 198 | ) -> Semantic.Symbols.Function? is | |
| 199 | let have_empty_array mut = false; | |
| 200 | ||
| 201 | for argument in positional do | |
| 202 | if is_empty_array(argument) then | |
| 203 | have_empty_array = true; | |
| 204 | fi | |
| 205 | od | |
| 206 | ||
| 207 | if !have_empty_array then | |
| 208 | return null; | |
| 209 | fi | |
| 210 | ||
| 211 | let match: Semantic.Symbols.Function? mut = null; | |
| 212 | ||
| 213 | for candidate in init_group.functions do | |
| 214 | if candidate.arguments.count != positional.count then | |
| 215 | continue; | |
| 216 | fi | |
| 217 | ||
| 218 | let ok mut = true; | |
| 219 | ||
| 220 | for i in 0..positional.count do | |
| 221 | let parameter = candidate.arguments[i]; | |
| 222 | ||
| 223 | if is_empty_array(positional[i]) then | |
| 224 | if !parameter.get_element_type()? then | |
| 225 | ok = false; | |
| 226 | fi | |
| 227 | elif !parameter.is_assignable_from(argument_types[i]) then | |
| 228 | ok = false; | |
| 229 | fi | |
| 230 | od | |
| 231 | ||
| 232 | if ok then | |
| 233 | if match? then | |
| 234 | return null; | |
| 235 | fi | |
| 236 | ||
| 237 | match = candidate; | |
| 238 | fi | |
| 239 | od | |
| 240 | ||
| 241 | return match; | |
| 242 | si | |
| 243 | ||
| 244 | // ghūl cannot omit an optional parameter in an ordinary call, | |
| 245 | // so a constructor called with fewer positional arguments | |
| 246 | // than its arity — e.g. an attribute whose .NET constructor | |
| 247 | // carries `CallerFilePath`/`CallerLineNumber` defaults — | |
| 248 | // would otherwise need every trailing default spelled out at | |
| 249 | // the call site. Retry accepting the supplied prefix against | |
| 250 | // a uniquely matching constructor whose unsupplied trailing | |
| 251 | // parameters all declare a default value; format_arguments | |
| 252 | // fills those defaults in. | |
| 253 | // | |
| 254 | // Only a concrete literal default qualifies — the "default" | |
| 255 | // sentinel (a ghūl source `= _` parameter, or a reflected | |
| 256 | // parameter whose CLR default is itself `default(T)`) is | |
| 257 | // excluded, since format_argument has no ilasm literal to | |
| 258 | // render it as for an arbitrary parameter type. | |
| 259 | resolve_constructor_with_omitted_optionals( | |
| 260 | init_group: Semantic.Symbols.FUNCTION_GROUP, | |
| 261 | argument_types: Collections.List[Type] | |
| 262 | ) -> Semantic.Symbols.Function? is | |
| 263 | let match: Semantic.Symbols.Function? mut = null; | |
| 264 | ||
| 265 | for candidate in init_group.functions do | |
| 266 | if candidate.arguments.count <= argument_types.count then | |
| 267 | continue; | |
| 268 | fi | |
| 269 | ||
| 270 | let ok mut = true; | |
| 271 | ||
| 272 | for i in 0..argument_types.count do | |
| 273 | if !candidate.arguments[i].is_assignable_from(argument_types[i]) then | |
| 274 | ok = false; | |
| 275 | fi | |
| 276 | od | |
| 277 | ||
| 278 | if ok then | |
| 279 | for i in argument_types.count..candidate.arguments.count do | |
| 280 | if | |
| 281 | i >= candidate.argument_defaults.count \/ | |
| 282 | !candidate.argument_defaults[i]? \/ | |
| 283 | candidate.argument_defaults[i] =~ "default" | |
| 284 | then | |
| 285 | ok = false; | |
| 286 | fi | |
| 287 | od | |
| 288 | fi | |
| 289 | ||
| 290 | if ok then | |
| 291 | if match? then | |
| 292 | return null; | |
| 293 | fi | |
| 294 | ||
| 295 | match = candidate; | |
| 296 | fi | |
| 297 | od | |
| 298 | ||
| 299 | return match; | |
| 300 | si | |
| 301 | ||
| 302 | is_empty_array(value: IR.Values.Value) -> bool is | |
| 303 | if let sequence: IR.Values.SEQUENCE = value then | |
| 304 | return sequence.values.count == 0; | |
| 305 | fi | |
| 306 | ||
| 307 | return false; | |
| 308 | si | |
| 309 | ||
| 310 | // The ilasm textual custom-attribute body: `( 01 00 00 00 )` when | |
| 311 | // there are no arguments at all, otherwise `{ <fixed>... <named>... }`. | |
| 312 | // Returns null if any argument is not a compile-time constant. | |
| 313 | format_arguments( | |
| 314 | pragma: Trees.Pragmas.PRAGMA, | |
| 315 | attribute_type: Semantic.Symbols.Classy, | |
| 316 | constructor: Semantic.Symbols.Function, | |
| 317 | positional: Collections.List[IR.Values.Value] | |
| 318 | ) -> string? is | |
| 319 | let named = pragma.named_arguments; | |
| 320 | let have_named = named? /\ named.count > 0; | |
| 321 | ||
| 322 | if constructor.arguments.count == 0 /\ !have_named then | |
| 323 | return "( 01 00 00 00 )"; | |
| 324 | fi | |
| 325 | ||
| 326 | let parameter_types = constructor.arguments; | |
| 327 | ||
| 328 | let buffer = StringBuilder(); | |
| 329 | ||
| 330 | buffer.append("{{"); | |
| 331 | ||
| 332 | for i in 0..parameter_types.count do | |
| 333 | let parameter_type = parameter_types[i]; | |
| 334 | ||
| 335 | // A trailing parameter beyond what was supplied only | |
| 336 | // reaches here via resolve_constructor_with_omitted_ | |
| 337 | // optionals, which already guarantees a usable literal | |
| 338 | // default at this index. | |
| 339 | let argument = | |
| 340 | if i < positional.count then | |
| 341 | positional[i]; | |
| 342 | else | |
| 343 | DEFAULT_ARGUMENT_VALUES.build( | |
| 344 | constructor.argument_defaults[i], | |
| 345 | parameter_type, | |
| 346 | _innate_symbol_lookup | |
| 347 | ); | |
| 348 | fi; | |
| 349 | ||
| 350 | let formatted = format_argument(parameter_type, argument); | |
| 351 | ||
| 352 | if !formatted? then | |
| 353 | _logger.error(pragma.location, "attribute argument {i + 1} is not a compile-time constant"); | |
| 354 | return null; | |
| 355 | fi | |
| 356 | ||
| 357 | buffer.append(" ").append(formatted); | |
| 358 | od | |
| 359 | ||
| 360 | if have_named then | |
| 361 | for named_argument in named! do | |
| 362 | let formatted = format_named_argument(attribute_type, named_argument); | |
| 363 | ||
| 364 | if !formatted? then | |
| 365 | return null; | |
| 366 | fi | |
| 367 | ||
| 368 | buffer.append(" ").append(formatted); | |
| 369 | od | |
| 370 | fi | |
| 371 | ||
| 372 | buffer.append(" }}"); | |
| 373 | ||
| 374 | return buffer.to_string(); | |
| 375 | si | |
| 376 | ||
| 377 | // One `name = value` argument in the ilasm textual form — | |
| 378 | // `property <type> 'Name' = <value>` or `field <type> 'Name' = ...`. | |
| 379 | // Returns null (after logging) if the name does not resolve to a | |
| 380 | // field or property of the attribute, or the value is unsupported. | |
| 381 | format_named_argument( | |
| 382 | attribute_type: Semantic.Symbols.Classy, | |
| 383 | named_argument: Trees.Pragmas.NAMED_ARGUMENT | |
| 384 | ) -> string? is | |
| 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(named_argument.name.location, "attribute {attribute_type.name} has no field or property {name}"); | |
| 391 | return null; | |
| 392 | fi | |
| 393 | ||
| 394 | let keyword: string mut; | |
| 395 | ||
| 396 | if member.symbol_kind == Semantic.Symbols.SymbolKind.PROPERTY then | |
| 397 | keyword = "property"; | |
| 398 | elif member.symbol_kind == Semantic.Symbols.SymbolKind.FIELD then | |
| 399 | keyword = "field"; | |
| 400 | else | |
| 401 | _logger.error(named_argument.name.location, "{name} is not a field or property of attribute {attribute_type.name}"); | |
| 402 | return null; | |
| 403 | fi | |
| 404 | ||
| 405 | let member_type = member.type; | |
| 406 | ||
| 407 | if !member_type? then | |
| 408 | _logger.error(named_argument.name.location, "attribute argument {name} has an unsupported type"); | |
| 409 | return null; | |
| 410 | fi | |
| 411 | ||
| 412 | let type_token = named_argument_type_token(member_type); | |
| 413 | ||
| 414 | if !type_token? then | |
| 415 | _logger.error(named_argument.name.location, "attribute argument {name} has an unsupported type"); | |
| 416 | return null; | |
| 417 | fi | |
| 418 | ||
| 419 | let formatted_value = format_argument(member_type, named_argument.value.value); | |
| 420 | ||
| 421 | if !formatted_value? then | |
| 422 | _logger.error(named_argument.value.location, "attribute argument {name} is not a compile-time constant"); | |
| 423 | return null; | |
| 424 | fi | |
| 425 | ||
| 426 | // The blob must carry the member's real .NET name. For a | |
| 427 | // reflected attribute that is its IL name (`TypeDiscriminator- | |
| 428 | // PropertyName`), not the snake_case identifier the call site | |
| 429 | // uses (`type_discriminator_property_name`). | |
| 430 | let il_member_name = if member.il_name_override? then member.il_name_override else name fi; | |
| 431 | ||
| 432 | return "{keyword} {type_token} '{il_member_name}' = {formatted_value}"; | |
| 433 | si | |
| 434 | ||
| 435 | // One positional or named-value argument in the ilasm textual | |
| 436 | // form, or null if `value` is not a supported compile-time | |
| 437 | // constant — a string, a number (optionally negated), a bool, an | |
| 438 | // enum member, `null`, or an array of those. | |
| 439 | format_argument(parameter_type: Type?, value: IR.Values.Value?) -> string? is | |
| 440 | if !value? then | |
| 441 | return null; | |
| 442 | fi | |
| 443 | ||
| 444 | if parameter_type? /\ parameter_type.matches(_innate_symbol_lookup.get_object_type()) then | |
| 445 | let inner = format_typed_value(value); | |
| 446 | ||
| 447 | if inner? then | |
| 448 | return "object({inner})"; | |
| 449 | fi | |
| 450 | ||
| 451 | return null; | |
| 452 | fi | |
| 453 | ||
| 454 | if isa IR.Values.NULL(value) then | |
| 455 | if parameter_type? /\ parameter_type.matches(_innate_symbol_lookup.get_string_type()) then | |
| 456 | return "string(nullref)"; | |
| 457 | fi | |
| 458 | ||
| 459 | return null; | |
| 460 | fi | |
| 461 | ||
| 462 | if let string_value: IR.Values.Literal.STRING = value then | |
| 463 | return "string({quote(string_value.value)})"; | |
| 464 | fi | |
| 465 | ||
| 466 | let number_text = try_get_number_text(value); | |
| 467 | ||
| 468 | if number_text? then | |
| 469 | let keyword = serialization_keyword(parameter_type); | |
| 470 | ||
| 471 | if keyword =~ "bool" then | |
| 472 | return "bool({number_text_to_bool(number_text)})"; | |
| 473 | fi | |
| 474 | ||
| 475 | return "{keyword}({number_text})"; | |
| 476 | fi | |
| 477 | ||
| 478 | if let sequence: IR.Values.SEQUENCE = value then | |
| 479 | // An empty array literal carried no elements to infer from | |
| 480 | // and fell back to object[]; take its element type from the | |
| 481 | // parameter instead. | |
| 482 | if sequence.values.count == 0 /\ parameter_type? then | |
| 483 | let element_type = parameter_type.get_element_type(); | |
| 484 | ||
| 485 | if element_type? then | |
| 486 | let element_token = named_argument_type_token(element_type); | |
| 487 | ||
| 488 | if element_token? then | |
| 489 | return "{element_token}[0]()"; | |
| 490 | fi | |
| 491 | fi | |
| 492 | fi | |
| 493 | ||
| 494 | return format_array(sequence); | |
| 495 | fi | |
| 496 | ||
| 497 | if let typeof_value: IR.Values.TYPEOF = value then | |
| 498 | return "type({typeof_value.typeof_type.get_il_class_name().trim()})"; | |
| 499 | fi | |
| 500 | ||
| 501 | return null; | |
| 502 | si | |
| 503 | ||
| 504 | // The ilasm `<element>[N](e0 e1 ...)` form for an array argument. | |
| 505 | format_array(sequence: IR.Values.SEQUENCE) -> string? is | |
| 506 | let element_token = named_argument_type_token(sequence.element_type); | |
| 507 | ||
| 508 | if !element_token? then | |
| 509 | return null; | |
| 510 | fi | |
| 511 | ||
| 512 | let buffer = StringBuilder(); | |
| 513 | ||
| 514 | buffer | |
| 515 | .append(element_token) | |
| 516 | .append("[") | |
| 517 | .append(sequence.values.count) | |
| 518 | .append("]("); | |
| 519 | ||
| 520 | for i in 0..sequence.values.count do | |
| 521 | if i > 0 then | |
| 522 | buffer.append(" "); | |
| 523 | fi | |
| 524 | ||
| 525 | let element = format_array_element(sequence.element_type, sequence.values[i]); | |
| 526 | ||
| 527 | if !element? then | |
| 528 | return null; | |
| 529 | fi | |
| 530 | ||
| 531 | buffer.append(element); | |
| 532 | od | |
| 533 | ||
| 534 | buffer.append(")"); | |
| 535 | ||
| 536 | return buffer.to_string(); | |
| 537 | si | |
| 538 | ||
| 539 | // A single array element — the bare form, without the | |
| 540 | // `keyword(...)` wrapper that a scalar argument carries. An | |
| 541 | // element of an `object[]` is itself self-describing, so for | |
| 542 | // an `object`-typed element type each element is emitted in its | |
| 543 | // value-typed form (`int32(5)`, `string('x')`, …) rather than | |
| 544 | // bare — that is what `format_typed_value` produces. | |
| 545 | format_array_element(element_type: Type?, value: IR.Values.Value) -> string? is | |
| 546 | if element_type? /\ element_type.matches(_innate_symbol_lookup.get_object_type()) then | |
| 547 | return format_typed_value(value); | |
| 548 | fi | |
| 549 | ||
| 550 | if let string_value: IR.Values.Literal.STRING = value then | |
| 551 | return quote(string_value.value); | |
| 552 | fi | |
| 553 | ||
| 554 | let number_text = try_get_number_text(value); | |
| 555 | ||
| 556 | if number_text? then | |
| 557 | if serialization_keyword(element_type) =~ "bool" then | |
| 558 | return number_text_to_bool(number_text); | |
| 559 | fi | |
| 560 | ||
| 561 | return number_text; | |
| 562 | fi | |
| 563 | ||
| 564 | return null; | |
| 565 | si | |
| 566 | ||
| 567 | // The ilasm typed self-describing form for a value, used as the | |
| 568 | // inner of an `object(...)` wrapper and as each element of an | |
| 569 | // `object[]` array. Keys off the value's own type rather than | |
| 570 | // any enclosing slot type, because that is exactly the | |
| 571 | // information that has to be re-asserted inline when the slot's | |
| 572 | // declared type is `System.Object`. | |
| 573 | format_typed_value(value: IR.Values.Value) -> string? is | |
| 574 | if isa IR.Values.NULL(value) then | |
| 575 | // No top-level `nullref` form inside ilasm's `object(...)`; | |
| 576 | // a null reference is encoded as a null string (the form | |
| 577 | // C# emits for `[Foo((object)null)]`). | |
| 578 | return "string(nullref)"; | |
| 579 | fi | |
| 580 | ||
| 581 | if let string_value: IR.Values.Literal.STRING = value then | |
| 582 | return "string({quote(string_value.value)})"; | |
| 583 | fi | |
| 584 | ||
| 585 | let number_text = try_get_number_text(value); | |
| 586 | ||
| 587 | if number_text? then | |
| 588 | if value.type? then | |
| 589 | let primitive = try_primitive_keyword(value.type!); | |
| 590 | ||
| 591 | if primitive? then | |
| 592 | if primitive =~ "bool" then | |
| 593 | return "bool({number_text_to_bool(number_text)})"; | |
| 594 | fi | |
| 595 | ||
| 596 | return "{primitive}({number_text})"; | |
| 597 | fi | |
| 598 | ||
| 599 | // An enum-typed value inside an `object` slot would | |
| 600 | // require encoding the enum's type-name SerString | |
| 601 | // into the blob, and ilasm's `{...}` grammar has no | |
| 602 | // production for `enum` inside `object(...)` or as | |
| 603 | // an object-array element — the whole `.custom` | |
| 604 | // would have to be emitted as raw bytes. Reject for | |
| 605 | // now; the caller surfaces "not a compile-time | |
| 606 | // constant". | |
| 607 | fi | |
| 608 | ||
| 609 | return null; | |
| 610 | fi | |
| 611 | ||
| 612 | if let typeof_value: IR.Values.TYPEOF = value then | |
| 613 | return "type({typeof_value.typeof_type.get_il_class_name().trim()})"; | |
| 614 | fi | |
| 615 | ||
| 616 | if let sequence: IR.Values.SEQUENCE = value then | |
| 617 | return format_array(sequence); | |
| 618 | fi | |
| 619 | ||
| 620 | return null; | |
| 621 | si | |
| 622 | ||
| 623 | // The numeric text of a NUMBER literal, or of a unary minus | |
| 624 | // applied to one — `-5` parses as a `-` innate over `5`, not as a | |
| 625 | // negative literal — or null if `value` is not a numeric constant. | |
| 626 | try_get_number_text(value: IR.Values.Value) -> string? is | |
| 627 | if let number: IR.Values.Literal.NUMBER = value then | |
| 628 | return number.value; | |
| 629 | fi | |
| 630 | ||
| 631 | if let innate_call: IR.Values.Call.INNATE = value then | |
| 632 | if | |
| 633 | innate_call.function.name =~ "-" /\ | |
| 634 | innate_call.arguments.count == 1 | |
| 635 | then | |
| 636 | if let number: IR.Values.Literal.NUMBER = innate_call.arguments[0] then | |
| 637 | return "-{number.value}"; | |
| 638 | fi | |
| 639 | fi | |
| 640 | fi | |
| 641 | ||
| 642 | return null; | |
| 643 | si | |
| 644 | ||
| 645 | number_text_to_bool(number_text: string) -> string => | |
| 646 | if number_text =~ "0" then "false" else "true" fi; | |
| 647 | ||
| 648 | // The ilasm type token for a named-argument member, an array | |
| 649 | // member, or an array element — a primitive keyword, `string`, | |
| 650 | // `enum <name>`, or `<element>[]`; null if the type cannot be | |
| 651 | // encoded into a custom-attribute blob from a textual literal. | |
| 652 | named_argument_type_token(type: Type?) -> string? is | |
| 653 | if !type? then | |
| 654 | return null; | |
| 655 | fi | |
| 656 | ||
| 657 | if type.matches(_innate_symbol_lookup.get_string_type()) then | |
| 658 | return "string"; | |
| 659 | fi | |
| 660 | ||
| 661 | if type.matches(_innate_symbol_lookup.get_type_type()) then | |
| 662 | return "type"; | |
| 663 | fi | |
| 664 | ||
| 665 | if type.matches(_innate_symbol_lookup.get_object_type()) then | |
| 666 | return "object"; | |
| 667 | fi | |
| 668 | ||
| 669 | let primitive = try_primitive_keyword(type); | |
| 670 | ||
| 671 | if primitive? then | |
| 672 | return primitive; | |
| 673 | fi | |
| 674 | ||
| 675 | if let array: Semantic.Types.ARRAY = type then | |
| 676 | let element_token = named_argument_type_token(array.arguments[0]); | |
| 677 | ||
| 678 | if element_token? then | |
| 679 | return "{element_token}[]"; | |
| 680 | fi | |
| 681 | ||
| 682 | return null; | |
| 683 | fi | |
| 684 | ||
| 685 | if type.symbol.symbol_kind == Semantic.Symbols.SymbolKind.ENUM then | |
| 686 | if let classy: Semantic.Symbols.Classy = type.symbol then | |
| 687 | if classy.il_assembly_name? then | |
| 688 | return "enum [{classy.il_assembly_name}]{classy.qualified_name}"; | |
| 689 | fi | |
| 690 | ||
| 691 | return "enum {classy.qualified_name}"; | |
| 692 | fi | |
| 693 | fi | |
| 694 | ||
| 695 | return null; | |
| 696 | si | |
| 697 | ||
| 698 | // The IL keyword for a primitive value type — the literal token | |
| 699 | // ilasm expects in a custom-attribute blob — or null for anything | |
| 700 | // that is not a primitive. | |
| 701 | try_primitive_keyword(type: Type?) -> string? is | |
| 702 | if !type? then | |
| 703 | return null; | |
| 704 | fi | |
| 705 | ||
| 706 | let il = type.get_il_type().trim(); | |
| 707 | ||
| 708 | if | |
| 709 | il =~ "bool" \/ il =~ "char" \/ | |
| 710 | il =~ "int8" \/ il =~ "uint8" \/ | |
| 711 | il =~ "int16" \/ il =~ "uint16" \/ | |
| 712 | il =~ "int32" \/ il =~ "uint32" \/ | |
| 713 | il =~ "int64" \/ il =~ "uint64" \/ | |
| 714 | il =~ "float32" \/ il =~ "float64" | |
| 715 | then | |
| 716 | return il; | |
| 717 | fi | |
| 718 | ||
| 719 | return null; | |
| 720 | si | |
| 721 | ||
| 722 | // The ilasm serialization keyword for a numeric or bool value: | |
| 723 | // the parameter type's primitive keyword, or `int32` for an enum, | |
| 724 | // whose fixed-argument form encodes the underlying value. | |
| 725 | serialization_keyword(parameter_type: Type?) -> string is | |
| 726 | let primitive = try_primitive_keyword(parameter_type); | |
| 727 | ||
| 728 | if primitive? then | |
| 729 | return primitive; | |
| 730 | fi | |
| 731 | ||
| 732 | return "int32"; | |
| 733 | si | |
| 734 | ||
| 735 | quote(value: string) -> string => | |
| 736 | Semantic.DotNet.IL_ATTRIBUTE_ARGUMENTS.quote(value); | |
| 737 | si | |
| 738 | si |