Skip to content
← Back

src/syntax/process/compile_bindings.ghul

1
namespace Syntax.Process is
2
use System.Exception;
3
4
use Logging;
5
6
use Semantic.Types.Type;
7
8
use IR.Values;
9
10
// Compiles bindings, assignments and returns: `let` statements,
11
// assignment statements and their simple-left targets, `let in`
12
// expressions, expression statements and `return`. Split out of
13
// COMPILE_EXPRESSIONS, which delegates the matching pre / visit
14
// methods here. The `super.pre` / `super.visit` base-visitor
15
// calls stay in the visitor's thin stubs; the methods here are
16
// the enclosed logic.
17
class COMPILE_BINDINGS is
18
_logger: Logger;
19
_symbol_table: Semantic.SYMBOL_TABLE;
20
_symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS;
21
_innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup;
22
_task_conversion: Semantic.TASK_CONVERSION;
23
_flow: NARROWING_FLOW;
24
_build_flags: Compiler.GLOBAL_BUILD_FLAGS;
25
_value_boxer: IR.VALUE_BOXER;
26
_pure_slots: PURE_SLOT_CHECK;
27
_visitor: COMPILE_EXPRESSIONS;
28
29
// Read-only accessor: COMPILE_EXPRESSIONS (the
30
// expression-body return path) goes through `_bindings` as
31
// its single shared facade and reaches the TASK helpers
32
// via this property.
33
task_conversion: Semantic.TASK_CONVERSION => _task_conversion;
34
35
init(
36
logger: Logger,
37
symbol_table: Semantic.SYMBOL_TABLE,
38
symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS,
39
innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup,
40
task_conversion: Semantic.TASK_CONVERSION,
41
flow: NARROWING_FLOW,
42
build_flags: Compiler.GLOBAL_BUILD_FLAGS,
43
value_boxer: IR.VALUE_BOXER,
44
pure_slots: PURE_SLOT_CHECK,
45
visitor: COMPILE_EXPRESSIONS
46
) is
47
super.init();
48
49
_logger = logger;
50
_symbol_table = symbol_table;
51
_symbol_use_locations = symbol_use_locations;
52
_innate_symbol_lookup = innate_symbol_lookup;
53
_task_conversion = task_conversion;
54
_flow = flow;
55
_build_flags = build_flags;
56
_value_boxer = value_boxer;
57
_pure_slots = pure_slots;
58
_visitor = visitor;
59
si
60
61
pre_let(`let: Trees.Statements.LET) -> bool is
62
// Reset is_defined on each LHS variable at the start of
63
// every body-retry walk of the let. Without this reset,
64
// an earlier walk's define() call leaves is_defined=true,
65
// and check_is_defined in `load_captured_value` no longer
66
// fires for a forward self-reference inside the RHS
67
// lambda on subsequent walks (`let f = ... f ...` is the
68
// canonical case). The error gets rolled back per iter
69
// via mark_consumed_any rolling back diagnostics, but
70
// the final iter's error survives so the user-visible
71
// diagnostic is preserved.
72
for v in `let.variables do
73
for name in v.names do
74
let symbol = _visitor.find(name);
75
if symbol? /\ isa Semantic.Symbols.Variable(symbol) then
76
let variable = symbol;
77
variable.is_defined = false;
78
fi
79
od
80
od
81
82
return false;
83
si
84
85
visit_let(`let: Trees.Statements.LET) is
86
// A non-mut local must carry an initializer — an
87
// immutable local variable without a value at its
88
// declaration could only ever be set by a later
89
// assignment, and those are rejected at the store site
90
// below. Catching it here keeps the diagnostic on the
91
// declaration that is missing the `= expr`.
92
for v in `let.variables do
93
if !v.initializer? /\ !v.is_mutable_marked then
94
_logger.error(v.location, "local value must be initialized");
95
fi
96
od
97
98
// Definite assignment: a `let` binding with no
99
// initializer introduces a deferred-init local. Until it
100
// is assigned, a read of it is a use-before-assignment.
101
if !_build_flags.no_warn_definite_assignment then
102
for v in `let.variables do
103
if !v.initializer? then
104
for name in v.names do
105
let symbol = _visitor.find(name);
106
107
if symbol? /\ isa Semantic.Symbols.Variable(symbol) then
108
_flow.track_deferred(symbol);
109
fi
110
od
111
fi
112
od
113
fi
114
115
if `let.want_dispose then
116
let idisposable = _innate_symbol_lookup.get_idisposable_type();
117
118
for v in `let.variables do
119
for name in v.names do
120
let symbol = _visitor.find(name);
121
122
if symbol? /\ isa Semantic.Symbols.Variable(symbol) then
123
let variable = symbol;
124
variable.is_disposed = true;
125
126
let type = variable.type;
127
128
if type? /\ !idisposable.is_assignable_from(type) then
129
_logger.error(name.location, "not disposable");
130
fi
131
132
_visitor.current_statement_list.add_variable_to_dispose(variable);
133
fi
134
od
135
od
136
fi
137
si
138
139
pre_simple_left(left: Trees.Expressions.SIMPLE_LEFT_EXPRESSION) -> bool is
140
if let left.value? then
141
if !value.is_consumable then
142
_logger.error(left.location, "cannot use this here");
143
return false;
144
fi
145
146
left.expression.compile_expressions_state.value = Need.STORE(value);
147
fi
148
149
return false;
150
si
151
152
visit_simple_left(left: Trees.Expressions.SIMPLE_LEFT_EXPRESSION) is
153
let value = left.value;
154
let expression_value = left.expression.value;
155
156
if !value? \/ !value.type? \/ !expression_value? \/ !expression_value.type? then
157
return;
158
fi
159
160
if !expression_value.is_consumable then
161
_logger.error(left.location, "cannot assign to this");
162
return;
163
fi
164
165
// Bypass narrowing for assignability — the LHS value's
166
// type may be a narrowed view, but assignment must
167
// typecheck against the variable's declared type, or
168
// `if isa T(x) then x = wider_value` (idiomatic across
169
// the codebase) would fail at the narrow.
170
let target_variable = _visitor.try_get_narrowing_target(left.expression);
171
let lhs_type =
172
if target_variable? then
173
_flow.declared_type_of(target_variable)!
174
else
175
expression_value.type!
176
fi;
177
178
if !lhs_type.is_assignable_from(value.type!) then
179
_logger.error(left.assign_location, "{value.type} is not assignable to {lhs_type}");
180
return;
181
fi
182
183
// Read the RHS static type and non-optional-ness off the
184
// RHS expression value before the overwrite below replaces
185
// `left.value` with the Store.SYMBOL IR whose type is the
186
// variable's declared type — so a `BOX? mut` slot would mask
187
// a non-null RHS like `BOX(1)`.
188
let rhs_type = value.type;
189
let rhs_is_non_optional = _visitor.is_non_optional_value(value);
190
191
left.compile_expressions_state.value = expression_value;
192
193
// Reassignment invalidates any narrow for the target —
194
// the new value may not satisfy it, so subsequent reads
195
// observe the declared type — and makes the target
196
// definitely assigned from here on.
197
if target_variable? then
198
// Kill the invalidated facts and re-narrow to the RHS
199
// static type when it is more specific than the
200
// declared type, so a later read sees DOG after
201
// `pet = DOG()`. The transfer also emits the editor
202
// hint for whichever of kill / re-narrow applied.
203
let narrowed = _flow.on_assignment(target_variable, left.assign_location, rhs_type);
204
205
_flow.mark_assigned(target_variable);
206
207
// A non-optional RHS leaves the target known to hold
208
// a value — so `_field = arg; _field.method()` does
209
// not warn. Keyed off the declared type, not the
210
// possibly-narrowed view above: the presence fact lives
211
// in its own lattice channel and must still be recorded
212
// when a branch also type-narrows, so it composes at a
213
// join with a sibling branch that carries only presence.
214
let declared_target = _flow.declared_type_of(target_variable);
215
let want_presence =
216
rhs_is_non_optional /\
217
declared_target? /\
218
declared_target.is_optional;
219
220
if want_presence then
221
_flow.mark_non_null(target_variable);
222
223
// A type narrow's hint has already been emitted by
224
// the transfer; the presence-only view is a hint of
225
// its own only when no narrow applied.
226
if !narrowed? then
227
_flow.report_narrowing_site(
228
left.assign_location,
229
"narrowing-assign",
230
"►",
231
INLAY_TYPE.render(target_variable.type!.as_non_optional())
232
);
233
fi
234
fi
235
fi
236
si
237
238
visit_let_in(let_in: Trees.Expressions.LET_IN) is
239
if let let_in.expression.value? /\ value.check_is_consumable_allow_void(_logger, let_in.expression.location) then
240
let_in.compile_expressions_state.value = value;
241
else
242
let_in.compile_expressions_state.value = IR.Values.DUMMY(Semantic.Types.ERROR(), let_in.location);
243
fi
244
si
245
246
pre_assignment(assignment: Trees.Statements.ASSIGNMENT) -> bool is
247
// A member / index target reads its receiver before the
248
// right-hand side runs, so a call there must not drop a field
249
// narrow the receiver relies on. Shield that field across the
250
// whole assignment; once the target is walked, drop it iff a
251
// call actually occurred, so later statements still see the
252
// post-call state.
253
let receiver_field = _try_get_assignment_receiver_field(assignment.left);
254
let shield_frame = _flow.push_shield(receiver_field);
255
256
// Push the LHS type down to the RHS as a constraint. The
257
// RHS expression node is responsible for either consuming
258
// it (FUNCTION uses it for argument-type inference,
259
// SEQUENCE / TUPLE forward to their elements) or ignoring
260
// it (literals, identifiers — Expression's default
261
// set_constraint is a no-op). The post-walk assignability
262
// check still verifies type correctness for the cases
263
// where the RHS doesn't act on the constraint.
264
//
265
// When the LHS is itself a not-yet-resolved placeholder
266
// (`let l; ...; l = X` shape), the LHS type is not
267
// useful as a constraint to push *down*. Instead, after
268
// walking the RHS, push the RHS's type back to the
269
// LHS variable's symbol via add_constraint — the
270
// existing iterative-inference machinery will collapse
271
// the LUB across all assignments on the next retry-
272
// loop iteration.
273
let lhs_type = _try_get_assignment_left_type(assignment.left);
274
275
// `!is_inferred` (not !is_sentinel or is_settled):
276
// we're asking "is the LHS anything other than a
277
// bare placeholder?". ERROR is pre-filtered by
278
// _try_get_assignment_left_type. A composite-with-
279
// placeholder LHS like Function[placeholder, int]
280
// *is* useful as an RHS constraint — the placeholder
281
// arg slot is filled by the lambda's body inference,
282
// and the return slot does its job.
283
if lhs_type? /\ !lhs_type.is_inferred /\ !lhs_type.is_error then
284
assignment.right.set_expected_type(lhs_type, "{{0}} is not assignable to {{1}}");
285
fi
286
287
assignment.right.walk(_visitor);
288
289
let right_value = assignment.right.value;
290
291
if !right_value? then
292
return true;
293
fi
294
295
if !right_value.is_consumable then
296
_logger.error(assignment.right.location, "cannot use this here");
297
assignment.left.compile_expressions_state.value = IR.Values.DUMMY(Semantic.Types.ERROR(), assignment.right.location);
298
else
299
assignment.left.compile_expressions_state.value = right_value;
300
301
_visitor.check_non_optional(lhs_type, assignment.right, assignment.right.location);
302
303
_pure_slots.check_store(assignment.right.location, lhs_type, right_value);
304
fi
305
306
// Back-feed the RHS type as a constraint onto a
307
// placeholder-typed LHS variable (`let l; l = X` etc.).
308
// The next retry-loop iteration of the enclosing
309
// function-body walk picks up the LUB via
310
// try_get_inferred_type at visit(SIMPLE_VARIABLE_LEFT).
311
//
312
// `!is_settled` (not is_inferred or contains_inferred): a
313
// provisional composite LHS like `Func[List[int],
314
// INFERRED_RETURN_TYPE]` — recorded on an early iter when
315
// the lambda's body was walked before the return type
316
// resolved — still needs refining once the placeholder
317
// inside settles. Same goes for an ERROR-carrying
318
// composite like `(ERROR, int)` left over from an iter
319
// where a sub-expression poisoned to DUMMY(ERROR):
320
// a later iter may produce a clean `(int, int)` value,
321
// and without the match propagation the LUB never picks it up
322
// and the per-position merge prefers the stale ERROR
323
// slot. `is_settled` ("no placeholders, no errors")
324
// captures both senses in one predicate.
325
if
326
lhs_type? /\
327
!lhs_type.is_settled /\
328
right_value.type? /\
329
_try_propagate_assignment_lhs(assignment.left, right_value.type!)
330
then
331
_logger.mark_consumed_any();
332
fi
333
334
assignment.left.assign_location = assignment.location;
335
336
assignment.left.walk(_visitor);
337
338
if _flow.release_shield(shield_frame) /\ receiver_field? then
339
_flow.forget(receiver_field);
340
fi
341
342
// A store to anything but a local can change what a
343
// property getter returns, so property facts must not
344
// survive it. Field facts do — a store to one field
345
// cannot alter another, and the assignment transfer
346
// above already handled the target itself.
347
if _is_heap_store_target(assignment.left) then
348
_flow.on_heap_store(assignment.location);
349
fi
350
351
assignment.compile_expressions_state.value = assignment.left.value;
352
353
return true;
354
si
355
356
// True when an assignment target writes the heap: a member,
357
// index, field or property target — anything except a plain
358
// local variable or parameter. Destructuring stores to the
359
// heap when any element does.
360
_is_heap_store_target(left: Trees.Expressions.AssignmentLeftExpression?) -> bool is
361
if !left? then
362
return true;
363
fi
364
365
if isa Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION(left) then
366
for element in (cast Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION(left)).elements do
367
if _is_heap_store_target(element) then
368
return true;
369
fi
370
od
371
372
return false;
373
fi
374
375
if !isa Trees.Expressions.SIMPLE_LEFT_EXPRESSION(left) then
376
return true;
377
fi
378
379
let expression = (cast Trees.Expressions.SIMPLE_LEFT_EXPRESSION(left)).expression;
380
381
if !isa Trees.Expressions.IDENTIFIER(expression) then
382
return true;
383
fi
384
385
let identifier = (cast Trees.Expressions.IDENTIFIER(expression)).identifier;
386
387
if identifier.is_qualified then
388
return true;
389
fi
390
391
let symbol = _visitor.try_find(identifier);
392
393
return
394
!symbol? \/
395
!(isa Semantic.Symbols.LOCAL_VARIABLE(symbol) \/ isa Semantic.Symbols.LOCAL_ARGUMENT(symbol));
396
si
397
398
// The field variable a member / index assignment target reads
399
// as its receiver — the shield subject in `pre_assignment`.
400
// Null unless the target is `field.member` / `field[index]`
401
// rooted at a bare field identifier.
402
_try_get_assignment_receiver_field(left: Trees.Expressions.AssignmentLeftExpression) -> Semantic.Symbols.Symbol? is
403
let simple = cast Trees.Expressions.SIMPLE_LEFT_EXPRESSION?(left);
404
405
if !simple? then
406
return null;
407
fi
408
409
let target = simple.expression;
410
let receiver =
411
if isa Trees.Expressions.MEMBER(target) then
412
(cast Trees.Expressions.MEMBER(target)).left
413
elif isa Trees.Expressions.INDEX(target) then
414
(cast Trees.Expressions.INDEX(target)).left
415
else
416
null
417
fi;
418
419
if !receiver? then
420
return null;
421
fi
422
423
return _visitor.try_get_narrowing_target(receiver);
424
si
425
426
// If the assignment's LHS is a simple identifier referring
427
// to a Variable whose current type is an inference
428
// placeholder, push the supplied RHS type onto the
429
// variable's LUB accumulator. Returns true when a real
430
// constraint actually landed (so the caller can signal
431
// progress to the retry loop).
432
_try_propagate_assignment_lhs(
433
left: Trees.Expressions.AssignmentLeftExpression,
434
rhs_type: Semantic.Types.Type
435
) -> bool is
436
let simple = cast Trees.Expressions.SIMPLE_LEFT_EXPRESSION?(left);
437
438
if !simple? then
439
return false;
440
fi
441
442
let identifier = cast Trees.Expressions.IDENTIFIER?(simple.expression);
443
444
if !identifier? then
445
return false;
446
fi
447
448
let symbol = _visitor.find(identifier.identifier);
449
450
if !symbol? \/ !isa Semantic.Symbols.Variable(symbol) then
451
return false;
452
fi
453
454
let variable = symbol;
455
456
return variable.add_lower_bound(rhs_type);
457
si
458
459
// Speculatively walk a SIMPLE_LEFT_EXPRESSION's inner expression
460
// as a regular load, read its type, and roll back the logger so
461
// the probe is invisible to later passes. Destructuring lefts
462
// are skipped — extracting their effective type is a follow-up
463
// that needs a tuple-shape construction we don't have yet.
464
// The probe walks the live expression rather than a copy: the
465
// subsequent normal walk through pre(SIMPLE_LEFT_EXPRESSION)
466
// overwrites the inner expression's value with the STORE form
467
// anyway, so the probe's read-form value never escapes.
468
_try_get_assignment_left_type(left: Trees.Expressions.AssignmentLeftExpression) -> Type? is
469
let simple = cast Trees.Expressions.SIMPLE_LEFT_EXPRESSION?(left);
470
471
if !simple? then
472
return null;
473
fi
474
475
let mark = _logger.mark();
476
let type: Type? mut = _;
477
478
// The probe's walk must be invisible to the symbol-use
479
// maps as well as to the logger: it compiles the target
480
// as a read, and the read-shaped use it would record
481
// out-ranks the store-shaped one the real walk records
482
// at the same location (first recorded wins a hover tie).
483
_symbol_use_locations.begin_suppress();
484
485
try
486
_logger.speculate();
487
488
simple.expression.walk(_visitor);
489
490
let expression_value = simple.expression.value;
491
492
// is_error left unfiltered here: an ERROR-bearing
493
// composite (e.g. `(ERROR, int)` set by an earlier
494
// iter on a delayed-init variable whose first-seen
495
// RHS poisoned to DUMMY(ERROR)) is exactly the case
496
// the assignment match propagation below needs to recognise
497
// — without the LHS type, the match propagation gate
498
// gets null, no refinement constraint lands, and
499
// the LUB stays frozen. Call sites that don't want
500
// to pass ERROR onward (the set_constraint push to
501
// RHS) gate it themselves.
502
if expression_value? /\ expression_value.type? then
503
let target_variable = _visitor.try_get_narrowing_target(simple.expression);
504
505
type =
506
if target_variable? then
507
_flow.declared_type_of(target_variable)
508
else
509
expression_value.type
510
fi;
511
fi
512
513
_logger.roll_back();
514
_logger.release(mark);
515
catch e: Exception
516
_logger.release(mark);
517
return null;
518
finally
519
_symbol_use_locations.end_suppress();
520
yrt
521
522
return type;
523
si
524
525
visit_expression_statement(expression: Trees.Statements.EXPRESSION) is
526
let inner_value = expression.expression.value;
527
if inner_value? then
528
Value.check_is_consumable_allow_void(_logger, expression.expression.location, inner_value);
529
fi
530
531
if !expression.want_value then
532
if expression.expression.must_be_consumed then
533
_logger.error(expression.expression.location, "{expression.expression.description} result is not used");
534
fi
535
536
return;
537
fi
538
539
expression.compile_expressions_state.value = expression.expression.value;
540
si
541
542
pre_return(r: Trees.Statements.RETURN) -> bool is
543
// Stamp the innermost-val-block target onto the RETURN
544
// AST node so visit_return and generate-il dispatch
545
// consistently from a single source of truth, even if the
546
// val-block stack is unwound by the time those fire.
547
r.val_block_target = _visitor.innermost_val_block;
548
549
if let target = r.val_block_target then
550
target.has_targeted_return = true;
551
fi
552
553
if !r.expression? then
554
return false;
555
fi
556
557
if let block = r.val_block_target then
558
// Return targets a val-block — push the block's
559
// pushed-down expected_type (if any) onto the return
560
// expression so it participates in inference /
561
// overload resolution against the consumer's context,
562
// mirroring the function-return path.
563
if let expected = block.expected_type then
564
if let error_message = block.expected_type_error_message then
565
r.expression.set_expected_type(expected, error_message);
566
fi
567
fi
568
569
return false;
570
fi
571
572
let function = _symbol_table.current_function;
573
574
assert function? else "return outside a function";
575
576
if
577
!function.return_type? \/
578
function.return_type.is_sentinel \/
579
function.return_type.matches(_innate_symbol_lookup.get_void_type())
580
then
581
return false;
582
fi
583
584
r.expression!.set_expected_type(function.return_type!, "cannot return value of type {{0}} where {{1}} expected");
585
586
return false;
587
si
588
589
visit_return(r: Trees.Statements.RETURN) is
590
// Control does not fall through a return — the rest of
591
// the enclosing block is unreachable.
592
_flow.set_unreachable();
593
594
if let block = r.val_block_target then
595
// Return targets the innermost enclosing val-block:
596
// collect the expression's type into the block's LUB
597
// pool and stop here. Function-level return-type
598
// inference, async-SM rewiring, and the various
599
// assignability / wrap rules below are scoped to
600
// function-return; a val-targeted return is a
601
// local control transfer, not a function exit.
602
if let expression = r.expression then
603
if let value = expression.value then
604
if let value_type = value.type then
605
if !value.check_is_consumable(_logger, expression.location) then
606
return;
607
fi
608
609
if !value_type.is_error then
610
if let expected = block.expected_type then
611
// Honour the pushed-down expected_type
612
// the same way function-return does:
613
// error if the expression's type isn't
614
// assignable.
615
if !expected.is_void /\ !expected.is_assignable_from(value_type) then
616
let error_message mut = block.expected_type_error_message;
617
if !error_message? then
618
error_message = "cannot return value of type {{0}} where {{1}} expected";
619
fi
620
_logger.error(
621
expression.location,
622
string.format(error_message, value_type, expected)
623
);
624
return;
625
fi
626
fi
627
628
block.return_types.add(value_type);
629
fi
630
fi
631
fi
632
else
633
// Bare `return;` inside a val-block. Diagnose
634
// only when an outer expected_type makes the
635
// value requirement unambiguous (typed `let`
636
// initializer, function argument, value-
637
// returning `=> body`). want_value alone reads
638
// stale for inferred-return lambdas — the
639
// lambda's return type isn't pinned until after
640
// the body walks, so a bare return that's
641
// consistent with a void inference would error
642
// here prematurely. Generate-il stores the
643
// default of the result type on the no-
644
// diagnostic path so the IL still verifies.
645
if let expected = block.expected_type then
646
if !expected.is_void then
647
_logger.error(r.location, "return without value from val block requiring a value");
648
fi
649
fi
650
fi
651
652
return;
653
fi
654
655
let function = _symbol_table.current_function;
656
657
if !function? \/ !function.return_type? then
658
// FIXME: null function happens for properties, null return type happens for anonymous functions
659
return;
660
fi
661
662
// State-machine async: `return X` provides X of type T
663
// (the Task element type), generate-il stashes X into
664
// `_result` and leaves to success_label.
665
let async_sm = Semantic.Symbols.async_state_machine_for(function);
666
if async_sm? /\ async_sm.frame? then
667
_visit_return_state_machine_async(r, function, async_sm);
668
return;
669
fi
670
671
if r.expression? then
672
if let
673
r.expression.value? /\
674
value.type? /\
675
value.check_is_consumable(_logger, r.expression!.location)
676
then
677
if function.return_type!.is_inferred then
678
// Filling in a previously-undeclared return
679
// type from this return statement. is_inferred
680
// (today only INFERRED_RETURN_TYPE) rather than
681
// is_sentinel: a function whose return type was
682
// already set to ERROR by an earlier failing
683
// return shouldn't get silently overwritten
684
// with a concrete type by a later (validly-
685
// typed) return — the original error should
686
// remain reported and the return-type contract
687
// should stay ERROR until the user fixes the
688
// source.
689
//
690
// Async closures: wrap a bare-T body return
691
// to Task[T] before pinning, so the closure's
692
// signature matches its SM emission shape.
693
// Values already typed Task[?] pass through.
694
let value_type mut = value.type;
695
696
if
697
function.wrap_inferred_return_as_task /\
698
value_type? /\
699
value_type.is_settled /\
700
!_task_conversion.is_task_type(value_type)
701
then
702
let task_type = _innate_symbol_lookup.get_task_type(value_type);
703
704
// Wrap as `Tasks.TASK.from_result(orig)`.
705
// Guard on `is_settled` so we don't fire
706
// during iterative-inference walks with a
707
// partially-resolved body type — those
708
// would over-wrap to `Task[Task[?]]`.
709
if task_type? /\ _task_conversion.try_wrap_value_as_task_return(r, task_type, _visitor) then
710
// The wrap replaced r.expression and
711
// re-walked it — read the fresh value,
712
// not the pre-wrap snapshot.
713
value_type = r.expression!.value!.type;
714
fi
715
fi
716
717
function.set_return_type(value_type);
718
elif function.return_type!.matches(_innate_symbol_lookup.get_void_type()) /\ !function.return_type!.is_type_variable then
719
_logger
720
.error(
721
r.expression!.location,
722
"cannot return value from function of void type"
723
);
724
elif !function.return_type!.is_assignable_from(value.type!) then
725
// Implicit T → TASK[T] widening at return position
726
// (the C# `async` return-rewrap rule applied to ghūl).
727
// Synthesises Tasks.TASK.from_result(orig) around the
728
// original expression and re-resolves the wrapper.
729
// Restricted to return position — variable init /
730
// argument passing slot boundaries don't widen this way.
731
if _task_conversion.try_wrap_value_as_task_return(r, function.return_type!, _visitor) then
732
// wrap succeeded — type now matches
733
elif function.return_type_was_inferred then
734
let lub = Semantic.LEAST_UPPER_BOUND_MAP();
735
lub.add(function.return_type!);
736
lub.add(value.type!);
737
let widened = lub.get_result();
738
739
if widened? then
740
function.set_return_type(widened);
741
else
742
_logger
743
.error(
744
r.expression!.location,
745
"cannot return value of type {value.type} where {function.return_type} expected"
746
);
747
fi
748
else
749
_logger
750
.error(
751
r.expression!.location,
752
"cannot return value of type {value.type} where {function.return_type} expected"
753
);
754
fi
755
fi
756
757
_visitor.check_non_optional(function.return_type, r.expression, r.expression!.location);
758
759
_pure_slots.check_store(r.expression!.location, function.return_type, r.expression!.value);
760
761
return;
762
fi
763
else
764
// Void async: bare `return;` sugar — synthesise
765
// `return Tasks.TASK.completed_task;` and re-walk.
766
if function.is_void_async then
767
r.expression = Semantic.TASK_CONVERSION.build_completed_task_expression(r.location);
768
r.expression!.walk(_visitor);
769
770
return;
771
fi
772
773
if !function.return_type!.matches(_innate_symbol_lookup.get_void_type()) then
774
_logger
775
.warn(
776
r.location,
777
"return-without-value",
778
"return without value from non void function returns default value of type {function.return_type}"
779
);
780
fi
781
fi
782
si
783
784
// Type-check `return X` against the element type T (not
785
// Task[T]); bare `return;` is left for generate-il to map to
786
// `leave success_label`.
787
_visit_return_state_machine_async(
788
r: Trees.Statements.RETURN,
789
function: Semantic.Symbols.Function,
790
async_sm: Semantic.Symbols.ASYNC_STATE_MACHINE
791
) is
792
let frame = async_sm.frame;
793
794
assert frame? else "async state machine has no frame at return";
795
796
if r.expression? then
797
let expression_value = r.expression.value;
798
799
if
800
!expression_value? \/
801
!expression_value.type? \/
802
!expression_value.check_is_consumable(_logger, r.expression!.location)
803
then
804
return;
805
fi
806
807
if frame.is_void then
808
_logger.error(
809
r.expression!.location,
810
"cannot return value from function of void type"
811
);
812
return;
813
fi
814
815
// Async-closure inference: lambdas with
816
// INFERRED_RETURN_TYPE + wrap_inferred_return_as_task
817
// pin from the body's value-returning statement here.
818
// The SM frame's result_type follows via
819
// ensure_result_field.
820
if
821
function.return_type? /\
822
function.return_type.is_inferred /\
823
function.wrap_inferred_return_as_task /\
824
expression_value.type? /\
825
expression_value.type!.is_settled /\
826
!_task_conversion.is_task_type(expression_value.type!)
827
then
828
let task_type =
829
_innate_symbol_lookup.get_task_type(expression_value.type!);
830
if task_type? then
831
function.set_return_type(task_type);
832
fi
833
fi
834
835
let element_type = frame.result_type;
836
837
if !element_type? then
838
return;
839
fi
840
841
if !element_type.is_assignable_from(expression_value.type!) then
842
_logger.error(
843
r.expression!.location,
844
"cannot return value of type {expression_value.type} where {element_type} expected"
845
);
846
return;
847
fi
848
849
_visitor.check_non_optional(element_type, r.expression, r.expression!.location);
850
851
_pure_slots.check_store(r.expression!.location, element_type, r.expression!.value);
852
else
853
// Bare `return;`. For value-async this is an
854
// error — the result slot is non-void. For
855
// void-async it's the usual no-op.
856
if !frame.is_void then
857
_logger.warn(
858
r.location,
859
"return-without-value",
860
"return without value from value-async function returns default value of type {frame.result_type}"
861
);
862
fi
863
fi
864
si
865
866
si
867
si