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