Skip to content
← Back

src/syntax/process/compile-expressions/compile_expressions_references.ghul

1
namespace Syntax.Process is
2
use System.Exception
3
use Ghul.Disposable
4
5
use IO.Std
6
7
use Logging
8
use Source
9
10
use IR.Values
11
use IR.VALUE_CONVERTER
12
use IR.VALUE_BOXER
13
14
use Semantic.LEAST_UPPER_BOUND_MAP
15
use Semantic.Types.Type
16
17
use Syntax.Trees.Definitions.PRAGMA
18
19
use Ghul.Pipes
20
21
22
// Name and reference walks: calls, identifiers, presence tests, unwraps, references, null and default values.
23
partial COMPILE_EXPRESSIONS is
24
pre(call: Trees.Expressions.CALL) -> bool => true
25
visit(call: Trees.Expressions.CALL) is
26
let mark = _logger.mark()
27
28
try
29
super.pre(call)
30
31
// Push the call's expected type into the callee
32
// expression. A unit-variant `Option.NONE` sitting at
33
// call.function reads it in visit_member and asks
34
// OWNER_CONSTRAINT_SPECIALIZER for the specialised
35
// owner — the same binding the resolve_constructor
36
// path performs for the parenthesised form. Non-variant
37
// callees ignore the field.
38
//
39
// A callee that takes its own type from its context is
40
// the exception: the call's expected type is what the
41
// callee returns, not what it is, so pushing it here
42
// would have `cast(v)` in callee position target the
43
// result type and then report that the result cannot be
44
// called. Such a callee is left until the arguments have
45
// been walked and the whole function type is known.
46
let defer_callee = call.function.awaits_context_type
47
48
if call.expected_type? /\ !defer_callee then
49
call.function.set_expected_type(call.expected_type, call.expected_type_error_message)
50
fi
51
52
// Mark the callee so a reflected TYPE_GROUP resolves to its
53
// generic member for construction rather than collapsing to
54
// the (often non-constructible) arity-0 member.
55
call.function.mark_call_target()
56
57
// Function walks outside the speculation level that
58
// `visit_call` rolls back during constraint-push
59
// retry, so diagnostics from sub-walks of the function
60
// expression aren't scrubbed alongside first-pass
61
// overload noise.
62
if !defer_callee then
63
call.function.walk(self)
64
fi
65
66
RETRY_SITE_STATS.note("references.call_arguments")
67
_logger.speculate()
68
69
// Snapshot the pre-argument narrowing facts so an
70
// overload-retry re-walk resets to what the first walk
71
// saw; on success the final walk's facts are kept.
72
let use flow_speculation = _flow.speculate_then_commit()
73
74
call.arguments.walk(self)
75
76
if defer_callee then
77
_walk_deferred_callee(call)
78
fi
79
80
_calls.visit_call(call)
81
82
// A terminal Pipe consumer (`.count()`, ...) over a
83
// fusible chain lowers to an inline driving loop instead
84
// of building the pipe objects and iterating them.
85
let fused = _recognize_consumer_fusion(call)
86
87
if fused? then
88
call.compile_expressions_state.value = fused
89
fi
90
91
_check_pure_slots(call.location, call.value, call.arguments)
92
_note_call(call.location, call.value)
93
94
_logger.commit()
95
catch e: Exception
96
call.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), call.location)
97
_logger.release(mark)
98
99
_logger.exception(call.location, e, "exception compiling call")
100
yrt
101
si
102
103
// A callee whose own type comes from its context - `cast(v)`
104
// with the target elided - can only take it from the call it
105
// heads, and the call determines it completely: the arguments
106
// give the parameter types and the call's own expected type
107
// gives the return type. That is why such a callee is walked
108
// here rather than before the arguments: the type it is waiting
109
// for is not known until they have been. With no expected type
110
// on the call there is no return type to supply, so the callee
111
// walks unaided and reports that it cannot infer one.
112
_walk_deferred_callee(call: Trees.Expressions.CALL) is
113
let function_type = _try_get_callee_function_type(call)
114
115
if function_type? then
116
call.function.set_expected_type(
117
function_type,
118
"{{0}} is not assignable to {{1}}"
119
)
120
fi
121
122
call.function.walk(self)
123
si
124
125
// The function type a call determines for its own callee:
126
// parameters from the walked arguments, return type from the
127
// call's expected type. Null when either is unavailable.
128
_try_get_callee_function_type(call: Trees.Expressions.CALL) -> Semantic.Types.Type? is
129
let return_type = call.expected_type
130
131
if !return_type? then
132
return null
133
fi
134
135
let types = Collections.LIST[Semantic.Types.Type]()
136
137
for a in call.arguments.expressions do
138
let value = a.value
139
140
if !value? then
141
return null
142
fi
143
144
let type = value.type
145
146
if !type? \/ type.is_error \/ !type.is_settled then
147
return null
148
fi
149
150
types.add(type)
151
od
152
153
types.add(return_type)
154
155
return _innate_symbol_lookup.get_function_type(types)
156
si
157
158
visit(identifier: Trees.Expressions.IDENTIFIER) is
159
let mark = _logger.mark()
160
161
try
162
_access.visit_identifier(identifier)
163
_note_call(identifier.location, identifier.value)
164
165
catch ex: Exception
166
_logger.exception(identifier.location, ex, "something went wrong with identifier")
167
168
finally
169
_logger.release(mark)
170
yrt
171
si
172
173
visit(has_value: Trees.Expressions.HAS_VALUE) is
174
// Stamp the log position the test is walked at, so facts
175
// formed from it skip the calls that ran before it.
176
_flow.note_test_site(has_value)
177
178
_access.visit_has_value(has_value)
179
_note_call(has_value.location, has_value.value)
180
si
181
182
visit(unwrap: Trees.Expressions.UNWRAP) is
183
_access.visit_unwrap(unwrap)
184
_note_call(unwrap.location, unwrap.value)
185
si
186
187
// An operand of `ref` is an address, not a value read. Suppress
188
// the operand's definite-assignment check by recording its target
189
// for the identifier load to skip, and reset the write flag so it
190
// is re-derived from scratch this walk (robust to speculative
191
// retry and inference iteration, which the flag does not otherwise
192
// participate in). The resolved call does the real read-check and
193
// assignment in `note_reference_arguments`.
194
pre(reference: Trees.Expressions.REFERENCE) -> bool is
195
reference.writes_target = false
196
_reference_operand_target = try_get_narrowing_target(reference.left)
197
198
return false
199
si
200
201
visit(reference: Trees.Expressions.REFERENCE) is
202
_reference_operand_target = null
203
_access.visit_reference(reference)
204
si
205
206
// True while walking the operand of the `ref` that writes to
207
// `symbol` — consulted by the identifier load to skip the
208
// definite-assignment check on an address-of operand.
209
is_reference_operand_target(symbol: Semantic.Symbols.Symbol) -> bool =>
210
_reference_operand_target? /\ symbol == _reference_operand_target
211
212
// Definite assignment for `ref` arguments, run once the call is
213
// resolved so the matched parameter's direction is known. A slot
214
// the callee reads (any by-ref except pure `out`) requires the
215
// target to be assigned already; a slot it writes (any by-ref
216
// except pure `in`) assigns it. The write flag is recorded on the
217
// REFERENCE so the condition analyzer can carry that assignment
218
// onto the branch edges a `ref` in a condition reaches.
219
note_reference_arguments(call: Trees.Expressions.CALL, function: Semantic.Symbols.Function) is
220
if call.argument_names? then
221
return
222
fi
223
224
for i in 0..call.arguments.count do
225
let expr = call.arguments.expressions[i]
226
227
if !isa Trees.Expressions.REFERENCE(expr) then
228
continue
229
fi
230
231
let reference = expr
232
let target = try_get_narrowing_target(reference.left)
233
234
if !target? then
235
continue
236
fi
237
238
if
239
function.argument_reads(i) /\
240
!_build_flags.no_warn_definite_assignment /\
241
isa Semantic.Symbols.Variable(target) /\
242
_flow.is_tracked(target) /\
243
!_flow.is_assigned(target)
244
then
245
_logger.warn(reference.location, "definite-assignment", "{target.name} may be used before it is assigned", target.location, "variable declared here")
246
fi
247
248
reference.writes_target = function.argument_writes(i)
249
250
if reference.writes_target then
251
_flow.mark_assigned(target)
252
fi
253
od
254
si
255
256
visit(`null: Trees.Expressions.NULL) is
257
// For a value-type `T?` target (NULLABLE[T]) `null` is the
258
// empty Nullable — a zeroed value, not a reference. Lower
259
// it as an IR.Values.DEFAULT; a plain ldnull would be
260
// invalid IL against a value-type slot. Reference-type
261
// targets keep the ordinary null reference.
262
if let `null.expected_type? /\ expected_type.is_value_type /\ expected_type.is_optional then
263
`null.compile_expressions_state.value = IR.Values.DEFAULT(expected_type)
264
else
265
`null.compile_expressions_state.value = NULL(Semantic.Types.NULL())
266
fi
267
268
si
269
270
visit(`default: Trees.Expressions.DEFAULT) is
271
_literals.visit_default(`default)
272
273
// Non-optional-by-default: `default` reaching a non-optional
274
// reference slot resolves to null at runtime, the same hole
275
// as a bare `null` literal. Same warning category and flag
276
// as visit(NULL) — one migration, one flip.
277
if
278
!_build_flags.no_warn_non_optional /\
279
`default.value? /\
280
_is_non_optional_reference(`default.value.type)
281
then
282
let target = `default.value!.type
283
_logger.warn(`default.location, "non-optional", "default where non-optional {target} expected")
284
fi
285
si
286
287
si
288
si