Skip to content
← Back

src/syntax/process/infer-effects/infer_store_free_walk.ghul

1
namespace Syntax.Process is
2
use System.Exception
3
4
use Ghul.Pipes
5
6
use Logging
7
use Trees
8
9
use Semantic.Types.Type
10
11
use Function = Semantic.Symbols.Function
12
use Symbol = Semantic.Symbols.Symbol
13
14
// The function-context walk: function, property, indexer and lambda entry, assignments and
15
// store-target classification.
16
partial INFER_STORE_FREE is
17
// ==== function context ====
18
19
pre(function: Definitions.FUNCTION) -> bool is
20
enter_scope(function)
21
22
if _current? then
23
// function definitions do not nest; if the tree ever
24
// produces one, poison the enclosing classification
25
// rather than mis-attribute the nested body's facts
26
_current.is_disqualified = true
27
fi
28
29
_typer.reset()
30
31
let symbol = function_for(function)
32
33
if symbol? then
34
let for_property = function.for_property
35
36
if for_property? /\ !function.is_assign_accessor then
37
let shape = MONOTONE_MEMOISER_SHAPE(_logger, _symbol_table, _namespaces, _typer)
38
39
MONOTONE_MEMOISER.note_getter(
40
symbol,
41
shape.classify_getter(function.body),
42
function.location.file_name)
43
fi
44
45
let facts = STORE_FREE_FACTS()
46
47
_facts[symbol] = facts
48
49
_current = facts
50
_current_function = symbol
51
52
_note_study_function(function, symbol)
53
else
54
_current = null
55
_current_function = null
56
_current_record = null
57
fi
58
59
return false
60
si
61
62
visit(function: Definitions.FUNCTION) is
63
leave_scope(function)
64
65
_current = null
66
_current_function = null
67
si
68
69
// Property and indexer accessor bodies arrive as synthesised
70
// sibling FUNCTION definitions; the original nodes carry the
71
// same body and would double-classify it.
72
pre(property: Definitions.PROPERTY) -> bool => true
73
visit(property: Definitions.PROPERTY) is si
74
75
pre(indexer: Definitions.INDEXER) -> bool => true
76
visit(indexer: Definitions.INDEXER) is si
77
78
// A lambda body does not run when the enclosing function
79
// merely creates the closure; any invocation happens through
80
// a delegate-typed value, which is never a bounded callee.
81
// Closure symbols are never marked store-free.
82
//
83
// In resolved mode the body is walked anyway, under the
84
// closure's own function symbol, so the effect-polymorphic
85
// absorption has a write set for every function value it can
86
// trace to a literal.
87
pre(function: Expressions.FUNCTION) -> bool is
88
if _resolved_mode then
89
_study_walk_lambda(function)
90
fi
91
92
return true
93
si
94
95
visit(function: Expressions.FUNCTION) is si
96
97
_study_walk_lambda(function: Expressions.FUNCTION) is
98
let symbol = function_value_of(function.value)
99
100
if !symbol? then
101
return
102
fi
103
104
let root = cast Function?(symbol.root_specialized_from)
105
106
if !root? \/ _facts.contains_key(root) then
107
return
108
fi
109
110
let saved_facts = _current
111
let saved_function = _current_function
112
let saved_record = _current_record
113
114
let facts = STORE_FREE_FACTS()
115
116
_facts[root] = facts
117
_current = facts
118
_current_function = root
119
120
EFFECT_FACTS.note_function(root, root.name, "", "{function.location}", "lambda")
121
_current_record = EFFECT_FACTS.record_for(root)
122
123
enter_scope(function)
124
function.body.walk(self)
125
leave_scope(function)
126
127
_current = saved_facts
128
_current_function = saved_function
129
_current_record = saved_record
130
si
131
132
// ==== stores ====
133
134
// The target of a store is written, not read: walking it
135
// generically would classify the member as a read as well,
136
// adding its getter's edges - and disqualifying outright when
137
// the property is assign-only and has no getter. So the
138
// children are driven by hand: the value and every receiver
139
// and index expression inside the target are walked, the
140
// target member itself is not.
141
pre(assign: Statements.ASSIGNMENT) -> bool is
142
assign.right.walk(self)
143
144
_walk_assignment_target_operands(assign.left)
145
146
return true
147
si
148
149
_walk_assignment_target_operands(left: Trees.Expressions.AssignmentLeftExpression?) is
150
if !left? then
151
return
152
fi
153
154
if isa Trees.Expressions.SIMPLE_LEFT_EXPRESSION(left) then
155
_walk_store_target_operands((cast Trees.Expressions.SIMPLE_LEFT_EXPRESSION(left)).expression)
156
elif isa Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION(left) then
157
for element in (cast Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION(left)).elements do
158
_walk_assignment_target_operands(element)
159
od
160
fi
161
si
162
163
_walk_store_target_operands(target: Trees.Expressions.Expression?) is
164
if !target? then
165
return
166
fi
167
168
if let member: Trees.Expressions.MEMBER = target then
169
if !isa Trees.Expressions.SELF(member.left) then
170
member.left.walk(self)
171
fi
172
173
return
174
fi
175
176
if let index: Trees.Expressions.INDEX = target then
177
index.left.walk(self)
178
index.index.walk(self)
179
return
180
fi
181
182
if let ambiguous: Trees.Expressions.AMBIGUOUS_EXPRESSION = target then
183
if ambiguous.result == Trees.Expressions.AmbiguousExpressionResult.INDEX then
184
_walk_store_target_operands(ambiguous.index)
185
elif ambiguous.left? then
186
ambiguous.left.walk(self)
187
fi
188
189
return
190
fi
191
192
// a bare identifier target: nothing inside it to read
193
si
194
195
visit(assign: Statements.ASSIGNMENT) is
196
if !_current? then
197
return
198
fi
199
200
// Study-only: whether this assignment's value could be
201
// absent. An RHS of declared non-optional type cannot be
202
// null under the language's own rules; anything optional or
203
// untypeable counts as possibly-null.
204
let rhs_type = _typer.try_type(assign.right)
205
206
_study_rhs_nulling = !rhs_type? \/ rhs_type.is_optional
207
208
_classify_assignment_target(assign.left)
209
210
_study_rhs_nulling = false
211
si
212
213
214
_study_rhs_nulling: bool
215
216
// Flag the current function as assigning a possibly-absent
217
// value to member-or-wider storage. A constructor's writes to
218
// its own fresh instance cannot make any pre-existing member
219
// absent, so they are excluded.
220
_study_note_nulling(own_instance: bool) is
221
if !_current_record? \/ !_study_rhs_nulling then
222
return
223
fi
224
225
if _in_constructor /\ own_instance then
226
return
227
fi
228
229
_current_record.assigns_optional = true
230
si
231
232
// The member-keyed refinement of the above: record which member
233
// the possibly-null assignment targets, or that it could not be
234
// resolved. Unlike the coarse flag, a constructor's writes to
235
// its own instance are NOT excluded: the fresh-instance
236
// argument only holds at a construction site, and the solved
237
// sets answer for plain calls too - a constructor reached by
238
// delegation, or invoked directly on an existing receiver,
239
// nulls a pre-existing member like any other method would.
240
_study_note_nulling_member(symbol: Symbol?, own_instance: bool) is
241
if !_current_record? \/ !_study_rhs_nulling then
242
return
243
fi
244
245
if symbol? then
246
_current_record.nulling_writes.add(symbol.root_specialized_from)
247
else
248
_current_record.nulling_unbounded = true
249
fi
250
si
251
252
_classify_assignment_target(left: Trees.Expressions.AssignmentLeftExpression?) is
253
if !left? then
254
_disqualify_because("assign-target-null")
255
return
256
fi
257
258
if isa Trees.Expressions.SIMPLE_LEFT_EXPRESSION(left) then
259
_classify_simple_store((cast Trees.Expressions.SIMPLE_LEFT_EXPRESSION(left)).expression)
260
elif isa Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION(left) then
261
for element in (cast Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION(left)).elements do
262
_classify_assignment_target(element)
263
od
264
else
265
_disqualify_because("assign-target-shape")
266
fi
267
si
268
269
_classify_simple_store(target: Trees.Expressions.Expression?) is
270
_study_note_store(target)
271
272
if _writes_own_instance_field_member(target) then
273
// `self.field = …` writes only the receiver's own
274
// state: disqualifies the strict bit but not
275
// construction, exactly like the bare-field form below
276
_disqualify_strict_only()
277
return
278
fi
279
280
let member_assign_function = _writes_own_instance_property_member(target)
281
282
if member_assign_function? then
283
// `self.property = …` reaches that same state through
284
// the property's accessor
285
_disqualify_strict_only()
286
_add_construction_callee(member_assign_function)
287
return
288
fi
289
290
if _is_local_target(target) then
291
// Reassigning the body's own local variable or
292
// parameter is callee-private. A local captured from
293
// an enclosing scope is not: the closure and the
294
// declaring scope share one heap cell, so the write
295
// escapes the callee - a heap store, recorded exactly
296
// (the write set carries the local's symbol) rather
297
// than unbounding the set.
298
if _is_captured_local_write(target) then
299
_disqualify_captured_store()
300
fi
301
302
return
303
fi
304
305
if !target? \/ !isa Trees.Expressions.IDENTIFIER(target) then
306
// a member, index or other compound target is a heap
307
// store
308
_disqualify_store()
309
return
310
fi
311
312
let identifier = (cast Trees.Expressions.IDENTIFIER(target)).identifier
313
314
if identifier.is_qualified then
315
_disqualify_store()
316
return
317
fi
318
319
let symbol = try_find(identifier)
320
321
if symbol? /\ symbol.is_field /\ symbol.is_instance then
322
// a bare instance-field name resolves to the receiver's
323
// own field, so the write disqualifies the strict bit
324
// but not construction
325
_disqualify_strict_only()
326
return
327
fi
328
329
let assign_function = _instance_property_assign(symbol)
330
331
if assign_function? then
332
// the bare-name form of the own-property write handled
333
// above
334
_disqualify_strict_only()
335
_add_construction_callee(assign_function)
336
return
337
fi
338
339
// a global variable, or a target the walk could not
340
// resolve: either way the write is not to a local
341
_disqualify_store()
342
si
343
344
// Whether `target` is a local the current body captured from
345
// an enclosing scope - the store then writes a heap cell the
346
// declaring scope shares, not private storage.
347
_is_captured_local_write(target: Trees.Expressions.Expression?) -> bool is
348
if !target? \/ !isa Trees.Expressions.IDENTIFIER(target) then
349
return false
350
fi
351
352
let symbol = try_find((cast Trees.Expressions.IDENTIFIER(target)).identifier)
353
354
if !symbol? then
355
return false
356
fi
357
358
return _is_captured_local_store(symbol)
359
si
360
361
_is_captured_local_store(symbol: Semantic.Symbols.Symbol) -> bool is
362
if !isa Semantic.Symbols.LOCAL_VARIABLE(symbol) then
363
return false
364
fi
365
366
let closure = current_closure
367
368
return closure? /\ symbol.owner != closure
369
si
370
371
// A bare name for a local variable or a parameter — the one
372
// storage location a function can write without any caller
373
// seeing it.
374
_is_local_target(target: Trees.Expressions.Expression?) -> bool is
375
if !target? \/ !isa Trees.Expressions.IDENTIFIER(target) then
376
return false
377
fi
378
379
let identifier = (cast Trees.Expressions.IDENTIFIER(target)).identifier
380
381
if identifier.is_qualified then
382
return false
383
fi
384
385
let symbol = try_find(identifier)
386
387
return
388
symbol? /\
389
(isa Semantic.Symbols.LOCAL_VARIABLE(symbol) \/ isa Semantic.Symbols.LOCAL_ARGUMENT(symbol))
390
si
391
392
si
393
si