Skip to content
← Back

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

1
namespace Syntax.Process is
2
use System.Exception
3
4
use IO.Std
5
6
use Logging
7
use Source
8
9
use IR.Values
10
use IR.VALUE_CONVERTER
11
use IR.VALUE_BOXER
12
13
use Semantic.LEAST_UPPER_BOUND_MAP
14
use Semantic.Types.Type
15
16
use Syntax.Trees.Definitions.PRAGMA
17
18
use Ghul.Pipes
19
20
21
// Definition and statement walks: pragmas, attributes, functions, let, for, destructuring
22
// strategy, let-in, assert-in and assignments.
23
partial COMPILE_EXPRESSIONS is
24
pre(pragma: Trees.Definitions.PRAGMA) -> bool is
25
_pragma_scope_stack.enter(pragma.pragma)
26
27
return false
28
si
29
30
visit(pragma: Trees.Definitions.PRAGMA) is
31
_pragma_scope_stack.leave(pragma.pragma)
32
33
resolve_attribute(pragma)
34
si
35
36
// A statement pragma has no definition to attach an attribute
37
// to; the resolver reports the pragma names it rejects.
38
visit(pragma: Trees.Statements.PRAGMA) is
39
_attribute_resolver.resolve(pragma.pragma, null)
40
si
41
42
// An attribute pragma applies to the definition it wraps; unwrap
43
// any nested pragmas to reach that definition's symbol.
44
resolve_attribute(pragma: Trees.Definitions.PRAGMA) is
45
let definition: Trees.Definitions.Definition mut = pragma.definition
46
47
while isa Trees.Definitions.PRAGMA(definition) do
48
definition = cast Trees.Definitions.PRAGMA(definition).definition
49
od
50
51
_attribute_resolver.resolve(pragma.pragma, symbol_for(definition))
52
si
53
54
// Iterative body walk. Returning true here suppresses the
55
// walk framework's default child traversal so visit() can walk
56
// arguments once and the body up to N times. Constraints set
57
// on AST nodes during a walk persist across iterations
58
// (TypeConstrained.upgrade_constraint is narrowing-only), so
59
// each pass either narrows the constraint set or stays put;
60
// convergence is _logger.is_clean (no errors and no flagged
61
// wild/inferred type consumption).
62
pre(function: Trees.Definitions.FUNCTION) -> bool is
63
super.pre(function)
64
return true
65
si
66
67
visit(function: Trees.Definitions.FUNCTION) is
68
let symbol = symbol_for(function)
69
70
// Declare-symbols rejected the declaration outright — a
71
// generator or async function in a context that has no such
72
// kind — so there is no function symbol to compile the body
73
// against, and walking it anyway trips assertions that assume
74
// an enclosing function. The rejection has already been
75
// reported.
76
if !isa Semantic.Symbols.Function(symbol) then
77
super.visit(function)
78
79
return
80
fi
81
82
let state_machine: Semantic.Symbols.STATE_MACHINE? mut = null
83
let async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE? mut = null
84
85
// Generator / async: realise the state-machine frame's
86
// argument fields and `_outer_self` field before the body
87
// walks. A closure inside the body captures loaded values
88
// at compile-expressions time, and
89
// the captured `Load.LOCAL_ARGUMENT.gen` only redirects
90
// through `ldarg.0; ldfld _arg_<name>` when
91
// `state_machine_field` is populated on the symbol —
92
// populating it later (generate-il) would be too late.
93
//
94
// Also install the function-T → class-T gen_type override
95
// so that any closure captured inside the body — and
96
// any IR.Values it captures that ref a function-T
97
// symbol — emits `!N` (class-level on the state machine)
98
// rather than `!!N` (method-level, which has no meaning
99
// inside MoveNext). The override is uninstalled after the
100
// body walk; generate-il re-installs it around its own
101
// body / state-machine emission paths.
102
if let function_symbol: Semantic.Symbols.Function = symbol then
103
// A closure body compiled during this walk re-marks any
104
// argument it captures; a mark left by an earlier walk is
105
// stale once an edit removes the capturing lambda, and
106
// would wrongly reject assignments to a mut argument.
107
// Re-derive from scratch on each walk of the owning body.
108
for argument_name in function_symbol.argument_names do
109
if let argument: Semantic.Symbols.LOCAL_ARGUMENT = function_symbol.find_direct(argument_name) then
110
argument.is_captured = false
111
fi
112
od
113
114
state_machine = Semantic.Symbols.state_machine_for(function_symbol)
115
async_state_machine = Semantic.Symbols.async_state_machine_for(function_symbol)
116
117
if state_machine? /\ state_machine.frame? then
118
state_machine.frame!.declare()
119
state_machine.install_body_emission_overrides()
120
elif async_state_machine? /\ async_state_machine.frame? then
121
async_state_machine.frame!.declare()
122
async_state_machine.install_body_emission_overrides()
123
fi
124
fi
125
126
_lambdas.visit_function_definition(function)
127
128
if state_machine? /\ state_machine.frame? then
129
state_machine.uninstall_body_emission_overrides()
130
elif async_state_machine? /\ async_state_machine.frame? then
131
async_state_machine.uninstall_body_emission_overrides()
132
fi
133
134
// Yield inside try/catch/finally needs the fault-block /
135
// state-machine-finalisation dance C# uses and isn't
136
// implemented in v1. The "yield + await in same body"
137
// diagnostic fires earlier in declare-symbols, where
138
// both AST forms are still observable.
139
if state_machine? /\ function.body? then
140
YIELD_IN_TRY_SCANNER(_logger).scan(function.body!)
141
fi
142
143
if async_state_machine? /\ function.body? then
144
AWAIT_IN_PROTECTED_SCANNER(_logger).scan(function.body!)
145
fi
146
147
super.visit(function)
148
si
149
150
pre(`let: Trees.Statements.LET) -> bool is
151
if `let.is_poisoned then
152
return true
153
fi
154
155
super.pre(`let)
156
return _bindings.pre_let(`let)
157
si
158
159
visit(`let: Trees.Statements.LET) is
160
super.visit(`let)
161
_bindings.visit_let(`let)
162
si
163
164
pre(`for: Trees.Statements.FOR) -> bool is
165
super.pre(`for)
166
167
_loops.push_loop_value_frame(
168
`for,
169
take_pending_label(),
170
`for.want_value,
171
`for.expected_type,
172
`for.expected_type_error_message
173
)
174
175
try
176
_pre(`for)
177
catch ex: Exception
178
_logger.exception(`for.location, ex, "exception compiling for")
179
yrt
180
181
_loops.pop_and_settle_loop_value(`for)
182
183
return true
184
si
185
186
_pre(`for: Trees.Statements.FOR) -> bool is
187
let symbol: Semantic.Symbols.Symbol mut
188
189
let type: Type mut = Semantic.Types.ERROR()
190
191
let expression = `for.expression
192
let variable = `for.variable
193
194
if expression? /\ !expression.is_poisoned then
195
expression.walk(self)
196
197
record_iterable_constraint(expression)
198
199
if let ev = expression.value /\ set_iterator_for(`for, ev.type!, false) then
200
check_receiver_present(expression)
201
202
if !variable? then
203
type = `for.read_current!.return_type!
204
elif let
205
te = variable.type_expression,
206
te_type = te.type /\
207
!isa Trees.TypeExpressions.INFER(te)
208
then
209
if te_type.is_assignable_from(`for.read_current!.return_type!) then
210
type = te_type
211
else
212
_logger.error(variable.location, "type mismatch")
213
fi
214
else
215
type = `for.read_current!.return_type!
216
fi
217
fi
218
fi
219
220
`for.fusion = _recognize_pipe_fusion(`for)
221
222
if variable? /\ !variable.is_poisoned then
223
set_symbol_type(variable.left, type)
224
225
// Generator: register the per-iteration loop variable
226
// on the state-machine frame so closures inside the
227
// body that capture it freeze correctly. Same
228
// rationale as the LET path above; `for.variable` is
229
// not walked through the visitor framework here, so
230
// we call the helper directly.
231
declare_state_machine_local_fields(variable.left)
232
fi
233
234
let body = `for.body
235
236
if body? then
237
// Loop kill-set narrowing: narrows on variables the
238
// loop writes are dropped (the back-edge could
239
// invalidate them); narrows on variables it never
240
// writes survive the loop.
241
let kept = _loops.loop_kept_env(`for)
242
let epoch = _flow.heap_epoch
243
let mark = _flow.crossing_mark
244
245
_flow.set_env(kept)
246
body.walk(self)
247
248
// The assignment kill-set covers direct writes but
249
// not member stores inside the body, so a store
250
// during the walk drops the kept environment's heap
251
// facts before restoration; the body's calls attach
252
// as crossings.
253
if _flow.heap_killed_since(epoch) then
254
kept.drop_heap_facts()
255
else
256
_flow.adopt_crossings_since(kept, mark)
257
fi
258
259
_flow.set_env(kept)
260
fi
261
return false
262
si
263
264
pre(left: Trees.Expressions.SIMPLE_LEFT_EXPRESSION) -> bool is
265
super.pre(left)
266
return _bindings.pre_simple_left(left)
267
si
268
269
visit(left: Trees.Expressions.SIMPLE_LEFT_EXPRESSION) is
270
super.visit(left)
271
_bindings.visit_simple_left(left)
272
si
273
274
// True iff `value` is statically known to hold a value — its
275
// type is settled and is not an optional / null type.
276
is_non_optional_value(value: IR.Values.Value?) -> bool =>
277
value? /\ value.type? /\ value.type.is_settled /\
278
!value.type!.is_null /\ !value.type!.is_optional
279
280
// Resolve the destructure strategy (see DESTRUCTURE_RESOLVER)
281
// and log an error against `location` for any unresolvable
282
// case. When `field_names` is null the destructure is
283
// positional and the failure is reported as a count
284
// mismatch if the source has positional members, otherwise
285
// as a not-destructurable source. When `field_names` is
286
// non-null the destructure is by-name and each missed name
287
// is reported individually.
288
resolve_destructure_strategy(
289
location: LOCATION,
290
from_type: Type?,
291
element_count: int,
292
field_names: Collections.List[string?]?
293
) -> DESTRUCTURE_STRATEGY =>
294
DESTRUCTURE_RESOLVER.resolve_strategy_reporting(_logger, location, from_type, element_count, field_names)
295
296
pre(left: Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION) -> bool is
297
super.pre(left)
298
299
let from = left.value
300
301
if !from? then
302
return true
303
fi
304
305
let from_type = from.type
306
307
if !from_type? then
308
return true
309
fi
310
311
if from_type.is_error then
312
return true
313
fi
314
315
let elements = left.elements
316
317
// Assignment-style destructure `(a, b) = expr` is
318
// positional-only — by-name destructure uses the `let
319
// (local = field, …) = expr` form, which goes through
320
// the VariableLeft path instead.
321
let strategy = resolve_destructure_strategy(left.location, from_type, elements.count, null)
322
323
let block = IR.Values.BLOCK(_innate_symbol_lookup.get_void_type())
324
325
if strategy.is_deconstruct then
326
let deconstruct = strategy.deconstruct_function!
327
let arg_temps = Collections.LIST[IR.TEMP]()
328
let call_args = Collections.LIST[IR.Values.Value]()
329
330
for i in 0..deconstruct.arguments.count do
331
let ref_type = deconstruct.arguments[i]
332
let element_type = ref_type.get_element_type()!
333
let arg_temp = IR.TEMP(block, "destructure_arg", i, element_type)
334
335
arg_temps.add(arg_temp)
336
call_args.add(IR.Values.ADDRESS(arg_temp.load(), ref_type))
337
od
338
339
let call_value =
340
deconstruct.call(
341
left.location,
342
from,
343
call_args,
344
null,
345
_function_caller
346
)
347
348
block.add(call_value)
349
350
for i in 0..elements.count do
351
let element = elements[i]
352
353
element.compile_expressions_state.value = arg_temps[i].load()
354
355
element.walk(self)
356
357
if element.value? then
358
block.add(element.value)
359
fi
360
od
361
else
362
let members = strategy.members
363
let get_from = from.get_temp_copier(block, "destructure")
364
365
for i in 0..elements.count do
366
let element = elements[i]
367
let member = members[i]
368
369
if member? then
370
element.compile_expressions_state.value = member.load(LOCATION.internal, get_from(), _symbol_loader)
371
372
element.walk(self)
373
374
if element.value? then
375
block.add(element.value)
376
fi
377
fi
378
od
379
fi
380
381
block.close()
382
left.compile_expressions_state.value = block
383
384
return true
385
si
386
387
pre(let_in: Trees.Expressions.LET_IN) -> bool is
388
super.pre(let_in)
389
390
return false
391
si
392
393
visit(let_in: Trees.Expressions.LET_IN) is
394
super.visit(let_in)
395
_bindings.visit_let_in(let_in)
396
si
397
398
pre(assert_in: Trees.Expressions.ASSERT_IN) -> bool is
399
super.pre(assert_in)
400
401
let epoch = _flow.heap_epoch
402
let mark = _flow.crossing_mark
403
404
assert_in.condition.walk(self)
405
406
// A store during the condition's own walk means its
407
// derived heap facts cannot be kept (`assert _f? /\
408
// mutate() in …`); its calls attach as crossings. The
409
// message's walk is outside the span: it only runs on
410
// the failure path, so its effects don't invalidate what
411
// the passing condition proves.
412
let condition_killed = _flow.heap_killed_since(epoch)
413
let condition_mark_end = _flow.crossing_mark
414
415
// Walk the message *before* installing the condition-holds
416
// narrowing, because the message expression is only
417
// evaluated on the failure path — where the condition does
418
// not hold — and must type-check against the unnarrowed env.
419
if assert_in.message? then
420
assert_in.message.walk(self)
421
_check_assertion_message(assert_in.message!)
422
fi
423
424
if _check_assertion_condition(assert_in.condition) then
425
let facts = _condition_analyzer.analyze_condition(assert_in.condition, _flow.current_env)
426
427
if condition_killed then
428
facts.then_env.drop_heap_facts()
429
else
430
_flow.adopt_crossings_between(facts.then_env, mark, condition_mark_end)
431
fi
432
433
_flow.set_env(facts.then_env)
434
fi
435
436
assert_in.expression.walk(self)
437
438
return true
439
si
440
441
visit(assert_in: Trees.Expressions.ASSERT_IN) is
442
let value = assert_in.expression.value
443
444
if
445
!value? \/
446
!value.check_is_consumable(_logger, assert_in.expression.location)
447
then
448
assert_in.compile_expressions_state.value = IR.Values.DUMMY(Semantic.Types.ERROR(), assert_in.location)
449
return
450
fi
451
452
assert_in.compile_expressions_state.value = value
453
si
454
455
pre(assignment: Trees.Statements.ASSIGNMENT) -> bool =>
456
_bindings.pre_assignment(assignment)
457
458
pre(expression: Trees.Statements.EXPRESSION) -> bool is
459
super.pre(expression)
460
461
// A STATEMENT- or VAL_BLOCK-shaped expression wrapped in
462
// an expression-statement is value-required only if the
463
// surrounding list demands a value at this slot. Push
464
// that down so a void-tail block / `if`-arm with no value
465
// in expression-statement position is accepted silently.
466
if let statement_expression: Trees.Expressions.STATEMENT = expression.expression then
467
statement_expression.want_value = expression.want_value
468
statement_expression.void_tolerated = expression.void_tolerated
469
elif let val_block: Trees.Expressions.VAL_BLOCK = expression.expression then
470
val_block.want_value = expression.want_value
471
val_block.void_tolerated = expression.void_tolerated
472
fi
473
474
return false
475
si
476
477
visit(expression: Trees.Statements.EXPRESSION) is
478
super.visit(expression)
479
_bindings.visit_expression_statement(expression)
480
481
if let val_block: Trees.Expressions.VAL_BLOCK = expression.expression then
482
if _is_redundant_val_block(val_block) then
483
_logger.warn(
484
val_block.location,
485
"redundant-val-block",
486
if val_block.is_parenthesised then
487
"parenthesised block is redundant"
488
else
489
"val block is redundant"
490
fi
491
)
492
fi
493
fi
494
si
495
496
// True when this val-block is in expression-statement position
497
// and could be inlined: the surrounding statement list already
498
// accepts the same shape of body. Two refusals: a return inside
499
// targets this block (inlining would re-route the return to the
500
// enclosing function), or the body declares a local (inlining
501
// would widen its scope).
502
_is_redundant_val_block(block: Trees.Expressions.VAL_BLOCK) -> bool is
503
if block.has_targeted_return then
504
return false
505
fi
506
for s in block.body.statements do
507
if isa Trees.Statements.LET(s) then
508
return false
509
fi
510
od
511
return true
512
si
513
514
pre(r: Trees.Statements.RETURN) -> bool is
515
super.pre(r)
516
return _bindings.pre_return(r)
517
si
518
519
visit(r: Trees.Statements.RETURN) is
520
super.visit(r)
521
_bindings.visit_return(r)
522
si
523
524
si
525
si