Skip to content
← Back

src/syntax/process/compile_lambdas.ghul

1
namespace Syntax.Process is
2
use Logging;
3
4
use Semantic.Types.Type;
5
6
use IR.Values;
7
8
use Ghul.Pipes;
9
10
// Compiles function bodies and function literals: the method-body
11
// walk with its iterative type-inference retry loop, lambda
12
// (closure) literals and `rec` references. Split out of
13
// COMPILE_EXPRESSIONS, which delegates the matching visit methods
14
// here. The `super.pre` / `super.visit` base-visitor calls and
15
// the exception / environment-save wrapper of visit(function
16
// literal) stay in the visitor; the methods here are the enclosed
17
// logic.
18
class COMPILE_LAMBDAS is
19
_logger: Logger;
20
_symbol_table: Semantic.SYMBOL_TABLE;
21
_symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS;
22
_symbol_loader: Semantic.SYMBOL_LOADER;
23
_innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup;
24
_task_conversion: Semantic.TASK_CONVERSION;
25
_closure_arg_resolver: Semantic.CLOSURE_ARG_RESOLVER;
26
_type_arg_placeholder_registry: Semantic.TYPE_ARG_PLACEHOLDER_REGISTRY;
27
_flow: NARROWING_FLOW;
28
_build_flags: Compiler.GLOBAL_BUILD_FLAGS;
29
_visitor: ScopedVisitor;
30
_delegate_shape: Semantic.DELEGATE_SHAPE;
31
_attribute_resolver: ATTRIBUTE_RESOLVER;
32
33
init(
34
logger: Logger,
35
symbol_table: Semantic.SYMBOL_TABLE,
36
symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS,
37
symbol_loader: Semantic.SYMBOL_LOADER,
38
innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup,
39
task_conversion: Semantic.TASK_CONVERSION,
40
closure_arg_resolver: Semantic.CLOSURE_ARG_RESOLVER,
41
type_arg_placeholder_registry: Semantic.TYPE_ARG_PLACEHOLDER_REGISTRY,
42
flow: NARROWING_FLOW,
43
build_flags: Compiler.GLOBAL_BUILD_FLAGS,
44
visitor: ScopedVisitor,
45
attribute_resolver: ATTRIBUTE_RESOLVER
46
) is
47
super.init();
48
49
_logger = logger;
50
_symbol_table = symbol_table;
51
_symbol_use_locations = symbol_use_locations;
52
_symbol_loader = symbol_loader;
53
_innate_symbol_lookup = innate_symbol_lookup;
54
_task_conversion = task_conversion;
55
_closure_arg_resolver = closure_arg_resolver;
56
_type_arg_placeholder_registry = type_arg_placeholder_registry;
57
_flow = flow;
58
_build_flags = build_flags;
59
_visitor = visitor;
60
_attribute_resolver = attribute_resolver;
61
_delegate_shape = Semantic.DELEGATE_SHAPE();
62
si
63
64
visit_function_definition(function: Trees.Definitions.FUNCTION) is
65
if function.name? then
66
function.name.walk(_visitor);
67
fi
68
69
function.type_expression.walk(_visitor);
70
71
function.arguments.walk(_visitor);
72
73
if function.body? then
74
// Recorded symbol uses follow the diagnostics speculation
75
// discipline through this walk: every roll_back below is
76
// followed by a full body re-walk, so uses recorded
77
// against not-yet-settled types are discarded with the
78
// diagnostics of the walk that produced them rather than
79
// surviving to shadow the settled walk's records.
80
_logger.speculate();
81
_symbol_use_locations.speculate();
82
83
let retries = 20;
84
85
for i in 1::retries do
86
// Narrowing is method-local — start each body
87
// walk (and each inference-retry re-walk) from
88
// an empty environment.
89
_flow.reset();
90
91
function.body!.walk(_visitor);
92
93
// Convergence: clean or no progress signal raised.
94
// Without a `has_consumed_any` flag from somewhere
95
// in the walk, errors that did fire are real and
96
// persistent — re-walking won't change them, and
97
// re-walking AST nodes that hold partial state
98
// produces *different* (often worse) error sets
99
// because the second walk sees state left over
100
// from the first. So retry only when something
101
// explicitly flagged "I consumed an unresolved
102
// type" — that's the case where iteration N+1
103
// might find narrower constraints and make
104
// progress.
105
if _logger.is_clean \/ !_logger.has_consumed_any then
106
break;
107
fi
108
109
if i < retries then
110
_logger.roll_back();
111
_logger.speculate();
112
113
_symbol_use_locations.roll_back();
114
_symbol_use_locations.speculate();
115
fi
116
od
117
118
// Any constructor-type-arg placeholder that didn't
119
// acquire a concrete constraint from any usage has no
120
// type to settle to - report rather than guess. This
121
// is also the backstop that keeps such a placeholder
122
// from surviving to IL emission, where its canary
123
// comment form would fail the ilasm step.
124
//
125
// Only sweep when the retry loop failed to converge.
126
// A walk that converged clean consumed no unresolved
127
// types, so every phantom that matters has settled -
128
// but the registry also holds phantom sets minted for
129
// overload candidates that lost their resolution and
130
// were rolled back, and those stay unresolved without
131
// being errors. Non-convergence is the signal that an
132
// unresolved phantom actually reached committed code.
133
if _logger.has_consumed_any /\ !_logger.is_clean then
134
_type_arg_placeholder_registry.report_unresolved(_logger);
135
fi
136
137
// Definite return: control can reach the end of a
138
// non-void, block-bodied function's body without a
139
// value having been returned. `_flow.is_unreachable`
140
// is false here only when at least one path falls
141
// off the end — every path that returns / throws
142
// leaves the flow at `bottom`.
143
let function_symbol = _symbol_table.current_function;
144
145
// Generators "fall off the end" by design — that's
146
// how the state machine signals end-of-stream. The
147
// definite-return rule doesn't apply to them.
148
let is_generator = Semantic.Symbols.state_machine_for(function_symbol)?;
149
150
if
151
!_build_flags.no_warn_definite_return /\
152
!is_generator /\
153
function.body!.is_block /\
154
!_flow.is_unreachable /\
155
function_symbol? /\
156
function_symbol.return_type? /\
157
!function_symbol.return_type!.is_sentinel /\
158
!function_symbol.return_type!.is_inferred /\
159
!function_symbol.return_type!.matches(_innate_symbol_lookup.get_void_type())
160
then
161
_logger.warn(function.location, "definite-return", "function may not return a value on all paths");
162
fi
163
164
_logger.commit();
165
_symbol_use_locations.commit();
166
fi
167
si
168
169
visit_function(function: Trees.Expressions.FUNCTION) is
170
if !function.value? then
171
function.compile_expressions_state.value = IR.Values.WRAPPER();
172
fi
173
174
let closure = cast Semantic.Symbols.Closure?(_symbol_table.scope_for(function))!;
175
176
closure.map_type_arguments();
177
178
let implied_type: Type mut;
179
let implied_argument_types: Collections.List[Type]? mut = null;
180
let constraint_return_type: Type? mut = null;
181
182
// have we been passed expected arguments types? If so we'll use them
183
// to argument types of any anonymous function literal arguments.
184
// Single source of truth: function.expected_type, set by the parent
185
// context — LHS of an assignment, typed variable initializer, or
186
// overload-resolution second-pass when this lambda was an actual
187
// arg whose chosen-formal type is now known (#1174).
188
// A context expecting a named delegate constrains the literal
189
// exactly as a function type does: the literal is compiled to
190
// the delegate's call shape and the delegate itself is
191
// constructed at the load site.
192
let expected_type: Type? mut = function.expected_type;
193
194
if let expected = expected_type then
195
if !expected.is_function then
196
let shape = _delegate_shape.try_get_function_type(expected, _innate_symbol_lookup);
197
198
if shape? then
199
expected_type = shape;
200
closure.delegate_target_type = expected;
201
fi
202
fi
203
fi
204
205
if let effective = expected_type /\ effective.is_function then
206
implied_argument_types = effective.arguments;
207
208
// Func slots store the return type as the last entry in
209
// `arguments`; Action slots strip the void return so
210
// `arguments` holds only the input types. Reading
211
// `arguments[count - 1]` on an Action would grab the
212
// last input arg, not the return.
213
if !effective.is_action then
214
let argument_count = effective.arguments.count;
215
if argument_count > 0 then
216
// The slot type may embed placeholders whose
217
// origins settled on an earlier iteration -
218
// collapse them, or the stale composite gets
219
// pushed onto the body and committed as the
220
// closure's return type every iteration,
221
// keeping the retry loop from converging.
222
let candidate =
223
Semantic.SETTLED_PLACEHOLDER_RESOLVER.instance.resolve(
224
effective.arguments[argument_count - 1]);
225
226
if Semantic.LAMBDA_RETURN_CONSTRAINT.should_push(candidate) then
227
constraint_return_type = candidate;
228
fi
229
fi
230
fi
231
fi
232
233
_symbol_table.current_closure_context.add_closure(closure);
234
235
// we are walking the arguments here while speculating
236
let arguments_resolved = _closure_arg_resolver.resolve(function.arguments.expressions, closure, implied_argument_types);
237
238
_resolve_argument_attributes(function, closure);
239
240
if arguments_resolved then
241
let return_type: Type mut;
242
243
if function.type_expression.type? then
244
return_type = function.type_expression.type!;
245
else
246
return_type = Semantic.Types.INFERRED_RETURN_TYPE();
247
closure.return_type_was_inferred = true;
248
fi
249
250
// Body contains `await` → closure must eventually
251
// return `Tasks.TASK[?]`. Void-async (no value
252
// returns) pins to non-generic `Tasks.TASK`; value-
253
// async stays INFERRED_RETURN_TYPE and pins to
254
// `Tasks.TASK[T]` via the wrap-paths.
255
if function.contains_let_await then
256
if function.is_void_async then
257
let void_task = _innate_symbol_lookup.get_void_task_type();
258
if void_task? then
259
return_type = void_task;
260
closure.return_type_was_inferred = false;
261
closure.wrap_inferred_return_as_task = false;
262
closure.is_void_async = true;
263
fi
264
else
265
closure.wrap_inferred_return_as_task = true;
266
fi
267
elif
268
closure.return_type_was_inferred /\
269
constraint_return_type? /\
270
_task_conversion.try_get_task_element_type(constraint_return_type)?
271
then
272
// Slot expects `Tasks.TASK[T]`; mark the closure
273
// so a bare-T body return wraps to
274
// `Tasks.TASK.from_result(orig)` via the same
275
// inferred-return paths used for async closures.
276
closure.wrap_inferred_return_as_task = true;
277
fi
278
279
closure.set_return_type(return_type);
280
fi
281
282
// Push the constraint's return-type slot onto the body's
283
// last expression before walking. The implicit-return path
284
// (pre(Bodies.EXPRESSION)) is gated on
285
// `!function.return_type.is_sentinel` and skips when the closure
286
// hasn't been given an explicit return-type annotation —
287
// but that's exactly the case where a bare variant
288
// constructor (`DONE()`) in the body would benefit from
289
// knowing the expected type. Set it here so the body's
290
// CALL nodes see the constraint; without setting
291
// closure.return_type, the lambda's emitted return type
292
// is still inferred from the body so covariant-return-via-
293
// assignment is unaffected.
294
if
295
constraint_return_type? /\
296
!function.type_expression.type? /\
297
isa Trees.Bodies.EXPRESSION(function.body)
298
then
299
let body = cast Trees.Bodies.EXPRESSION?(function.body)!;
300
301
body.expression.set_expected_type(constraint_return_type, "cannot return value of type {{0}} where {{1}} expected");
302
fi
303
304
_logger.commit();
305
_logger.speculate();
306
307
_symbol_use_locations.commit();
308
_symbol_use_locations.speculate();
309
310
// Each body re-walk below must start from the narrowing
311
// facts in force at the literal, not the ones the previous
312
// walk recorded: a body ending in `return` leaves the
313
// environment unreachable (a re-walk from there loses every
314
// narrow in the body), and a body that unwraps its own
315
// parameter leaves the recorded presence fact (a re-walk
316
// over it misreports the unwrap redundant).
317
let use flow_speculation = _flow.speculate_then_commit();
318
319
_walk_literal_body(function, closure);
320
321
let reported_inference_failure = false;
322
323
if closure.is_recursive then
324
_logger.roll_back();
325
_logger.speculate();
326
327
_symbol_use_locations.roll_back();
328
_symbol_use_locations.speculate();
329
330
_flow.restore();
331
332
_walk_literal_body(function, closure);
333
fi
334
335
// A recursive literal's self-call goes through the
336
// `$recurse` field, whose type mirrors the closure's own
337
// (still-impure) type — so a genuinely store-free
338
// recursive body poisons itself the moment it calls
339
// itself. Retype `$recurse` to the pure shape and re-walk:
340
// if nothing else in the body proves impure, the
341
// optimistic assumption is self-consistent (the closure
342
// really is store-free — by induction, every recursive
343
// invocation is too) and stands; otherwise some other
344
// operation is genuinely impure regardless of how the
345
// self-call is typed, and reverting the retype and
346
// re-walking once more reproduces the original verdict.
347
if closure.is_recursive /\ closure.literal_body_impure /\ closure.frame? /\ closure.type? then
348
let frame = closure.frame;
349
let pure_recurse_type = Semantic.Types.PURE_FUNCTION_SHAPE.pure_shape_of(closure.type, _innate_symbol_lookup);
350
351
if pure_recurse_type? then
352
frame.try_update_recurse_type(pure_recurse_type);
353
354
_logger.roll_back();
355
_logger.speculate();
356
357
_symbol_use_locations.roll_back();
358
_symbol_use_locations.speculate();
359
360
_flow.restore();
361
362
_walk_literal_body(function, closure);
363
364
if closure.literal_body_impure then
365
frame.try_update_recurse_type(closure.type!);
366
367
_logger.roll_back();
368
_logger.speculate();
369
370
_symbol_use_locations.roll_back();
371
_symbol_use_locations.speculate();
372
373
_flow.restore();
374
375
_walk_literal_body(function, closure);
376
fi
377
fi
378
fi
379
380
if _build_flags.want_assembler /\ arguments_resolved /\ closure.could_be_delegate then
381
_logger.roll_back();
382
_logger.speculate();
383
384
_symbol_use_locations.roll_back();
385
_symbol_use_locations.speculate();
386
387
_flow.restore();
388
389
closure.convert_to_delegate();
390
391
_walk_literal_body(function, closure);
392
fi
393
394
// Default an unresolved return type to void only when the
395
// arguments are *fully* resolved — concrete types, not still
396
// INFERRED_VARIABLE_TYPE placeholders. With placeholder args,
397
// an unresolved return is the symptom of an identity-shaped
398
// body (`a => a`) where the body offered no constraint and
399
// we're depending on the call-site formal-arg-push to
400
// resolve the args on the next outer iteration. Defaulting
401
// the return to void here would lock in `*** -> void` and
402
// poison the overload resolution that follows.
403
if closure.return_type!.is_inferred /\ closure.arguments |> all(a => a.is_settled) then
404
closure.set_return_type(_innate_symbol_lookup.get_void_type());
405
fi
406
407
closure.unmap_type_arguments();
408
409
let value mut = closure.load(function.location, _symbol_loader).freeze();
410
411
// A literal whose body proved store-free surfaces at the
412
// pure shape of its function type, so it satisfies a pure
413
// function-typed slot on type alone — freezing to raw IL
414
// has discarded the closure symbol by the time the slot
415
// check sees the value. The shape is the literal's own
416
// property, so it does not depend on the slot it is
417
// heading for: a store-free literal reads as pure
418
// wherever its type is shown.
419
if
420
!closure.literal_body_impure /\ value.type?
421
then
422
let pure_type = Semantic.Types.PURE_FUNCTION_SHAPE.pure_shape_of(value.type!, _innate_symbol_lookup);
423
424
if pure_type? then
425
value = IR.Values.TYPE_WRAPPER(pure_type, value);
426
fi
427
fi
428
429
cast IR.Values.WRAPPER?(function.value)!.value = value;
430
si
431
432
// visit_function re-runs on every retry of the enclosing
433
// function's body (a lambda literal is re-walked along with
434
// everything else in the body until the retry loop converges),
435
// so guard on custom_attributes already being populated —
436
// otherwise the same attribute would be resolved and appended
437
// again on each retry.
438
_resolve_argument_attributes(function: Trees.Expressions.FUNCTION, closure: Semantic.Symbols.Closure) is
439
for expr in function.arguments.expressions do
440
if let argument: Trees.Expressions.VARIABLE = expr, pragmas = argument.pragmas then
441
// Trees.Expressions.FUNCTION.pre() suppresses the
442
// default child walk (so visit_function can walk
443
// the body up to N times), so nothing else ever
444
// walks a pragma's own argument expressions.
445
for pragma in pragmas do
446
pragma.walk(_visitor);
447
od
448
449
let symbol = closure.find_direct(argument.name.name);
450
451
if symbol? /\ !symbol.custom_attributes? then
452
for pragma in pragmas do
453
_attribute_resolver.resolve(pragma, symbol);
454
od
455
fi
456
fi
457
od
458
si
459
460
// Walk a literal's body with a purity frame in force, so the
461
// flow transfers record whether the body performed anything
462
// possibly heap-visible. Re-walks (recursion, delegate
463
// conversion) overwrite the recorded flag — the last walk is
464
// the one whose compilation stands.
465
_walk_literal_body(function: Trees.Expressions.FUNCTION, closure: Semantic.Symbols.Closure) is
466
_flow.push_literal_frame();
467
468
closure.enter_literal_body();
469
function.body.walk(_visitor);
470
closure.leave_literal_body();
471
472
closure.literal_body_impure = _flow.pop_literal_frame();
473
si
474
475
visit_recurse(recurse: Trees.Expressions.RECURSE) is
476
let function = _symbol_table.current_function;
477
478
if !function? \/ !function.is_closure then
479
recurse.compile_expressions_state.value = IR.Values.DUMMY(Semantic.Types.ERROR(), recurse.location);
480
481
_logger.error(recurse.location, "rec can only be used in a function literal body");
482
return;
483
fi
484
485
let closure = cast Semantic.Symbols.Closure?(function)!;
486
487
if closure.is_recursive then
488
recurse.compile_expressions_state.value = closure.load_recurse(recurse.location, _symbol_loader);
489
return;
490
fi
491
492
// Walk up enclosing closures looking for a recursive
493
// ancestor whose $recurse we can capture. The chain in
494
// between needs to capture it too so the value can be
495
// threaded down through nested frame constructors.
496
let stack = _symbol_table.stack;
497
let index mut = stack.count - 1;
498
let seen_self mut = false;
499
let outer_recursive: Semantic.Symbols.Closure? mut = null;
500
let intermediates = Collections.LIST[Semantic.Symbols.Closure]();
501
502
while index >= 0 do
503
let scope = stack[index];
504
505
if scope.is_closure then
506
let c = cast Semantic.Symbols.Closure?(scope)!;
507
508
if seen_self then
509
if c.is_recursive then
510
outer_recursive = c;
511
break;
512
fi
513
intermediates.add(c);
514
elif c == closure then
515
seen_self = true;
516
fi
517
fi
518
519
index = index - 1;
520
od
521
522
if !outer_recursive? then
523
recurse.compile_expressions_state.value = IR.Values.DUMMY(Semantic.Types.ERROR(), recurse.location);
524
525
_logger.error(recurse.location, "rec can only be used in a recursive function");
526
return;
527
fi
528
529
for intermediate in intermediates do
530
intermediate.find_or_add_captured_outer_recurse(outer_recursive);
531
od
532
533
recurse.compile_expressions_state.value = closure.load_captured_outer_recurse(recurse.location, outer_recursive, _symbol_loader);
534
si
535
si
536
si