Appearance
| 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 |