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