Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use System.Reflection.Metadata.ILOpCode | |
| 3 | use IO.Std | |
| 4 | ||
| 5 | use System.Text.StringBuilder | |
| 6 | ||
| 7 | use Logging | |
| 8 | use Trees | |
| 9 | use Source | |
| 10 | ||
| 11 | use IR | |
| 12 | use IR.Values | |
| 13 | ||
| 14 | use Ghul.Pipes | |
| 15 | ||
| 16 | ||
| 17 | // Conditional IL: if-let clause tests and binds, if statements, case and match arms, and null tests. | |
| 18 | partial GENERATE_IL is | |
| 19 | // Emit the scrutinee evaluation, optional isinst cast, the | |
| 20 | // presence test, and the branch-to-fail for one `if let` | |
| 21 | // clause. Returns the temp holding the (possibly cast) | |
| 22 | // scrutinee value so the caller can bind the clause's | |
| 23 | // pattern from it; returns null when there is nothing to | |
| 24 | // bind (a missing scrutinee value, defensive). | |
| 25 | _emit_if_let_clause_test( | |
| 26 | c: Statements.REFUTABLE_BINDING_CLAUSE, | |
| 27 | fail: LABEL, | |
| 28 | is_first: bool, | |
| 29 | brancher: BLOCK_BRANCHER | |
| 30 | ) -> TEMP? is | |
| 31 | c.scrutinee.walk(self) | |
| 32 | ||
| 33 | if !c.scrutinee.value? then | |
| 34 | return null | |
| 35 | fi | |
| 36 | ||
| 37 | let scrutinee_value = c.scrutinee.value | |
| 38 | let typed_value: Value mut = scrutinee_value | |
| 39 | ||
| 40 | if let c.narrow_type_expression?, narrow_type_expression.type? then | |
| 41 | // Test against the narrow type compile-expressions | |
| 42 | // stamped on the pattern: it is the written type | |
| 43 | // specialised against the scrutinee, so a variant of a | |
| 44 | // generic union carries its type arguments. The written | |
| 45 | // form names the open generic, which is not a loadable | |
| 46 | // `isinst` operand. Falls back to the written form for | |
| 47 | // a clause compile-expressions left unstamped. | |
| 48 | let narrow_target = | |
| 49 | PATTERN_CHECKER.runtime_test_type( | |
| 50 | _variable_left_state.get(c.pattern)?.narrow_type ?? type, | |
| 51 | scrutinee_value.type, | |
| 52 | _innate_symbol_lookup | |
| 53 | ) | |
| 54 | ||
| 55 | // An optional source already of the test type needs no | |
| 56 | // conversion, and the checked one would unwrap it when | |
| 57 | // absent. A reference scrutinee read at its narrowed type | |
| 58 | // still needs the runtime test the cast carries. | |
| 59 | if !narrow_target.is_value_type \/ !(scrutinee_value.type?.matches(narrow_target) ?? false) then | |
| 60 | typed_value = | |
| 61 | _type_caster.cast_value( | |
| 62 | c.location, | |
| 63 | scrutinee_value, | |
| 64 | narrow_target, | |
| 65 | true, | |
| 66 | false | |
| 67 | ) | |
| 68 | fi | |
| 69 | fi | |
| 70 | ||
| 71 | let temp = TEMP(current_block, "if_let", typed_value) | |
| 72 | ||
| 73 | let init_type = typed_value.type! | |
| 74 | ||
| 75 | // The presence test: a reference type is absent when | |
| 76 | // null; an option-shaped value type is absent when its | |
| 77 | // `has_value` member is false. | |
| 78 | let presence: Value? mut = null | |
| 79 | ||
| 80 | if init_type.is_value_type then | |
| 81 | let has_value_member = init_type.find_member("has_value") | |
| 82 | ||
| 83 | if has_value_member? then | |
| 84 | presence = has_value_member.load(LOCATION.internal, temp.load(), _symbol_loader) | |
| 85 | fi | |
| 86 | else | |
| 87 | presence = temp.load() | |
| 88 | fi | |
| 89 | ||
| 90 | if presence? then | |
| 91 | if is_first then | |
| 92 | brancher.branch(BRANCH.Z, presence, fail, "if") | |
| 93 | else | |
| 94 | brancher.branch(BRANCH.Z, presence, fail, "elif") | |
| 95 | fi | |
| 96 | fi | |
| 97 | ||
| 98 | return temp | |
| 99 | si | |
| 100 | ||
| 101 | // Bind one clause's pattern from the temp produced by the | |
| 102 | // clause's test. Reference bindings take the value as-is; | |
| 103 | // option-shaped value-type bindings unwrap `.value` first. | |
| 104 | _emit_if_let_clause_bind( | |
| 105 | c: Statements.REFUTABLE_BINDING_CLAUSE, | |
| 106 | temp: TEMP, | |
| 107 | fail: LABEL | |
| 108 | ) is | |
| 109 | let init_type = temp.load().type | |
| 110 | ||
| 111 | // Option-shaped means both `has_value` and `value` — a bare | |
| 112 | // `value` member is not enough (see the case-arm equivalent | |
| 113 | // above). | |
| 114 | if init_type.is_value_type then | |
| 115 | let has_value_member = init_type.find_member("has_value") | |
| 116 | ||
| 117 | if has_value_member? then | |
| 118 | let value_member = init_type.find_member("value") | |
| 119 | ||
| 120 | if value_member? then | |
| 121 | gen_destructuring_initialize( | |
| 122 | c.pattern, | |
| 123 | value_member.load(LOCATION.internal, temp.load(), _symbol_loader), | |
| 124 | fail | |
| 125 | ) | |
| 126 | ||
| 127 | return | |
| 128 | fi | |
| 129 | fi | |
| 130 | fi | |
| 131 | ||
| 132 | gen_destructuring_initialize(c.pattern, temp.load(), fail) | |
| 133 | si | |
| 134 | ||
| 135 | pre(i: Statements.IF) -> bool is | |
| 136 | super.pre(i) | |
| 137 | ||
| 138 | return true | |
| 139 | si | |
| 140 | ||
| 141 | visit(`if: Statements.IF) is | |
| 142 | let is_first mut = true | |
| 143 | let seen_else mut = false | |
| 144 | ||
| 145 | let end = LABEL() | |
| 146 | ||
| 147 | // Spill the IF's value through a frame field when the | |
| 148 | // body contains a suspend; otherwise capture as usual. | |
| 149 | // See COMPOSITE_VALUE_SPILLER. Recursive: inner | |
| 150 | // composites (val-blocks, nested IFs) handle their own | |
| 151 | // suspends the same way. `if.value` is non-null exactly | |
| 152 | // when `if.want_value` is true (visit_if sets it only | |
| 153 | // in that case), so the spiller takes the value | |
| 154 | // directly. | |
| 155 | let spiller = COMPOSITE_VALUE_SPILLER( | |
| 156 | self, | |
| 157 | `if, | |
| 158 | `if.value, | |
| 159 | _current_state_machine_frame() | |
| 160 | ) | |
| 161 | ||
| 162 | spiller.enter() | |
| 163 | ||
| 164 | let brancher = get_brancher_for_block() | |
| 165 | ||
| 166 | for b in `if.branches do | |
| 167 | let next = LABEL() | |
| 168 | let first_clause_temp: TEMP? mut = null | |
| 169 | ||
| 170 | if let b.condition? /\ !b.binding? then | |
| 171 | // Enter the arm's own scope before walking the | |
| 172 | // condition: a `val ... lav` condition can declare | |
| 173 | // its own locals (`if val let v = e; v lav then`), | |
| 174 | // and those live in the arm's scope, not the | |
| 175 | // enclosing one. | |
| 176 | self.pre(b) | |
| 177 | ||
| 178 | condition.walk(self) | |
| 179 | ||
| 180 | if is_first then | |
| 181 | brancher.branch(BRANCH.Z, condition.value!, next, "if") | |
| 182 | else | |
| 183 | brancher.branch(BRANCH.Z, condition.value!, next, "elif") | |
| 184 | fi | |
| 185 | elif let b.binding? then | |
| 186 | // `if let` branch: for each clause, evaluate its | |
| 187 | // scrutinee into a temp, emit an isinst for the | |
| 188 | // type test when the clause carries `: V`, branch | |
| 189 | // past this arm if the result has no value, then | |
| 190 | // bind the clause's pattern. Clauses chain left | |
| 191 | // to right; every clause's test and optional | |
| 192 | // guard must succeed for the then-arm to fire. | |
| 193 | // | |
| 194 | // Every clause's scrutinee evaluates inside the | |
| 195 | // if-arm scope, where compile-expressions resolved | |
| 196 | // it: a block in the scrutinee declares its locals | |
| 197 | // there. Earlier clauses' bindings are stored | |
| 198 | // before a later clause's scrutinee runs, letting | |
| 199 | // `if let x = a, y = x.b` chain values through. | |
| 200 | self.pre(b) | |
| 201 | ||
| 202 | first_clause_temp = _emit_if_let_clause_test(binding.clauses[0], next, is_first, brancher) | |
| 203 | else | |
| 204 | seen_else = true | |
| 205 | ||
| 206 | // enter if block scope: | |
| 207 | self.pre(b) | |
| 208 | fi | |
| 209 | ||
| 210 | if let b.binding? then | |
| 211 | // Declare CLR locals for every clause's pattern | |
| 212 | // up front — pre(REFUTABLE_BINDING) iterates all | |
| 213 | // clauses. | |
| 214 | self.pre(binding) | |
| 215 | ||
| 216 | if first_clause_temp? then | |
| 217 | _emit_if_let_clause_bind(binding.clauses[0], first_clause_temp, next) | |
| 218 | fi | |
| 219 | ||
| 220 | if binding.clauses[0].guard? then | |
| 221 | binding.clauses[0].guard!.walk(self) | |
| 222 | brancher.branch(BRANCH.Z, binding.clauses[0].guard!.value!, next, "if-let-guard") | |
| 223 | fi | |
| 224 | ||
| 225 | let i mut = 1 | |
| 226 | while i < binding.clauses.count do | |
| 227 | let clause = binding.clauses[i] | |
| 228 | ||
| 229 | let temp = _emit_if_let_clause_test(clause, next, false, brancher) | |
| 230 | ||
| 231 | if temp? then | |
| 232 | _emit_if_let_clause_bind(clause, temp, next) | |
| 233 | fi | |
| 234 | ||
| 235 | if clause.guard? then | |
| 236 | clause.guard.walk(self) | |
| 237 | brancher.branch(BRANCH.Z, clause.guard!.value!, next, "if-let-guard") | |
| 238 | fi | |
| 239 | ||
| 240 | i = i + 1 | |
| 241 | od | |
| 242 | fi | |
| 243 | ||
| 244 | b.body.walk(self) | |
| 245 | ||
| 246 | // no-op? | |
| 247 | b.accept(self) | |
| 248 | ||
| 249 | if `if.want_value then | |
| 250 | if let b.body.value? then | |
| 251 | // Coerce the branch value to the IF's result type | |
| 252 | // so a value-type branch flowing into a wider slot | |
| 253 | // is boxed (T -> object) or wrapped (T -> T?); the | |
| 254 | // else branch already lowers null -> default at | |
| 255 | // compile time, but a present-value branch pushes | |
| 256 | // its bare value here and must match the join type. | |
| 257 | let block_type = `if.value?.type | |
| 258 | let coerced = | |
| 259 | if block_type? /\ !block_type.is_void /\ !block_type.is_error then | |
| 260 | _boxer.box_if_needed(value, block_type) | |
| 261 | else | |
| 262 | value | |
| 263 | fi | |
| 264 | ||
| 265 | spiller.emit_value(coerced) | |
| 266 | ||
| 267 | // Discard the capture-mode value when the IF is | |
| 268 | // in expected-void position but a branch supplied | |
| 269 | // a non-void value. Spill mode stfld'd into a | |
| 270 | // typed field so no extra pop is needed. | |
| 271 | if let | |
| 272 | `if.expected_type? /\ | |
| 273 | !spiller.is_spilling /\ | |
| 274 | expected_type.is_void /\ | |
| 275 | !value.type!.is_void | |
| 276 | then | |
| 277 | add(Values.INSTRUCTION(ILOpCode.POP)) | |
| 278 | fi | |
| 279 | fi | |
| 280 | fi | |
| 281 | ||
| 282 | brancher.branch(end) | |
| 283 | brancher.label(next) | |
| 284 | ||
| 285 | is_first = false | |
| 286 | od | |
| 287 | ||
| 288 | // A missing-else if yields absence on its fall-through edge - | |
| 289 | // the same default-of-optional a natural loop exit pushes. | |
| 290 | if `if.yields_absence_on_fall_through /\ `if.value? then | |
| 291 | spiller.emit_value(IR.Values.DEFAULT(`if.value!.type!)) | |
| 292 | fi | |
| 293 | ||
| 294 | brancher.label(end) | |
| 295 | ||
| 296 | super.visit(`if) | |
| 297 | ||
| 298 | spiller.leave() | |
| 299 | si | |
| 300 | ||
| 301 | pre(`case: Statements.CASE) -> bool is | |
| 302 | super.pre(`case) | |
| 303 | ||
| 304 | return true | |
| 305 | si | |
| 306 | ||
| 307 | visit(`case: Statements.CASE) is | |
| 308 | // Spill the CASE's value through a frame field when the | |
| 309 | // body contains a suspend; otherwise capture as usual. | |
| 310 | // See COMPOSITE_VALUE_SPILLER. `case.value` is non-null | |
| 311 | // exactly when `case.want_value` is true. | |
| 312 | let spiller = COMPOSITE_VALUE_SPILLER( | |
| 313 | self, | |
| 314 | `case, | |
| 315 | `case.value, | |
| 316 | _current_state_machine_frame() | |
| 317 | ) | |
| 318 | ||
| 319 | spiller.enter() | |
| 320 | ||
| 321 | // The scrutinee was spilled once into a temp in | |
| 322 | // compile-expressions (see CASE_STATE); emit the spill and | |
| 323 | // read the temp. Reusing the single load Value across every | |
| 324 | // label and pattern arm is safe - Load.TEMP.gen is an | |
| 325 | // idempotent ldloc - and preserves the scrutinee's | |
| 326 | // single-evaluation semantics. | |
| 327 | // pre(CASE) takes over the walk, so the scrutinee is walked | |
| 328 | // here: an `if` or a block in it builds its arms' IL as it | |
| 329 | // is walked, and the spill only reads the value that built. | |
| 330 | `case.expression.walk(self) | |
| 331 | ||
| 332 | if `case.scrutinee_spill? then | |
| 333 | current_block.add(`case.scrutinee_spill!) | |
| 334 | fi | |
| 335 | ||
| 336 | let scrutinee_load = `case.scrutinee_load! | |
| 337 | ||
| 338 | let brancher = get_brancher_for_block() | |
| 339 | ||
| 340 | // The case's own exit, as a plain label rather than an entry | |
| 341 | // on the loop stack: an arm branches here to skip the arms | |
| 342 | // below it. A case is not a loop, and with no fall-through | |
| 343 | // between arms there is nothing in one to break out of, so | |
| 344 | // putting it on that stack only hid the enclosing loop: a | |
| 345 | // `break` in an arm left the case rather than the loop, and a | |
| 346 | // `continue` targeted a `start` the case never marks. The `if` | |
| 347 | // statement above uses a plain label for the same job. | |
| 348 | let case_end = LABEL() | |
| 349 | ||
| 350 | let has_else mut = false | |
| 351 | for m in `case.matches do | |
| 352 | if !m.expressions? /\ !m.pattern? then | |
| 353 | has_else = true | |
| 354 | fi | |
| 355 | od | |
| 356 | ||
| 357 | for m in `case.matches do | |
| 358 | let next = LABEL() | |
| 359 | ||
| 360 | if m.expressions? then | |
| 361 | // Per-label value-equality test Values, built in | |
| 362 | // compile-expressions (see CASE_MATCH_STATE). A | |
| 363 | // non-null test embeds its label, so it is emitted | |
| 364 | // and branched on directly; a null test means no | |
| 365 | // operator resolved (or the label is `null`), so | |
| 366 | // generate-il falls back to a raw compare. | |
| 367 | let tests = m.tests | |
| 368 | ||
| 369 | if m.expressions.expressions.count == 1 then | |
| 370 | let test: IR.Values.Value? = if tests? then tests[0] else null fi | |
| 371 | ||
| 372 | if test? then | |
| 373 | brancher.branch(BRANCH.Z, test, next) | |
| 374 | else | |
| 375 | m.expressions.expressions[0].walk(self) | |
| 376 | ||
| 377 | let label_value = m.expressions!.expressions[0].value! | |
| 378 | ||
| 379 | if (label_value.type?.is_null ?? false) then | |
| 380 | // `when null` is a presence test, not a | |
| 381 | // value compare: match absence. Branch | |
| 382 | // to `next` when the scrutinee is present. | |
| 383 | gen_case_null_test(scrutinee_load, `case.expression.value!.type!, next, false) | |
| 384 | else | |
| 385 | brancher.branch(BRANCH.NE, scrutinee_load, label_value, next) | |
| 386 | fi | |
| 387 | fi | |
| 388 | elif m.expressions.expressions.count > 1 then | |
| 389 | let match = LABEL() | |
| 390 | ||
| 391 | for i in 0..m.expressions!.expressions.count do | |
| 392 | let test: IR.Values.Value? = if tests? then tests[i] else null fi | |
| 393 | ||
| 394 | if test? then | |
| 395 | brancher.branch(BRANCH.NZ, test, match) | |
| 396 | else | |
| 397 | let e = m.expressions!.expressions[i] | |
| 398 | e.walk(self) | |
| 399 | ||
| 400 | let label_value = e.value! | |
| 401 | ||
| 402 | if (label_value.type?.is_null ?? false) then | |
| 403 | // `when null` in a multi-label arm: | |
| 404 | // branch to `match` when absent. | |
| 405 | gen_case_null_test(scrutinee_load, `case.expression.value!.type!, match, true) | |
| 406 | else | |
| 407 | brancher.branch(BRANCH.EQ, scrutinee_load, label_value, match) | |
| 408 | fi | |
| 409 | fi | |
| 410 | od | |
| 411 | ||
| 412 | brancher.branch(next) | |
| 413 | ||
| 414 | brancher.label(match) | |
| 415 | fi | |
| 416 | ||
| 417 | m.walk(self) | |
| 418 | elif m.pattern? then | |
| 419 | // Pattern arm: enter the arm's scope, narrow the | |
| 420 | // case temp against the pattern's type (ascription | |
| 421 | // → isinst; option-shape value type → has_value), | |
| 422 | // branch to `next` on failure, then bind. Mirrors | |
| 423 | // `if let`'s presence-test + bind shape, applied | |
| 424 | // against the case's scrutinee TEMP instead of a | |
| 425 | // per-branch initializer TEMP. | |
| 426 | self.pre(m) | |
| 427 | ||
| 428 | let pattern = m.pattern! | |
| 429 | let init_type: Semantic.Types.Type mut = `case.expression.value!.type! | |
| 430 | let arm_source: IR.Values.Value mut = scrutinee_load | |
| 431 | ||
| 432 | if pattern.is_explicit_type /\ pattern.type_expression.type? then | |
| 433 | let source_type = init_type | |
| 434 | ||
| 435 | init_type = PATTERN_CHECKER.runtime_test_type(pattern.type_expression.type, init_type, _innate_symbol_lookup) | |
| 436 | ||
| 437 | if PATTERN_CHECKER.is_lifted_test(pattern.type_expression.type, source_type) then | |
| 438 | // A test against the optional of a plain value | |
| 439 | // type unboxes as well as testing. | |
| 440 | if !source_type.matches(init_type) then | |
| 441 | arm_source = _type_caster.cast_value(m.location, arm_source, init_type, true, false) | |
| 442 | fi | |
| 443 | else | |
| 444 | arm_source = IR.Values.CAST(init_type, arm_source, false) | |
| 445 | fi | |
| 446 | fi | |
| 447 | ||
| 448 | let arm_temp = TEMP(current_block, "case_let", arm_source) | |
| 449 | ||
| 450 | // An option-shape value type is one with both | |
| 451 | // `has_value` and `value` members. Requiring both | |
| 452 | // matters: a plain value type can legitimately have | |
| 453 | // a member named `value` — a tuple with an element | |
| 454 | // so named, for instance — and unwrapping through | |
| 455 | // it would bind the pattern against that element | |
| 456 | // instead of the whole value. | |
| 457 | let value_member: Semantic.Symbols.Symbol? mut = null | |
| 458 | let presence: IR.Values.Value? mut = null | |
| 459 | if init_type.is_value_type then | |
| 460 | let has_value_member = init_type.find_member("has_value") | |
| 461 | if has_value_member? then | |
| 462 | presence = has_value_member.load(LOCATION.internal, arm_temp.load(), _symbol_loader) | |
| 463 | value_member = init_type.find_member("value") | |
| 464 | fi | |
| 465 | else | |
| 466 | presence = arm_temp.load() | |
| 467 | fi | |
| 468 | ||
| 469 | if presence? then | |
| 470 | brancher.branch(BRANCH.Z, presence, next, "case-let") | |
| 471 | fi | |
| 472 | ||
| 473 | self.pre(pattern) | |
| 474 | ||
| 475 | if value_member? then | |
| 476 | // Option-shape value type: bind from the | |
| 477 | // unwrapped `.value` member. | |
| 478 | gen_destructuring_initialize( | |
| 479 | pattern.left, | |
| 480 | value_member.load(LOCATION.internal, arm_temp.load(), _symbol_loader), | |
| 481 | next | |
| 482 | ) | |
| 483 | else | |
| 484 | gen_destructuring_initialize(pattern.left, arm_temp.load(), next) | |
| 485 | fi | |
| 486 | ||
| 487 | if m.guard? then | |
| 488 | m.guard.walk(self) | |
| 489 | brancher.branch(BRANCH.Z, m.guard!.value!, next, "case-when-guard") | |
| 490 | fi | |
| 491 | ||
| 492 | self.visit(m) | |
| 493 | else | |
| 494 | m.walk(self) | |
| 495 | fi | |
| 496 | ||
| 497 | if `case.want_value then | |
| 498 | if let m.statements.value? then | |
| 499 | // Coerce the arm value to the CASE's result type | |
| 500 | // so a value-type arm flowing into a wider slot is | |
| 501 | // boxed (T -> object) or wrapped (T -> T?), matching | |
| 502 | // the join type the other arms deliver. | |
| 503 | let block_type = `case.value?.type | |
| 504 | let coerced = | |
| 505 | if block_type? /\ !block_type.is_void /\ !block_type.is_error then | |
| 506 | _boxer.box_if_needed(value, block_type) | |
| 507 | else | |
| 508 | value | |
| 509 | fi | |
| 510 | ||
| 511 | spiller.emit_value(coerced) | |
| 512 | ||
| 513 | if let | |
| 514 | `case.expected_type? /\ | |
| 515 | !spiller.is_spilling /\ | |
| 516 | expected_type.is_void /\ | |
| 517 | !value.type!.is_void | |
| 518 | then | |
| 519 | add(Values.INSTRUCTION(ILOpCode.POP)) | |
| 520 | fi | |
| 521 | fi | |
| 522 | fi | |
| 523 | ||
| 524 | brancher.branch(case_end) | |
| 525 | ||
| 526 | brancher.label(next) | |
| 527 | od | |
| 528 | ||
| 529 | // The no-match path. For a case-expression it is | |
| 530 | // unreachable but the verifier cannot see that, so the | |
| 531 | // throw is there to keep the stack invariant. In a session | |
| 532 | // it is reachable for either form: a later cell can add an | |
| 533 | // alternative this case never saw, and a statement-form | |
| 534 | // case would otherwise run no arm and carry on as though it | |
| 535 | // had. An ordinary build's statement form needs neither. | |
| 536 | if | |
| 537 | `case.is_exhaustive /\ !has_else /\ | |
| 538 | (`case.want_value \/ _context.is_submission) | |
| 539 | then | |
| 540 | add( | |
| 541 | Literal.STRING( | |
| 542 | "case is not exhaustive at runtime", | |
| 543 | _innate_symbol_lookup.get_string_type() | |
| 544 | ) | |
| 545 | ) | |
| 546 | add(Values.NEW_ASSERT_FAILED_EXCEPTION()) | |
| 547 | add(Values.INSTRUCTION(ILOpCode.THROW)) | |
| 548 | elif `case.want_value /\ `case.requires_default_fallthrough /\ !has_else then | |
| 549 | // Open-domain case-expression with a defaultable | |
| 550 | // expected type and no `else` arm — push default(T) | |
| 551 | // for the no-match path. CASE_EXHAUSTIVENESS_CHECKER | |
| 552 | // has emitted the `case-needs-else` warning. | |
| 553 | add(IR.Values.DEFAULT(`case.expected_type!)) | |
| 554 | fi | |
| 555 | ||
| 556 | brancher.label(case_end) | |
| 557 | ||
| 558 | super.visit(`case) | |
| 559 | ||
| 560 | spiller.leave() | |
| 561 | si | |
| 562 | ||
| 563 | // Emit a `when null` test against the case scrutinee: a presence | |
| 564 | // test, not a value compare. For a value-type optional | |
| 565 | // (`Nullable[T]`, `MAYBE[T]`) read `has_value`; for a reference | |
| 566 | // optional the receiver itself is the presence. `branch_on_absent` | |
| 567 | // selects which edge branches to `target`: a single-label `when | |
| 568 | // null` arm skips to `next` when the scrutinee is present; a null | |
| 569 | // label in a multi-label arm jumps to `match` when it is absent. | |
| 570 | gen_case_null_test( | |
| 571 | scrutinee_load: Values.Value, | |
| 572 | scrutinee_type: Semantic.Types.Type, | |
| 573 | target: IR.LABEL, | |
| 574 | branch_on_absent: bool | |
| 575 | ) is | |
| 576 | let presence: Values.Value? mut = | |
| 577 | if scrutinee_type.is_value_type then | |
| 578 | let has_value_member = scrutinee_type.find_member("has_value") | |
| 579 | ||
| 580 | if has_value_member? then | |
| 581 | has_value_member.load(LOCATION.internal, scrutinee_load, _symbol_loader) | |
| 582 | else | |
| 583 | null | |
| 584 | fi | |
| 585 | else | |
| 586 | scrutinee_load | |
| 587 | fi | |
| 588 | ||
| 589 | if presence? then | |
| 590 | if branch_on_absent then | |
| 591 | get_brancher_for_block().branch(BRANCH.Z, presence, target) | |
| 592 | else | |
| 593 | get_brancher_for_block().branch(BRANCH.NZ, presence, target) | |
| 594 | fi | |
| 595 | else | |
| 596 | let null_value = IR.Values.NULL(scrutinee_type) | |
| 597 | ||
| 598 | if branch_on_absent then | |
| 599 | get_brancher_for_block().branch(BRANCH.EQ, scrutinee_load, null_value, target) | |
| 600 | else | |
| 601 | get_brancher_for_block().branch(BRANCH.NE, scrutinee_load, null_value, target) | |
| 602 | fi | |
| 603 | fi | |
| 604 | si | |
| 605 | ||
| 606 | pre(match: Statements.CASE_MATCH) -> bool is | |
| 607 | super.pre(match) | |
| 608 | ||
| 609 | return true | |
| 610 | si | |
| 611 | ||
| 612 | visit(match: Statements.CASE_MATCH) is | |
| 613 | match.statements.accept(self) | |
| 614 | ||
| 615 | super.visit(match) | |
| 616 | si | |
| 617 | ||
| 618 | si | |
| 619 | si |