Skip to content
← Back

src/syntax/process/check_pure_overrides.ghul

1
namespace Syntax.Process is
2
use Logging
3
use Trees
4
use Source
5
6
// Enforces the pure-override contract after the effects solve
7
// has settled the store-free answers: a function that overrides or
8
// implements a declared-pure base must itself be pure — declared
9
// or proven. The declared bit on the base is what call sites
10
// trust across dispatch, including dispatch to overrides this
11
// compilation is introducing, so an unprovable, undeclared
12
// override is a hole in that trust and is rejected. Bases can be
13
// local or imported: a PURE_ATTRIBUTE read on import marks the
14
// reflected base declared-pure, so overriding a pure member from
15
// another assembly is checked here in the overriding assembly.
16
//
17
// The same visit enforces the store-free mandate on the equality
18
// and order operators: a member `=~` or `<>` must be store-free,
19
// declared or proven. These operators are trusted wherever they
20
// run — on operands whose type is not known when the store-free
21
// walk runs, and through the runtime's comparer when a bare type
22
// parameter is compared — so an implementation that could store
23
// would make that trust unsound rather than merely unprovable.
24
// Declaring `pure` satisfies the mandate and, through the
25
// override contract above, binds every override of the operator
26
// to the same rule.
27
//
28
// The same visit applies the purity expectation to methods and
29
// accessors supplied through a partial or impl block whose target
30
// is a union or a single variant: those blocks are the only way
31
// member code reaches a union, and union state is read-only
32
// variant fields, so a storing member there is reported as a
33
// warning rather than rejected - declared `pure` satisfies it as
34
// everywhere else.
35
//
36
// The same visit applies the type-level purity contract: instance
37
// members of a class, struct or trait declared `pure` must be
38
// proven store-free or declared pure. Bodiless members received the
39
// declaration when they were declared (DECLARE_MEMBERS grants it so
40
// implementors inherit the obligation); constructors, statics,
41
// assign accessors, and members of unions and variants are exempt.
42
// A public assign accessor is rejected outright on a pure type:
43
// writes through one happen at arbitrary times from outside, which
44
// the promise does not cover.
45
//
46
// The property visit enforces the stable-override contract: a
47
// property overriding a declared-stable base must itself be
48
// stable — declared, or self-stable by the classifier for both
49
// fact kinds — since a call through the base can dispatch to it,
50
// and call sites trust the base's contract across dispatch.
51
class CHECK_PURE_OVERRIDES: ScopedVisitor is
52
_logger: Logger
53
54
init(
55
logger: Logger,
56
symbol_table: Semantic.SYMBOL_TABLE,
57
namespaces: Semantic.NAMESPACES
58
)
59
is
60
super.init(logger, symbol_table, namespaces)
61
62
_logger = logger
63
si
64
65
apply(root: Trees.Node) is
66
root.walk(self)
67
si
68
69
visit(function: Definitions.FUNCTION) is
70
super.visit(function)
71
72
let symbol = symbol_for(function)
73
74
if !symbol? \/ !isa Semantic.Symbols.Function(symbol) then
75
return
76
fi
77
78
let function_symbol = cast Semantic.Symbols.Function(symbol)
79
80
if _is_standard_operator(function_symbol) /\ !EFFECTS.is_store_free(function_symbol) then
81
_logger.error(
82
function.name!.location,
83
"operator {function_symbol.name} must be declared pure or provably store-free"
84
)
85
86
return
87
fi
88
89
if _union_injection_target()? /\ !EFFECTS.is_store_free(function_symbol) then
90
// Accessors are reported through their property, the
91
// way other diagnostics word them - never as the
92
// synthesised functions they compile into.
93
let message =
94
if function.for_property? then
95
let property_name = function.for_property.name!.name
96
97
if function.is_assign_accessor then
98
"the assign accessor of union property {property_name} must be pure"
99
else
100
"the getter of union property {property_name} must be pure"
101
fi
102
else
103
"union method {function_symbol.name} must be pure"
104
fi
105
106
_logger.warn(
107
function.name!.location,
108
"impure-union-method",
109
message
110
)
111
fi
112
113
let owner = _owner_classy(function_symbol)
114
115
// Indexer accessors have no property to route through, and
116
// their synthesised names never appear in source, so they
117
// are worded and anchored through the indexer declaration.
118
let anchor =
119
if function.for_indexer? then
120
function.for_indexer.location
121
else
122
function.name!.location
123
fi
124
125
if
126
owner? /\
127
owner.is_pure /\
128
!_is_exempt_from_type_purity(function, function_symbol)
129
then
130
if !EFFECTS.is_store_free(function_symbol) then
131
let kind = _kind_word(owner)
132
let direction = if function.is_assign_accessor then "assign" else "read" fi
133
134
// Accessors are reported through their property or
135
// indexer, the way other diagnostics word them -
136
// never as the synthesised functions they compile
137
// into.
138
let message =
139
if function.for_property? /\ !function.is_assign_accessor then
140
"the getter of property {function.for_property.name!.name} in pure {kind} {owner.name} must be declared pure or provably store-free"
141
elif function.for_indexer? then
142
"the {direction} accessor of the indexer in pure {kind} {owner.name} must be declared pure or provably store-free"
143
else
144
"{function_symbol.name} is a member of pure {kind} {owner.name} so must be declared pure or provably store-free"
145
fi
146
147
_logger.error(
148
anchor,
149
message,
150
owner.location,
151
"pure {kind} declared here"
152
)
153
154
return
155
fi
156
fi
157
158
// Accessor overrides are judged through their property, so
159
// the message names the property rather than the
160
// synthesised getter the accessor compiles into.
161
if function.for_property? then
162
return
163
fi
164
165
let overridees = function_symbol.overridees
166
167
if !overridees? \/ EFFECTS.is_store_free(function_symbol) then
168
return
169
fi
170
171
for base in overridees do
172
if isa Semantic.Symbols.Function(base) /\ (cast Semantic.Symbols.Function(base)).is_declared_pure then
173
let base_function = cast Semantic.Symbols.Function(base)
174
175
let base_word =
176
if base_function.is_indexer_accessor then
177
let base_owner = _owner_classy(base_function)
178
179
if base_owner? then
180
"the indexer of {base_owner.qualified_name}"
181
else
182
base_function.qualified_name
183
fi
184
else
185
base_function.qualified_name
186
fi
187
188
_logger.error(
189
anchor,
190
"{_override_subject(function, function_symbol)} overrides pure {base_word} so must be declared pure or provably store-free",
191
base.location,
192
"pure member declared here"
193
)
194
195
return
196
fi
197
od
198
si
199
200
visit(property: Definitions.PROPERTY) is
201
super.visit(property)
202
203
let symbol = symbol_for(property)
204
205
if !symbol? \/ !isa Semantic.Symbols.Property(symbol) then
206
return
207
fi
208
209
let property_symbol = cast Semantic.Symbols.Property(symbol)
210
211
let owner = _owner_classy(property_symbol)
212
213
if
214
owner? /\
215
owner.is_pure /\
216
property.modifiers.is_public
217
then
218
let kind = _kind_word(owner)
219
220
_logger.error(
221
property.name!.location,
222
"{property_symbol.name} is a member of pure {kind} {owner.name} so its assign accessor cannot be public",
223
owner.location,
224
"pure {kind} declared here"
225
)
226
fi
227
228
// The pure-override contract for accessors, judged here
229
// rather than on the accessor functions so the message
230
// names the property: an accessor that overrides a
231
// declared-pure one must itself be proven store-free or
232
// declared pure. Both directions are checked - a declared-
233
// pure assign accessor binds the overriding setter exactly
234
// as a declared-pure getter binds the read.
235
let pure_overridees = property_symbol.overridees
236
237
if !pure_overridees? then
238
return
239
fi
240
241
let getter = property_symbol.read_function
242
243
if !(getter? /\ EFFECTS.is_store_free(getter)) then
244
for base in pure_overridees do
245
if isa Semantic.Symbols.Property(base) then
246
let base_property = cast Semantic.Symbols.Property(base)
247
let base_getter = base_property.read_function
248
249
if base_getter? /\ base_getter.is_declared_pure then
250
_logger.error(
251
property.name!.location,
252
"{property_symbol.name} overrides pure {base_property.qualified_name} so must be declared pure or provably store-free",
253
base.location,
254
"pure member declared here"
255
)
256
257
return
258
fi
259
fi
260
od
261
fi
262
263
let setter = property_symbol.assign_function
264
265
if setter? /\ !EFFECTS.is_store_free(setter) then
266
for base in pure_overridees do
267
if isa Semantic.Symbols.Property(base) then
268
let base_property = cast Semantic.Symbols.Property(base)
269
let base_setter = base_property.assign_function
270
271
if base_setter? /\ base_setter.is_declared_pure then
272
_logger.error(
273
property.name!.location,
274
"the assign accessor of {property_symbol.name} overrides pure {base_property.qualified_name} so must be declared pure or provably store-free",
275
base.location,
276
"pure member declared here"
277
)
278
279
return
280
fi
281
fi
282
od
283
fi
284
285
if property_symbol.is_declared_stable then
286
return
287
fi
288
289
let overridees = property_symbol.overridees
290
291
if !overridees? then
292
return
293
fi
294
295
for base in overridees do
296
if isa Semantic.Symbols.Property(base) /\ (cast Semantic.Symbols.Property(base)).is_declared_stable then
297
// Self-stable by the classifier satisfies the
298
// contract without the declaration: the getter's
299
// own call backs both fact kinds.
300
if
301
!RELIANCES.first_unproven_getter(property_symbol, null, true)? /\
302
!RELIANCES.first_unproven_getter(property_symbol, null, false)?
303
then
304
return
305
fi
306
307
_logger.error(
308
property.name!.location,
309
"{property_symbol.name} overrides stable {base.qualified_name} so must be declared stable or provably self-stable",
310
base.location,
311
"stable property declared here"
312
)
313
314
return
315
fi
316
od
317
si
318
319
_is_standard_operator(function_symbol: Semantic.Symbols.Function) -> bool =>
320
(function_symbol.name =~ "=~" \/ function_symbol.name =~ "<>") /\
321
isa Semantic.Symbols.Classy(function_symbol.owner)
322
323
_owner_classy(symbol: Semantic.Symbols.Symbol) -> Semantic.Symbols.Classy? =>
324
if isa Semantic.Symbols.Classy(symbol.owner) then
325
cast Semantic.Symbols.Classy?(symbol.owner)
326
else
327
null
328
fi
329
330
_kind_word(owner: Semantic.Symbols.Classy) -> string =>
331
if owner.is_trait then
332
"trait"
333
elif isa Semantic.Symbols.STRUCT(owner) then
334
"struct"
335
else
336
"class"
337
fi
338
339
// How to name the overriding member in a diagnostic: methods by
340
// their symbol name, indexer accessors through the indexer -
341
// their synthesised names never appear in source.
342
_override_subject(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function) -> string is
343
if !function.for_indexer? then
344
return symbol.name
345
fi
346
347
if function.is_assign_accessor then
348
return "the assign accessor of the indexer"
349
fi
350
351
return "the read accessor of the indexer"
352
si
353
354
_is_exempt_from_type_purity(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function) -> bool =>
355
function.modifiers.is_static \/
356
symbol.is_constructor \/
357
function.is_synthesized \/
358
function.is_assign_accessor \/
359
!function.body? \/
360
function.body.is_null
361
362
// The target when the function just visited was declared through
363
// a partial or impl block reopening a union or a single variant,
364
// null for any other declaration site. Members reach a union only
365
// through such a block - a union body holds variants alone - so
366
// this one question covers the whole population the union purity
367
// expectation applies to. The scoped walk leaves each visited
368
// function's scope before this runs, so the current scope is the
369
// block's injection scope exactly when the declaration came from
370
// one.
371
_union_injection_target() -> Semantic.Symbols.Classy? is
372
if !isa Semantic.INJECTION_SCOPE(current_scope) then
373
return null
374
fi
375
376
let target = (cast Semantic.INJECTION_SCOPE(current_scope)).target
377
378
if target.is_union \/ target.is_variant then
379
return target
380
fi
381
382
return null
383
si
384
si
385
si