Skip to content
← Back

src/semantic/symbols/ineffective_trait_override_checker.ghul

1
namespace Semantic.Symbols is
2
use Logging
3
use Source
4
5
use Types.Type
6
7
// Detects the .NET DIM trap where a class B overrides a trait default
8
// method whose owning trait T enters B's class chain at an intermediate
9
// class A that does not itself redeclare the member. In that case A has
10
// no vtable slot for the member, so B's "override" lands in a new slot
11
// and any dispatch routed through an A- or T-typed reference resolves
12
// to T's default rather than B's body.
13
//
14
// The fix is to redeclare the member in A (typically as
15
// `name: T => super.name;`), which forces a vtable slot the override
16
// can latch onto.
17
class INEFFECTIVE_TRAIT_OVERRIDE_CHECKER is
18
init() is si
19
20
check(
21
into: Classy?,
22
overrider_location: LOCATION,
23
overrider_description: string,
24
overridee: Symbol,
25
member_name: string,
26
trait_owner: Classy?,
27
logger: Logger
28
)
29
is
30
if !into? \/ !trait_owner? \/ !trait_owner.is_trait then
31
return
32
fi
33
34
if !into.is_class \/ into.is_reflected then
35
return
36
fi
37
38
let intermediate = _find_intermediate_without_redeclaration(into, trait_owner, member_name)
39
40
if intermediate? then
41
logger.error(
42
overrider_location,
43
"{overrider_description} does not override {overridee} because {intermediate.qualified_name} does not redeclare it",
44
intermediate.location,
45
"help: redeclare {member_name} here to allow overriding"
46
)
47
fi
48
si
49
50
_find_intermediate_without_redeclaration(start: Classy, trait_owner: Classy, member_name: string) -> Classy? is
51
let current: Classy? mut = _direct_class_parent(start)
52
53
while current? do
54
if _has_trait_in_direct_ancestors(current, trait_owner) then
55
if !_class_declares_member(current, member_name) then
56
return current
57
fi
58
return null
59
fi
60
61
current = _direct_class_parent(current)
62
od
63
64
return null
65
si
66
67
_direct_class_parent(classy: Classy) -> Classy? is
68
for a in classy.ancestors do
69
if !a.is_trait /\ isa Classy(a.symbol) then
70
return cast Classy?(a.symbol)!
71
fi
72
od
73
74
return null
75
si
76
77
_has_trait_in_direct_ancestors(classy: Classy, trait_owner: Classy) -> bool is
78
let trait_unspec = trait_owner.unspecialized_symbol
79
80
for a in classy.ancestors do
81
if a.is_trait /\ a.unspecialized_symbol == trait_unspec then
82
return true
83
fi
84
od
85
86
return false
87
si
88
89
_class_declares_member(classy: Classy, name: string) -> bool is
90
let direct = classy.find_direct(name)
91
92
if !direct? then
93
return false
94
fi
95
96
let classy_unspec = classy.unspecialized_symbol
97
98
if isa FUNCTION_GROUP(direct) then
99
for f in direct.functions do
100
if f.is_default_trait_method \/ f.is_abstract then
101
continue
102
fi
103
104
if f.owner? /\ f.owner.unspecialized_symbol == classy_unspec then
105
return true
106
fi
107
od
108
109
return false
110
fi
111
112
if isa Property(direct) then
113
let prop = direct
114
let read_is_default = prop.read_function? /\ prop.read_function.is_default_trait_method
115
let assign_is_default = prop.assign_function? /\ prop.assign_function.is_default_trait_method
116
117
if read_is_default /\ (!prop.assign_function? \/ assign_is_default) then
118
return false
119
fi
120
fi
121
122
return direct.owner? /\ direct.owner.unspecialized_symbol == classy_unspec
123
si
124
si
125
si