Skip to content
← Back

src/syntax/process/infer-effects/infer_store_free_defaults.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
// Variable declarations and the no-op default visits for node kinds that cannot store.
15
partial INFER_STORE_FREE is
16
// ==== local variable declarations ====
17
18
visit(variable: Variables.VARIABLE) is
19
if !_current? then
20
return
21
fi
22
23
if variable.want_dispose then
24
// `let use` runs dispose() at scope exit. Before
25
// compile-expressions the value's type is unknown, so
26
// the disposal cannot be bounded; after it, the
27
// dispose member resolves like any other callee.
28
if !_resolved_mode then
29
_disqualify_because("let-use-dispose")
30
return
31
fi
32
33
let disposed_type = _typer.try_type(variable.initializer)
34
35
if !disposed_type? then
36
_disqualify_because("let-use-dispose-untyped")
37
return
38
fi
39
40
let dispose = disposed_type.find_member("dispose")
41
42
if !dispose? then
43
_disqualify_because("let-use-dispose-unresolved")
44
return
45
fi
46
47
_add_callee_edges(dispose)
48
fi
49
50
if variable.is_argument then
51
// parameter types come from the symbol; a default
52
// value expression is walked as part of this body and
53
// classified like any other code
54
return
55
fi
56
57
let name = variable.name
58
59
if !name? then
60
return
61
fi
62
63
let symbol = find(name.name)
64
65
if !symbol? \/ !isa Semantic.Symbols.LOCAL_VARIABLE(symbol) then
66
return
67
fi
68
69
if _resolved_mode /\ !(cast Semantic.Symbols.LOCAL_VARIABLE(symbol)).is_mutable_marked then
70
let fn = function_value_of(
71
if variable.initializer? then variable.initializer.value else null fi)
72
73
if fn? then
74
function_values.note_local(symbol, fn)
75
fi
76
fi
77
78
// An explicitly-declared local carries its type on the
79
// declaration node, resolved before this pass. Read it there,
80
// not from the symbol: the symbol's `type` field is also
81
// written later by compile-expressions inference, so reading
82
// it would make the store-free classification depend on
83
// whether that later pass has run — it has in an analysis-mode
84
// refresh, it has not in a batch build.
85
if !isa TypeExpressions.INFER(variable.type_expression) then
86
if let declared = _typer.usable(variable.type_expression.type) then
87
_typer.set_local_type(symbol, declared)
88
fi
89
90
return
91
fi
92
93
let initializer_type = _typer.try_type(variable.initializer)
94
95
if initializer_type? then
96
_typer.set_local_type(symbol, initializer_type)
97
fi
98
si
99
100
// An assert's condition and message classify like any other
101
// code as the walk reaches them; the construct-and-throw
102
// machinery a failing assert runs is compiler-synthesised —
103
// it allocates a fresh exception and stores nothing that
104
// existed before it, so even a caller that catches the
105
// failure observes an unchanged heap.
106
visit(`assert: Statements.ASSERT) is si
107
visit(assert_in: Expressions.ASSERT_IN) is si
108
109
// Interpolation formats each fragment by calling to_string
110
// on it. When a fragment's static type is known, that
111
// dispatch is bounded like any other member call — the
112
// fixpoint checks the type's to_string and every override a
113
// call could reach; primitive and object to_string bodies
114
// are trusted imports. An untyped fragment, or one with a
115
// format specifier (which selects a different, culture-aware
116
// formatting path), cannot be bounded.
117
visit(interpolation: Expressions.STRING_INTERPOLATION) is
118
if !_current? then
119
return
120
fi
121
122
for fragment in interpolation.values do
123
if fragment.is_expression then
124
if fragment.format? then
125
_disqualify_because("interpolation-format")
126
return
127
fi
128
129
let fragment_type = _typer.try_type(fragment.expression)
130
131
if !fragment_type? then
132
_disqualify_because("interpolation-fragment-untyped")
133
return
134
fi
135
136
_add_callee_edges(fragment_type.find_member("to_string"))
137
fi
138
od
139
si
140
141
// ==== innate bodies ====
142
143
visit(block: Bodies.INNATE) is
144
if !_current? then
145
return
146
fi
147
148
if !STORE_FREE_INNATES.is_store_free(block.name) then
149
_disqualify_because("innate-not-store-free")
150
fi
151
si
152
153
// ==== audited-harmless node kinds ====
154
//
155
// Everything below either has no effect of its own (its
156
// children are classified independently as the walk reaches
157
// them) or cannot occur in an executable position.
158
159
visit(identifier: Identifiers.Identifier) is si
160
visit(identifier: Identifiers.QUALIFIED) is si
161
visit(modifier: Modifiers.Modifier) is si
162
visit(modifiers: Modifiers.LIST) is si
163
visit(pragma: Pragmas.PRAGMA) is si
164
visit(pragma: Statements.PRAGMA) is si
165
166
visit(type_expression: TypeExpressions.TypeExpression) is si
167
visit(type_expression: TypeExpressions.INFER) is si
168
visit(structured: TypeExpressions.Structured) is si
169
visit(array: TypeExpressions.ARRAY_) is si
170
visit(pointer: TypeExpressions.POINTER) is si
171
visit(optional: TypeExpressions.OPTIONAL) is si
172
visit(reference: TypeExpressions.REFERENCE) is si
173
visit(member: TypeExpressions.MEMBER) is si
174
visit(named: TypeExpressions.NAMED) is si
175
visit(types: TypeExpressions.LIST) is si
176
visit(generic: TypeExpressions.GENERIC) is si
177
visit(function: TypeExpressions.FUNCTION) is si
178
visit(functions: TypeExpressions.FUNCTION_GROUP) is si
179
visit(tuple: TypeExpressions.TUPLE) is si
180
visit(element: TypeExpressions.NAMED_TUPLE_ELEMENT) is si
181
visit(element: TypeExpressions.UNDEFINED) is si
182
visit(constraint: TypeExpressions.TYPE_PARAMETER_CONSTRAINT) is si
183
184
visit(literal: Expressions.Literals.Literal) is si
185
visit(`string: Expressions.Literals.STRING) is si
186
visit(integer: Expressions.Literals.INTEGER) is si
187
visit(float: Expressions.Literals.FLOAT) is si
188
visit(character: Expressions.Literals.CHARACTER) is si
189
visit(boolean: Expressions.Literals.BOOLEAN) is si
190
visit(none: Expressions.Literals.NONE) is si
191
192
visit(`null: Expressions.NULL) is si
193
visit(`self: Expressions.SELF) is si
194
visit(`super: Expressions.SUPER) is si
195
visit(`cast: Expressions.CAST) is si
196
visit(`isa: Expressions.ISA) is si
197
visit(`isa: Expressions.TYPEOF) is si
198
visit(`default: Expressions.DEFAULT) is si
199
visit(has_value: Expressions.HAS_VALUE) is si
200
visit(unwrap: Expressions.UNWRAP) is si
201
visit(tuple: Expressions.TUPLE) is si
202
visit(variable: Expressions.TUPLE_ELEMENT) is si
203
visit(sequence: Expressions.SEQUENCE) is si
204
visit(list: Expressions.LIST) is si
205
visit(variable: Expressions.VARIABLE) is si
206
visit(statement: Expressions.STATEMENT) is si
207
visit(block: Expressions.VAL_BLOCK) is si
208
209
// In resolved mode the disposal is bounded per variable, from
210
// the initializer's compiled type - see the want_dispose case
211
// in visit(variable) - so the statement-level catch-alls only
212
// fire when the type was unknowable.
213
visit(statement: Expressions.LET_IN) is
214
if statement.want_dispose /\ !_resolved_mode then
215
_disqualify_because("let-in-dispose")
216
fi
217
218
leave_scope(statement)
219
si
220
221
visit(left: Trees.Expressions.SIMPLE_LEFT_EXPRESSION) is si
222
visit(destructure_left: Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION) is si
223
224
visit(left: Trees.Variables.SIMPLE_VARIABLE_LEFT) is si
225
visit(destructure_left: Trees.Variables.DESTRUCTURING_VARIABLE_LEFT) is si
226
visit(left: Trees.Variables.LITERAL_VARIABLE_LEFT) is si
227
visit(variables: Variables.LIST) is si
228
229
visit(statements: Statements.LIST) is si
230
visit(expression: Statements.EXPRESSION) is si
231
visit(`return: Statements.RETURN) is si
232
visit(`throw: Statements.THROW) is si
233
visit(`if: Statements.IF) is si
234
visit(`break: Statements.BREAK) is si
235
visit(`continue: Statements.CONTINUE) is si
236
visit(rb: Statements.REFUTABLE_BINDING) is si
237
238
visit(l: Statements.LET) is
239
if l.want_dispose /\ !_resolved_mode then
240
_disqualify_because("let-dispose")
241
fi
242
si
243
244
visit(if_branch: Statements.IF_BRANCH) is
245
leave_scope(if_branch)
246
si
247
248
visit(`case: Statements.CASE) is
249
leave_scope(`case)
250
si
251
252
visit(case_match: Statements.CASE_MATCH) is
253
leave_scope(case_match)
254
si
255
256
visit(`try: Statements.TRY) is
257
leave_scope(`try)
258
si
259
260
visit(`catch: Statements.CATCH) is
261
leave_scope(`catch)
262
si
263
264
visit(`do: Statements.DO) is
265
leave_scope(`do)
266
si
267
268
visit(labelled: Statements.LABELLED) is
269
si
270
271
visit(expression: Bodies.EXPRESSION) is
272
leave_scope(expression)
273
si
274
275
visit(block: Bodies.BLOCK) is
276
leave_scope(block)
277
si
278
279
// a bodiless declaration stores nothing; whether calls to it
280
// are safe is decided by its overriders through the fixpoint
281
visit(block: Bodies.NULL) is si
282
si
283
si