Skip to content
← Back

src/syntax/process/rewrite-syntax-trees/add_accessors_for_properties_synthesis.ghul

1
namespace Syntax.Process is
2
use Source
3
use Trees
4
5
use Logging
6
7
use Collections.LIST
8
9
use Ghul.Pipes
10
11
12
// Synthesis helpers for the accessors and union members the
13
// visitor adds: typed and object equals, get_hash_code, variant
14
// init, and the type-expression helpers they read.
15
// Turn a type-parameter declaration list into a list of
16
// type-argument references. A declaration carries the parameter's
17
// bound / kind / variance (e.g. E: Comparable[E]); those belong only
18
// at the declaration site. In the self-referential UL[E] spelled by
19
// the synthesised operators each parameter must appear as a bare
20
// name, or the bound resolves to an error type and IL generation
21
// fails on the malformed argument.
22
get_type_argument_references(arguments: TypeExpressions.LIST) -> TypeExpressions.LIST is
23
let references = Collections.LIST[TypeExpressions.TypeExpression]()
24
25
for a in arguments do
26
let name = a.name
27
28
if name? then
29
references.add(TypeExpressions.NAMED(Source.LOCATION.internal, name.copy()))
30
else
31
references.add(a.copy())
32
fi
33
od
34
35
return TypeExpressions.LIST(Source.LOCATION.internal, references)
36
si
37
38
// Build a TypeExpression for the parent union — used as the
39
// declared parameter type of the synthesised `=~` so the method
40
// overrides the union's base implementation. The reference stands
41
// in for a type the user never wrote, so it carries an internal
42
// location: a real declaration-span location would otherwise be
43
// recorded as a phantom self-use of the union, surfacing in
44
// find-references and rename of the union type.
45
get_union_type_expression(`union: Definitions.UNION) -> TypeExpressions.TypeExpression =>
46
if `union.arguments? /\ `union.arguments.count > 0 then
47
TypeExpressions.GENERIC(
48
Source.LOCATION.internal,
49
Identifiers.Identifier(Source.LOCATION.internal, `union.name.name),
50
get_type_argument_references(`union.arguments!)
51
)
52
else
53
TypeExpressions.NAMED(
54
Source.LOCATION.internal,
55
Identifiers.Identifier(Source.LOCATION.internal, `union.name.name)
56
)
57
fi
58
59
// Build a TypeExpression for the variant — variants share the
60
// union's type-arg names, so we mirror them. Internal location for
61
// the same reason as get_union_type_expression.
62
get_variant_type_expression(variant: Definitions.VARIANT, `union: Definitions.UNION) -> TypeExpressions.TypeExpression =>
63
if `union.arguments? /\ `union.arguments.count > 0 then
64
TypeExpressions.GENERIC(
65
Source.LOCATION.internal,
66
Trees.Identifiers.QUALIFIED(
67
Source.LOCATION.internal,
68
Identifiers.Identifier(Source.LOCATION.internal, `union.name.name),
69
variant.name.name,
70
Source.LOCATION.internal,
71
Source.LOCATION.internal
72
),
73
get_type_argument_references(`union.arguments!)
74
)
75
else
76
TypeExpressions.NAMED(
77
Source.LOCATION.internal,
78
Trees.Identifiers.QUALIFIED(
79
Source.LOCATION.internal,
80
Identifiers.Identifier(Source.LOCATION.internal, `union.name.name),
81
variant.name.name,
82
Source.LOCATION.internal,
83
Source.LOCATION.internal
84
)
85
)
86
fi
87
88
get_typed_equals_method_for_union(`union: Definitions.UNION) -> Definitions.FUNCTION is
89
let location = LOCATION.internal
90
91
let other_arg = Variables.VARIABLE(
92
location,
93
Identifiers.Identifier(location, "other"),
94
get_union_type_expression(`union),
95
false,
96
false,
97
null
98
)
99
100
let arguments = Variables.LIST(
101
location,
102
Collections.LIST[Variables.VARIABLE]([other_arg])
103
)
104
105
let body = Trees.Bodies.EXPRESSION(
106
location,
107
Trees.Expressions.Literals.BOOLEAN(location, "false")
108
)
109
110
let modifiers = Modifiers.LIST(location, null, null)
111
modifiers.is_pure = true
112
113
return Definitions.FUNCTION(
114
location,
115
Identifiers.Identifier(`union.name.location, "=~"),
116
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
117
arguments,
118
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "bool")),
119
modifiers,
120
body
121
)
122
si
123
124
// The name a variant reads one of its fields by. A field taken
125
// from the union's header is the union's member, which a private
126
// header parameter captures under another name.
127
get_variant_field_member_name(variable: Variables.VARIABLE) -> string static =>
128
variable.inherited_member_name ?? variable.name!.name
129
130
get_typed_equals_method_for_variant(
131
variant: Definitions.VARIANT,
132
`union: Definitions.UNION
133
) -> Definitions.FUNCTION is
134
let names = Collections.LIST[string]()
135
let locations = Collections.LIST[LOCATION]()
136
137
for f in variant.fields do
138
names.add(get_variant_field_member_name(f))
139
locations.add(f.location)
140
od
141
142
return get_memberwise_equals(
143
get_union_type_expression(`union),
144
names,
145
locations,
146
true
147
)
148
si
149
150
// Synthesise `equals(other: object?) -> bool` on the union — this
151
// overrides .NET's `Object.Equals(object)` so unions work as
152
// dictionary keys / set members. Body forwards to typed `=~`
153
// after a runtime type check; the variant override of `=~` does
154
// the per-variant work.
155
get_object_equals_method_for_union(`union: Definitions.UNION) -> Definitions.FUNCTION is
156
let location = LOCATION.internal
157
158
let other_arg = Variables.VARIABLE(
159
location,
160
Identifiers.Identifier(location, "other"),
161
TypeExpressions.OPTIONAL(
162
location,
163
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "object"))
164
),
165
false,
166
false,
167
null
168
)
169
170
let arguments = Variables.LIST(
171
location,
172
Collections.LIST[Variables.VARIABLE]([other_arg])
173
)
174
175
let union_type = get_union_type_expression(`union)
176
177
let isa_check = Trees.Expressions.ISA(
178
location,
179
union_type,
180
Trees.Expressions.IDENTIFIER(
181
location,
182
Identifiers.Identifier(location, "other")
183
)
184
)
185
186
let typed_call = Trees.Expressions.BINARY(
187
location,
188
Identifiers.Identifier(location, "=~"),
189
"=~",
190
Trees.Expressions.SELF(location),
191
Trees.Expressions.CAST(
192
location,
193
get_union_type_expression(`union),
194
Trees.Expressions.IDENTIFIER(
195
location,
196
Identifiers.Identifier(location, "other")
197
),
198
false
199
)
200
)
201
202
let condition = Trees.Expressions.BINARY(
203
location,
204
Identifiers.Identifier(location, "/\\"),
205
"/\\",
206
isa_check,
207
typed_call
208
)
209
210
let body = Trees.Bodies.EXPRESSION(location, condition)
211
212
return Definitions.FUNCTION(
213
location,
214
Identifiers.Identifier(`union.name.location, "equals"),
215
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
216
arguments,
217
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "bool")),
218
Modifiers.LIST(location, null, null),
219
body
220
)
221
si
222
223
// Union's `get_hash_code` is overridden by every variant. The
224
// base body returns 0 — only ever called via super, which never
225
// happens on synthesised methods.
226
get_hash_code_method_for_union() -> Definitions.FUNCTION is
227
let location = LOCATION.internal
228
229
let body = Trees.Bodies.EXPRESSION(
230
location,
231
Trees.Expressions.Literals.INTEGER(location, "0")
232
)
233
234
return Definitions.FUNCTION(
235
location,
236
Identifiers.Identifier(location, "get_hash_code"),
237
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
238
Variables.LIST(location, Collections.LIST[Variables.VARIABLE]()),
239
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "int")),
240
Modifiers.LIST(location, null, null),
241
body
242
)
243
si
244
245
// Variant's `get_hash_code` combines a per-variant seed (hash of
246
// the variant's name string, distinct per variant in an union)
247
// with each field's hash code via XOR. Not the strongest mixer
248
// but cheap and respects the equality contract: any two values
249
// that compare `=~` will have hashed the same field values.
250
get_hash_code_method_for_variant(variant: Definitions.VARIANT) -> Definitions.FUNCTION is
251
let location = LOCATION.internal
252
253
let seed = Trees.Expressions.CALL(
254
location,
255
Trees.Expressions.MEMBER(
256
location,
257
Trees.Expressions.Literals.STRING(location, variant.name.name),
258
Identifiers.Identifier(location, "get_hash_code"),
259
location
260
),
261
Trees.Expressions.LIST(
262
location,
263
Collections.LIST[Trees.Expressions.Expression]()
264
)
265
)
266
267
let combined: Trees.Expressions.Expression mut = seed
268
269
for f in variant.fields do
270
let field_hash =
271
get_member_hash_code(
272
location,
273
Trees.Expressions.MEMBER(
274
location,
275
Trees.Expressions.SELF(location),
276
Identifiers.Identifier(location, get_variant_field_member_name(f)),
277
location
278
)
279
)
280
281
// h = h * 31 + field_hash — standard multiply-and-add
282
// hash combiner (ghūl has no XOR operator on int).
283
let mul = Trees.Expressions.BINARY(
284
location,
285
Identifiers.Identifier(location, "*"),
286
"*",
287
combined,
288
Trees.Expressions.Literals.INTEGER(location, "31")
289
)
290
291
combined = Trees.Expressions.BINARY(
292
location,
293
Identifiers.Identifier(location, "+"),
294
"+",
295
mul,
296
field_hash
297
)
298
od
299
300
let body = Trees.Bodies.EXPRESSION(location, combined)
301
302
return Definitions.FUNCTION(
303
location,
304
Identifiers.Identifier(location, "get_hash_code"),
305
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
306
Variables.LIST(location, Collections.LIST[Variables.VARIABLE]()),
307
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "int")),
308
Modifiers.LIST(location, null, null),
309
body
310
)
311
si
312
313
get_init_method_for_variant(variant: Trees.Definitions.VARIANT) -> Definitions.FUNCTION is
314
// variant.fields holds the variant's full positional shape,
315
// including any `..` splice expanded to copies of the union's
316
// primary params (marked is_inherited_primary). Inherited
317
// entries become init args that the body forwards to
318
// super.init(...); own entries get the usual
319
// self.<f> = <f>; assignment.
320
let arguments =
321
variant.fields |>
322
map((v) -> Variables.VARIABLE =>
323
Variables.VARIABLE(
324
v.location,
325
v.name!.copy(),
326
v.type_expression.copy(),
327
false,
328
false,
329
null
330
)
331
) |>
332
collect_list()
333
334
let body_statements = Collections.LIST[Trees.Statements.Statement]()
335
336
let super_args = Collections.LIST[Trees.Expressions.Expression]()
337
for f in variant.fields do
338
if f.is_inherited_primary then
339
super_args.add(
340
Trees.Expressions.IDENTIFIER(
341
LOCATION.internal,
342
Trees.Identifiers.Identifier(LOCATION.internal, f.name!.name)
343
)
344
)
345
fi
346
od
347
348
if super_args.count > 0 then
349
let super_member =
350
Trees.Expressions.MEMBER(
351
LOCATION.internal,
352
Trees.Expressions.SUPER(LOCATION.internal),
353
Trees.Identifiers.Identifier(LOCATION.internal, "init"),
354
LOCATION.internal
355
)
356
let super_call =
357
Trees.Expressions.CALL(
358
LOCATION.internal,
359
super_member,
360
Trees.Expressions.LIST(LOCATION.internal, super_args)
361
)
362
body_statements.add(Trees.Statements.EXPRESSION(LOCATION.internal, super_call))
363
fi
364
365
for f in variant.fields do
366
if f.is_inherited_primary then
367
continue
368
fi
369
370
let member_access =
371
Trees.Expressions.MEMBER(
372
LOCATION.internal,
373
Trees.Expressions.SELF(LOCATION.internal),
374
Trees.Identifiers.Identifier(LOCATION.internal, f.name!.name),
375
LOCATION.internal
376
)
377
378
let left =
379
Trees.Expressions.SIMPLE_LEFT_EXPRESSION(LOCATION.internal, member_access)
380
381
body_statements.add(
382
Trees.Statements.ASSIGNMENT(
383
LOCATION.internal,
384
left,
385
Trees.Expressions.IDENTIFIER(
386
LOCATION.internal,
387
Trees.Identifiers.Identifier(LOCATION.internal, f.name!.name)
388
)
389
)
390
)
391
od
392
393
let body_statement_list = Trees.Statements.LIST(
394
LOCATION.internal,
395
body_statements
396
)
397
398
let init_body = Trees.Bodies.BLOCK(
399
LOCATION.internal,
400
body_statement_list
401
)
402
403
let init_function = Definitions.FUNCTION(
404
LOCATION.internal,
405
// The constructor is the one synthesised member with a home in
406
// the source: it is what a variant construction resolves to, so
407
// its name carries the variant's own location.
408
Identifiers.Identifier(
409
variant.location,
410
"init"
411
),
412
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
413
Variables.LIST(
414
LOCATION.internal,
415
arguments
416
),
417
TypeExpressions.NAMED(
418
LOCATION.internal,
419
Identifiers.Identifier(
420
LOCATION.internal,
421
"void"
422
)
423
),
424
Modifiers.LIST(
425
LOCATION.internal,
426
null,
427
null
428
),
429
init_body
430
)
431
432
return init_function
433
si
434
435
// The declarations that hold a value's state, in source order: a
436
// member with neither accessor written, which is an auto-property
437
// or a `field`. A property that supplies a body is derived from
438
// that state rather than part of it, and a static member belongs
439
// to the type rather than to the value, so neither takes part in
440
// equality. Read before the property rewrite splits an
441
// auto-property into a backing variable and accessors, so members
442
// are still named as they were written.
443
get_state_members(body: Definitions.LIST) -> Collections.LIST[Definitions.PROPERTY] static is
444
let result = Collections.LIST[Definitions.PROPERTY]()
445
446
for definition in body do
447
if
448
isa Definitions.PROPERTY(definition) /\
449
definition.name? /\
450
!definition.is_poisoned /\
451
!definition.modifiers.is_static /\
452
!definition.read_body? /\
453
!definition.assign_body?
454
then
455
result.add(definition)
456
fi
457
od
458
459
return result
460
si
461
462
// How a state member is read from another value of the same type.
463
// An auto-property's backing field is read directly rather than
464
// through the getter it will be given: the getter of a struct
465
// property hands back a copy, and the comparison wants the value
466
// itself. A `field` member is already the storage and keeps its
467
// own name, and so is one the accessor rewrite leaves alone - a
468
// non-public member never becomes an auto-property, so there is
469
// no backing field behind it to reach.
470
get_state_member_read_name(member: Definitions.PROPERTY) -> string static =>
471
if
472
member.modifiers.is_field \/
473
member.modifiers.is_private \/
474
member.name!.name.starts_with('_')
475
then
476
member.name!.name
477
else
478
"${member.name!.name}"
479
fi
480
481
// Whether every declaration holding the type's state is part of
482
// the type's public contract. Private state is an implementation
483
// detail that only its author knows the equality significance of,
484
// and comparing the public members alone would answer equal for
485
// two values a private member distinguishes, so a type with any
486
// is left without a synthesized operator. `protected` counts as
487
// non-public: it is readable inside the assembly but is no part of
488
// what consumers see.
489
has_only_public_state(body: Definitions.LIST) -> bool static is
490
for member in get_state_members(body) do
491
if
492
member.name!.name.starts_with('_') \/
493
member.modifiers.is_private \/
494
member.modifiers.is_protected
495
then
496
return false
497
fi
498
od
499
500
return true
501
si
502
503
// Whether the type gets a synthesized `=~` and `get_hash_code`.
504
// Declaring either operator opts out of both: a hand-written `=~`
505
// is the author's own answer, and a hand-written `get_hash_code`
506
// beside a synthesized operator could disagree with it, which is
507
// the pair invariant `equality-without-hash` exists to protect.
508
// `<>` opts out too, since equality is already derived from it.
509
wants_synthesized_equality(body: Definitions.LIST) -> bool static =>
510
declares_no_equality(body) /\
511
has_only_public_state(body)
512
513
// A class is synthesized for only where an `@equality` pragma
514
// asked, so the state it holds is state the author meant to
515
// compare and its visibility says nothing about that. What still
516
// opts out is an equality already written.
517
declares_no_equality(body: Definitions.LIST) -> bool static =>
518
!declares_function(body, "=~") /\
519
!declares_function(body, "<>") /\
520
!declares_function(body, "get_hash_code") /\
521
!declares_object_equals(body)
522
523
// A struct compares memberwise. The parameter is the struct's own
524
// type and a struct value has no absent form, so the body needs no
525
// guard: every operand it is handed is a value of exactly this
526
// type. Each member is compared by a FIELD_EQUALS node rather than
527
// a `=~` operator, because which comparison to run is not knowable
528
// here - only once the member's static type has resolved.
529
get_memberwise_equals_for_struct(`struct: Definitions.STRUCT) -> Definitions.FUNCTION is
530
return get_memberwise_equals(
531
get_classy_type_expression(`struct),
532
get_member_read_names(`struct.body),
533
get_member_locations(`struct.body),
534
false
535
)
536
si
537
538
// A class compares by runtime type and then memberwise. The
539
// runtime-type test comes first because a base's comparison run
540
// against a subclass value would otherwise compare the base's
541
// fields and answer true, while the subclass's own override
542
// answered false, leaving the relation asymmetric.
543
//
544
// The parameter is the class's own type here. Where the class
545
// turns out to extend one that is itself synthesized, the pass
546
// that resolves that retypes it to the base's parameter type, so
547
// that the two are one virtual slot rather than two overloads.
548
// The members are read straight off `other`, which is this class
549
// here. Where the parameter is retyped, the pass that does it
550
// prefixes an `isa` to narrow it back.
551
get_memberwise_equals_for_class(`class: Definitions.CLASS) -> Definitions.FUNCTION is
552
let equals =
553
get_memberwise_equals(
554
get_classy_type_expression(`class),
555
get_member_read_names(`class.body),
556
get_member_locations(`class.body),
557
false
558
)
559
560
return equals
561
si
562
563
// `expression.get_type()`, the runtime type of a value.
564
get_runtime_type(
565
location: LOCATION,
566
expression: Trees.Expressions.Expression
567
) -> Trees.Expressions.Expression static =>
568
Trees.Expressions.CALL(
569
location,
570
Trees.Expressions.MEMBER(
571
location,
572
expression,
573
Identifiers.Identifier(location, "get_type"),
574
location
575
),
576
Trees.Expressions.LIST(
577
location,
578
Collections.LIST[Trees.Expressions.Expression]()
579
)
580
)
581
582
// One member's contribution to a synthesized `get_hash_code`,
583
// read through `object?` so that a member of any kind answers:
584
// boxing a value type, reaching a bare type parameter's own hash,
585
// and giving an absent optional a hash rather than a cast that
586
// would throw on it. What is hashed is a HASH_OPERAND rather than
587
// the member itself, since a member compared element-wise, or one
588
// compared more finely than its own hash answers for, cannot be
589
// hashed as it stands without two equal values hashing
590
// differently.
591
get_member_hash_code(
592
location: LOCATION,
593
member_read: Trees.Expressions.Expression
594
) -> Trees.Expressions.Expression static is
595
let boxed = Trees.Expressions.CAST(
596
location,
597
TypeExpressions.OPTIONAL(
598
location,
599
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "object"))
600
),
601
Trees.Expressions.HASH_OPERAND(location, member_read),
602
false
603
)
604
605
let hash_member = Trees.Expressions.MEMBER(
606
location,
607
boxed,
608
Identifiers.Identifier(location, "get_hash_code"),
609
location
610
)
611
612
hash_member.is_coalesce = true
613
614
return Trees.Expressions.BINARY(
615
location,
616
Identifiers.Identifier(location, "??"),
617
"??",
618
Trees.Expressions.CALL(
619
location,
620
hash_member,
621
Trees.Expressions.LIST(
622
location,
623
Collections.LIST[Trees.Expressions.Expression]()
624
)
625
),
626
Trees.Expressions.Literals.INTEGER(location, "0")
627
)
628
si
629
630
// The hash the memberwise comparison implies: every member the
631
// comparison reads, mixed by multiply-and-add. Each is read
632
// through `object?` so that a member of any kind answers - boxing
633
// a value type, and giving an absent optional a hash of its own
634
// rather than a cast that would throw on it.
635
get_memberwise_hash_code(body: Definitions.LIST) -> Definitions.FUNCTION is
636
let location = LOCATION.internal
637
638
return Definitions.FUNCTION(
639
location,
640
Identifiers.Identifier(location, "get_hash_code"),
641
TypeExpressions.LIST(location, Collections.LIST[TypeExpressions.TypeExpression](0)),
642
Variables.LIST(location, Collections.LIST[Variables.VARIABLE]()),
643
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "int")),
644
Modifiers.LIST(location, null, null),
645
Trees.Bodies.EXPRESSION(
646
location,
647
Trees.Expressions.MEMBERWISE_HASH(
648
location,
649
get_member_read_names(body),
650
get_member_locations(body)
651
)
652
)
653
)
654
si
655
656
// The `=~` a type is given: one parameter at the type the whole
657
// chain compares at, and a body that stands for the comparison
658
// rather than spelling it. What it does - which guard, how each
659
// member compares, what a base contributes - is settled where
660
// those are known, which is not here.
661
get_memberwise_equals(
662
parameter_type: TypeExpressions.TypeExpression,
663
members: Collections.LIST[string],
664
member_locations: Collections.LIST[LOCATION],
665
reports_fallback: bool
666
) -> Definitions.FUNCTION is
667
let location = LOCATION.internal
668
669
let other_argument = Variables.VARIABLE(
670
location,
671
Identifiers.Identifier(location, "other"),
672
parameter_type,
673
false,
674
false,
675
null
676
)
677
678
let body = Trees.Expressions.MEMBERWISE_EQUALS(location, members, member_locations)
679
680
body.reports_fallback = reports_fallback
681
682
// Declared rather than left to be proven, and not because the
683
// body needs covering: it reads members and compares them, and
684
// the effects walk is told which comparisons it reaches. What
685
// the declaration buys is the contract over dispatch. An `open`
686
// class can be subclassed in another assembly, so no analysis
687
// here can prove what an override does, and the mandate that
688
// makes a member `=~` trustworthy is what binds every override
689
// to the same rule. A user writing the operator on an open
690
// class declares it for the same reason.
691
let modifiers = Modifiers.LIST(location, null, null)
692
modifiers.is_pure = true
693
694
return Definitions.FUNCTION(
695
location,
696
Identifiers.Identifier(location, "=~"),
697
TypeExpressions.LIST(location, Collections.LIST[TypeExpressions.TypeExpression](0)),
698
Variables.LIST(location, Collections.LIST[Variables.VARIABLE]([other_argument])),
699
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "bool")),
700
modifiers,
701
Trees.Bodies.EXPRESSION(location, body)
702
)
703
si
704
705
// The members a value of the type is compared and hashed by, under
706
// the names it is read by: an auto-property through its backing
707
// field, a `field` member by its own name.
708
get_member_locations(body: Definitions.LIST) -> Collections.LIST[LOCATION] static is
709
let locations = Collections.LIST[LOCATION]()
710
711
for member in get_state_members(body) do
712
locations.add(member.location)
713
od
714
715
return locations
716
si
717
718
get_member_read_names(body: Definitions.LIST) -> Collections.LIST[string] static is
719
let names = Collections.LIST[string]()
720
721
for member in get_state_members(body) do
722
names.add(get_state_member_read_name(member))
723
od
724
725
return names
726
si
727
728
// A TypeExpression naming the type itself, mirroring its own type
729
// parameters where it has them. Internal location, so the
730
// synthesised reference is not recorded as a use of the type.
731
get_classy_type_expression(classy: Definitions.Classy) -> TypeExpressions.TypeExpression static =>
732
if classy.arguments? /\ classy.arguments.count > 0 then
733
TypeExpressions.GENERIC(
734
Source.LOCATION.internal,
735
Identifiers.Identifier(Source.LOCATION.internal, classy.name.name),
736
get_type_argument_references(classy.arguments!)
737
)
738
else
739
TypeExpressions.NAMED(
740
Source.LOCATION.internal,
741
Identifiers.Identifier(Source.LOCATION.internal, classy.name.name)
742
)
743
fi
744
745
// Whether the member of that name in the type's body is one the
746
// compiler put there.
747
declares_synthesized_function(body: Definitions.LIST, name: string) -> bool static is
748
for definition in body do
749
if
750
isa Definitions.FUNCTION(definition) /\
751
definition.is_synthesized /\
752
definition.name? /\
753
definition.name.name =~ name
754
then
755
return true
756
fi
757
od
758
759
return false
760
si
761
762
// Whether a type's own body declares a member of the given name.
763
// Only the body is consulted, so a member arriving from a
764
// `partial` or `impl` block is not seen here: those are declared
765
// later, in declare-members, and a type reopened that way does
766
// not get the synthesised bridge below.
767
declares_function(body: Definitions.LIST, name: string) -> bool static is
768
for definition in body do
769
if
770
isa Definitions.FUNCTION(definition) /\
771
definition.name? /\
772
definition.name.name =~ name
773
then
774
return true
775
fi
776
od
777
778
return false
779
si
780
781
// A type expression naming `object`, with or without a `?`.
782
is_object_type_expression(type_expression: TypeExpressions.TypeExpression) -> bool static is
783
let inner =
784
if isa TypeExpressions.OPTIONAL(type_expression) then
785
type_expression.element
786
else
787
type_expression
788
fi
789
790
return
791
isa TypeExpressions.NAMED(inner) /\
792
inner.name.name =~ "object"
793
si
794
795
// Whether the body already supplies the `Object.Equals` the bridge
796
// would add. Both spellings reach it: a member named `equals`, and
797
// `=~` itself when its parameter is `object` - an equality
798
// operator over `object` is emitted under the name `Equals` and so
799
// already overrides it. Adding a bridge alongside either produces
800
// a duplicate method.
801
declares_object_equals(body: Definitions.LIST) -> bool static is
802
for definition in body do
803
if !isa Definitions.FUNCTION(definition) \/ !definition.name? then
804
continue
805
fi
806
807
let name = definition.name.name
808
809
if !(name =~ "equals") /\ !(name =~ "=~") then
810
continue
811
fi
812
813
if definition.arguments.count == 1 then
814
for argument in definition.arguments do
815
if is_object_type_expression(argument.type_expression) then
816
return true
817
fi
818
od
819
fi
820
821
if name =~ "equals" then
822
return true
823
fi
824
od
825
826
return false
827
si
828
829
// `=~` is emitted as a typed `Equals` method, which .NET does not
830
// recognise as an equality implementation on its own: an
831
// `EqualityComparer[T]` reaches for `IEquatable[T]` and then for
832
// `Object.Equals(object)`, and a type carrying neither falls back
833
// to the default for its kind. A class then compares by reference
834
// and a struct by reflected memberwise comparison, so a user's own
835
// `=~` is bypassed either way when the value is used as a
836
// dictionary key or searched for in a collection.
837
//
838
// Bridge it: a type whose body declares `=~` gets an
839
// `equals(other: object?)` that casts and delegates, so the
840
// operator the user wrote is what .NET calls. Written as a cast to
841
// the optional form and a presence test rather than an `isa`
842
// narrow, which is the one shape that holds for a struct as well
843
// as a class, and which accepts either spelling of the operator's
844
// own parameter.
845
// Marked synthesized as well as carrying an internal location:
846
// the class path takes the bridge back out with the operator it
847
// bridges to, and finds it by that mark.
848
get_object_equals_bridge(
849
location: LOCATION,
850
type_expression: TypeExpressions.TypeExpression
851
) -> Definitions.FUNCTION static is
852
let other_argument = Variables.VARIABLE(
853
location,
854
Identifiers.Identifier(location, "other"),
855
TypeExpressions.OPTIONAL(
856
location,
857
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "object"))
858
),
859
false,
860
false,
861
null
862
)
863
864
let narrowed = Variables.VARIABLE(
865
location,
866
Identifiers.Identifier(location, "narrowed"),
867
TypeExpressions.INFER(location),
868
false,
869
false,
870
Trees.Expressions.CAST(
871
location,
872
TypeExpressions.OPTIONAL(location, type_expression),
873
Trees.Expressions.IDENTIFIER(
874
location,
875
Identifiers.Identifier(location, "other")
876
),
877
false
878
)
879
)
880
881
let narrowed_reference =
882
Trees.Expressions.IDENTIFIER(
883
location,
884
Identifiers.Identifier(location, "narrowed")
885
)
886
887
let then_branch = Trees.Statements.IF_BRANCH(
888
location,
889
Trees.Expressions.HAS_VALUE(location, narrowed_reference),
890
Trees.Statements.LIST(
891
location,
892
Collections.LIST[Statements.Statement]([
893
Trees.Statements.RETURN(
894
location,
895
Trees.Expressions.BINARY(
896
location,
897
Identifiers.Identifier(location, "=~"),
898
"=~",
899
Trees.Expressions.SELF(location),
900
Trees.Expressions.IDENTIFIER(
901
location,
902
Identifiers.Identifier(location, "narrowed")
903
)
904
)
905
)
906
]: Statements.Statement)
907
)
908
)
909
910
let body = Trees.Bodies.BLOCK(
911
location,
912
Trees.Statements.LIST(
913
location,
914
Collections.LIST[Statements.Statement]([
915
Trees.Statements.LET(
916
location,
917
false,
918
Variables.LIST(
919
location,
920
Collections.LIST[Variables.VARIABLE]([narrowed])
921
)
922
),
923
Trees.Statements.IF(
924
location,
925
Collections.LIST[Trees.Statements.IF_BRANCH]([then_branch])
926
),
927
Trees.Statements.RETURN(
928
location,
929
Trees.Expressions.Literals.BOOLEAN(location, "false")
930
)
931
]: Statements.Statement)
932
)
933
)
934
935
let bridge = Definitions.FUNCTION(
936
location,
937
Identifiers.Identifier(location, "equals"),
938
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
939
Variables.LIST(
940
location,
941
Collections.LIST[Variables.VARIABLE]([other_argument])
942
),
943
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "bool")),
944
Modifiers.LIST(location, null, null),
945
body
946
)
947
948
bridge.mark_synthesized()
949
950
return bridge
951
si
952
953
si