Skip to content
← Back

src/semantic/symbols/symbol_inheritance_resolver.ghul

1
namespace Semantic is
2
use IO.Std;
3
4
use Collections.Iterable;
5
use Collections.Iterator;
6
7
use Collections.SET;
8
9
use Pair = Collections.KeyValuePair;
10
11
use Symbols.Symbol;
12
use Symbols.Classy;
13
use Symbols.Function;
14
use Symbols.FUNCTION_GROUP;
15
16
use Types.Type;
17
18
use Logging;
19
20
use Ghul.Pipes;
21
22
// TODO this really needs cleaning up. Could use double dispatch between the overrider and the overridee
23
// for non-method symbols to determine what to do, which might simplify it a bit
24
class SYMBOL_INHERITANCE_RESOLVER is
25
into: Classy;
26
27
init(into: Classy) is
28
self.into = into;
29
si
30
31
pull_down_super_symbols_into() is
32
let logger = IoC.CONTAINER.instance.logger;
33
34
let members = into.get_all_direct_ancestor_members();
35
36
let overridees = get_overridees_map(members);
37
38
for p in overridees do
39
let overridee_name = p.key;
40
let overridee_map = p.value;
41
42
pull_down_for_name(overridee_name, overridee_map, logger);
43
od
44
si
45
46
pull_down_for_name(overridee_name: string, overridee_map: METHOD_OVERRIDE_MAP, logger: Logging.Logger) is
47
let overriders = get_overriders_map_for(overridee_name);
48
49
let overrider_other_symbol = overriders.first_symbol!;
50
51
let overridee_other_symbols = overridee_map.symbols;
52
53
if overridee_map.contains_any_methods then
54
for overridee_list in overridee_map do
55
let overrider_list = overriders[overridee_list.override_class];
56
57
try_inherit_including_methods(
58
overridee_name,
59
overrider_list,
60
overrider_other_symbol,
61
overridee_list,
62
overridee_other_symbols,
63
logger
64
);
65
od
66
67
warn_likely_override_mismatch(overridee_map, overriders, logger);
68
elif overridee_map.contains_any_symbols /\ !overriders.contains_any_methods then
69
try_inherit_non_methods(
70
overridee_name,
71
overrider_other_symbol,
72
overridee_other_symbols,
73
logger
74
);
75
fi
76
si
77
78
// When the derived class declares a method by a name that
79
// also exists in an ancestor but with a different signature,
80
// ghūl treats it as an overload — even when the derived's
81
// argument types are narrower (`LIST[T]` vs `List[T]`,
82
// `Cat` vs `Animal`) and the user almost certainly meant
83
// to override. There is no `override` keyword to make the
84
// intent explicit, so the only way to surface this is a
85
// heuristic on the inheritance walk.
86
//
87
// Heuristic: for each ancestor signature with no exact
88
// overrider, check whether any same-named method in the
89
// current class has the same arity and arg types that are
90
// all subtypes of the ancestor's (with at least one strict
91
// narrowing). If so, warn — the user probably intended an
92
// override.
93
warn_likely_override_mismatch(
94
overridees: METHOD_OVERRIDE_MAP,
95
overriders: METHOD_OVERRIDE_MAP,
96
logger: Logging.Logger
97
) is
98
if IoC.CONTAINER.instance.build_flags.no_warn_likely_override_mismatch then
99
return;
100
fi
101
102
if into.is_reflected then
103
return;
104
fi
105
106
for overridee_set in overridees do
107
// Skip when an exact overrider exists — try_override
108
// has already handled this signature.
109
if overriders[overridee_set.override_class]? then
110
continue;
111
fi
112
113
for overridee in overridee_set.iterable do
114
if overridee.is_internal \/ overridee.is_reflected then
115
continue;
116
fi
117
118
for overrider_set in overriders do
119
// Skip when this overrider exactly overrides some
120
// *other* same-named base signature. In the visitor
121
// double-dispatch pattern the base declares both a
122
// catch-all `visit(Node)` and a per-subtype
123
// `visit(SpecificNode)`; a subclass's
124
// `visit(SpecificNode)` cleanly overrides the
125
// specific one and only looks like a narrowing
126
// mistake against the catch-all.
127
if overridees[overrider_set.override_class]? then
128
continue;
129
fi
130
131
for overrider in overrider_set.iterable |> filter(f => f.owner == into /\ !f.is_internal) do
132
if is_likely_intended_override(overrider, overridee) then
133
logger.warn(
134
overrider.location,
135
"likely-override-mismatch",
136
"{overrider} hides {overridee} due to narrower argument types"
137
);
138
fi
139
od
140
od
141
od
142
od
143
si
144
145
// Same arity, every argument's type is a subtype of the
146
// overridee's, with at least one strict narrowing. Generic-
147
// type-arguments aren't compared — the heuristic stays
148
// conservative; method-level generics are rare enough that
149
// we don't try to be clever about them.
150
is_likely_intended_override(overrider: Function, overridee: Function) -> bool is
151
if overrider.arguments.count != overridee.arguments.count then
152
return false;
153
fi
154
155
let saw_narrowing mut = false;
156
157
for i in 0..overrider.arguments.count do
158
let rider_arg = overrider.arguments[i];
159
let ridee_arg = overridee.arguments[i];
160
161
if rider_arg.matches(ridee_arg) then
162
continue;
163
fi
164
165
// rider_arg is a subtype of ridee_arg iff ridee_arg
166
// can be assigned from rider_arg — MATCH.ASSIGNABLE
167
// means "self is assignable from other" (per
168
// `feedback_dedup_use_equals_hash`).
169
let match = ridee_arg.compare(rider_arg);
170
171
if match != Types.MATCH.SAME /\ match != Types.MATCH.ASSIGNABLE then
172
return false;
173
fi
174
175
if match == Types.MATCH.ASSIGNABLE then
176
saw_narrowing = true;
177
fi
178
od
179
180
return saw_narrowing;
181
si
182
183
184
get_overridees_map(members: Collections.LIST[Symbol]) -> Collections.Map[string, METHOD_OVERRIDE_MAP] is
185
let overridees = Collections.MAP[string,METHOD_OVERRIDE_MAP]();
186
187
for symbol in members do
188
let overridees_with_this_name: METHOD_OVERRIDE_MAP mut;
189
let symbol_name = symbol.name;
190
191
if !overridees.try_get_value(symbol_name, overridees_with_this_name ref) then
192
overridees_with_this_name = METHOD_OVERRIDE_MAP(symbol_name);
193
overridees[symbol_name] = overridees_with_this_name;
194
fi
195
196
overridees_with_this_name.add(symbol);
197
od
198
199
return overridees;
200
si
201
202
try_inherit_including_methods(
203
name: string,
204
overriders: METHOD_OVERRIDE_SET?,
205
other_overrider_symbol: Symbol?,
206
overridees: METHOD_OVERRIDE_SET,
207
other_overridee_symbols: Iterable[Symbol],
208
logger: Logging.Logger
209
)
210
is
211
let is_reflected = into.is_reflected;
212
let other_overridee_symbols_count = other_overridee_symbols |> count();
213
let overriding_method: Function? mut = null;
214
215
if overriders? then
216
overriding_method = overriders.get_overrider(into);
217
fi
218
219
if overriding_method? then
220
try_override_methods(overriding_method, overridees, other_overridee_symbols, is_reflected, logger);
221
elif other_overrider_symbol? then
222
handle_non_method_overrider(other_overrider_symbol, overridees, other_overridee_symbols, is_reflected, logger);
223
elif other_overridee_symbols_count == 1 /\ overridees.count != 0 then
224
into.add_member(other_overridee_symbols |> only());
225
elif other_overridee_symbols_count > 0 then
226
handle_multiple_symbols_with_same_name(overridees, other_overridee_symbols, into, logger);
227
else
228
handle_inherit_nothing_to_override(overridees, other_overridee_symbols, into, logger);
229
fi
230
si
231
232
try_override_methods(
233
overriding_method: Function,
234
overridees: METHOD_OVERRIDE_SET,
235
other_overridee_symbols: Iterable[Symbol],
236
is_reflected: bool,
237
logger: Logging.Logger
238
)
239
is
240
for f in overridees.iterable do
241
overriding_method.try_override(into, f, logger);
242
od
243
244
if !is_reflected then
245
for os in other_overridee_symbols do
246
logger.warn(overriding_method.location, "hides-inherited", "hides {os} (case A)");
247
od
248
fi
249
si
250
251
handle_non_method_overrider(
252
other_overrider_symbol: Symbol,
253
overridees: METHOD_OVERRIDE_SET,
254
other_overridee_symbols: Iterable[Symbol],
255
is_reflected: bool,
256
logger: Logging.Logger
257
)
258
is
259
if !is_reflected then
260
for f in overridees.iterable do
261
logger.warn(other_overrider_symbol.location, "hides-inherited", "hides {f} (case B)");
262
od
263
264
for os in other_overridee_symbols do
265
logger.warn(other_overrider_symbol.location, "hides-inherited", "hides {os} (case C)");
266
od
267
fi
268
si
269
270
handle_multiple_symbols_with_same_name(
271
overridees: METHOD_OVERRIDE_SET,
272
other_overridee_symbols: Iterable[Symbol],
273
into: Classy,
274
logger: Logging.Logger
275
)
276
is
277
let all_symbols = Collections.LIST[Symbol]();
278
279
all_symbols.add_range(
280
overridees.iterable |>
281
map(function -> Symbol => function)
282
);
283
284
all_symbols.add_range(other_overridee_symbols);
285
286
logger.error(into.location,
287
"cannot inherit multiple symbols with the same name: {all_symbols|> sort()}"
288
);
289
si
290
291
handle_inherit_nothing_to_override(
292
overridees: METHOD_OVERRIDE_SET,
293
other_overridee_symbols: Iterable[Symbol],
294
into: Classy,
295
logger: Logging.Logger
296
)
297
is
298
let other_overridee_symbols_count = other_overridee_symbols|> count();
299
300
if overridees.count == 1 then
301
let first = overridees.iterable |> only();
302
first.try_pull_down_into(into, other_overridee_symbols, logger);
303
elif overridees.count == 0 then
304
if other_overridee_symbols_count == 0 then
305
// nothing to do
306
elif other_overridee_symbols_count == 1 then
307
let first = other_overridee_symbols |> only();
308
first.try_pull_down_into(into, other_overridee_symbols, logger);
309
else
310
for os in other_overridee_symbols do
311
os.try_pull_down_into(into, other_overridee_symbols, logger);
312
od
313
314
logger.error(
315
into.location,
316
"cannot inherit multiple symbols with the same name: {
317
overridees.iterable |>
318
map(f => f.to_string()) |>
319
sort()
320
}"
321
);
322
fi
323
else
324
handle_inherit_multiple_methods(overridees, into, logger);
325
fi
326
si
327
328
handle_inherit_multiple_methods(
329
overridees: METHOD_OVERRIDE_SET,
330
into: Classy,
331
logger: Logging.Logger
332
)
333
is
334
let concrete: Function? mut = null;
335
let abstract_method: Function? mut = null;
336
337
let seen_multiple_concrete mut = false;
338
let seen_multiple_abstract mut = false;
339
340
for f in overridees.iterable do
341
if f.is_abstract then
342
if abstract_method? then
343
seen_multiple_abstract = true;
344
else
345
abstract_method = f;
346
fi
347
elif f.is_instance then
348
if concrete? /\ f != concrete then
349
seen_multiple_concrete = true;
350
else
351
concrete = f;
352
fi
353
fi
354
od
355
356
if
357
seen_multiple_concrete /\
358
overridees.iterable |>
359
any(function => function.is_instance /\ !function.is_reflected)
360
then
361
let concretes =
362
overridees.iterable |>
363
filter(function => function.is_instance);
364
365
logger
366
.error(
367
into.location,
368
"cannot inherit multiple concrete methods: {
369
concretes |>
370
map(f => f.to_string()) |>
371
sort()
372
}"
373
);
374
375
elif concrete? then
376
into.add_member(concrete);
377
378
for f in overridees.iterable do
379
if f != concrete then
380
concrete.try_override(into, f, logger);
381
fi
382
od
383
384
elif seen_multiple_abstract /\ !into.location.is_internal then
385
// METHOD_OVERRIDE_SET groups by argument signature. The
386
// return types may also align: identical (a single virtual
387
// slot) or form a subtype chain (C# 9-style covariant
388
// returns on re-declared interface methods — e.g. EF Core's
389
// IReadOnlyAnnotatable / IAnnotatable / IConventionAnnotatable
390
// chain). In either case one most-derived abstract subsumes
391
// the others; pull it down so a single implementation
392
// satisfies every inherited slot via .NET's implicit
393
// interface implementation.
394
let most_derived = most_derived_abstract(overridees);
395
396
if most_derived? then
397
most_derived.try_pull_down_into(into, Collections.LIST[Symbol](), logger);
398
else
399
let abstracts =
400
overridees.iterable |>
401
filter(function => function.is_abstract);
402
403
logger.error(
404
into.location,
405
"cannot inherit multiple abstract methods: {
406
abstracts |>
407
map(f => f.to_string()) |>
408
sort()
409
}"
410
);
411
fi
412
413
elif abstract_method? then
414
into.add_member(abstract_method);
415
fi
416
si
417
418
// If the inherited abstracts share name+args (guaranteed by
419
// METHOD_OVERRIDE_SET grouping) AND their return types form a
420
// single subtype chain — identical types being the degenerate
421
// one-link case — returns the most-derived abstract. Returns
422
// null when the return types are unrelated (not in a chain),
423
// which is the genuine error case.
424
most_derived_abstract(overridees: METHOD_OVERRIDE_SET) -> Function? is
425
let abstracts = Collections.LIST[Function]();
426
427
for f in overridees.iterable do
428
if f.is_abstract then
429
abstracts.add(f);
430
fi
431
od
432
433
let most_derived =
434
MOST_DERIVED_TYPE_SELECTOR()
435
.select(abstracts |> map(f -> Type? => f.return_type));
436
437
if most_derived < 0 then
438
return null;
439
fi
440
441
return abstracts[most_derived];
442
si
443
444
try_inherit_non_methods(
445
name: string,
446
overrider_symbol: Symbol?,
447
overridee_symbols: Iterable[Symbol],
448
logger: Logging.Logger
449
)
450
is
451
if overrider_symbol? then
452
if isa Symbols.Property(overrider_symbol) then
453
handle_property_overrider(
454
overrider_symbol, overridee_symbols, logger);
455
else
456
handle_non_property_overrider(overrider_symbol, overridee_symbols, logger);
457
fi
458
459
return;
460
fi
461
462
let count = overridee_symbols |> count();
463
464
if count == 1 then
465
let first = overridee_symbols |> only();
466
467
into.add_member(first);
468
elif count > 1 then
469
handle_multiple_overridee_symbols(overridee_symbols, logger);
470
fi
471
si
472
473
handle_property_overrider(
474
overrider_symbol: Symbols.Property,
475
overridee_symbols: Iterable[Symbol],
476
logger: Logging.Logger
477
)
478
is
479
for overridee in overridee_symbols do
480
if isa Symbols.Property(overridee) then
481
handle_property_overridee(
482
overrider_symbol, overridee, logger);
483
elif !(isa Symbols.Variable(overridee)) then
484
logger.warn(overrider_symbol.location, "hides-inherited", "{overrider_symbol} hides non-property {overridee}");
485
fi
486
od
487
si
488
489
handle_property_overridee(
490
overrider_symbol: Symbols.Property,
491
overridee: Symbols.Property,
492
logger: Logging.Logger
493
)
494
is
495
let overridee_type = overridee.type;
496
let overrider_type = overrider_symbol.type;
497
498
if !overridee_type? \/ !overrider_type? then
499
return;
500
fi
501
502
if !overridee_type.is_assignable_from(overrider_type) then
503
// Optionality is not part of the emitted accessor
504
// signature, so a property differing from its overridee
505
// only in optionality overrides it at run time no matter
506
// what is decided here. Widening to optional would let
507
// null reach readers typed by the overridee, so it cannot
508
// be allowed to stand; genuinely different types really
509
// do hide, and keep the warning.
510
if !overrider_symbol.is_reflected /\ overrider_type.matches(overridee_type) /\ overrider_type.is_optional /\ !overridee_type.is_optional then
511
logger.error(overrider_symbol.location, "cannot override {overridee} with optional type {overrider_type}");
512
elif overrider_symbol.is_reflected then
513
// Imported declarations model optionality more loosely
514
// than ghūl does, so a redeclaration that only widens
515
// to optional reads here as a type mismatch. It still
516
// hides, but there is nothing to report about it.
517
return;
518
elif overridee.is_instance /\ overrider_symbol.is_instance then
519
logger.warn(overrider_symbol.location, "override-mismatch-type", "{overrider_symbol} does not override {overridee} due to non-assignable type ({overrider_symbol.type} vs {overridee.type})");
520
return;
521
else
522
logger.warn(overrider_symbol.location, "hides-inherited", "{overrider_symbol} hides {overridee}");
523
return;
524
fi
525
elif
526
!overrider_symbol.is_reflected /\
527
overridee.is_assignable /\
528
overrider_type.matches(overridee_type) /\
529
!overrider_type.is_optional /\ overridee_type.is_optional
530
then
531
// Tightening an assignable property to non-optional is
532
// unsound in the assign direction: writers typed by the
533
// overridee can still store an absent value.
534
logger.error(overrider_symbol.location, "cannot override assignable {overridee} with non-optional type {overrider_type}");
535
fi
536
537
let overrides mut = true;
538
539
if overridee.is_assignable then
540
let overridee_assign_is_default_trait =
541
overridee.assign_function? /\ overridee.assign_function.is_default_trait_method;
542
543
if overridee.is_private then
544
if !overrider_symbol.is_assignable then
545
logger.warn(overrider_symbol.location, "override-missing-assign-accessor", "does not override assign accessor for {overridee}");
546
fi
547
else
548
if !overrider_symbol.is_assignable /\ !overrider_symbol.is_reflected then
549
if overridee_assign_is_default_trait then
550
// Inherit the trait's default assign accessor when the
551
// overrider only redeclares the read half.
552
overrider_symbol.assign_function = overridee.assign_function;
553
overrider_symbol.is_assignable = true;
554
555
if let journal = Symbols.INHERITANCE_JOURNAL.current then
556
journal.record(Symbols.InheritanceOp.PROPERTY_ASSIGN_INHERITED(overrider_symbol));
557
fi
558
else
559
logger.error(overrider_symbol.location, "does not override public assign accessor for {overridee}");
560
fi
561
elif overrider_symbol.is_private then
562
logger.error(overrider_symbol.location, "hides public assign accessor for {overridee}");
563
fi
564
fi
565
fi
566
567
if overrider_symbol.il_name_override? then
568
if overridee.il_name_override? /\ overrider_symbol.il_name_override !~ overridee.il_name_override then
569
logger.warn(overrider_symbol.location, "override-mismatch-il-name", "{overrider_symbol} does not override {overridee} due to different IL name ({overrider_symbol.il_name_override} vs {overridee.il_name_override})");
570
overrides = false;
571
fi
572
else
573
overrider_symbol.il_name_override = overridee.il_name_override;
574
575
if overrider_symbol.il_name_override? then
576
if let journal = Symbols.INHERITANCE_JOURNAL.current then
577
journal.record(Symbols.InheritanceOp.IL_NAME_SET(overrider_symbol));
578
fi
579
fi
580
fi
581
582
if overrides then
583
overrider_symbol.add_overridee(overridee);
584
overridee.add_overrider(overrider_symbol);
585
586
let read_is_default = overridee.read_function? /\ overridee.read_function.is_default_trait_method;
587
let assign_is_default = overridee.assign_function? /\ overridee.assign_function.is_default_trait_method;
588
589
if (read_is_default \/ assign_is_default) /\ isa Symbols.Classy(overridee.owner) then
590
Symbols.INEFFECTIVE_TRAIT_OVERRIDE_CHECKER()
591
.check(
592
into,
593
overrider_symbol.location,
594
overrider_symbol.to_string(),
595
overridee,
596
overridee.name,
597
cast Symbols.Classy?(overridee.owner)!,
598
logger
599
);
600
fi
601
fi
602
si
603
604
handle_non_property_overrider(
605
overrider_symbol: Symbol,
606
overridee_symbols: Iterable[Symbol],
607
logger: Logging.Logger
608
)
609
is
610
if overrider_symbol.can_hide_inherited then
611
// we don't care if this particular symbol hides an inherited symbol
612
return;
613
fi
614
615
for overridee in overridee_symbols do
616
if !(isa Symbols.Variable(overridee)) /\ !overridee.can_hide_inherited then
617
logger.info(overrider_symbol.location, "overrider {overrider_symbol.get_type()} hides overridee {overridee.get_type()}");
618
logger.warn(overrider_symbol.location, "hides-inherited", "{overrider_symbol} hides (case 3): {overridee_symbols|}");
619
fi
620
od
621
si
622
623
handle_multiple_overridee_symbols(
624
overridee_symbols: Iterable[Symbol],
625
logger: Logging.Logger
626
)
627
is
628
let properties = Collections.LIST[Symbol]();
629
let any_non_properties mut = false;
630
631
for s in overridee_symbols do
632
if isa Symbols.Property(s) then
633
properties.add(s);
634
else
635
any_non_properties = true;
636
break;
637
fi
638
od
639
640
// Same reasoning as the abstract-method case: an interface
641
// hierarchy that redeclares a property covariantly reaches an
642
// implementing type once per level, and the most derived
643
// declaration subsumes the rest.
644
let most_derived =
645
if any_non_properties then
646
-1
647
else
648
MOST_DERIVED_TYPE_SELECTOR().select(properties |> map(s -> Type? => s.type))
649
fi;
650
651
let inconsistent_types = !any_non_properties /\ most_derived < 0;
652
653
let property: Symbol? mut = null;
654
655
if most_derived >= 0 then
656
property = properties[most_derived];
657
fi
658
659
if any_non_properties then
660
overridee_symbols | .reset();
661
logger.error(
662
into.location,
663
"cannot inherit multiple symbols with the same name {
664
overridee_symbols |>
665
map(s => s.to_string()) |>
666
sort()
667
}"
668
);
669
elif inconsistent_types then
670
overridee_symbols | .reset();
671
logger.error(
672
into.location,
673
"cannot inherit multiple properties with the same name but different types {
674
overridee_symbols |>
675
map(s => s.to_string()) |>
676
sort()
677
}"
678
);
679
elif property? then
680
into.add_member(property);
681
else
682
overridee_symbols | .reset();
683
logger.error(
684
into.location,
685
"did nothing with multiple parent symbols {
686
overridee_symbols |>
687
map(s => s.to_string()) |>
688
sort()
689
}"
690
);
691
fi
692
si
693
694
get_overriders_map_for(overridee_name: string) -> METHOD_OVERRIDE_MAP is
695
let symbol = into.find_direct(overridee_name);
696
697
if !symbol? then
698
return METHOD_OVERRIDE_MAP.empty;
699
fi
700
701
let result = METHOD_OVERRIDE_MAP(overridee_name);
702
703
if isa FUNCTION_GROUP(symbol) then
704
for f in symbol.functions do
705
result.add(f);
706
od
707
elif !symbol.is_type_variable then
708
result.add(symbol);
709
fi
710
711
return result;
712
si
713
si
714
si