Skip to content
← Back

src/syntax/process/compile-expressions/attribute_resolver.ghul

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