Skip to content
← Back

src/syntax/process/add_accessors_for_properties.ghul

1
namespace Syntax.Process is
2
use Source;
3
use Trees;
4
5
use Logging;
6
7
use Ghul.Pipes;
8
9
// TODO rename this: it's not just for properties
10
class ADD_ACCESSORS_FOR_PROPERTIES: Visitor is
11
_pragma_scope_stack: PRAGMA_SCOPE_STACK;
12
_stack: Collections.STACK[Definitions.LIST];
13
14
enclosing_definition: Definitions.LIST => _stack.peek();
15
16
init() is
17
super.init();
18
19
_pragma_scope_stack = PRAGMA_SCOPE_STACK();
20
_stack = Collections.STACK[Definitions.LIST]();
21
si
22
23
apply(root: Node) is
24
root.walk(self);
25
si
26
27
pre(pragma: Definitions.PRAGMA) -> bool is
28
_pragma_scope_stack.enter(pragma.pragma);
29
30
return false;
31
si
32
33
visit(pragma: Definitions.PRAGMA) is
34
_pragma_scope_stack.leave(pragma.pragma);
35
si
36
37
pre(property: Definitions.PROPERTY) -> bool is
38
// The parser synthesises a `Bodies.NULL` read_body for properties
39
// declared in a trait without an explicit body (the abstract-method
40
// marker), so test for a *real* body here — otherwise this fires
41
// spuriously for `field x: T;` in a trait, swallowing the proper
42
// "field is not valid here" diagnostic from declare-symbols.
43
let has_real_body =
44
(property.read_body? /\ !isa Bodies.NULL(property.read_body))
45
\/ property.assign_body?
46
\/ property.assign_argument?;
47
48
if property.modifiers.is_field /\ has_real_body then
49
IoC.CONTAINER.instance.logger
50
.error(property.modifiers.storage_class!.location, "a field cannot have body");
51
property.modifiers.clear_storage_class();
52
fi
53
54
let name = property.name;
55
56
// `private` on a body declaration has no member to rename
57
// onto — unlike the primary-constructor parameter form, which
58
// the rewriter turns into an underscore-named capture before
59
// this pass ever sees it. Left alone, a plain-named private
60
// property would fall through with no auto-property and no
61
// real body, giving empty accessors that silently discard
62
// writes and read back the default. Reject it and point at
63
// the working spelling.
64
if
65
property.modifiers.is_private /\ name? /\
66
!name.name.starts_with('_') /\ !has_real_body
67
then
68
IoC.CONTAINER.instance.logger.error(
69
property.modifiers.access_modifier!.location,
70
"private is not valid on a body declaration; name the member _{name.name} instead"
71
);
72
property.modifiers.clear_access_modifier();
73
fi
74
75
if
76
!property.is_poisoned /\ !property.modifiers.is_field /\ name? /\ (
77
!name.name.starts_with('_') \/
78
property.read_body? \/
79
property.assign_body?
80
)
81
then
82
let is_assignable = !property.read_body? \/ property.assign_argument?;
83
84
add_accessor_functions_for_property(property, is_assignable);
85
fi
86
87
return true;
88
si
89
90
add_accessor_functions_for_property(property: Definitions.PROPERTY, is_assignable: bool) is
91
// gated on a present name by the caller
92
let property_name = property.name!;
93
94
let read_name =
95
Identifiers.Identifier(
96
property_name.location,
97
"$get_{property_name.name}"
98
);
99
100
property.is_auto_property =
101
!property.read_body? /\
102
!property.assign_body? /\
103
!property_name.name.starts_with('_') /\
104
!property.modifiers.is_private;
105
106
let backing_variable_name = "${property_name.name}";
107
108
let assign_argument_name mut = "$$value";
109
110
if property.assign_argument != null then
111
assign_argument_name = property.assign_argument.name;
112
fi
113
114
if property.is_auto_property then
115
enclosing_definition.add(
116
Variables.VARIABLE(
117
property_name.location,
118
Identifiers.Identifier(
119
LOCATION.internal,
120
backing_variable_name
121
),
122
property.type_expression.copy(),
123
property.modifiers.is_static,
124
true,
125
null
126
)
127
);
128
129
// The synthesised accessor bodies have no user source —
130
// BLOCK + Statements.LIST locations are internal so the
131
// incremental body re-walk's BODY_SPANS skips them and
132
// does not treat the property declaration itself (at
133
// `property.location`) as "inside" its own getter body.
134
// Inner statements keep `property.location` so any later
135
// diagnostic against the synthesised RETURN / ASSIGNMENT
136
// still anchors on the property declaration the user
137
// wrote.
138
property.read_body =
139
Trees.Bodies.BLOCK(LOCATION.internal,
140
Trees.Statements.LIST(LOCATION.internal,
141
Collections.LIST[Statements.Statement]([
142
Trees.Statements.RETURN(property.location,
143
Trees.Expressions.IDENTIFIER(property.location,
144
Trees.Identifiers.Identifier(property.location, backing_variable_name)
145
)
146
)
147
]: Statements.Statement)
148
)
149
);
150
151
property.assign_body =
152
Trees.Bodies.BLOCK(LOCATION.internal,
153
Trees.Statements.LIST(LOCATION.internal,
154
Collections.LIST[Statements.Statement]([
155
Trees.Statements.ASSIGNMENT(property.location,
156
Trees.Expressions.SIMPLE_LEFT_EXPRESSION(property.location,
157
Trees.Expressions.IDENTIFIER(property.location,
158
Trees.Identifiers.Identifier(property.location, backing_variable_name)
159
)
160
),
161
Trees.Expressions.IDENTIFIER(property.location,
162
Trees.Identifiers.Identifier(property.location, assign_argument_name)
163
)
164
)
165
]: Statements.Statement)
166
)
167
);
168
fi
169
170
let read_function = Definitions.FUNCTION(
171
property.location,
172
read_name,
173
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
174
Variables.LIST(property_name.location, Collections.LIST[Variables.VARIABLE](0)),
175
property.type_expression.copy(),
176
property.modifiers.copy(),
177
property.read_body!
178
);
179
180
read_function.for_property = property;
181
read_function.is_underscore_scoped = property_name.name.starts_with('_');
182
183
enclosing_definition.add(
184
read_function
185
);
186
187
property.read_function = read_function;
188
189
if is_assignable \/ property.is_auto_property then
190
let assign_name =
191
Identifiers.Identifier(
192
property_name.location,
193
"$set_{property_name.name}"
194
);
195
196
if !property.assign_argument? then
197
property.assign_argument = Identifiers.Identifier(property.location, "$$value");
198
fi
199
200
let assign_function = Definitions.FUNCTION(
201
property_name.location,
202
assign_name,
203
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
204
Variables.LIST(LOCATION.internal, Collections.LIST[Variables.VARIABLE]([
205
Variables.VARIABLE(
206
property.assign_argument!.location,
207
property.assign_argument!,
208
property.type_expression.copy(),
209
false,
210
true,
211
null
212
)
213
]:Variables.VARIABLE)),
214
TypeExpressions.NAMED(
215
LOCATION.internal,
216
Identifiers.Identifier(
217
LOCATION.internal,
218
"void"
219
)
220
),
221
property.modifiers.copy(),
222
property.assign_body!
223
);
224
225
assign_function.for_property = property;
226
assign_function.is_underscore_scoped = property_name.name.starts_with('_');
227
228
enclosing_definition.add(
229
assign_function
230
);
231
232
property.assign_function = assign_function;
233
fi
234
si
235
236
visit(property: Definitions.PROPERTY) is
237
si
238
239
pre(indexer: Definitions.INDEXER) -> bool is
240
let name: string mut;
241
let location: LOCATION mut;
242
243
if indexer.name? then
244
name = indexer.name.name;
245
location = indexer.name.location;
246
else
247
name = "Item";
248
location = indexer.location;
249
fi
250
251
if indexer.read_body? then
252
let read_accessor = Definitions.FUNCTION(
253
indexer.location,
254
Identifiers.Identifier(
255
location,
256
"get_{name}"
257
),
258
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
259
Variables.LIST(
260
LOCATION.internal,
261
Collections.LIST[Variables.VARIABLE]([indexer.index_argument.copy()]:Variables.VARIABLE)),
262
indexer.type_expression.copy(),
263
indexer.modifiers.copy(),
264
indexer.read_body!
265
);
266
read_accessor.for_indexer = indexer;
267
enclosing_definition.add(read_accessor);
268
fi
269
270
if indexer.assign_body? then
271
let assign_accessor = Definitions.FUNCTION(
272
indexer.location,
273
Identifiers.Identifier(
274
location,
275
"set_{name}"
276
),
277
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
278
Variables.LIST(LOCATION.internal, Collections.LIST[Variables.VARIABLE]([
279
indexer.index_argument.copy(),
280
Variables.VARIABLE(
281
indexer.assign_argument!.location,
282
indexer.assign_argument!.copy(),
283
indexer.type_expression.copy(),
284
false,
285
true,
286
null
287
)
288
]:Variables.VARIABLE)),
289
TypeExpressions.NAMED(
290
LOCATION.internal,
291
Identifiers.Identifier(
292
LOCATION.internal,
293
"void"
294
)
295
),
296
indexer.modifiers.copy(),
297
indexer.assign_body!
298
);
299
assign_accessor.for_indexer = indexer;
300
enclosing_definition.add(assign_accessor);
301
fi
302
303
return true;
304
si
305
306
pre(`class: Definitions.CLASS) -> bool is
307
_stack.push(`class.body);
308
309
return false;
310
si
311
312
visit(`class: Definitions.CLASS) is
313
_stack.pop();
314
si
315
316
pre(`trait: Definitions.TRAIT) -> bool is
317
_stack.push(`trait.body);
318
319
return false;
320
si
321
322
visit(`trait: Definitions.TRAIT) is
323
_stack.pop();
324
si
325
326
pre(`struct: Definitions.STRUCT) -> bool is
327
_stack.push(`struct.body);
328
329
return false;
330
si
331
332
visit(`struct: Definitions.STRUCT) is
333
_stack.pop();
334
si
335
336
pre(`union: Definitions.UNION) -> bool is
337
_stack.push(`union.body);
338
339
if `union.is_poisoned then
340
return false;
341
fi
342
343
let unit_variant_count mut = 0;
344
let non_unit_variant_count mut = 0;
345
let default_variants = Collections.LIST[Definitions.VARIANT]();
346
347
for definition in `union.body do
348
if isa Definitions.VARIANT(definition) then
349
let variant = definition;
350
351
let own_field_count =
352
variant.fields
353
|> filter(f => !f.is_inherited_primary)
354
|> count();
355
356
if own_field_count > 0 then
357
non_unit_variant_count = non_unit_variant_count + 1;
358
else
359
unit_variant_count = unit_variant_count + 1;
360
fi
361
362
if variant.is_default then
363
default_variants.add(variant);
364
fi
365
fi
366
od
367
368
if non_unit_variant_count == 0 /\ unit_variant_count == 0 then
369
IoC.CONTAINER.instance.logger
370
.error(`union.location, "union must have at least one variant");
371
fi
372
373
// Default-variant validation. The actual pick used by
374
// `compile_access` for `?`/`!` lives on `Symbols.UNION`,
375
// set during declare_members once the variant symbols
376
// exist. Here we just diagnose obvious mistakes that
377
// would otherwise produce a surprising default later.
378
if default_variants.count > 1 then
379
for d in default_variants do
380
IoC.CONTAINER.instance.logger
381
.error(d.location, "a union can have at most one default variant");
382
od
383
elif default_variants.count == 1 then
384
let d = default_variants[0];
385
386
let own_field_count =
387
d.fields
388
|> filter(f => !f.is_inherited_primary)
389
|> count();
390
391
if own_field_count == 0 then
392
IoC.CONTAINER.instance.logger
393
.error(d.location, "a default variant must hold at least one value");
394
fi
395
fi
396
397
// Synthesise structural equality on the union and its
398
// variants. The union's `=~` returns false (the runtime
399
// type is always some variant; this base is purely
400
// overridden). Each variant overrides `=~` with an isa
401
// check + field-by-field `object.equals`, the latter of
402
// which gives value semantics for primitives via boxing,
403
// for strings, and recursively for other unions whose
404
// synthesised `equals(object?)` we add here too.
405
// Synthesised `equals(object?)` makes the union a sound
406
// dictionary key; `get_hash_code` is required by the
407
// .NET equality contract (equal objects must hash equal).
408
`union.body.add(get_typed_equals_method_for_union(`union));
409
`union.body.add(get_object_equals_method_for_union(`union));
410
`union.body.add(get_hash_code_method_for_union());
411
412
for definition in `union.body do
413
if isa Definitions.VARIANT(definition) then
414
let variant = definition;
415
416
variant.body.add(get_typed_equals_method_for_variant(variant, `union));
417
variant.body.add(get_hash_code_method_for_variant(variant));
418
fi
419
od
420
421
return false;
422
si
423
424
visit(`union: Definitions.UNION) is
425
_stack.pop();
426
si
427
428
pre(variant: Trees.Definitions.VARIANT) -> bool is
429
super.pre(variant);
430
_stack.push(variant.body);
431
return false;
432
si
433
434
visit(variant: Trees.Definitions.VARIANT) is
435
if variant.is_poisoned then
436
_stack.pop();
437
return;
438
fi
439
440
let init_function = get_init_method_for_variant(variant);
441
442
variant.body.add(init_function);
443
444
_stack.pop();
445
si
446
447
pre(`namespace: Definitions.NAMESPACE) -> bool is
448
_stack.push(`namespace.body);
449
450
return false;
451
si
452
453
visit(`namespace: Definitions.NAMESPACE) is
454
_stack.pop();
455
si
456
si
457
458
// Turn a type-parameter declaration list into a list of
459
// type-argument references. A declaration carries the parameter's
460
// bound / kind / variance (e.g. E: Comparable[E]); those belong only
461
// at the declaration site. In the self-referential UL[E] spelled by
462
// the synthesised operators each parameter must appear as a bare
463
// name, or the bound resolves to an error type and IL generation
464
// fails on the malformed argument.
465
get_type_argument_references(arguments: TypeExpressions.LIST) -> TypeExpressions.LIST is
466
let references = Collections.LIST[TypeExpressions.TypeExpression]();
467
468
for a in arguments do
469
let name = a.name;
470
471
if name? then
472
references.add(TypeExpressions.NAMED(Source.LOCATION.internal, name.copy()));
473
else
474
references.add(a.copy());
475
fi
476
od
477
478
return TypeExpressions.LIST(Source.LOCATION.internal, references);
479
si
480
481
// Build a TypeExpression for the parent union — used as the
482
// declared parameter type of the synthesised `=~` so the method
483
// overrides the union's base implementation. The reference stands
484
// in for a type the user never wrote, so it carries an internal
485
// location: a real declaration-span location would otherwise be
486
// recorded as a phantom self-use of the union, surfacing in
487
// find-references and rename of the union type.
488
get_union_type_expression(`union: Definitions.UNION) -> TypeExpressions.TypeExpression =>
489
if `union.arguments? /\ `union.arguments.count > 0 then
490
TypeExpressions.GENERIC(
491
Source.LOCATION.internal,
492
Identifiers.Identifier(Source.LOCATION.internal, `union.name.name),
493
get_type_argument_references(`union.arguments!)
494
)
495
else
496
TypeExpressions.NAMED(
497
Source.LOCATION.internal,
498
Identifiers.Identifier(Source.LOCATION.internal, `union.name.name)
499
)
500
fi;
501
502
// Build a TypeExpression for the variant — variants share the
503
// union's type-arg names, so we mirror them. Internal location for
504
// the same reason as get_union_type_expression.
505
get_variant_type_expression(variant: Definitions.VARIANT, `union: Definitions.UNION) -> TypeExpressions.TypeExpression =>
506
if `union.arguments? /\ `union.arguments.count > 0 then
507
TypeExpressions.GENERIC(
508
Source.LOCATION.internal,
509
Trees.Identifiers.QUALIFIED(
510
Source.LOCATION.internal,
511
Identifiers.Identifier(Source.LOCATION.internal, `union.name.name),
512
variant.name.name,
513
Source.LOCATION.internal,
514
Source.LOCATION.internal
515
),
516
get_type_argument_references(`union.arguments!)
517
)
518
else
519
TypeExpressions.NAMED(
520
Source.LOCATION.internal,
521
Trees.Identifiers.QUALIFIED(
522
Source.LOCATION.internal,
523
Identifiers.Identifier(Source.LOCATION.internal, `union.name.name),
524
variant.name.name,
525
Source.LOCATION.internal,
526
Source.LOCATION.internal
527
)
528
)
529
fi;
530
531
get_typed_equals_method_for_union(`union: Definitions.UNION) -> Definitions.FUNCTION is
532
let location = `union.location;
533
534
let other_arg = Variables.VARIABLE(
535
location,
536
Identifiers.Identifier(location, "other"),
537
get_union_type_expression(`union),
538
false,
539
false,
540
null
541
);
542
543
let arguments = Variables.LIST(
544
location,
545
Collections.LIST[Variables.VARIABLE]([other_arg])
546
);
547
548
let body = Trees.Bodies.EXPRESSION(
549
location,
550
Trees.Expressions.Literals.BOOLEAN(location, "false")
551
);
552
553
return Definitions.FUNCTION(
554
location,
555
Identifiers.Identifier(location, "=~"),
556
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
557
arguments,
558
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "bool")),
559
Modifiers.LIST(location, null, null),
560
body
561
);
562
si
563
564
get_typed_equals_method_for_variant(
565
variant: Definitions.VARIANT,
566
`union: Definitions.UNION
567
) -> Definitions.FUNCTION is
568
let location = variant.location;
569
570
let other_arg = Variables.VARIABLE(
571
location,
572
Identifiers.Identifier(location, "other"),
573
get_union_type_expression(`union),
574
false,
575
false,
576
null
577
);
578
579
let arguments = Variables.LIST(
580
location,
581
Collections.LIST[Variables.VARIABLE]([other_arg])
582
);
583
584
// For a unit variant, runtime-type identity is the answer:
585
// isa Self_Variant(other)
586
// For a non-unit variant, also compare each field with
587
// System.Object.Equals (`object.equals` in ghūl) — works for
588
// primitives via boxing, for strings (string.Equals), for
589
// recursively-synthesised union members (their Equals(object)
590
// override kicks in once we synthesise it), and for any
591
// reference type with a virtual Equals(object).
592
let isa_check = Trees.Expressions.ISA(
593
location,
594
get_variant_type_expression(variant, `union),
595
Trees.Expressions.IDENTIFIER(
596
location,
597
Identifiers.Identifier(location, "other")
598
)
599
);
600
601
let condition: Trees.Expressions.Expression mut = isa_check;
602
603
for f in variant.fields do
604
let other_cast = Trees.Expressions.CAST(
605
location,
606
get_variant_type_expression(variant, `union),
607
Trees.Expressions.IDENTIFIER(
608
location,
609
Identifiers.Identifier(location, "other")
610
)
611
);
612
613
let other_field = Trees.Expressions.MEMBER(
614
location,
615
other_cast,
616
Identifiers.Identifier(location, f.name!.name),
617
location
618
);
619
620
let self_field = Trees.Expressions.MEMBER(
621
location,
622
Trees.Expressions.SELF(location),
623
Identifiers.Identifier(location, f.name!.name),
624
location
625
);
626
627
let equals_call = Trees.Expressions.CALL(
628
location,
629
Trees.Expressions.MEMBER(
630
location,
631
Trees.Expressions.IDENTIFIER(
632
location,
633
Identifiers.Identifier(location, "object")
634
),
635
Identifiers.Identifier(location, "equals"),
636
location
637
),
638
Trees.Expressions.LIST(
639
location,
640
Collections.LIST[Trees.Expressions.Expression]([
641
cast Trees.Expressions.Expression(self_field),
642
cast Trees.Expressions.Expression(other_field)
643
])
644
)
645
);
646
647
condition = Trees.Expressions.BINARY(
648
location,
649
Identifiers.Identifier(location, "/\\"),
650
"/\\",
651
condition,
652
equals_call
653
);
654
od
655
656
let body = Trees.Bodies.EXPRESSION(location, condition);
657
658
return Definitions.FUNCTION(
659
location,
660
Identifiers.Identifier(location, "=~"),
661
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
662
arguments,
663
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "bool")),
664
Modifiers.LIST(location, null, null),
665
body
666
);
667
si
668
669
// Synthesise `equals(other: object?) -> bool` on the union — this
670
// overrides .NET's `Object.Equals(object)` so unions work as
671
// dictionary keys / set members. Body forwards to typed `=~`
672
// after a runtime type check; the variant override of `=~` does
673
// the per-variant work.
674
get_object_equals_method_for_union(`union: Definitions.UNION) -> Definitions.FUNCTION is
675
let location = `union.location;
676
677
let other_arg = Variables.VARIABLE(
678
location,
679
Identifiers.Identifier(location, "other"),
680
TypeExpressions.OPTIONAL(
681
location,
682
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "object"))
683
),
684
false,
685
false,
686
null
687
);
688
689
let arguments = Variables.LIST(
690
location,
691
Collections.LIST[Variables.VARIABLE]([other_arg])
692
);
693
694
let union_type = get_union_type_expression(`union);
695
696
let isa_check = Trees.Expressions.ISA(
697
location,
698
union_type,
699
Trees.Expressions.IDENTIFIER(
700
location,
701
Identifiers.Identifier(location, "other")
702
)
703
);
704
705
let typed_call = Trees.Expressions.BINARY(
706
location,
707
Identifiers.Identifier(location, "=~"),
708
"=~",
709
Trees.Expressions.SELF(location),
710
Trees.Expressions.CAST(
711
location,
712
get_union_type_expression(`union),
713
Trees.Expressions.IDENTIFIER(
714
location,
715
Identifiers.Identifier(location, "other")
716
)
717
)
718
);
719
720
let condition = Trees.Expressions.BINARY(
721
location,
722
Identifiers.Identifier(location, "/\\"),
723
"/\\",
724
isa_check,
725
typed_call
726
);
727
728
let body = Trees.Bodies.EXPRESSION(location, condition);
729
730
return Definitions.FUNCTION(
731
location,
732
Identifiers.Identifier(location, "equals"),
733
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
734
arguments,
735
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "bool")),
736
Modifiers.LIST(location, null, null),
737
body
738
);
739
si
740
741
// Union's `get_hash_code` is overridden by every variant. The
742
// base body returns 0 — only ever called via super, which never
743
// happens on synthesised methods.
744
get_hash_code_method_for_union() -> Definitions.FUNCTION is
745
let location = LOCATION.internal;
746
747
let body = Trees.Bodies.EXPRESSION(
748
location,
749
Trees.Expressions.Literals.INTEGER(location, "0")
750
);
751
752
return Definitions.FUNCTION(
753
location,
754
Identifiers.Identifier(location, "get_hash_code"),
755
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
756
Variables.LIST(location, Collections.LIST[Variables.VARIABLE]()),
757
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "int")),
758
Modifiers.LIST(location, null, null),
759
body
760
);
761
si
762
763
// Variant's `get_hash_code` combines a per-variant seed (hash of
764
// the variant's name string, distinct per variant in an union)
765
// with each field's hash code via XOR. Not the strongest mixer
766
// but cheap and respects the equality contract: any two values
767
// that compare `=~` will have hashed the same field values.
768
get_hash_code_method_for_variant(variant: Definitions.VARIANT) -> Definitions.FUNCTION is
769
let location = variant.location;
770
771
let seed = Trees.Expressions.CALL(
772
location,
773
Trees.Expressions.MEMBER(
774
location,
775
Trees.Expressions.Literals.STRING(location, variant.name.name),
776
Identifiers.Identifier(location, "get_hash_code"),
777
location
778
),
779
Trees.Expressions.LIST(
780
location,
781
Collections.LIST[Trees.Expressions.Expression]()
782
)
783
);
784
785
let combined: Trees.Expressions.Expression mut = seed;
786
787
for f in variant.fields do
788
// Cast the field to `object` so we can call
789
// `get_hash_code` on it regardless of T's static
790
// capabilities. Boxes value-type fields, no-op for
791
// reference fields. Stays consistent with the
792
// `object.equals` we use for the equality body.
793
let boxed_field = Trees.Expressions.CAST(
794
location,
795
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "object")),
796
Trees.Expressions.MEMBER(
797
location,
798
Trees.Expressions.SELF(location),
799
Identifiers.Identifier(location, f.name!.name),
800
location
801
)
802
);
803
804
let field_hash = Trees.Expressions.CALL(
805
location,
806
Trees.Expressions.MEMBER(
807
location,
808
boxed_field,
809
Identifiers.Identifier(location, "get_hash_code"),
810
location
811
),
812
Trees.Expressions.LIST(
813
location,
814
Collections.LIST[Trees.Expressions.Expression]()
815
)
816
);
817
818
// h = h * 31 + field_hash — standard multiply-and-add
819
// hash combiner (ghūl has no XOR operator on int).
820
let mul = Trees.Expressions.BINARY(
821
location,
822
Identifiers.Identifier(location, "*"),
823
"*",
824
combined,
825
Trees.Expressions.Literals.INTEGER(location, "31")
826
);
827
828
combined = Trees.Expressions.BINARY(
829
location,
830
Identifiers.Identifier(location, "+"),
831
"+",
832
mul,
833
field_hash
834
);
835
od
836
837
let body = Trees.Bodies.EXPRESSION(location, combined);
838
839
return Definitions.FUNCTION(
840
location,
841
Identifiers.Identifier(location, "get_hash_code"),
842
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
843
Variables.LIST(location, Collections.LIST[Variables.VARIABLE]()),
844
TypeExpressions.NAMED(location, Identifiers.Identifier(location, "int")),
845
Modifiers.LIST(location, null, null),
846
body
847
);
848
si
849
850
get_init_method_for_variant(variant: Trees.Definitions.VARIANT) -> Definitions.FUNCTION is
851
// variant.fields holds the variant's full positional shape,
852
// including any `..` splice expanded to copies of the union's
853
// primary params (marked is_inherited_primary). Inherited
854
// entries become init args that the body forwards to
855
// super.init(...); own entries get the usual
856
// self.<f> = <f>; assignment.
857
let arguments =
858
variant.fields |>
859
map((v) -> Variables.VARIABLE =>
860
Variables.VARIABLE(
861
v.location,
862
v.name!.copy(),
863
v.type_expression.copy(),
864
false,
865
false,
866
null
867
)
868
) |>
869
collect_list();
870
871
let body_statements = Collections.LIST[Trees.Statements.Statement]();
872
873
let super_args = Collections.LIST[Trees.Expressions.Expression]();
874
for f in variant.fields do
875
if f.is_inherited_primary then
876
super_args.add(
877
Trees.Expressions.IDENTIFIER(
878
variant.location,
879
Trees.Identifiers.Identifier(variant.location, f.name!.name)
880
)
881
);
882
fi
883
od
884
885
if super_args.count > 0 then
886
let super_member =
887
Trees.Expressions.MEMBER(
888
variant.location,
889
Trees.Expressions.SUPER(variant.location),
890
Trees.Identifiers.Identifier(variant.location, "init"),
891
variant.location
892
);
893
let super_call =
894
Trees.Expressions.CALL(
895
variant.location,
896
super_member,
897
Trees.Expressions.LIST(variant.location, super_args)
898
);
899
body_statements.add(Trees.Statements.EXPRESSION(variant.location, super_call));
900
fi
901
902
for f in variant.fields do
903
if f.is_inherited_primary then
904
continue;
905
fi
906
907
let member_access =
908
Trees.Expressions.MEMBER(
909
variant.location,
910
Trees.Expressions.SELF(variant.location),
911
Trees.Identifiers.Identifier(variant.location, f.name!.name),
912
variant.location
913
);
914
915
let left =
916
Trees.Expressions.SIMPLE_LEFT_EXPRESSION(variant.location, member_access);
917
918
body_statements.add(
919
Trees.Statements.ASSIGNMENT(
920
variant.location,
921
left,
922
Trees.Expressions.IDENTIFIER(
923
variant.location,
924
Trees.Identifiers.Identifier(variant.location, f.name!.name)
925
)
926
)
927
);
928
od
929
930
let body_statement_list = Trees.Statements.LIST(
931
variant.location,
932
body_statements
933
);
934
935
let init_body = Trees.Bodies.BLOCK(
936
LOCATION.internal,
937
body_statement_list
938
);
939
940
let init_function = Definitions.FUNCTION(
941
LOCATION.internal,
942
Identifiers.Identifier(
943
variant.location,
944
"init"
945
),
946
TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)),
947
Variables.LIST(
948
LOCATION.internal,
949
arguments
950
),
951
TypeExpressions.NAMED(
952
LOCATION.internal,
953
Identifiers.Identifier(
954
variant.location,
955
"void"
956
)
957
),
958
Modifiers.LIST(
959
LOCATION.internal,
960
null,
961
null
962
),
963
init_body
964
);
965
966
return init_function;
967
si
968
969
si