Skip to content
← Back

src/syntax/process/compile_conditionals.ghul

1
namespace Syntax.Process is
2
use Logging;
3
4
use Semantic.Types.Type;
5
use Semantic.LEAST_UPPER_BOUND_MAP;
6
7
use IR.Values;
8
9
use Ghul.Pipes;
10
11
// Compiles `if` statements / expressions and their flow-sensitive
12
// narrowing. Split out of COMPILE_EXPRESSIONS, which delegates the
13
// matching pre / visit methods here. The `super.pre` / `super.visit`
14
// base-visitor calls stay in the visitor's thin stubs; the methods
15
// here are the enclosed logic, free of the visitor hierarchy.
16
//
17
// Each IF opens an IF_FLOW_FRAME on the shared flow stack;
18
// pre_if_branch does a controlled walk of each branch (condition
19
// under the running-else environment, body under the then-
20
// environment) and records the branch's exit environment;
21
// visit_if joins them into the after-IF environment.
22
class COMPILE_CONDITIONALS is
23
_logger: Logger;
24
_innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup;
25
_flow: NARROWING_FLOW;
26
_condition_analyzer: CONDITION_ANALYZER;
27
_if_flow_stack: Collections.LIST[IF_FLOW_FRAME];
28
_visitor: COMPILE_EXPRESSIONS;
29
_build_flags: Compiler.GLOBAL_BUILD_FLAGS;
30
_variable_left_state: VARIABLE_LEFT_STATE_STORE;
31
_pattern_checker: PATTERN_CHECKER;
32
_case_scrutinee_stack: Collections.LIST[Type?];
33
_match_propagator: Semantic.MATCH_PROPAGATOR;
34
_case_exhaustiveness_checker: CASE_EXHAUSTIVENESS_CHECKER;
35
_arm_null_join: ARM_NULL_JOIN;
36
37
init(
38
logger: Logger,
39
innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup,
40
flow: NARROWING_FLOW,
41
condition_analyzer: CONDITION_ANALYZER,
42
if_flow_stack: Collections.LIST[IF_FLOW_FRAME],
43
visitor: COMPILE_EXPRESSIONS,
44
build_flags: Compiler.GLOBAL_BUILD_FLAGS,
45
variable_left_state: VARIABLE_LEFT_STATE_STORE
46
) is
47
super.init();
48
49
_logger = logger;
50
_innate_symbol_lookup = innate_symbol_lookup;
51
_flow = flow;
52
_condition_analyzer = condition_analyzer;
53
_if_flow_stack = if_flow_stack;
54
_visitor = visitor;
55
_build_flags = build_flags;
56
_variable_left_state = variable_left_state;
57
_pattern_checker = PATTERN_CHECKER(logger, build_flags, visitor, flow);
58
_case_scrutinee_stack = Collections.LIST[Type?]();
59
_match_propagator = Semantic.MATCH_PROPAGATOR(logger);
60
_case_exhaustiveness_checker = CASE_EXHAUSTIVENESS_CHECKER(logger, innate_symbol_lookup);
61
_arm_null_join = ARM_NULL_JOIN();
62
si
63
64
pre_if_branch(`if: Trees.Statements.IF_BRANCH) -> bool is
65
// Controlled walk: walk the condition, derive its
66
// then/else narrowing environments, walk the body under
67
// the then-environment, and record the body's exit
68
// environment for the join in visit(IF). The condition
69
// itself walks under the running-else environment so
70
// within-condition narrowing (`isa T(x) /\ x.foo`)
71
// applies as it compiles.
72
let frame = _if_flow_stack[_if_flow_stack.count - 1];
73
74
if `if.condition? /\ !`if.binding? then
75
let condition = `if.condition;
76
let epoch = _flow.heap_epoch;
77
78
_flow.set_env(frame.running_else);
79
condition.walk(_visitor);
80
81
let facts = _condition_analyzer.analyze_condition(condition, frame.running_else);
82
83
// The branch environments derive from the snapshot
84
// taken before the condition walked. A call or store
85
// inside the condition (`x? /\ mutate()`) kills heap
86
// facts the snapshot still carries — and may run
87
// after the presence check it shares an edge with —
88
// so neither carried-in nor condition-derived heap
89
// facts can be kept on either edge.
90
if _flow.heap_killed_since(epoch) then
91
facts.then_env.drop_heap_facts();
92
facts.else_env.drop_heap_facts();
93
fi
94
95
_flow.set_env(facts.then_env);
96
97
`if.body.walk(_visitor);
98
99
frame.branch_exits.add(_flow.current_env.copy());
100
frame.running_else = facts.else_env;
101
elif `if.binding? then
102
// `if let` branch. Semantically equivalent to
103
// if isa V(scrutinee) /\ <bind pattern from scrutinee>
104
// when the binding has `: V`, or just a `?` presence
105
// test plus a bind from the unwrapped value for the
106
// bare form. Both shapes are handled inline off the
107
// REFUTABLE_BINDING node so that the scrutinee remains
108
// visible to flow narrowing and to the destructure's
109
// DESTRUCTURE_CONSTRAINT path — that visibility is
110
// what gives recursive-lambda inference parity with
111
// the hand-written `isa V(t) ... let p = t` shape.
112
_flow.set_env(frame.running_else);
113
114
// Snapshot the scrutinee's pre-narrow receiver type
115
// for the else-arm complement computation. The
116
// then-arm narrowing inside `check_refutable_binding`
117
// mutates the symbol's `.type` to the cast target;
118
// computing the complement against that would see a
119
// variant, not the union it belongs to.
120
// Only the first clause is a candidate for complement
121
// narrowing on the else edge — see
122
// `apply_refutable_binding_else_narrow` for the rule.
123
let binding = `if.binding!;
124
let else_receiver =
125
if binding.clauses.count > 0 then
126
_resolve_clause_receiver_type(binding.clauses[0]);
127
else
128
null;
129
fi;
130
131
let epoch = _flow.heap_epoch;
132
133
check_refutable_binding(binding);
134
135
// The else edge derives from the pre-walk snapshot,
136
// but the scrutinee (and any clause guard) has run on
137
// that edge too — a kill during its walk invalidates
138
// the snapshot's heap facts. Captured before the body
139
// walks: the body does not run on the else edge.
140
let scrutinee_killed = _flow.heap_killed_since(epoch);
141
142
`if.body.walk(_visitor);
143
144
frame.branch_exits.add(_flow.current_env.copy());
145
146
frame.running_else =
147
_condition_analyzer.apply_refutable_binding_else_narrow(
148
binding,
149
else_receiver,
150
frame.running_else
151
);
152
153
if scrutinee_killed then
154
frame.running_else.drop_heap_facts();
155
fi
156
else
157
_flow.set_env(frame.running_else);
158
159
`if.body.walk(_visitor);
160
161
frame.branch_exits.add(_flow.current_env.copy());
162
frame.has_else = true;
163
fi
164
165
return true;
166
si
167
168
// Resolve a clause's scrutinee variable's declared (pre-narrow)
169
// type for the else-arm complement computation. Returns null
170
// when the scrutinee isn't a simple-identifier local —
171
// complement narrowing has nothing to attach to in that case.
172
_resolve_clause_receiver_type(c: Trees.Statements.REFUTABLE_BINDING_CLAUSE) -> Type? is
173
let target = _visitor.try_get_narrowing_target(c.scrutinee);
174
175
if !target? then
176
return null;
177
fi
178
179
let declared = _flow.declared_type_of(target);
180
181
if !declared? then
182
return null;
183
fi
184
185
if isa Semantic.Types.INFERRED_VARIABLE_TYPE(declared) then
186
let placeholder = cast Semantic.Types.INFERRED_VARIABLE_TYPE(declared);
187
let resolved = placeholder.origin.try_get_inferred_type();
188
189
if resolved? /\ !resolved.is_sentinel then
190
return resolved;
191
fi
192
193
return null;
194
fi
195
196
return declared;
197
si
198
199
// Process a REFUTABLE_BINDING (the AST shape of an `if let`
200
// arm). For each clause, mirrors what `isa V(scrutinee) ...
201
// let p = scrutinee` would do — walk the narrow target,
202
// narrow the scrutinee on the then-edge, walk the scrutinee
203
// under the narrowed env, hand its value down to the pattern
204
// as the destructure source, walk the pattern, then the
205
// optional per-clause guard. Each clause inherits the env
206
// produced by the previous clauses, so later scrutinees see
207
// earlier bindings narrowed and in scope.
208
//
209
// The narrowing reuses the same `_apply_one` machinery the
210
// isa form uses, so the destructure walks against the same
211
// shape (INFERRED in iter 1 → DESTRUCTURE_CONSTRAINT path on
212
// the placeholder origin; concrete in later iters → normal
213
// destructure of the narrowed type) — which is what gives
214
// `if let` inference parity with `isa`.
215
check_refutable_binding(rb: Trees.Statements.REFUTABLE_BINDING) is
216
217
for c in rb.clauses do
218
_check_refutable_binding_clause(c);
219
od
220
si
221
222
_check_refutable_binding_clause(c: Trees.Statements.REFUTABLE_BINDING_CLAUSE) is
223
// 1. Resolve the narrow type (when ascribed).
224
let narrow_type: Type? mut = null;
225
226
if let c.narrow_type_expression? then
227
narrow_type_expression.walk(_visitor);
228
narrow_type = narrow_type_expression.type;
229
fi
230
231
// 2. Apply isa-style narrowing on the scrutinee BEFORE
232
// walking it. The narrowing mutates the scrutinee's
233
// symbol type via `_apply_one`; the subsequent load
234
// picks up the narrowed type, which propagates into
235
// the destructure source.
236
if narrow_type? then
237
let then_env =
238
_condition_analyzer.apply_refutable_binding_then_narrow(
239
c.scrutinee,
240
narrow_type,
241
_flow.current_env
242
);
243
244
_flow.set_env(then_env);
245
fi
246
247
// 3. Walk the scrutinee under the (possibly narrowed)
248
// env — produces `c.scrutinee.value`.
249
c.scrutinee.walk(_visitor);
250
251
// Peel a flow-narrowing projection back to its wrapper —
252
// the presence test and destructure path want the
253
// optional shape, not the already-projected `T`.
254
c.scrutinee.compile_expressions_state.value = IR.Values.NARROW_PROJECT.peel(c.scrutinee.value);
255
256
// 4. Hand the scrutinee value down to the pattern as the
257
// destructure source. For an optional source the
258
// presence test in generate_il unwraps the value
259
// before the pattern destructures it.
260
let pattern = c.pattern;
261
262
pattern.mark_refutable_recursive();
263
264
// For a simple-name pattern, pin the binding's static
265
// type to the narrow target — specialised against the
266
// scrutinee's receiver type so a bare variant target
267
// (`Maybe.YES`) becomes its closed-generic form
268
// (`Maybe.YES[int]`) rather than the open generic
269
// (which IL gen would emit as an unloadable class
270
// reference). Falls back to the unspecialised written
271
// form when no receiver is available.
272
//
273
// Destructure patterns leave `explicit_type` unset so
274
// pre(DESTRUCTURING_VARIABLE_LEFT)'s constraint path
275
// can fire when the scrutinee's type is still inferred —
276
// that's what gives recursive lambdas inference parity
277
// with `isa V(x) ... let (a, b) = x`.
278
if narrow_type? /\ pattern.is_simple_name then
279
let specialized_narrow: Type mut = narrow_type;
280
281
if let receiver_type = c.scrutinee.value?.type then
282
let specialized =
283
_condition_analyzer.specialize_variant_for_receiver(
284
receiver_type,
285
narrow_type
286
);
287
288
if specialized? then
289
specialized_narrow = specialized;
290
fi
291
fi
292
293
_variable_left_state.get_or_add(pattern).explicit_type = specialized_narrow;
294
fi
295
296
let source_value: IR.Values.Value mut =
297
if c.scrutinee.value? then
298
c.scrutinee.value;
299
else
300
IR.Values.DUMMY(Semantic.Types.ERROR(), c.scrutinee.location);
301
fi;
302
303
let pattern_state = _variable_left_state.get_or_add(pattern);
304
305
pattern_state.right_value = source_value;
306
pattern_state.variable_location = c.location;
307
pattern_state.right_location = c.scrutinee.location;
308
309
// 5. Walk the pattern. Destructure operates on
310
// `source_value.type` (the scrutinee's narrowed type).
311
pattern.walk(_visitor);
312
313
// 6. Diagnostics: redundancy / impossibility warnings
314
// (`narrowing-always-succeeds`, value-type-narrow
315
// error, etc.). Use the scrutinee variable's
316
// DECLARED type (pre-narrow) as the source — passing
317
// the already-narrowed `scrutinee.value.type` here
318
// would make every narrow look like a no-op and
319
// spuriously fire the redundancy warning.
320
let source_type: Type? mut = null;
321
let target_variable = _visitor.try_get_narrowing_target(c.scrutinee);
322
323
if target_variable? then
324
source_type = _flow.declared_type_of(target_variable);
325
elif let c.scrutinee.value? then
326
source_type = value.type;
327
fi
328
329
_pattern_checker.check_pattern(
330
pattern,
331
source_type,
332
narrow_type,
333
c.location,
334
true
335
);
336
337
// 7. Per-clause guard — walked after the clause's names
338
// are in scope, under the then-arm env. Narrowing
339
// inside the guard applies to the rest of the chain
340
// and the then-arm.
341
if c.guard? then
342
let guard = c.guard;
343
let epoch = _flow.heap_epoch;
344
345
guard.walk(_visitor);
346
347
let facts = _condition_analyzer.analyze_condition(guard, _flow.current_env);
348
349
// Guard-derived heap facts are stale when the guard's
350
// own walk killed (`_f? /\ mutate()`) — the kill may
351
// run after the check it shares the edge with.
352
if _flow.heap_killed_since(epoch) then
353
facts.then_env.drop_heap_facts();
354
fi
355
356
_flow.set_env(facts.then_env);
357
fi
358
si
359
360
visit_if_branch(`if: Trees.Statements.IF_BRANCH) is
361
362
if let `if.condition? /\ Value.check_is_consumable(_logger, condition.location, condition.value) then
363
// check_is_consumable is true only for a present value
364
if !condition.value!.type!.matches(_innate_symbol_lookup.get_bool_type()) then
365
_logger.error(condition.location, "if condition must be bool");
366
fi
367
fi
368
si
369
370
pre_if(`if: Trees.Statements.IF) -> bool is
371
if `if.want_value then
372
for i in `if.branches do
373
i.body.compile_expressions_state.want_value = true;
374
od
375
fi
376
377
// Open a flow frame for this IF. Each branch's controlled
378
// walk (pre(IF_BRANCH)) records its exit environment on
379
// the frame; visit(IF) joins them.
380
_if_flow_stack.add(IF_FLOW_FRAME(_flow.current_env.copy()));
381
382
return false;
383
si
384
385
visit_if(`if: Trees.Statements.IF) is
386
// Merge the branch exit environments into the environment
387
// in force after the IF. A branch whose body diverges
388
// contributes the bottom environment; with no else
389
// branch, the no-branch-taken fall-through contributes
390
// the trailing running-else environment.
391
let if_flow = _if_flow_stack[_if_flow_stack.count - 1];
392
_if_flow_stack.remove_at(_if_flow_stack.count - 1);
393
394
let after mut = NARROW_ENV.bottom();
395
396
for branch_exit in if_flow.branch_exits do
397
after = NARROW_ENV.join(after, branch_exit);
398
od
399
400
if !if_flow.has_else then
401
after = NARROW_ENV.join(after, if_flow.running_else);
402
fi
403
404
_flow.set_env(after);
405
406
for i in `if.branches do
407
if let i.condition? then
408
IR.Values.Value.check_is_consumable(_logger, condition.location, condition.value);
409
fi
410
od
411
412
if !`if.want_value then
413
return;
414
fi
415
416
if `if.is_poisoned then
417
// if the if is syntactically incomplete don't bother reporting any
418
// semantic errors:
419
`if.compile_expressions_state.value = DUMMY_BLOCK(Semantic.Types.ERROR(), `if.location, "if is poisoned");
420
421
return;
422
fi
423
424
let expected_type = `if.expected_type;
425
426
if
427
!`if.branches |> any(b => !b.condition? /\ !b.binding?) /\
428
(!expected_type? \/ !expected_type.is_void)
429
then
430
_logger.error(`if.location, "expected else in if expression");
431
`if.compile_expressions_state.value = DUMMY_BLOCK(Semantic.Types.ERROR(), `if.location, "no else");
432
return;
433
fi
434
435
let type mut = expected_type;
436
437
let seen_any_values mut = false;
438
let seen_non_null_values mut = false;
439
let seen_null_values mut = false;
440
// A genuine null literal, as opposed to an unsettled inferred
441
// sentinel or an error type (both of which also answer is_null).
442
// Only a genuine null arm justifies widening a value-type LUB
443
// to its optional carrier; a sentinel arm during inference
444
// must leave the LUB untouched so it can still converge.
445
let seen_genuine_null_values mut = false;
446
let all_tuple_literals mut = true;
447
448
let lub = LEAST_UPPER_BOUND_MAP();
449
450
for i in `if.branches do
451
let branch = i.body;
452
453
let value = branch.value;
454
455
if !value? then
456
continue;
457
fi
458
459
if !value.type? then
460
continue;
461
fi
462
463
if expected_type? then
464
if !value.check_is_consumable_allow_void(_logger, branch.location) then
465
continue;
466
fi
467
else
468
if !value.check_is_consumable(_logger, branch.location) then
469
continue;
470
fi
471
fi
472
473
debug_indent();
474
475
if expected_type? then
476
if !expected_type.is_void /\ !expected_type.is_assignable_from(value.type!) then
477
// set_expected_type assigns both fields together — caller pairs them
478
_logger.error(branch.location, string.format(`if.expected_type_error_message!, value.type, type));
479
fi
480
481
// Assignability alone does not propagate the branch
482
// type into phantom slots of the pushed-down
483
// constraint; without this match propagation an outer
484
// generic call's type-arg slot stays unresolved and
485
// is reported as cannot infer type here even though
486
// the branch supplied the concrete type.
487
_match_propagator.propagate_match(expected_type, value.type!);
488
elif value.type!.is_null then
489
// could also be error
490
seen_null_values = true;
491
492
if _arm_null_join.is_genuine_null(value.type!) then
493
seen_genuine_null_values = true;
494
fi
495
else
496
seen_non_null_values = true;
497
498
if !branch.is_tuple_literal then
499
all_tuple_literals = false;
500
fi
501
502
lub.add(value.type!);
503
fi
504
505
seen_any_values = true;
506
od
507
508
if !expected_type? then
509
type = lub.get_result();
510
511
if all_tuple_literals /\ seen_non_null_values /\ (!type? \/ !type.is_value_tuple) then
512
let tuple_types = Collections.LIST[Type]();
513
514
for i in `if.branches do
515
let branch = i.body;
516
517
if let branch.value? /\ value.type? then
518
tuple_types.add(value.type!);
519
fi
520
od
521
522
type = Semantic.TUPLE_ELEMENT_LUB(_innate_symbol_lookup).combine(tuple_types, lub.element_names);
523
fi
524
525
let decision = _arm_null_join.decide(type, seen_genuine_null_values, seen_null_values);
526
527
if decision == ArmNullJoinDecision.WIDEN_VALUE_OPTIONAL then
528
type = _innate_symbol_lookup.get_optional_type(type!);
529
elif decision == ArmNullJoinDecision.WIDEN_REFERENCE_OPTIONAL then
530
type = type!.as_optional();
531
elif decision == ArmNullJoinDecision.INCOMPATIBLE then
532
// A value-type or reference-type LUB against a non-
533
// genuine null arm: an unsettled inferred sentinel
534
// (resolves later, so the error is discarded on
535
// speculative passes) or a permanently error-typed
536
// branch (which surfaces here).
537
for i in `if.branches do
538
let branch = i.body;
539
540
let value = branch.value;
541
542
if !value? \/ !value.type? then
543
continue;
544
fi
545
546
if value.type!.is_null then
547
_logger.error(branch.location, "incompatible types in if branches: {value.type} and {type}");
548
fi
549
od
550
fi
551
fi
552
553
if !type? then
554
if seen_any_values then
555
if seen_non_null_values then
556
_logger.error(`if.location, "no type inferred for if expression");
557
else
558
_logger.error(`if.location, "all branch values are null");
559
fi
560
fi
561
562
`if.compile_expressions_state.value = DUMMY_BLOCK(Semantic.Types.ERROR(), `if.location, "no type inferred");
563
return;
564
else
565
`if.compile_expressions_state.value = IR.Values.BLOCK(type);
566
fi
567
si
568
569
pre_case(`case: Trees.Statements.CASE) -> bool is
570
if `case.want_value then
571
for m in `case.matches do
572
m.statements.compile_expressions_state.want_value = true;
573
od
574
fi
575
576
// Controlled walk: walk the scrutinee first so its value
577
// type is available to each match's `pre_case_match` for
578
// binding-pattern checking, then walk every match. The
579
// scrutinee's type is held on a stack so nested `case`
580
// statements still find their own scrutinee at the top.
581
`case.expression.walk(_visitor);
582
583
let scrutinee_type = `case.expression.value?.type;
584
585
_case_scrutinee_stack.add(scrutinee_type);
586
587
for m in `case.matches do
588
m.walk(_visitor);
589
od
590
591
_case_scrutinee_stack.remove_at(_case_scrutinee_stack.count - 1);
592
593
return true;
594
si
595
596
pre_case_match(arm: Trees.Statements.CASE_MATCH) -> bool is
597
let pattern = arm.pattern;
598
599
if !pattern? then
600
return false;
601
fi
602
603
let scrutinee_type: Type? mut = null;
604
if _case_scrutinee_stack.count > 0 then
605
scrutinee_type = _case_scrutinee_stack[_case_scrutinee_stack.count - 1];
606
fi
607
608
// Controlled walk of the pattern. The standard
609
// `pre(VARIABLE)` flow expects an `=` initializer to thread
610
// through into `left.right_value`; case arms don't have
611
// one, so we synthesise the pieces by hand: resolve the
612
// type ascription, push the explicit type + refutability
613
// down to the left, build a stand-in right_value of the
614
// scrutinee type (wrapped in a CAST for the ascribed form
615
// so the bound symbols are typed at the narrowed type, not
616
// the scrutinee's), then walk the left to fire the
617
// per-shape symbol-typing in `visit(SIMPLE_VARIABLE_LEFT)`
618
// / `pre(DESTRUCTURING_VARIABLE_LEFT)`.
619
pattern.type_expression.walk(_visitor);
620
621
let left_state = _variable_left_state.get_or_add(pattern.left);
622
623
let target_type: Type? mut = null;
624
if pattern.is_explicit_type /\ pattern.type_expression.type? then
625
// A variant names the union's generic parameters, so
626
// an arm written `when v: OK` over a scrutinee of
627
// `Result[double, string]` resolves to the
628
// unconstructed `OK`. Bind the union's arguments onto
629
// it, so the arm tests and binds at
630
// `OK[double, string]`. Written back onto the type
631
// expression because the emitted isinst, the arm's
632
// locals and the field-access owner spec all read it,
633
// and an unconstructed variant names no loadable type.
634
target_type =
635
_condition_analyzer.specialize_variant_for_receiver(
636
scrutinee_type,
637
pattern.type_expression.type
638
);
639
640
pattern.type_expression.type = target_type;
641
left_state.explicit_type = target_type!;
642
fi
643
644
pattern.left.mark_refutable_recursive();
645
646
if scrutinee_type? then
647
let synthesized: IR.Values.Value mut =
648
IR.Values.DUMMY(scrutinee_type, arm.location);
649
650
if target_type? then
651
synthesized = IR.Values.CAST(target_type, synthesized);
652
fi
653
654
left_state.right_value = synthesized;
655
left_state.right_location = arm.location;
656
left_state.variable_location = arm.location;
657
fi
658
659
pattern.left.walk(_visitor);
660
661
// `case`-when patterns do not require implicit refutability
662
// on the bare form — a destructure of a non-nullable tuple
663
// is a valid arm that simply always matches, not an error
664
// the way `if let v = some_int` is.
665
_pattern_checker.check_pattern(
666
pattern.left,
667
scrutinee_type,
668
target_type,
669
arm.location,
670
false
671
);
672
673
// Per-arm guard — walked after the pattern's names are in
674
// scope, so identifiers the pattern bound resolve inside it.
675
if arm.guard? then
676
arm.guard.walk(_visitor);
677
fi
678
679
arm.statements.walk(_visitor);
680
681
return true;
682
si
683
684
visit_case_match(arm: Trees.Statements.CASE_MATCH) is
685
si
686
687
visit_case(`case: Trees.Statements.CASE) is
688
if !`case.is_poisoned then
689
_case_exhaustiveness_checker.check(`case);
690
fi
691
692
if !`case.want_value then
693
return;
694
fi
695
696
if `case.is_poisoned then
697
`case.compile_expressions_state.value = DUMMY_BLOCK(Semantic.Types.ERROR(), `case.location, "case is poisoned");
698
return;
699
fi
700
701
let has_else mut = false;
702
for m in `case.matches do
703
if !m.expressions? then
704
has_else = true;
705
fi
706
od
707
708
// Whether the case is guaranteed to produce a value when
709
// none of the arms match:
710
// - has_else → else arm runs
711
// - is_exhaustive → checker proved one arm matches
712
// - requires_default_fallthrough → IL pushes default(expected_type)
713
// Otherwise no value can be produced. CASE_EXHAUSTIVENESS_CHECKER
714
// has already emitted the relevant diagnostic
715
// (`non-exhaustive-case` for a closed domain with gaps,
716
// `case-needs-else` for an open domain with no else).
717
let expected_type = `case.expected_type;
718
719
if
720
!has_else /\
721
!`case.is_exhaustive /\
722
!`case.requires_default_fallthrough /\
723
(!expected_type? \/ !expected_type.is_void)
724
then
725
`case.compile_expressions_state.value = DUMMY_BLOCK(Semantic.Types.ERROR(), `case.location, "no else");
726
return;
727
fi
728
729
let type mut = expected_type;
730
731
let seen_any_values mut = false;
732
let seen_non_null_values mut = false;
733
let seen_null_values mut = false;
734
// See the IF path: only a genuine settled null arm justifies
735
// widening a value-type LUB, not an unsettled inferred sentinel.
736
let seen_genuine_null_values mut = false;
737
738
let lub = LEAST_UPPER_BOUND_MAP();
739
740
for m in `case.matches do
741
let body = m.statements;
742
743
let value = body.value;
744
745
if !value? then
746
continue;
747
fi
748
749
if !value.type? then
750
continue;
751
fi
752
753
if expected_type? then
754
if !value.check_is_consumable_allow_void(_logger, body.location) then
755
continue;
756
fi
757
else
758
if !value.check_is_consumable(_logger, body.location) then
759
continue;
760
fi
761
fi
762
763
if expected_type? then
764
if !expected_type.is_void /\ !expected_type.is_assignable_from(value.type!) then
765
// set_expected_type assigns both fields together — caller pairs them
766
_logger.error(body.location, string.format(`case.expected_type_error_message!, value.type, type));
767
fi
768
elif value.type!.is_null then
769
seen_null_values = true;
770
771
if _arm_null_join.is_genuine_null(value.type!) then
772
seen_genuine_null_values = true;
773
fi
774
else
775
seen_non_null_values = true;
776
777
lub.add(value.type!);
778
fi
779
780
seen_any_values = true;
781
od
782
783
if !expected_type? then
784
type = lub.get_result();
785
786
let decision = _arm_null_join.decide(type, seen_genuine_null_values, seen_null_values);
787
788
if decision == ArmNullJoinDecision.WIDEN_VALUE_OPTIONAL then
789
type = _innate_symbol_lookup.get_optional_type(type!);
790
elif decision == ArmNullJoinDecision.WIDEN_REFERENCE_OPTIONAL then
791
type = type!.as_optional();
792
elif decision == ArmNullJoinDecision.INCOMPATIBLE then
793
// A value-type or reference-type LUB against a non-
794
// genuine null arm — see the IF path.
795
for m in `case.matches do
796
let body = m.statements;
797
798
let value = body.value;
799
800
if !value? \/ !value.type? then
801
continue;
802
fi
803
804
if value.type!.is_null then
805
_logger.error(body.location, "incompatible types in case arms: {value.type} and {type}");
806
fi
807
od
808
fi
809
fi
810
811
if !type? then
812
if seen_any_values then
813
if seen_non_null_values then
814
_logger.error(`case.location, "no type inferred for case expression");
815
else
816
_logger.error(`case.location, "all arm values are null");
817
fi
818
fi
819
820
`case.compile_expressions_state.value = DUMMY_BLOCK(Semantic.Types.ERROR(), `case.location, "no type inferred");
821
return;
822
else
823
`case.compile_expressions_state.value = IR.Values.BLOCK(type);
824
fi
825
si
826
si
827
si