Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use Logging | |
| 3 | ||
| 4 | use Semantic.Types.Type | |
| 5 | use Semantic.LEAST_UPPER_BOUND_MAP | |
| 6 | use Semantic.ARM_NULL_JOIN | |
| 7 | use Semantic.ArmNullJoinDecision | |
| 8 | ||
| 9 | use IR.Values | |
| 10 | ||
| 11 | use Ghul.Pipes | |
| 12 | ||
| 13 | // Compiles `if` statements / expressions and their flow-sensitive | |
| 14 | // narrowing. Split out of COMPILE_EXPRESSIONS, which delegates the | |
| 15 | // matching pre / visit methods here. The `super.pre` / `super.visit` | |
| 16 | // base-visitor calls stay in the visitor's thin stubs; the methods | |
| 17 | // here are the enclosed logic, free of the visitor hierarchy. | |
| 18 | // | |
| 19 | // Each IF opens an IF_FLOW_FRAME on the shared flow stack; | |
| 20 | // pre_if_branch does a controlled walk of each branch (condition | |
| 21 | // under the running-else environment, body under the then- | |
| 22 | // environment) and records the branch's exit environment; | |
| 23 | // visit_if joins them into the after-IF environment. | |
| 24 | class COMPILE_CONDITIONALS is | |
| 25 | _logger: Logger | |
| 26 | _innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup | |
| 27 | _flow: NARROWING_FLOW | |
| 28 | _condition_analyzer: CONDITION_ANALYZER | |
| 29 | _if_flow_stack: Collections.LIST[IF_FLOW_FRAME] | |
| 30 | _visitor: COMPILE_EXPRESSIONS | |
| 31 | _build_flags: Compiler.GLOBAL_BUILD_FLAGS | |
| 32 | _variable_left_state: VARIABLE_LEFT_STATE_STORE | |
| 33 | _pattern_checker: PATTERN_CHECKER | |
| 34 | _case_scrutinee_stack: Collections.LIST[Type?] | |
| 35 | // Parallel to _case_scrutinee_stack: the once-spilled scrutinee | |
| 36 | // load Value for the innermost case, so each `when` label's | |
| 37 | // value-equality test references the single evaluation. | |
| 38 | _case_scrutinee_load_stack: Collections.LIST[IR.Values.Value?] | |
| 39 | // Parallel again: the scrutinee expression node itself, so a | |
| 40 | // pattern arm can narrow the scrutinee variable in its body the | |
| 41 | // way `if let` narrows the tested expression. | |
| 42 | _case_scrutinee_expression_stack: Collections.LIST[Trees.Expressions.Expression?] | |
| 43 | // Per-case flow bookkeeping, mirroring `_if_flow_stack`: each | |
| 44 | // arm records its exit environment here and visit_case joins them. | |
| 45 | _case_flow_stack: Collections.LIST[CASE_FLOW_FRAME] | |
| 46 | _match_propagator: Semantic.MATCH_PROPAGATOR | |
| 47 | _case_exhaustiveness_checker: CASE_EXHAUSTIVENESS_CHECKER | |
| 48 | _arm_null_join: ARM_NULL_JOIN | |
| 49 | ||
| 50 | init( | |
| 51 | logger: Logger, | |
| 52 | innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup, | |
| 53 | flow: NARROWING_FLOW, | |
| 54 | condition_analyzer: CONDITION_ANALYZER, | |
| 55 | if_flow_stack: Collections.LIST[IF_FLOW_FRAME], | |
| 56 | visitor: COMPILE_EXPRESSIONS, | |
| 57 | build_flags: Compiler.GLOBAL_BUILD_FLAGS, | |
| 58 | variable_left_state: VARIABLE_LEFT_STATE_STORE | |
| 59 | ) is | |
| 60 | super.init() | |
| 61 | ||
| 62 | _logger = logger | |
| 63 | _innate_symbol_lookup = innate_symbol_lookup | |
| 64 | _flow = flow | |
| 65 | _condition_analyzer = condition_analyzer | |
| 66 | _if_flow_stack = if_flow_stack | |
| 67 | _visitor = visitor | |
| 68 | _build_flags = build_flags | |
| 69 | _variable_left_state = variable_left_state | |
| 70 | _pattern_checker = PATTERN_CHECKER(logger, build_flags, visitor, flow) | |
| 71 | _case_scrutinee_stack = Collections.LIST[Type?]() | |
| 72 | _case_scrutinee_load_stack = Collections.LIST[IR.Values.Value?]() | |
| 73 | _case_scrutinee_expression_stack = Collections.LIST[Trees.Expressions.Expression?]() | |
| 74 | _case_flow_stack = Collections.LIST[CASE_FLOW_FRAME]() | |
| 75 | _match_propagator = Semantic.MATCH_PROPAGATOR(logger) | |
| 76 | _case_exhaustiveness_checker = CASE_EXHAUSTIVENESS_CHECKER(logger, innate_symbol_lookup) | |
| 77 | _arm_null_join = ARM_NULL_JOIN() | |
| 78 | si | |
| 79 | ||
| 80 | pre_if_branch(`if: Trees.Statements.IF_BRANCH) -> bool is | |
| 81 | // Controlled walk: walk the condition, derive its | |
| 82 | // then/else narrowing environments, walk the body under | |
| 83 | // the then-environment, and record the body's exit | |
| 84 | // environment for the join in visit(IF). The condition | |
| 85 | // itself walks under the running-else environment so | |
| 86 | // within-condition narrowing (`isa T(x) /\ x.foo`) | |
| 87 | // applies as it compiles. | |
| 88 | let frame = _if_flow_stack[_if_flow_stack.count - 1] | |
| 89 | ||
| 90 | if `if.condition? /\ !`if.binding? then | |
| 91 | let condition = `if.condition | |
| 92 | let epoch = _flow.heap_epoch | |
| 93 | let mark = _flow.crossing_mark | |
| 94 | ||
| 95 | _flow.set_env(frame.running_else) | |
| 96 | condition.walk(_visitor) | |
| 97 | ||
| 98 | let facts = _condition_analyzer.analyze_condition(condition, frame.running_else) | |
| 99 | ||
| 100 | // The branch environments derive from the snapshot | |
| 101 | // taken before the condition walked. A direct store | |
| 102 | // inside the condition kills heap facts the snapshot | |
| 103 | // still carries — and may run after the presence | |
| 104 | // check it shares an edge with — so neither | |
| 105 | // carried-in nor condition-derived heap facts survive | |
| 106 | // it. A call is gentler: the facts keep, carrying the | |
| 107 | // call as a crossing the snapshot never saw, for the | |
| 108 | // reliance judge to answer. | |
| 109 | if _flow.heap_killed_since(epoch) then | |
| 110 | facts.then_env.drop_heap_facts() | |
| 111 | facts.else_env.drop_heap_facts() | |
| 112 | else | |
| 113 | _flow.adopt_crossings_since(facts.then_env, mark) | |
| 114 | _flow.adopt_crossings_since(facts.else_env, mark) | |
| 115 | fi | |
| 116 | ||
| 117 | _flow.set_env(facts.then_env) | |
| 118 | ||
| 119 | `if.body.walk(_visitor) | |
| 120 | ||
| 121 | frame.branch_exits.add(_flow.current_env.copy()) | |
| 122 | frame.running_else = facts.else_env | |
| 123 | elif `if.binding? then | |
| 124 | // `if let` branch. Semantically equivalent to | |
| 125 | // if isa V(scrutinee) /\ <bind pattern from scrutinee> | |
| 126 | // when the binding has `: V`, or just a `?` presence | |
| 127 | // test plus a bind from the unwrapped value for the | |
| 128 | // bare form. Both shapes are handled inline off the | |
| 129 | // REFUTABLE_BINDING node so that the scrutinee remains | |
| 130 | // visible to flow narrowing and to the destructure's | |
| 131 | // DESTRUCTURE_CONSTRAINT path — that visibility is | |
| 132 | // what gives recursive-lambda inference parity with | |
| 133 | // the hand-written `isa V(t) ... let p = t` shape. | |
| 134 | _flow.set_env(frame.running_else) | |
| 135 | ||
| 136 | // Snapshot the scrutinee's pre-narrow receiver type | |
| 137 | // for the else-arm complement computation. The | |
| 138 | // then-arm narrowing inside `check_refutable_binding` | |
| 139 | // mutates the symbol's `.type` to the cast target; | |
| 140 | // computing the complement against that would see a | |
| 141 | // variant, not the union it belongs to. | |
| 142 | // Only the first clause is a candidate for complement | |
| 143 | // narrowing on the else edge — see | |
| 144 | // `apply_refutable_binding_else_narrow` for the rule. | |
| 145 | let binding = `if.binding! | |
| 146 | let else_receiver = | |
| 147 | if binding.clauses.count > 0 then | |
| 148 | _resolve_clause_receiver_type(binding.clauses[0]) | |
| 149 | else | |
| 150 | null | |
| 151 | fi | |
| 152 | ||
| 153 | let epoch = _flow.heap_epoch | |
| 154 | let mark = _flow.crossing_mark | |
| 155 | ||
| 156 | check_refutable_binding(binding) | |
| 157 | ||
| 158 | // The else edge derives from the pre-walk snapshot, | |
| 159 | // but the scrutinee (and any clause guard) has run on | |
| 160 | // that edge too — a store during its walk invalidates | |
| 161 | // the snapshot's heap facts, and its calls are owed | |
| 162 | // as crossings. Both captured before the body walks: | |
| 163 | // the body does not run on the else edge. | |
| 164 | let scrutinee_killed = _flow.heap_killed_since(epoch) | |
| 165 | let scrutinee_mark_end = _flow.crossing_mark | |
| 166 | ||
| 167 | `if.body.walk(_visitor) | |
| 168 | ||
| 169 | frame.branch_exits.add(_flow.current_env.copy()) | |
| 170 | ||
| 171 | frame.running_else = | |
| 172 | _condition_analyzer.apply_refutable_binding_else_narrow( | |
| 173 | binding, | |
| 174 | else_receiver, | |
| 175 | frame.running_else | |
| 176 | ) | |
| 177 | ||
| 178 | if scrutinee_killed then | |
| 179 | frame.running_else.drop_heap_facts() | |
| 180 | else | |
| 181 | _flow.adopt_crossings_between(frame.running_else, mark, scrutinee_mark_end) | |
| 182 | fi | |
| 183 | else | |
| 184 | _flow.set_env(frame.running_else) | |
| 185 | ||
| 186 | `if.body.walk(_visitor) | |
| 187 | ||
| 188 | frame.branch_exits.add(_flow.current_env.copy()) | |
| 189 | frame.has_else = true | |
| 190 | fi | |
| 191 | ||
| 192 | return true | |
| 193 | si | |
| 194 | ||
| 195 | // Resolve a clause's scrutinee variable's declared (pre-narrow) | |
| 196 | // type for the else-arm complement computation. Returns null | |
| 197 | // when the scrutinee isn't a simple-identifier local — | |
| 198 | // complement narrowing has nothing to attach to in that case. | |
| 199 | _resolve_clause_receiver_type(c: Trees.Statements.REFUTABLE_BINDING_CLAUSE) -> Type? is | |
| 200 | let target = _visitor.try_get_narrowing_target(c.scrutinee) | |
| 201 | ||
| 202 | if !target? then | |
| 203 | return null | |
| 204 | fi | |
| 205 | ||
| 206 | let declared = _flow.declared_type_of(target) | |
| 207 | ||
| 208 | if !declared? then | |
| 209 | return null | |
| 210 | fi | |
| 211 | ||
| 212 | if isa Semantic.Types.INFERRED_VARIABLE_TYPE(declared) then | |
| 213 | let placeholder = cast Semantic.Types.INFERRED_VARIABLE_TYPE(declared) | |
| 214 | let resolved = placeholder.origin.try_get_inferred_type() | |
| 215 | ||
| 216 | if resolved? /\ !resolved.is_sentinel then | |
| 217 | return resolved | |
| 218 | fi | |
| 219 | ||
| 220 | return null | |
| 221 | fi | |
| 222 | ||
| 223 | return declared | |
| 224 | si | |
| 225 | ||
| 226 | // Process a REFUTABLE_BINDING (the AST shape of an `if let` | |
| 227 | // arm). For each clause, mirrors what `isa V(scrutinee) ... | |
| 228 | // let p = scrutinee` would do — walk the narrow target, | |
| 229 | // narrow the scrutinee on the then-edge, walk the scrutinee | |
| 230 | // under the narrowed env, hand its value down to the pattern | |
| 231 | // as the destructure source, walk the pattern, then the | |
| 232 | // optional per-clause guard. Each clause inherits the env | |
| 233 | // produced by the previous clauses, so later scrutinees see | |
| 234 | // earlier bindings narrowed and in scope. | |
| 235 | // | |
| 236 | // The narrowing reuses the same `_apply_one` machinery the | |
| 237 | // isa form uses, so the destructure walks against the same | |
| 238 | // shape (INFERRED in iter 1 → DESTRUCTURE_CONSTRAINT path on | |
| 239 | // the placeholder origin; concrete in later iters → normal | |
| 240 | // destructure of the narrowed type) — which is what gives | |
| 241 | // `if let` inference parity with `isa`. | |
| 242 | check_refutable_binding(rb: Trees.Statements.REFUTABLE_BINDING) is | |
| 243 | ||
| 244 | for c in rb.clauses do | |
| 245 | _check_refutable_binding_clause(c) | |
| 246 | od | |
| 247 | si | |
| 248 | ||
| 249 | _check_refutable_binding_clause(c: Trees.Statements.REFUTABLE_BINDING_CLAUSE) is | |
| 250 | // 1. Resolve the narrow type (when ascribed). | |
| 251 | let narrow_type: Type? mut = null | |
| 252 | ||
| 253 | if let c.narrow_type_expression? then | |
| 254 | narrow_type_expression.walk(_visitor) | |
| 255 | narrow_type = narrow_type_expression.type | |
| 256 | fi | |
| 257 | ||
| 258 | // 2. Apply isa-style narrowing on the scrutinee BEFORE | |
| 259 | // walking it. The narrowing mutates the scrutinee's | |
| 260 | // symbol type via `_apply_one`; the subsequent load | |
| 261 | // picks up the narrowed type, which propagates into | |
| 262 | // the destructure source. | |
| 263 | // A test against the optional of a plain value type is | |
| 264 | // the exception: narrowing first would unwrap the source | |
| 265 | // before the test could find it absent. | |
| 266 | let lifted = | |
| 267 | PATTERN_CHECKER.is_lifted_test( | |
| 268 | narrow_type, | |
| 269 | if let target = _visitor.try_get_narrowing_target(c.scrutinee) then _flow.declared_type_of(target) else null fi | |
| 270 | ) | |
| 271 | ||
| 272 | if narrow_type? /\ !lifted then | |
| 273 | let then_env = | |
| 274 | _condition_analyzer.apply_refutable_binding_then_narrow( | |
| 275 | c.scrutinee, | |
| 276 | narrow_type, | |
| 277 | _flow.current_env | |
| 278 | ) | |
| 279 | ||
| 280 | _flow.set_env(then_env) | |
| 281 | fi | |
| 282 | ||
| 283 | // 3. Walk the scrutinee under the (possibly narrowed) | |
| 284 | // env — produces `c.scrutinee.value`. | |
| 285 | c.scrutinee.walk(_visitor) | |
| 286 | ||
| 287 | ||
| 288 | // A method named without calling it is a function value, | |
| 289 | // which is never absent, so there is nothing for the clause | |
| 290 | // to test. Reported here rather than left to the use site, | |
| 291 | // where it surfaces as a missing member on a function type, | |
| 292 | // and in a generator not at all. | |
| 293 | if let referenced = c.scrutinee.value?.referenced_function then | |
| 294 | _logger.error( | |
| 295 | c.scrutinee.location, | |
| 296 | "cannot test {referenced.name} for presence: a function is never absent") | |
| 297 | ||
| 298 | c.scrutinee.compile_expressions_state.value = | |
| 299 | DUMMY(Semantic.Types.ERROR(), c.scrutinee.location) | |
| 300 | fi | |
| 301 | ||
| 302 | // Peel a flow-narrowing projection back to its wrapper — | |
| 303 | // the presence test and destructure path want the | |
| 304 | // optional shape, not the already-projected `T`. | |
| 305 | c.scrutinee.compile_expressions_state.value = IR.Values.NARROW_PROJECT.peel(c.scrutinee.value) | |
| 306 | ||
| 307 | // 4. Hand the scrutinee value down to the pattern as the | |
| 308 | // destructure source. For an optional source the | |
| 309 | // presence test in generate_il unwraps the value | |
| 310 | // before the pattern destructures it. | |
| 311 | let pattern = c.pattern | |
| 312 | ||
| 313 | pattern.mark_refutable_recursive() | |
| 314 | ||
| 315 | // For a simple-name pattern, pin the binding's static | |
| 316 | // type to the narrow target — specialised against the | |
| 317 | // scrutinee's receiver type so a bare variant target | |
| 318 | // (`Maybe.YES`) becomes its closed-generic form | |
| 319 | // (`Maybe.YES[int]`) rather than the open generic | |
| 320 | // (which IL gen would emit as an unloadable class | |
| 321 | // reference). Falls back to the unspecialised written | |
| 322 | // form when no receiver is available. | |
| 323 | // | |
| 324 | // Destructure patterns leave `explicit_type` unset so | |
| 325 | // pre(DESTRUCTURING_VARIABLE_LEFT)'s constraint path | |
| 326 | // can fire when the scrutinee's type is still inferred — | |
| 327 | // that's what gives recursive lambdas inference parity | |
| 328 | // with `isa V(x) ... let (a, b) = x`. | |
| 329 | let specialized_narrow: Type? mut = null | |
| 330 | ||
| 331 | if narrow_type? then | |
| 332 | specialized_narrow = narrow_type | |
| 333 | ||
| 334 | if let receiver_type = c.scrutinee.value?.type then | |
| 335 | let specialized = | |
| 336 | _condition_analyzer.specialize_variant_for_receiver( | |
| 337 | receiver_type, | |
| 338 | narrow_type | |
| 339 | ) | |
| 340 | ||
| 341 | if specialized? then | |
| 342 | specialized_narrow = specialized | |
| 343 | fi | |
| 344 | fi | |
| 345 | fi | |
| 346 | ||
| 347 | if specialized_narrow? then | |
| 348 | // The runtime test IL generation emits for this clause | |
| 349 | // is against the specialized form: the written type | |
| 350 | // expression names the open generic for a variant of a | |
| 351 | // generic union, which is not a loadable operand. The | |
| 352 | // scrutinee's own value type carries the specialization | |
| 353 | // only when the then-edge narrowing had a target to | |
| 354 | // mutate, so a call result would otherwise be left with | |
| 355 | // the written form. | |
| 356 | _variable_left_state.get_or_add(pattern).narrow_type = specialized_narrow | |
| 357 | ||
| 358 | if pattern.is_simple_name then | |
| 359 | _variable_left_state.get_or_add(pattern).explicit_type = specialized_narrow | |
| 360 | fi | |
| 361 | fi | |
| 362 | ||
| 363 | let source_value: IR.Values.Value mut = | |
| 364 | if c.scrutinee.value? then | |
| 365 | c.scrutinee.value | |
| 366 | else | |
| 367 | IR.Values.DUMMY(Semantic.Types.ERROR(), c.scrutinee.location) | |
| 368 | fi | |
| 369 | ||
| 370 | // A destructure pattern reads its members off the source | |
| 371 | // value's type. When the scrutinee is something the | |
| 372 | // then-edge narrowing could not reach - a call result, an | |
| 373 | // expression - that type is still the declared one, so | |
| 374 | // view the source at the ascribed variant instead. The | |
| 375 | // runtime test that makes the view sound is the isinst | |
| 376 | // generate-il already emits for the clause. A source type | |
| 377 | // still being inferred is left alone, so the destructure's | |
| 378 | // own constraint path can drive it. | |
| 379 | if let narrow = specialized_narrow /\ !pattern.is_simple_name then | |
| 380 | if let source_type = source_value.type then | |
| 381 | if | |
| 382 | !source_type.is_inferred /\ | |
| 383 | !source_type.is_error /\ | |
| 384 | !narrow.is_assignable_from(source_type) | |
| 385 | then | |
| 386 | source_value = IR.Values.TYPE_WRAPPER(narrow, source_value) | |
| 387 | fi | |
| 388 | fi | |
| 389 | fi | |
| 390 | ||
| 391 | let pattern_state = _variable_left_state.get_or_add(pattern) | |
| 392 | ||
| 393 | pattern_state.right_value = source_value | |
| 394 | pattern_state.variable_location = c.location | |
| 395 | pattern_state.right_location = c.scrutinee.location | |
| 396 | ||
| 397 | // 5. Walk the pattern. Destructure operates on | |
| 398 | // `source_value.type` (the scrutinee's narrowed type). | |
| 399 | pattern.walk(_visitor) | |
| 400 | ||
| 401 | // 5b. Register the bound names on the state-machine frame | |
| 402 | // when inside a generator / async function, so their | |
| 403 | // loads and stores route through frame fields rather | |
| 404 | // than CLR-local slots MoveNext never declares. | |
| 405 | _visitor.declare_state_machine_local_fields(pattern) | |
| 406 | ||
| 407 | // 6. Diagnostics: redundancy / impossibility warnings | |
| 408 | // (`narrowing-always-succeeds`, value-type-narrow | |
| 409 | // error, etc.). Use the scrutinee variable's | |
| 410 | // DECLARED type (pre-narrow) as the source — passing | |
| 411 | // the already-narrowed `scrutinee.value.type` here | |
| 412 | // would make every narrow look like a no-op and | |
| 413 | // spuriously fire the redundancy warning. | |
| 414 | let source_type: Type? mut = null | |
| 415 | let target_variable = _visitor.try_get_narrowing_target(c.scrutinee) | |
| 416 | ||
| 417 | if target_variable? then | |
| 418 | source_type = _flow.declared_type_of(target_variable) | |
| 419 | elif let c.scrutinee.value? then | |
| 420 | source_type = value.type | |
| 421 | fi | |
| 422 | ||
| 423 | // The bare form (no top-level `: T`) needs the scrutinee | |
| 424 | // itself to be refutable — UNLESS the pattern already | |
| 425 | // carries its own refutability somewhere in its shape (a | |
| 426 | // literal leaf, or a nested ascription): `if let (1, y) = | |
| 427 | // pair` over a plain `(int, string)` can fail to match on | |
| 428 | // the `1`, so it does not also need `pair` to be optional. | |
| 429 | _pattern_checker.check_pattern( | |
| 430 | pattern, | |
| 431 | source_type, | |
| 432 | narrow_type, | |
| 433 | c.location, | |
| 434 | !pattern.has_intrinsic_refutability | |
| 435 | ) | |
| 436 | ||
| 437 | // 7. Per-clause guard — walked after the clause's names | |
| 438 | // are in scope, under the then-arm env. Narrowing | |
| 439 | // inside the guard applies to the rest of the chain | |
| 440 | // and the then-arm. | |
| 441 | if c.guard? then | |
| 442 | let guard = c.guard | |
| 443 | let epoch = _flow.heap_epoch | |
| 444 | let mark = _flow.crossing_mark | |
| 445 | ||
| 446 | guard.walk(_visitor) | |
| 447 | ||
| 448 | let facts = _condition_analyzer.analyze_condition(guard, _flow.current_env) | |
| 449 | ||
| 450 | // Guard-derived heap facts die when the guard's own | |
| 451 | // walk stored — the store may run after the check it | |
| 452 | // shares the edge with; its calls attach as | |
| 453 | // crossings instead. | |
| 454 | if _flow.heap_killed_since(epoch) then | |
| 455 | facts.then_env.drop_heap_facts() | |
| 456 | else | |
| 457 | _flow.adopt_crossings_since(facts.then_env, mark) | |
| 458 | fi | |
| 459 | ||
| 460 | _flow.set_env(facts.then_env) | |
| 461 | fi | |
| 462 | si | |
| 463 | ||
| 464 | visit_if_branch(`if: Trees.Statements.IF_BRANCH) is | |
| 465 | ||
| 466 | if let `if.condition? /\ Value.check_is_consumable(_logger, condition.location, condition.value) then | |
| 467 | // check_is_consumable is true only for a present value | |
| 468 | if !condition.value!.type!.matches(_innate_symbol_lookup.get_bool_type()) then | |
| 469 | _logger.error(condition.location, "if condition must be bool") | |
| 470 | fi | |
| 471 | fi | |
| 472 | si | |
| 473 | ||
| 474 | pre_if(`if: Trees.Statements.IF) -> bool is | |
| 475 | if `if.want_value then | |
| 476 | for i in `if.branches do | |
| 477 | i.body.compile_expressions_state.want_value = true | |
| 478 | i.body.compile_expressions_state.void_tolerated = `if.void_tolerated | |
| 479 | od | |
| 480 | fi | |
| 481 | ||
| 482 | // Open a flow frame for this IF. Each branch's controlled | |
| 483 | // walk (pre(IF_BRANCH)) records its exit environment on | |
| 484 | // the frame; visit(IF) joins them. | |
| 485 | _if_flow_stack.add(IF_FLOW_FRAME(_flow.current_env.copy())) | |
| 486 | ||
| 487 | return false | |
| 488 | si | |
| 489 | ||
| 490 | visit_if(`if: Trees.Statements.IF) is | |
| 491 | // Cleared before anything else runs, so early-return paths | |
| 492 | // below leave it false rather than inheriting a previous | |
| 493 | // walk's value. | |
| 494 | `if.yields_absence_on_fall_through = false | |
| 495 | ||
| 496 | // Merge the branch exit environments into the environment | |
| 497 | // in force after the IF. A branch whose body diverges | |
| 498 | // contributes the bottom environment; with no else | |
| 499 | // branch, the no-branch-taken fall-through contributes | |
| 500 | // the trailing running-else environment. | |
| 501 | let if_flow = _if_flow_stack[_if_flow_stack.count - 1] | |
| 502 | _if_flow_stack.remove_at(_if_flow_stack.count - 1) | |
| 503 | ||
| 504 | let after mut = NARROW_ENV.bottom() | |
| 505 | ||
| 506 | for branch_exit in if_flow.branch_exits do | |
| 507 | after = NARROW_ENV.join(after, branch_exit) | |
| 508 | od | |
| 509 | ||
| 510 | if !if_flow.has_else then | |
| 511 | after = NARROW_ENV.join(after, if_flow.running_else) | |
| 512 | fi | |
| 513 | ||
| 514 | _flow.set_env(after) | |
| 515 | ||
| 516 | for i in `if.branches do | |
| 517 | if let i.condition? then | |
| 518 | IR.Values.Value.check_is_consumable(_logger, condition.location, condition.value) | |
| 519 | fi | |
| 520 | od | |
| 521 | ||
| 522 | if !`if.want_value then | |
| 523 | return | |
| 524 | fi | |
| 525 | ||
| 526 | if `if.is_poisoned then | |
| 527 | // if the if is syntactically incomplete don't bother reporting any | |
| 528 | // semantic errors: | |
| 529 | `if.compile_expressions_state.value = DUMMY_BLOCK(Semantic.Types.ERROR(), `if.location, "if is poisoned") | |
| 530 | ||
| 531 | return | |
| 532 | fi | |
| 533 | ||
| 534 | let expected_type = `if.expected_type | |
| 535 | ||
| 536 | if | |
| 537 | VOID_TOLERANT_POSITION.is_undecided( | |
| 538 | `if.void_tolerated, | |
| 539 | `if.branches |> any(b => VOID_TOLERANT_POSITION.counts_as_non_void(b.body.value?.type)), | |
| 540 | `if.branches |> any(b => VOID_TOLERANT_POSITION.is_in_error(b.body.value?.type)) | |
| 541 | ) | |
| 542 | then | |
| 543 | `if.compile_expressions_state.value = DUMMY_BLOCK(Semantic.Types.ERROR(), `if.location, "an arm is in error") | |
| 544 | ||
| 545 | return | |
| 546 | fi | |
| 547 | ||
| 548 | if | |
| 549 | VOID_TOLERANT_POSITION.declines_value( | |
| 550 | `if.void_tolerated, | |
| 551 | `if.branches |> any(b => VOID_TOLERANT_POSITION.counts_as_non_void(b.body.value?.type)) | |
| 552 | ) | |
| 553 | then | |
| 554 | `if.compile_expressions_state.want_value = false | |
| 555 | ||
| 556 | for i in `if.branches do | |
| 557 | _drop_arm_value_request(i.body) | |
| 558 | od | |
| 559 | ||
| 560 | return | |
| 561 | fi | |
| 562 | ||
| 563 | let missing_else = | |
| 564 | !(`if.branches |> any(b => !b.condition? /\ !b.binding?)) /\ | |
| 565 | (!expected_type? \/ !expected_type.is_void) | |
| 566 | ||
| 567 | let type mut = expected_type | |
| 568 | ||
| 569 | let seen_any_values mut = false | |
| 570 | let seen_non_null_values mut = false | |
| 571 | let seen_null_values mut = false | |
| 572 | // A genuine null literal, as opposed to an unsettled inferred | |
| 573 | // sentinel or an error type (both of which also answer is_null). | |
| 574 | // Only a genuine null arm justifies widening a value-type LUB | |
| 575 | // to its optional carrier; a sentinel arm during inference | |
| 576 | // must leave the LUB untouched so it can still converge. | |
| 577 | let seen_genuine_null_values mut = false | |
| 578 | let all_tuple_literals mut = true | |
| 579 | ||
| 580 | let lub = LEAST_UPPER_BOUND_MAP() | |
| 581 | ||
| 582 | let arm_join = Semantic.EXPECTED_ARM_JOIN(expected_type) | |
| 583 | ||
| 584 | let empty_literal_arms = Collections.LIST[Trees.Statements.LIST]() | |
| 585 | ||
| 586 | for i in `if.branches do | |
| 587 | let branch = i.body | |
| 588 | ||
| 589 | let value = branch.value | |
| 590 | ||
| 591 | if !value? then | |
| 592 | continue | |
| 593 | fi | |
| 594 | ||
| 595 | if !value.type? then | |
| 596 | arm_join.add_untyped_arm() | |
| 597 | continue | |
| 598 | fi | |
| 599 | ||
| 600 | if expected_type? then | |
| 601 | if !value.check_is_consumable_allow_void(_logger, branch.location) then | |
| 602 | arm_join.add_untyped_arm() | |
| 603 | continue | |
| 604 | fi | |
| 605 | else | |
| 606 | if !value.check_is_consumable(_logger, branch.location) then | |
| 607 | continue | |
| 608 | fi | |
| 609 | fi | |
| 610 | ||
| 611 | debug_indent() | |
| 612 | ||
| 613 | if expected_type? then | |
| 614 | if !expected_type.is_void /\ !expected_type.is_assignable_from(value.type!) then | |
| 615 | // set_expected_type assigns both fields together — caller pairs them | |
| 616 | _logger.error(branch.location, string.format(`if.expected_type_error_message!, value.type, type)) | |
| 617 | fi | |
| 618 | ||
| 619 | // Assignability alone does not propagate the branch | |
| 620 | // type into phantom slots of the pushed-down | |
| 621 | // constraint; without this match propagation an outer | |
| 622 | // generic call's type-arg slot stays unresolved and | |
| 623 | // is reported as cannot infer type here even though | |
| 624 | // the branch supplied the concrete type. | |
| 625 | _match_propagator.propagate_match(expected_type, value.type!) | |
| 626 | ||
| 627 | arm_join.add(value.type!) | |
| 628 | elif CONTEXTLESS_EMPTY_LITERAL.is_arm(branch) then | |
| 629 | empty_literal_arms.add(branch) | |
| 630 | elif value.type!.is_null then | |
| 631 | // could also be error | |
| 632 | seen_null_values = true | |
| 633 | ||
| 634 | if _arm_null_join.is_genuine_null(value.type!) then | |
| 635 | seen_genuine_null_values = true | |
| 636 | fi | |
| 637 | else | |
| 638 | seen_non_null_values = true | |
| 639 | ||
| 640 | if !branch.is_tuple_literal /\ !((value.type!.is_value_tuple \/ isa Semantic.Types.TUPLE(value.type)) /\ !value.type!.is_optional) then | |
| 641 | all_tuple_literals = false | |
| 642 | fi | |
| 643 | ||
| 644 | lub.add(value.type!) | |
| 645 | fi | |
| 646 | ||
| 647 | seen_any_values = true | |
| 648 | od | |
| 649 | ||
| 650 | if _settle_empty_literal_arms(empty_literal_arms, lub, seen_non_null_values) then | |
| 651 | seen_non_null_values = true | |
| 652 | all_tuple_literals = false | |
| 653 | fi | |
| 654 | ||
| 655 | // A missing unconditional else makes the construct refutable: | |
| 656 | // it yields an optional of its completing arms' LUB, absent | |
| 657 | // when no arm runs - the same answer a loop expression gives | |
| 658 | // exhaustion. An if whose every arm diverges delivers nothing | |
| 659 | // and cannot self-type, and a non-optional expected type has | |
| 660 | // no slot for absence, so both keep demanding an else. | |
| 661 | let yields_absence mut = false | |
| 662 | ||
| 663 | if missing_else then | |
| 664 | if IF_MISSING_ELSE_DECIDER().decide(expected_type, seen_any_values) == IfMissingElseDecision.REQUIRE_ELSE then | |
| 665 | _logger.error(`if.location, "expected else in if expression") | |
| 666 | `if.compile_expressions_state.value = DUMMY_BLOCK(Semantic.Types.ERROR(), `if.location, "no else") | |
| 667 | return | |
| 668 | fi | |
| 669 | ||
| 670 | yields_absence = true | |
| 671 | ||
| 672 | // The fall-through path contributes absence: genuinely | |
| 673 | // null, so the LUB below widens to the optional carrier. | |
| 674 | seen_genuine_null_values = true | |
| 675 | seen_null_values = true | |
| 676 | fi | |
| 677 | ||
| 678 | `if.yields_absence_on_fall_through = yields_absence | |
| 679 | ||
| 680 | if !expected_type? then | |
| 681 | type = lub.get_result() | |
| 682 | ||
| 683 | if all_tuple_literals /\ seen_non_null_values /\ (!type? \/ !type.is_value_tuple) then | |
| 684 | let tuple_types = Collections.LIST[Type]() | |
| 685 | ||
| 686 | for i in `if.branches do | |
| 687 | let branch = i.body | |
| 688 | ||
| 689 | if let branch.value? /\ value.type? then | |
| 690 | tuple_types.add(value.type) | |
| 691 | fi | |
| 692 | od | |
| 693 | ||
| 694 | type = Semantic.TUPLE_ELEMENT_LUB(_innate_symbol_lookup).combine(tuple_types, lub.element_names) | |
| 695 | fi | |
| 696 | ||
| 697 | // An arm still holding placeholders - a function literal | |
| 698 | // whose parameter only a member access constrains - is | |
| 699 | // represented in the joined type by the other arms, and | |
| 700 | // learns from them what a use of the whole expression | |
| 701 | // pushes into that type. An arm that is itself a | |
| 702 | // placeholder - a local variable whose type its own | |
| 703 | // assignments decide - takes nothing from the other arms: | |
| 704 | // their join is no bound on what it can hold. | |
| 705 | if type? then | |
| 706 | for i in `if.branches do | |
| 707 | if let arm_value = i.body.value, arm_type = arm_value.type /\ arm_type.contains_inferred /\ !arm_type.is_inferred then | |
| 708 | _match_propagator.propagate_match(type, arm_type) | |
| 709 | fi | |
| 710 | od | |
| 711 | fi | |
| 712 | ||
| 713 | let decision = _arm_null_join.decide(type, seen_genuine_null_values, seen_null_values) | |
| 714 | ||
| 715 | if decision == ArmNullJoinDecision.WIDEN_VALUE_OPTIONAL then | |
| 716 | type = _innate_symbol_lookup.get_optional_type(type!) | |
| 717 | elif decision == ArmNullJoinDecision.WIDEN_REFERENCE_OPTIONAL then | |
| 718 | type = type!.as_optional() | |
| 719 | elif decision == ArmNullJoinDecision.INCOMPATIBLE then | |
| 720 | // A value-type or reference-type LUB against a non- | |
| 721 | // genuine null arm: an unsettled inferred sentinel | |
| 722 | // (resolves later, so the error is discarded on | |
| 723 | // speculative passes) or a permanently error-typed | |
| 724 | // branch (which surfaces here). | |
| 725 | for i in `if.branches do | |
| 726 | let branch = i.body | |
| 727 | ||
| 728 | let value = branch.value | |
| 729 | ||
| 730 | if !value? \/ !value.type? then | |
| 731 | continue | |
| 732 | fi | |
| 733 | ||
| 734 | if value.type.is_null then | |
| 735 | _logger.error(branch.location, "incompatible types in if branches: {value.type} and {type}") | |
| 736 | fi | |
| 737 | od | |
| 738 | fi | |
| 739 | fi | |
| 740 | ||
| 741 | if let joined = arm_join.result(yields_absence) then | |
| 742 | type = joined | |
| 743 | fi | |
| 744 | ||
| 745 | if !type? then | |
| 746 | if seen_any_values then | |
| 747 | if seen_non_null_values then | |
| 748 | _logger.error(`if.location, "no type inferred for if expression") | |
| 749 | else | |
| 750 | _logger.error(`if.location, "all branch values are null") | |
| 751 | fi | |
| 752 | fi | |
| 753 | ||
| 754 | `if.compile_expressions_state.value = DUMMY_BLOCK(Semantic.Types.ERROR(), `if.location, "no type inferred") | |
| 755 | return | |
| 756 | else | |
| 757 | `if.compile_expressions_state.value = IR.Values.BLOCK(type) | |
| 758 | fi | |
| 759 | si | |
| 760 | ||
| 761 | // Arms that are an empty list literal with nothing to say what | |
| 762 | // it holds have stayed out of the join. Where the other arms | |
| 763 | // join to an array, each is walked again at that type; otherwise | |
| 764 | // they join as they are. Answers whether any arm was added. | |
| 765 | _settle_empty_literal_arms( | |
| 766 | arms: Collections.List[Trees.Statements.LIST], | |
| 767 | lub: LEAST_UPPER_BOUND_MAP, | |
| 768 | seen_sibling: bool | |
| 769 | ) -> bool is | |
| 770 | if arms.count == 0 then | |
| 771 | return false | |
| 772 | fi | |
| 773 | ||
| 774 | let sibling_type: Type? = if seen_sibling then lub.get_result() else null fi | |
| 775 | ||
| 776 | if sibling_type? /\ isa Semantic.Types.ARRAY(sibling_type) /\ !sibling_type.contains_inferred then | |
| 777 | let use retry_site = RETRY_SITE_STATS.enter("conditionals.empty_literal_arm_rewalk", RetrySiteKind.REWALK_WITH_INFORMATION) | |
| 778 | ||
| 779 | for arm in arms do | |
| 780 | arm.set_expected_type(sibling_type, "arm type {{0}} not compatible with type {{1}} of the other arms") | |
| 781 | _visitor.rewalk(arm) | |
| 782 | od | |
| 783 | fi | |
| 784 | ||
| 785 | for arm in arms do | |
| 786 | if let arm.value? /\ value.type? then | |
| 787 | lub.add(value.type) | |
| 788 | fi | |
| 789 | od | |
| 790 | ||
| 791 | return true | |
| 792 | si | |
| 793 | ||
| 794 | // Undo the value request an arm was walked under, once the | |
| 795 | // composite has decided it produces no value after all. The | |
| 796 | // arm's captured BLOCK would otherwise swallow the arm's IL: | |
| 797 | // nothing consumes it, so nothing ever emits it. Clearing it | |
| 798 | // leaves the arm emitting its own statements, which is the | |
| 799 | // shape a body whose return type was written out as void has | |
| 800 | // been compiled to all along. | |
| 801 | _drop_arm_value_request(arm: Trees.Statements.LIST) is | |
| 802 | arm.compile_expressions_state.want_value = false | |
| 803 | arm.compile_expressions_state.value = null | |
| 804 | ||
| 805 | if let arm.last? then | |
| 806 | last.compile_expressions_state.want_value = false | |
| 807 | ||
| 808 | if let tail: Trees.Statements.EXPRESSION = last then | |
| 809 | if let statement_expression: Trees.Expressions.STATEMENT = tail.expression then | |
| 810 | statement_expression.want_value = false | |
| 811 | elif let val_block: Trees.Expressions.VAL_BLOCK = tail.expression then | |
| 812 | val_block.want_value = false | |
| 813 | fi | |
| 814 | elif | |
| 815 | isa Trees.Statements.FOR(last) \/ | |
| 816 | isa Trees.Statements.DO(last) | |
| 817 | then | |
| 818 | // A loop walked under the demand captured a value | |
| 819 | // block for its break convergence; with the request | |
| 820 | // dropped that block has no consumer, and IL | |
| 821 | // generation's spiller would capture the loop's | |
| 822 | // body into it - silently losing the loop. Drop the | |
| 823 | // value so the loop emits as the statement it now | |
| 824 | // is. | |
| 825 | last.compile_expressions_state.value = null | |
| 826 | fi | |
| 827 | fi | |
| 828 | si | |
| 829 | ||
| 830 | pre_case(`case: Trees.Statements.CASE) -> bool is | |
| 831 | if `case.want_value then | |
| 832 | for m in `case.matches do | |
| 833 | m.statements.compile_expressions_state.want_value = true | |
| 834 | m.statements.compile_expressions_state.void_tolerated = `case.void_tolerated | |
| 835 | od | |
| 836 | fi | |
| 837 | ||
| 838 | // Controlled walk: walk the scrutinee first so its value | |
| 839 | // is available to spill, then walk every match. The | |
| 840 | // scrutinee's type is held on a stack so nested `case` | |
| 841 | // statements still find their own scrutinee at the top. | |
| 842 | `case.expression.walk(_visitor) | |
| 843 | ||
| 844 | let scrutinee_value = `case.expression.value | |
| 845 | ||
| 846 | // Spill the scrutinee once into a temp, so every `when` | |
| 847 | // label's value-equality test (built in pre_case_match) | |
| 848 | // and every pattern arm reads the single evaluation - | |
| 849 | // matching the single-evaluation semantics the case | |
| 850 | // scrutinee has always had. `get_temp_copier` is a no-op | |
| 851 | // spill for an already-repeatable load (a local/parameter). | |
| 852 | let spill_block = IR.Values.BLOCK(scrutinee_value?.type ?? Semantic.Types.ERROR()) | |
| 853 | let scrutinee_load = | |
| 854 | if scrutinee_value? then | |
| 855 | scrutinee_value.get_temp_copier(spill_block, "case")() | |
| 856 | else | |
| 857 | scrutinee_value | |
| 858 | fi | |
| 859 | ||
| 860 | `case.case_state.set_scrutinee(spill_block, scrutinee_load) | |
| 861 | ||
| 862 | let scrutinee_type = scrutinee_value?.type | |
| 863 | ||
| 864 | _case_scrutinee_stack.add(scrutinee_type) | |
| 865 | _case_scrutinee_load_stack.add(scrutinee_load) | |
| 866 | _case_scrutinee_expression_stack.add(`case.expression) | |
| 867 | ||
| 868 | // Every arm walks from this same entry environment — the one | |
| 869 | // in force once the scrutinee has been walked and spilled. | |
| 870 | _case_flow_stack.add(CASE_FLOW_FRAME(_flow.current_env.copy())) | |
| 871 | ||
| 872 | for m in `case.matches do | |
| 873 | m.walk(_visitor) | |
| 874 | od | |
| 875 | ||
| 876 | _case_scrutinee_stack.remove_at(_case_scrutinee_stack.count - 1) | |
| 877 | _case_scrutinee_load_stack.remove_at(_case_scrutinee_load_stack.count - 1) | |
| 878 | _case_scrutinee_expression_stack.remove_at(_case_scrutinee_expression_stack.count - 1) | |
| 879 | ||
| 880 | // The arm exits are joined in `visit_case`, which runs next: | |
| 881 | // the join has to know whether the case is exhaustive, the | |
| 882 | // checker settles that, and the checker in turn has to run | |
| 883 | // after the void-tolerant decision there. Nothing between | |
| 884 | // here and there reads the flow environment. | |
| 885 | ||
| 886 | return true | |
| 887 | si | |
| 888 | ||
| 889 | pre_case_match(arm: Trees.Statements.CASE_MATCH) -> bool is | |
| 890 | let pattern = arm.pattern | |
| 891 | ||
| 892 | // Expression-list arm (`when expr, ...`): walk each label, | |
| 893 | // build its value-equality test against the once-spilled | |
| 894 | // scrutinee (the way `=~`, falling back to `<>`, would), | |
| 895 | // and park the results on the arm for generate-il to branch | |
| 896 | // on. A null test means no operator resolved (or the label | |
| 897 | // is `null`); generate-il then falls back to a raw compare | |
| 898 | // or a presence test. Doing this here keeps the operator | |
| 899 | // resolution - and any overload-resolution failure - in the | |
| 900 | // compile-expressions pass. | |
| 901 | if !pattern? then | |
| 902 | // An expression-list arm narrows nothing, but its body | |
| 903 | // still walks from the shared entry environment and still | |
| 904 | // has to contribute its exit to the join — a kill inside | |
| 905 | // it (an assignment, a heap-mutating call) must survive | |
| 906 | // the `case` just as it would from a pattern arm. A null | |
| 907 | // `expressions` marks the `else` arm, which covers the | |
| 908 | // no-arm-matched path. | |
| 909 | _reset_to_case_entry() | |
| 910 | ||
| 911 | if arm.expressions? then | |
| 912 | let scrutinee_load = | |
| 913 | if _case_scrutinee_load_stack.count > 0 then | |
| 914 | _case_scrutinee_load_stack[_case_scrutinee_load_stack.count - 1] | |
| 915 | else | |
| 916 | null | |
| 917 | fi | |
| 918 | ||
| 919 | let tests = Collections.LIST[IR.Values.Value?]() | |
| 920 | ||
| 921 | for e in arm.expressions.expressions do | |
| 922 | // A label names a value of the scrutinee's | |
| 923 | // type, which is what settles the type | |
| 924 | // arguments of a bare generic variant. | |
| 925 | if _case_scrutinee_stack.count > 0 then | |
| 926 | let scrutinee_type = _case_scrutinee_stack[_case_scrutinee_stack.count - 1] | |
| 927 | ||
| 928 | if scrutinee_type? /\ scrutinee_type.is_settled then | |
| 929 | e.set_expected_type(scrutinee_type, "") | |
| 930 | fi | |
| 931 | fi | |
| 932 | ||
| 933 | e.walk(_visitor) | |
| 934 | ||
| 935 | let test: IR.Values.Value? mut = null | |
| 936 | let label_value = e.value | |
| 937 | ||
| 938 | // A `null` label is a presence test, not a value | |
| 939 | // comparison: skip operator resolution (a null | |
| 940 | // operand would build a null-typed temp the back | |
| 941 | // end cannot encode), leaving a null test for | |
| 942 | // generate-il to handle as match-absence. | |
| 943 | if label_value? /\ scrutinee_load? /\ !(label_value.type?.is_null ?? false) then | |
| 944 | test = | |
| 945 | _visitor.build_equality_test(scrutinee_load, label_value, "=~", arm.location) ?? | |
| 946 | _visitor.build_equality_test(scrutinee_load, label_value, "<>", arm.location) | |
| 947 | fi | |
| 948 | ||
| 949 | tests.add(test) | |
| 950 | od | |
| 951 | ||
| 952 | arm.match_state.tests = tests | |
| 953 | fi | |
| 954 | ||
| 955 | arm.statements.walk(_visitor) | |
| 956 | ||
| 957 | _record_case_arm_exit(!arm.expressions?) | |
| 958 | ||
| 959 | return true | |
| 960 | fi | |
| 961 | ||
| 962 | let scrutinee_type: Type? mut = null | |
| 963 | if _case_scrutinee_stack.count > 0 then | |
| 964 | scrutinee_type = _case_scrutinee_stack[_case_scrutinee_stack.count - 1] | |
| 965 | fi | |
| 966 | ||
| 967 | // Controlled walk of the pattern. The standard | |
| 968 | // `pre(VARIABLE)` flow expects an `=` initializer to thread | |
| 969 | // through into `left.right_value`; case arms don't have | |
| 970 | // one, so we synthesise the pieces by hand: resolve the | |
| 971 | // type ascription, push the explicit type + refutability | |
| 972 | // down to the left, build a stand-in right_value of the | |
| 973 | // scrutinee type (wrapped in a CAST for the ascribed form | |
| 974 | // so the bound symbols are typed at the narrowed type, not | |
| 975 | // the scrutinee's), then walk the left to fire the | |
| 976 | // per-shape symbol-typing in `visit(SIMPLE_VARIABLE_LEFT)` | |
| 977 | // / `pre(DESTRUCTURING_VARIABLE_LEFT)`. | |
| 978 | pattern.type_expression.walk(_visitor) | |
| 979 | ||
| 980 | let left_state = _variable_left_state.get_or_add(pattern.left) | |
| 981 | ||
| 982 | let target_type: Type? mut = null | |
| 983 | if pattern.is_explicit_type /\ pattern.type_expression.type? then | |
| 984 | // A variant names the union's generic parameters, so | |
| 985 | // an arm written `when v: OK` over a scrutinee of | |
| 986 | // `Result[double, string]` resolves to the | |
| 987 | // unconstructed `OK`. Bind the union's arguments onto | |
| 988 | // it, so the arm tests and binds at | |
| 989 | // `OK[double, string]`. Written back onto the type | |
| 990 | // expression because the emitted isinst, the arm's | |
| 991 | // locals and the field-access owner spec all read it, | |
| 992 | // and an unconstructed variant names no loadable type. | |
| 993 | let pattern_type_expression = pattern.type_expression | |
| 994 | ||
| 995 | target_type = | |
| 996 | _condition_analyzer.specialize_variant_for_receiver( | |
| 997 | scrutinee_type, | |
| 998 | pattern_type_expression.type | |
| 999 | ) | |
| 1000 | ||
| 1001 | pattern_type_expression.type = target_type | |
| 1002 | left_state.explicit_type = target_type! | |
| 1003 | fi | |
| 1004 | ||
| 1005 | pattern.left.mark_refutable_recursive() | |
| 1006 | ||
| 1007 | if scrutinee_type? then | |
| 1008 | let synthesized: IR.Values.Value mut = | |
| 1009 | IR.Values.DUMMY(scrutinee_type, arm.location) | |
| 1010 | ||
| 1011 | if target_type? then | |
| 1012 | synthesized = IR.Values.CAST(target_type, synthesized, false) | |
| 1013 | fi | |
| 1014 | ||
| 1015 | left_state.right_value = synthesized | |
| 1016 | left_state.right_location = arm.location | |
| 1017 | left_state.variable_location = arm.location | |
| 1018 | fi | |
| 1019 | ||
| 1020 | pattern.left.walk(_visitor) | |
| 1021 | ||
| 1022 | // Same registration the `if let` clause path does: a | |
| 1023 | // case-when pattern's bound names are body locals, so | |
| 1024 | // inside a generator they need state-machine frame fields. | |
| 1025 | _visitor.declare_state_machine_local_fields(pattern.left) | |
| 1026 | ||
| 1027 | // `case`-when patterns do not require implicit refutability | |
| 1028 | // on the bare form — a destructure of a non-nullable tuple | |
| 1029 | // is a valid arm that simply always matches, not an error | |
| 1030 | // the way `if let v = some_int` is. | |
| 1031 | _pattern_checker.check_pattern( | |
| 1032 | pattern.left, | |
| 1033 | scrutinee_type, | |
| 1034 | target_type, | |
| 1035 | arm.location, | |
| 1036 | false | |
| 1037 | ) | |
| 1038 | ||
| 1039 | // Arms are alternatives, so each one walks from the shared | |
| 1040 | // pre-case entry environment rather than from whatever the | |
| 1041 | // previous arm left behind. | |
| 1042 | let epoch = _flow.heap_epoch | |
| 1043 | let mark = _flow.crossing_mark | |
| 1044 | ||
| 1045 | _reset_to_case_entry() | |
| 1046 | ||
| 1047 | // An ascribed pattern is a runtime type test on the | |
| 1048 | // scrutinee, so the scrutinee itself reads at the tested | |
| 1049 | // type inside the arm, exactly as `if let v: T = e` narrows | |
| 1050 | // `e`. The scrutinee was walked and spilled once before any | |
| 1051 | // arm ran and no arm has run yet, so the fact holds on entry | |
| 1052 | // to every arm. | |
| 1053 | if | |
| 1054 | target_type? /\ !PATTERN_CHECKER.is_lifted_test(target_type, scrutinee_type) /\ | |
| 1055 | _case_scrutinee_expression_stack.count > 0 | |
| 1056 | then | |
| 1057 | let scrutinee_expression = | |
| 1058 | _case_scrutinee_expression_stack[_case_scrutinee_expression_stack.count - 1] | |
| 1059 | ||
| 1060 | if scrutinee_expression? then | |
| 1061 | _flow.set_env( | |
| 1062 | _condition_analyzer.apply_refutable_binding_then_narrow( | |
| 1063 | scrutinee_expression, | |
| 1064 | target_type, | |
| 1065 | _flow.current_env | |
| 1066 | ) | |
| 1067 | ) | |
| 1068 | fi | |
| 1069 | fi | |
| 1070 | ||
| 1071 | // Per-arm guard — walked after the pattern's names are in | |
| 1072 | // scope, so identifiers the pattern bound resolve inside it. | |
| 1073 | // Run the guard through the condition analyzer and apply its | |
| 1074 | // then-environment to the arm body, mirroring an `if let` | |
| 1075 | // guard (see _check_refutable_binding_clause): a presence or | |
| 1076 | // `isa` test in the guard narrows within the body. | |
| 1077 | if let guard = arm.guard then | |
| 1078 | guard.walk(_visitor) | |
| 1079 | ||
| 1080 | let facts = _condition_analyzer.analyze_condition(guard, _flow.current_env) | |
| 1081 | ||
| 1082 | if _flow.heap_killed_since(epoch) then | |
| 1083 | facts.then_env.drop_heap_facts() | |
| 1084 | else | |
| 1085 | _flow.adopt_crossings_since(facts.then_env, mark) | |
| 1086 | fi | |
| 1087 | ||
| 1088 | _flow.set_env(facts.then_env) | |
| 1089 | fi | |
| 1090 | ||
| 1091 | arm.statements.walk(_visitor) | |
| 1092 | ||
| 1093 | _record_case_arm_exit(false) | |
| 1094 | ||
| 1095 | return true | |
| 1096 | si | |
| 1097 | ||
| 1098 | visit_case_match(arm: Trees.Statements.CASE_MATCH) is | |
| 1099 | si | |
| 1100 | ||
| 1101 | // Reset to the environment every arm starts from. Arms are | |
| 1102 | // alternatives, so no arm inherits what a previous arm narrowed | |
| 1103 | // or killed. | |
| 1104 | _reset_to_case_entry() is | |
| 1105 | if _case_flow_stack.count > 0 then | |
| 1106 | _flow.set_env(_case_flow_stack[_case_flow_stack.count - 1].entry.copy()) | |
| 1107 | fi | |
| 1108 | si | |
| 1109 | ||
| 1110 | // Record the environment an arm body exits with, so visit_case | |
| 1111 | // can join it with the other arms'. A body that diverges exits | |
| 1112 | // at the bottom environment and contributes nothing to the join. | |
| 1113 | _record_case_arm_exit(is_else: bool) is | |
| 1114 | if _case_flow_stack.count > 0 then | |
| 1115 | let frame = _case_flow_stack[_case_flow_stack.count - 1] | |
| 1116 | ||
| 1117 | frame.branch_exits.add(_flow.current_env.copy()) | |
| 1118 | ||
| 1119 | if is_else then | |
| 1120 | frame.has_else = true | |
| 1121 | fi | |
| 1122 | fi | |
| 1123 | si | |
| 1124 | ||
| 1125 | // Join the arm exits into the environment in force after the | |
| 1126 | // `case`. Without an `else` arm the no-arm-matched path falls | |
| 1127 | // through carrying the entry environment, so that joins too — | |
| 1128 | // and because the join is a meet over facts, anything an arm | |
| 1129 | // killed is correctly absent from the result. An exhaustive | |
| 1130 | // case has no such path: one arm always matches, so joining the | |
| 1131 | // entry there would make the code after look reachable when | |
| 1132 | // every arm diverged, and a body ending in one would draw a | |
| 1133 | // spurious definite-return. | |
| 1134 | _join_case_arm_exits(is_exhaustive: bool) is | |
| 1135 | if _case_flow_stack.count == 0 then | |
| 1136 | return | |
| 1137 | fi | |
| 1138 | ||
| 1139 | let frame = _case_flow_stack[_case_flow_stack.count - 1] | |
| 1140 | _case_flow_stack.remove_at(_case_flow_stack.count - 1) | |
| 1141 | ||
| 1142 | let after mut = NARROW_ENV.bottom() | |
| 1143 | ||
| 1144 | for branch_exit in frame.branch_exits do | |
| 1145 | after = NARROW_ENV.join(after, branch_exit) | |
| 1146 | od | |
| 1147 | ||
| 1148 | if !frame.has_else /\ !is_exhaustive then | |
| 1149 | after = NARROW_ENV.join(after, frame.entry) | |
| 1150 | fi | |
| 1151 | ||
| 1152 | _flow.set_env(after) | |
| 1153 | si | |
| 1154 | ||
| 1155 | visit_case(`case: Trees.Statements.CASE) is | |
| 1156 | let undecided = | |
| 1157 | VOID_TOLERANT_POSITION.is_undecided( | |
| 1158 | `case.void_tolerated, | |
| 1159 | `case.matches |> any(m => VOID_TOLERANT_POSITION.counts_as_non_void(m.statements.value?.type)), | |
| 1160 | `case.matches |> any(m => VOID_TOLERANT_POSITION.is_in_error(m.statements.value?.type)) | |
| 1161 | ) | |
| 1162 | ||
| 1163 | // Decided before the exhaustiveness check, so a case whose | |
| 1164 | // arms only yield void is checked as the statement it is: | |
| 1165 | // a missing `else` there is the statement form's warning, | |
| 1166 | // not the expression form's error. | |
| 1167 | if | |
| 1168 | !undecided /\ | |
| 1169 | VOID_TOLERANT_POSITION.declines_value( | |
| 1170 | `case.void_tolerated, | |
| 1171 | `case.matches |> any(m => VOID_TOLERANT_POSITION.counts_as_non_void(m.statements.value?.type)) | |
| 1172 | ) | |
| 1173 | then | |
| 1174 | `case.compile_expressions_state.want_value = false | |
| 1175 | ||
| 1176 | for m in `case.matches do | |
| 1177 | _drop_arm_value_request(m.statements) | |
| 1178 | od | |
| 1179 | fi | |
| 1180 | ||
| 1181 | if !`case.is_poisoned then | |
| 1182 | _case_exhaustiveness_checker.check(`case) | |
| 1183 | fi | |
| 1184 | ||
| 1185 | // Deferred from `pre_case`, and placed after the check so it | |
| 1186 | // knows whether the case is exhaustive: one that is has no | |
| 1187 | // fall-through path for the join to add. | |
| 1188 | _join_case_arm_exits(`case.is_exhaustive) | |
| 1189 | ||
| 1190 | if !`case.want_value then | |
| 1191 | return | |
| 1192 | fi | |
| 1193 | ||
| 1194 | if `case.is_poisoned then | |
| 1195 | `case.compile_expressions_state.value = DUMMY_BLOCK(Semantic.Types.ERROR(), `case.location, "case is poisoned") | |
| 1196 | return | |
| 1197 | fi | |
| 1198 | ||
| 1199 | if undecided then | |
| 1200 | `case.compile_expressions_state.value = DUMMY_BLOCK(Semantic.Types.ERROR(), `case.location, "an arm is in error") | |
| 1201 | ||
| 1202 | return | |
| 1203 | fi | |
| 1204 | ||
| 1205 | let has_else mut = false | |
| 1206 | for m in `case.matches do | |
| 1207 | if !m.expressions? then | |
| 1208 | has_else = true | |
| 1209 | fi | |
| 1210 | od | |
| 1211 | ||
| 1212 | // Whether the case is guaranteed to produce a value when | |
| 1213 | // none of the arms match: | |
| 1214 | // - has_else → else arm runs | |
| 1215 | // - is_exhaustive → checker proved one arm matches | |
| 1216 | // - requires_default_fallthrough → IL pushes default(expected_type) | |
| 1217 | // Otherwise no value can be produced. CASE_EXHAUSTIVENESS_CHECKER | |
| 1218 | // has already emitted the relevant diagnostic | |
| 1219 | // (`non-exhaustive-case` for a closed domain with gaps, | |
| 1220 | // `case-needs-else` for an open domain with no else). | |
| 1221 | let expected_type = `case.expected_type | |
| 1222 | ||
| 1223 | if | |
| 1224 | !has_else /\ | |
| 1225 | !`case.is_exhaustive /\ | |
| 1226 | !`case.requires_default_fallthrough /\ | |
| 1227 | (!expected_type? \/ !expected_type.is_void) | |
| 1228 | then | |
| 1229 | `case.compile_expressions_state.value = DUMMY_BLOCK(Semantic.Types.ERROR(), `case.location, "no else") | |
| 1230 | return | |
| 1231 | fi | |
| 1232 | ||
| 1233 | let type mut = expected_type | |
| 1234 | ||
| 1235 | let seen_any_values mut = false | |
| 1236 | let seen_non_null_values mut = false | |
| 1237 | let seen_null_values mut = false | |
| 1238 | // See the IF path: only a genuine settled null arm justifies | |
| 1239 | // widening a value-type LUB, not an unsettled inferred sentinel. | |
| 1240 | let seen_genuine_null_values mut = false | |
| 1241 | ||
| 1242 | let lub = LEAST_UPPER_BOUND_MAP() | |
| 1243 | ||
| 1244 | let empty_literal_arms = Collections.LIST[Trees.Statements.LIST]() | |
| 1245 | ||
| 1246 | for m in `case.matches do | |
| 1247 | let body = m.statements | |
| 1248 | ||
| 1249 | let value = body.value | |
| 1250 | ||
| 1251 | if !value? then | |
| 1252 | continue | |
| 1253 | fi | |
| 1254 | ||
| 1255 | if !value.type? then | |
| 1256 | continue | |
| 1257 | fi | |
| 1258 | ||
| 1259 | if expected_type? then | |
| 1260 | if !value.check_is_consumable_allow_void(_logger, body.location) then | |
| 1261 | continue | |
| 1262 | fi | |
| 1263 | else | |
| 1264 | if !value.check_is_consumable(_logger, body.location) then | |
| 1265 | continue | |
| 1266 | fi | |
| 1267 | fi | |
| 1268 | ||
| 1269 | if expected_type? then | |
| 1270 | if !expected_type.is_void /\ !expected_type.is_assignable_from(value.type!) then | |
| 1271 | // set_expected_type assigns both fields together — caller pairs them | |
| 1272 | _logger.error(body.location, string.format(`case.expected_type_error_message!, value.type, type)) | |
| 1273 | fi | |
| 1274 | elif CONTEXTLESS_EMPTY_LITERAL.is_arm(body) then | |
| 1275 | empty_literal_arms.add(body) | |
| 1276 | elif value.type!.is_null then | |
| 1277 | seen_null_values = true | |
| 1278 | ||
| 1279 | if _arm_null_join.is_genuine_null(value.type!) then | |
| 1280 | seen_genuine_null_values = true | |
| 1281 | fi | |
| 1282 | else | |
| 1283 | seen_non_null_values = true | |
| 1284 | ||
| 1285 | lub.add(value.type!) | |
| 1286 | fi | |
| 1287 | ||
| 1288 | seen_any_values = true | |
| 1289 | od | |
| 1290 | ||
| 1291 | if _settle_empty_literal_arms(empty_literal_arms, lub, seen_non_null_values) then | |
| 1292 | seen_non_null_values = true | |
| 1293 | fi | |
| 1294 | ||
| 1295 | if !expected_type? then | |
| 1296 | type = lub.get_result() | |
| 1297 | ||
| 1298 | let decision = _arm_null_join.decide(type, seen_genuine_null_values, seen_null_values) | |
| 1299 | ||
| 1300 | if decision == ArmNullJoinDecision.WIDEN_VALUE_OPTIONAL then | |
| 1301 | type = _innate_symbol_lookup.get_optional_type(type!) | |
| 1302 | elif decision == ArmNullJoinDecision.WIDEN_REFERENCE_OPTIONAL then | |
| 1303 | type = type!.as_optional() | |
| 1304 | elif decision == ArmNullJoinDecision.INCOMPATIBLE then | |
| 1305 | // A value-type or reference-type LUB against a non- | |
| 1306 | // genuine null arm — see the IF path. | |
| 1307 | for m in `case.matches do | |
| 1308 | let body = m.statements | |
| 1309 | ||
| 1310 | let value = body.value | |
| 1311 | ||
| 1312 | if !value? \/ !value.type? then | |
| 1313 | continue | |
| 1314 | fi | |
| 1315 | ||
| 1316 | if value.type.is_null then | |
| 1317 | _logger.error(body.location, "incompatible types in case arms: {value.type} and {type}") | |
| 1318 | fi | |
| 1319 | od | |
| 1320 | fi | |
| 1321 | fi | |
| 1322 | ||
| 1323 | if !type? then | |
| 1324 | if seen_any_values then | |
| 1325 | if seen_non_null_values then | |
| 1326 | _logger.error(`case.location, "no type inferred for case expression") | |
| 1327 | else | |
| 1328 | _logger.error(`case.location, "all arm values are null") | |
| 1329 | fi | |
| 1330 | fi | |
| 1331 | ||
| 1332 | `case.compile_expressions_state.value = DUMMY_BLOCK(Semantic.Types.ERROR(), `case.location, "no type inferred") | |
| 1333 | return | |
| 1334 | else | |
| 1335 | `case.compile_expressions_state.value = IR.Values.BLOCK(type) | |
| 1336 | fi | |
| 1337 | si | |
| 1338 | si | |
| 1339 | si |