Skip to content
← Back

src/syntax/process/synthesize_class_equality.ghul

1
namespace Syntax.Process is
2
use Source
3
4
// Settles the synthesized equality members a class was given
5
// before its ancestors were known.
6
//
7
// ADD_ACCESSORS_FOR_PROPERTIES answers eligibility from a class's
8
// own body, which is all it can see: at rewrite time a superclass
9
// is an unresolved name. What inheritance decides is settled here,
10
// once every file's ancestors have resolved and before argument
11
// types are stamped, so that a retyped parameter is stamped from
12
// the tree like any other.
13
//
14
// Two things are decided. A class that inherits an equality it did
15
// not get from here - one the author wrote, one reached through a
16
// trait, one imported - keeps that operator and loses the
17
// synthesized members: a second one at this class's own parameter
18
// type would be an overload against the inherited one rather than
19
// an override of it, and every use would be ambiguous. And a class
20
// whose base is synthesized too joins the base's operator instead
21
// of opening a second: its parameter is retyped to the base's, so
22
// the two are one virtual slot, and its comparison ends by
23
// delegating the base's own members to the base.
24
//
25
// A class over a base that has no equality at all also loses them.
26
// The base's state is part of this class's values and the operator
27
// could not read it, so a comparison over this class's members
28
// alone would answer equal for two values the base distinguishes -
29
// the same unsoundness that keeps a partly-private type out.
30
class SYNTHESIZE_CLASS_EQUALITY: Visitor is
31
_logger: Logging.Logger
32
_symbol_table: Semantic.SYMBOL_TABLE
33
_innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup
34
35
init(
36
logger: Logging.Logger,
37
symbol_table: Semantic.SYMBOL_TABLE,
38
innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup
39
) is
40
super.init()
41
42
_logger = logger
43
_symbol_table = symbol_table
44
_innate_symbol_lookup = innate_symbol_lookup
45
si
46
47
apply(root: Trees.Node) is
48
root.walk(self)
49
si
50
51
pre(`class: Trees.Definitions.CLASS) -> bool is
52
_settle(`class)
53
54
return false
55
si
56
57
_settle(`class: Trees.Definitions.CLASS) is
58
let class_symbol = cast Semantic.Symbols.CLASS?(_symbol_table.scope_for(`class))
59
60
if !class_symbol? then
61
return
62
fi
63
64
let equals = _find_synthesized(`class.body, "=~")
65
66
if !equals? then
67
_report_uncomparable_state(`class, class_symbol)
68
69
return
70
fi
71
72
_ensure_return_type(equals)
73
74
// Enforcing the override requirement needs to see every
75
// subclass, and an open class can be extended from another
76
// assembly where nothing here is checked. The requirement
77
// would be silently unenforced there, and the operator it
78
// guards would answer for subclasses it cannot read.
79
if class_symbol.is_declared_open then
80
_withdraw(`class, class_symbol)
81
82
_logger.error(
83
`class.name.location,
84
"{`class.name.name} is open, so equality cannot be synthesized for it: a subclass in another assembly cannot be held to supplying its own"
85
)
86
87
return
88
fi
89
90
if !_keeps_equality(class_symbol, 0) then
91
_withdraw(`class, class_symbol)
92
93
_report_uncomparable_state(`class, class_symbol)
94
95
return
96
fi
97
98
let base = _base_class_type(class_symbol)
99
100
if !base? \/ !_is_synthesized_equality(base) then
101
return
102
fi
103
104
_join_to_base(`class, equals)
105
si
106
107
// A class under one whose equality the compiler wrote. The
108
// inherited operator reads the base's members and compares by
109
// exact runtime type, so it answers for this class without
110
// ever seeing what this class adds. Every subclass therefore
111
// supplies its own, by asking for one or by writing it.
112
_report_uncomparable_state(
113
`class: Trees.Definitions.CLASS,
114
class_symbol: Semantic.Symbols.CLASS
115
) is
116
// Answered from this class's own declarations: a member
117
// lookup on the symbol reaches inherited members, and
118
// what is inherited is the very thing being reported.
119
if declares_function(`class.body, "=~") then
120
return
121
fi
122
123
let owner = _synthesized_equality_ancestor(class_symbol, 0)
124
125
if !owner? then
126
return
127
fi
128
129
_logger.error(
130
`class.name.location,
131
"{`class.name.name} must declare =~ and get_hash_code",
132
owner.location,
133
"help: this equality applies only to {owner.name} - @equality() writes them for a subclass"
134
)
135
si
136
137
// The nearest ancestor carrying an operator the compiler
138
// wrote, so that the report can point at what is inherited.
139
_synthesized_equality_ancestor(
140
symbol: Semantic.Symbols.Classy,
141
depth: int
142
) -> Semantic.Symbols.Classy? is
143
if depth > 64 then
144
return null
145
fi
146
147
for ancestor in symbol.ancestors do
148
let ancestor_symbol = cast Semantic.Symbols.Classy?(ancestor.symbol)
149
150
if !ancestor_symbol? then
151
continue
152
fi
153
154
let equals = ancestor_symbol.find_member("=~")
155
156
if equals? /\ equals.is_internal then
157
return ancestor_symbol
158
fi
159
160
let further = _synthesized_equality_ancestor(ancestor_symbol, depth + 1)
161
162
if further? then
163
return further
164
fi
165
od
166
167
return null
168
si
169
170
// The operator returns `bool`, which the synthesis names and
171
// resolve-type-expressions looks up like any other name. A file
172
// whose scope is already broken - globals mixed with a
173
// namespace, say - may not have it in reach, and a synthesized
174
// member reporting that is noise on top of the real diagnostic.
175
_ensure_return_type(equals: Trees.Definitions.FUNCTION) is
176
if equals.type_expression.type? then
177
return
178
fi
179
180
equals.type_expression.type = _innate_symbol_lookup.get_bool_type()
181
si
182
183
// Whether a class's synthesized members survive. Answered from
184
// the ancestors alone, so the order files are visited in does
185
// not change the answer: a subclass reaches the same decision
186
// whether or not its base has been visited yet.
187
_keeps_equality(class_symbol: Semantic.Symbols.CLASS, depth: int) -> bool is
188
// A hierarchy that refers to itself is already an error;
189
// stopping here keeps this from following it forever.
190
if depth > 64 then
191
return false
192
fi
193
194
let own = class_symbol.find_member("=~")
195
196
// A `partial` or `impl` block declared an operator that the
197
// body this was synthesized from could not see.
198
if !own? \/ !own.is_internal then
199
return false
200
fi
201
202
// An operator reached through a trait, or through any
203
// ancestor that did not get one from here, answers for this
204
// class already. A second one at this class's parameter
205
// type would be an overload against it rather than an
206
// override of it.
207
if _inherits_foreign_equality(class_symbol, 0) then
208
return false
209
fi
210
211
let base = _base_class_type(class_symbol)
212
213
if !base? then
214
return true
215
fi
216
217
// A base taking type arguments is declined. The parameter
218
// a subclass overrides at is the base's own read through
219
// the instantiation, and an ancestor lookup on a closed
220
// instantiation answers the unspecialised type instead.
221
if base.arguments.count > 0 then
222
return false
223
fi
224
225
let base_symbol = cast Semantic.Symbols.CLASS?(base.symbol)
226
227
if !base_symbol? then
228
return false
229
fi
230
231
if !base_symbol.find_member("=~")? then
232
// `object` holds no state, so a class directly over it
233
// is the root of its own comparison. Any other base
234
// without an operator holds state this one cannot read.
235
return _is_object(base)
236
fi
237
238
if !_is_synthesized_equality(base) then
239
return false
240
fi
241
242
// A base whose own members are withdrawn leaves nothing to
243
// join to.
244
return _keeps_equality(base_symbol, depth + 1)
245
si
246
247
// Whether any ancestor - a superclass, a trait, or one of
248
// theirs - declares an equality this class did not get from
249
// here. Genericity is not in the way of the question: what is
250
// being asked is whether an operator is there at all.
251
_inherits_foreign_equality(symbol: Semantic.Symbols.Classy, depth: int) -> bool is
252
if depth > 64 then
253
return false
254
fi
255
256
for ancestor in symbol.ancestors do
257
if _is_object(ancestor) then
258
continue
259
fi
260
261
let ancestor_symbol = cast Semantic.Symbols.Classy?(ancestor.symbol)
262
263
if !ancestor_symbol? then
264
continue
265
fi
266
267
let equals = ancestor_symbol.find_member("=~")
268
269
if equals? /\ !equals.is_internal then
270
return true
271
fi
272
273
if _inherits_foreign_equality(ancestor_symbol, depth + 1) then
274
return true
275
fi
276
od
277
278
return false
279
si
280
281
// The class this one extends, or null where it extends nothing
282
// but `object`.
283
_base_class_type(class_symbol: Semantic.Symbols.CLASS) -> Semantic.Types.Type? is
284
for ancestor in class_symbol.ancestors do
285
if !ancestor.is_class then
286
continue
287
fi
288
289
if _is_object(ancestor) then
290
return null
291
fi
292
293
return ancestor
294
od
295
296
return null
297
si
298
299
_is_object(type: Semantic.Types.Type) -> bool =>
300
type.unspecialized_symbol == _innate_symbol_lookup.get_object_type().unspecialized_symbol
301
302
_is_synthesized_equality(type: Semantic.Types.Type?) -> bool is
303
if !type? then
304
return false
305
fi
306
307
let symbol = cast Semantic.Symbols.CLASS?(type.symbol)
308
309
if !symbol? then
310
return false
311
fi
312
313
let equals = symbol.find_member("=~")
314
315
return equals? /\ equals.is_internal
316
si
317
318
// The type the whole chain's operator takes, which is the type
319
// of the topmost class the members were synthesized for. Every
320
// class below it overrides at that type rather than narrowing
321
// to its own, since a narrowed parameter would be a second
322
// overload rather than an override.
323
_root_type(class_symbol: Semantic.Symbols.CLASS, depth: int) -> Semantic.Types.Type is
324
if depth > 64 then
325
return class_symbol.type!
326
fi
327
328
let base = _base_class_type(class_symbol)
329
330
if !base? \/ !_is_synthesized_equality(base) then
331
return class_symbol.type!
332
fi
333
334
let base_symbol = cast Semantic.Symbols.CLASS?(base.symbol)
335
336
if !base_symbol? \/ !_keeps_equality(base_symbol, 0) then
337
return class_symbol.type!
338
fi
339
340
return _root_type(base_symbol, depth + 1)
341
si
342
343
// Retypes the operator's parameter to the one the base takes,
344
// so that the two are one virtual slot rather than two
345
// overloads. Nothing else: what the body does about the base
346
// is decided where it is emitted, which is the only place that
347
// knows what the base holds.
348
//
349
// The type expression keeps the name it was built with and is
350
// given the resolved type directly - it carries an internal
351
// location, so the name is never read back, and
352
// resolve-type-expressions has already run.
353
_join_to_base(
354
`class: Trees.Definitions.CLASS,
355
equals: Trees.Definitions.FUNCTION
356
) is
357
let class_symbol = cast Semantic.Symbols.CLASS?(_symbol_table.scope_for(`class))!
358
359
let root_type = _root_type(class_symbol, 0)
360
361
for argument in equals.arguments do
362
argument.type_expression.type = root_type
363
od
364
si
365
366
// Takes the synthesized members back out, tree node and symbol
367
// alike, so that what the class inherits is what answers.
368
_withdraw(`class: Trees.Definitions.CLASS, class_symbol: Semantic.Symbols.CLASS) is
369
for name in ["=~", "get_hash_code", "equals"] do
370
let definition = _find_synthesized(`class.body, name)
371
372
if !definition? then
373
continue
374
fi
375
376
`class.body.remove(definition)
377
378
let group = cast Semantic.Symbols.FUNCTION_GROUP?(class_symbol.find_member(name))
379
380
if !group? then
381
continue
382
fi
383
384
// Only the synthesized halves go: the class may hold a
385
// `partial` block's own member under the same name,
386
// and that is the one left to answer.
387
for function in Collections.LIST[Semantic.Symbols.Function](group.functions) do
388
if function.is_internal then
389
group.remove(function)
390
fi
391
od
392
393
if group.is_empty then
394
class_symbol.remove_direct(name)
395
fi
396
od
397
si
398
399
_find_synthesized(
400
body: Trees.Definitions.LIST,
401
name: string
402
) -> Trees.Definitions.FUNCTION? static is
403
for definition in body do
404
if
405
isa Trees.Definitions.FUNCTION(definition) /\
406
definition.is_synthesized /\
407
definition.name? /\
408
definition.name.name =~ name
409
then
410
return definition
411
fi
412
od
413
414
return null
415
si
416
si
417
si