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 | _awaitable_resolver: Semantic.AWAITABLE_RESOLVER | |
| 24 | _flow: NARROWING_FLOW | |
| 25 | _build_flags: Compiler.GLOBAL_BUILD_FLAGS | |
| 26 | _value_boxer: IR.VALUE_BOXER | |
| 27 | _pure_slots: PURE_SLOT_CHECK | |
| 28 | _visitor: COMPILE_EXPRESSIONS | |
| 29 | _match_propagator: Semantic.MATCH_PROPAGATOR | |
| 30 | ||
| 31 | // Read-only accessor: COMPILE_EXPRESSIONS (the | |
| 32 | // expression-body return path) goes through `_bindings` as | |
| 33 | // its single shared facade and reaches the TASK helpers | |
| 34 | // via this property. | |
| 35 | task_conversion: Semantic.TASK_CONVERSION => _task_conversion | |
| 36 | ||
| 37 | init( | |
| 38 | logger: Logger, | |
| 39 | symbol_table: Semantic.SYMBOL_TABLE, | |
| 40 | symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS, | |
| 41 | innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup, | |
| 42 | task_conversion: Semantic.TASK_CONVERSION, | |
| 43 | awaitable_resolver: Semantic.AWAITABLE_RESOLVER, | |
| 44 | flow: NARROWING_FLOW, | |
| 45 | build_flags: Compiler.GLOBAL_BUILD_FLAGS, | |
| 46 | value_boxer: IR.VALUE_BOXER, | |
| 47 | pure_slots: PURE_SLOT_CHECK, | |
| 48 | visitor: COMPILE_EXPRESSIONS | |
| 49 | ) is | |
| 50 | super.init() | |
| 51 | ||
| 52 | _logger = logger | |
| 53 | _symbol_table = symbol_table | |
| 54 | _symbol_use_locations = symbol_use_locations | |
| 55 | _innate_symbol_lookup = innate_symbol_lookup | |
| 56 | _task_conversion = task_conversion | |
| 57 | _awaitable_resolver = awaitable_resolver | |
| 58 | _flow = flow | |
| 59 | _build_flags = build_flags | |
| 60 | _value_boxer = value_boxer | |
| 61 | _pure_slots = pure_slots | |
| 62 | _visitor = visitor | |
| 63 | _match_propagator = Semantic.MATCH_PROPAGATOR(logger) | |
| 64 | si | |
| 65 | ||
| 66 | pre_let(`let: Trees.Statements.LET) -> bool is | |
| 67 | // Reset is_defined on each LHS variable at the start of | |
| 68 | // every body-retry walk of the let. Without this reset, | |
| 69 | // an earlier walk's define() call leaves is_defined=true, | |
| 70 | // and check_is_defined in `load_captured_value` no longer | |
| 71 | // fires for a forward self-reference inside the RHS | |
| 72 | // lambda on subsequent walks (`let f = ... f ...` is the | |
| 73 | // canonical case). The error gets rolled back per iter | |
| 74 | // via mark_consumed_any rolling back diagnostics, but | |
| 75 | // the final iter's error survives so the user-visible | |
| 76 | // diagnostic is preserved. | |
| 77 | for v in `let.variables do | |
| 78 | for name in v.names do | |
| 79 | let symbol = _visitor.find(name) | |
| 80 | if symbol? /\ isa Semantic.Symbols.Variable(symbol) then | |
| 81 | let variable = symbol | |
| 82 | variable.is_defined = false | |
| 83 | fi | |
| 84 | od | |
| 85 | od | |
| 86 | ||
| 87 | return false | |
| 88 | si | |
| 89 | ||
| 90 | visit_let(`let: Trees.Statements.LET) is | |
| 91 | // A non-mut local must carry an initializer — an | |
| 92 | // immutable local variable without a value at its | |
| 93 | // declaration could only ever be set by a later | |
| 94 | // assignment, and those are rejected at the store site | |
| 95 | // below. Catching it here keeps the diagnostic on the | |
| 96 | // declaration that is missing the `= expr`. | |
| 97 | for v in `let.variables do | |
| 98 | if !v.initializer? /\ !v.is_mutable_marked then | |
| 99 | _logger.error(v.location, "local value must be initialized") | |
| 100 | fi | |
| 101 | od | |
| 102 | ||
| 103 | // Definite assignment: a `let` binding with no | |
| 104 | // initializer introduces a deferred-init local. Until it | |
| 105 | // is assigned, a read of it is a use-before-assignment. | |
| 106 | if !_build_flags.no_warn_definite_assignment then | |
| 107 | for v in `let.variables do | |
| 108 | if !v.initializer? then | |
| 109 | for name in v.names do | |
| 110 | let symbol = _visitor.find(name) | |
| 111 | ||
| 112 | if symbol? /\ isa Semantic.Symbols.Variable(symbol) then | |
| 113 | _flow.track_deferred(symbol) | |
| 114 | fi | |
| 115 | od | |
| 116 | fi | |
| 117 | od | |
| 118 | fi | |
| 119 | ||
| 120 | if `let.want_dispose then | |
| 121 | // A parenthesised block's statements are not lowered | |
| 122 | // through the statement-list path that builds a | |
| 123 | // disposal region, so a local declared directly in one | |
| 124 | // has nothing to dispose it. Reported here rather than | |
| 125 | // left to generate-il, which cannot report anything. | |
| 126 | if | |
| 127 | let block = _visitor.innermost_val_block /\ | |
| 128 | block.body == _visitor.current_statement_list | |
| 129 | then | |
| 130 | _logger.error( | |
| 131 | `let.location, | |
| 132 | "cannot dispose a local declared in a parenthesised block") | |
| 133 | fi | |
| 134 | ||
| 135 | let idisposable = _innate_symbol_lookup.get_idisposable_type() | |
| 136 | ||
| 137 | for v in `let.variables do | |
| 138 | for name in v.names do | |
| 139 | let symbol = _visitor.find(name) | |
| 140 | ||
| 141 | if symbol? /\ isa Semantic.Symbols.Variable(symbol) then | |
| 142 | let variable = symbol | |
| 143 | variable.is_disposed = true | |
| 144 | ||
| 145 | let type = variable.type | |
| 146 | ||
| 147 | if type? /\ !idisposable.is_assignable_from(type) then | |
| 148 | _logger.error(name.location, "not disposable") | |
| 149 | fi | |
| 150 | ||
| 151 | _visitor.current_statement_list.add_variable_to_dispose(variable) | |
| 152 | fi | |
| 153 | od | |
| 154 | od | |
| 155 | fi | |
| 156 | si | |
| 157 | ||
| 158 | pre_simple_left(left: Trees.Expressions.SIMPLE_LEFT_EXPRESSION) -> bool is | |
| 159 | if let left.value? then | |
| 160 | if !value.is_consumable then | |
| 161 | _logger.error(left.location, "cannot use this here") | |
| 162 | return false | |
| 163 | fi | |
| 164 | ||
| 165 | left.expression.compile_expressions_state.value = Need.STORE(value) | |
| 166 | fi | |
| 167 | ||
| 168 | return false | |
| 169 | si | |
| 170 | ||
| 171 | visit_simple_left(left: Trees.Expressions.SIMPLE_LEFT_EXPRESSION) is | |
| 172 | let value = left.value | |
| 173 | let expression_value = left.expression.value | |
| 174 | ||
| 175 | if !value? \/ !value.type? \/ !expression_value? \/ !expression_value.type? then | |
| 176 | return | |
| 177 | fi | |
| 178 | ||
| 179 | // Snapshot the presence-narrowed type before the queries | |
| 180 | // below cross the fact it was read through. | |
| 181 | let value_type = value.type | |
| 182 | ||
| 183 | if !expression_value.is_consumable then | |
| 184 | _logger.error(left.location, "cannot assign to this") | |
| 185 | return | |
| 186 | fi | |
| 187 | ||
| 188 | // Bypass narrowing for assignability — the LHS value's | |
| 189 | // type may be a narrowed view, but assignment must | |
| 190 | // typecheck against the variable's declared type, or | |
| 191 | // `if isa T(x) then x = wider_value` (idiomatic across | |
| 192 | // the codebase) would fail at the narrow. | |
| 193 | let target_variable = _visitor.try_get_narrowing_target(left.expression) | |
| 194 | ||
| 195 | // A member of the enclosing type is recorded as written | |
| 196 | // whether or not the narrowing target agrees - see | |
| 197 | // try_get_member_assigned. Locals are left to the | |
| 198 | // mark_assigned below, which the narrowing target covers. | |
| 199 | let member_target = _visitor.try_get_member_assigned(left.expression) | |
| 200 | ||
| 201 | if member_target? then | |
| 202 | _flow.mark_assigned(member_target) | |
| 203 | fi | |
| 204 | ||
| 205 | let lhs_type = | |
| 206 | if target_variable? then | |
| 207 | // A local assigned before the declaration that types | |
| 208 | // it - an assignment inside the local's own | |
| 209 | // initializer - has no declared type to check | |
| 210 | // against, and the read that reached it is already | |
| 211 | // reported. What the target compiled to is then all | |
| 212 | // there is to judge by. | |
| 213 | _flow.declared_type_of(target_variable) ?? expression_value.type! | |
| 214 | else | |
| 215 | expression_value.type! | |
| 216 | fi | |
| 217 | ||
| 218 | if !lhs_type.is_assignable_from(value_type) then | |
| 219 | _logger.error(left.assign_location, "{value_type} is not assignable to {lhs_type}") | |
| 220 | return | |
| 221 | fi | |
| 222 | ||
| 223 | // Read the RHS static type and non-optional-ness off the | |
| 224 | // RHS expression value before the overwrite below replaces | |
| 225 | // `left.value` with the Store.SYMBOL IR whose type is the | |
| 226 | // variable's declared type — so a `BOX? mut` slot would mask | |
| 227 | // a non-null RHS like `BOX(1)`. | |
| 228 | let rhs_type = value_type | |
| 229 | let rhs_is_non_optional = _visitor.is_non_optional_value(value) | |
| 230 | ||
| 231 | left.compile_expressions_state.value = expression_value | |
| 232 | ||
| 233 | // Reassignment invalidates any narrow for the target — | |
| 234 | // the new value may not satisfy it, so subsequent reads | |
| 235 | // observe the declared type — and makes the target | |
| 236 | // definitely assigned from here on. | |
| 237 | if target_variable? then | |
| 238 | // Kill the invalidated facts and re-narrow to the RHS | |
| 239 | // static type when it is more specific than the | |
| 240 | // declared type, so a later read sees DOG after | |
| 241 | // `pet = DOG()`. The transfer also emits the editor | |
| 242 | // hint for whichever of kill / re-narrow applied. | |
| 243 | let narrowed = _flow.on_assignment(target_variable, left.assign_location, rhs_type) | |
| 244 | ||
| 245 | _flow.mark_assigned(target_variable) | |
| 246 | ||
| 247 | // A non-optional RHS leaves the target known to hold | |
| 248 | // a value — so `_field = arg; _field.method()` does | |
| 249 | // not warn. Keyed off the declared type, not the | |
| 250 | // possibly-narrowed view above: the presence fact lives | |
| 251 | // in its own lattice channel and must still be recorded | |
| 252 | // when a branch also type-narrows, so it composes at a | |
| 253 | // join with a sibling branch that carries only presence. | |
| 254 | let declared_target = _flow.declared_type_of(target_variable) | |
| 255 | let want_presence = | |
| 256 | rhs_is_non_optional /\ | |
| 257 | declared_target? /\ | |
| 258 | declared_target.is_optional | |
| 259 | ||
| 260 | if want_presence then | |
| 261 | _flow.mark_non_null(target_variable) | |
| 262 | ||
| 263 | // A type narrow's hint has already been emitted by | |
| 264 | // the transfer; the presence-only view is a hint of | |
| 265 | // its own only when no narrow applied. | |
| 266 | if !narrowed? then | |
| 267 | _flow.report_narrowing_site( | |
| 268 | left.assign_location, | |
| 269 | "narrowing-assign", | |
| 270 | "►", | |
| 271 | INLAY_TYPE.render(target_variable.type!.as_non_optional()) | |
| 272 | ) | |
| 273 | fi | |
| 274 | fi | |
| 275 | fi | |
| 276 | si | |
| 277 | ||
| 278 | visit_let_in(let_in: Trees.Expressions.LET_IN) is | |
| 279 | if let let_in.expression.value? /\ value.check_is_consumable_allow_void(_logger, let_in.expression.location) then | |
| 280 | let_in.compile_expressions_state.value = value | |
| 281 | else | |
| 282 | let_in.compile_expressions_state.value = IR.Values.DUMMY(Semantic.Types.ERROR(), let_in.location) | |
| 283 | fi | |
| 284 | si | |
| 285 | ||
| 286 | pre_assignment(assignment: Trees.Statements.ASSIGNMENT) -> bool is | |
| 287 | // A member / index target reads its receiver before the | |
| 288 | // right-hand side runs, so a call there must not drop a field | |
| 289 | // narrow the receiver relies on. Shield that field across the | |
| 290 | // whole assignment; once the target is walked, drop it iff a | |
| 291 | // call actually occurred, so later statements still see the | |
| 292 | // post-call state. | |
| 293 | let receiver_field = _try_get_assignment_receiver_field(assignment.left) | |
| 294 | let shield_frame = _flow.push_shield(receiver_field) | |
| 295 | ||
| 296 | // Push the LHS type down to the RHS as a constraint. The | |
| 297 | // RHS expression node is responsible for either consuming | |
| 298 | // it (FUNCTION uses it for argument-type inference, | |
| 299 | // SEQUENCE / TUPLE forward to their elements) or ignoring | |
| 300 | // it (literals, identifiers — Expression's default | |
| 301 | // set_constraint is a no-op). The post-walk assignability | |
| 302 | // check still verifies type correctness for the cases | |
| 303 | // where the RHS doesn't act on the constraint. | |
| 304 | // | |
| 305 | // When the LHS is itself a not-yet-resolved placeholder | |
| 306 | // (`let l; ...; l = X` shape), the LHS type is not | |
| 307 | // useful as a constraint to push *down*. Instead, after | |
| 308 | // walking the RHS, push the RHS's type back to the | |
| 309 | // LHS variable's symbol via add_constraint — the | |
| 310 | // existing iterative-inference machinery will collapse | |
| 311 | // the LUB across all assignments on the next retry- | |
| 312 | // loop iteration. | |
| 313 | let lhs_type = _try_get_assignment_left_type(assignment.left) | |
| 314 | ||
| 315 | // `!is_inferred` (not !is_sentinel or is_settled): | |
| 316 | // we're asking "is the LHS anything other than a | |
| 317 | // bare placeholder?". ERROR is pre-filtered by | |
| 318 | // _try_get_assignment_left_type. A composite-with- | |
| 319 | // placeholder LHS like Function[placeholder, int] | |
| 320 | // *is* useful as an RHS constraint — the placeholder | |
| 321 | // arg slot is filled by the lambda's body inference, | |
| 322 | // and the return slot does its job. | |
| 323 | if lhs_type? /\ !lhs_type.is_inferred /\ !lhs_type.is_error then | |
| 324 | assignment.right.set_expected_type(lhs_type, "{{0}} is not assignable to {{1}}") | |
| 325 | fi | |
| 326 | ||
| 327 | assignment.right.walk(_visitor) | |
| 328 | ||
| 329 | let right_value = assignment.right.value | |
| 330 | ||
| 331 | if !right_value? then | |
| 332 | return true | |
| 333 | fi | |
| 334 | ||
| 335 | if !right_value.is_consumable then | |
| 336 | _logger.error(assignment.right.location, "cannot use this here") | |
| 337 | assignment.left.compile_expressions_state.value = IR.Values.DUMMY(Semantic.Types.ERROR(), assignment.right.location) | |
| 338 | else | |
| 339 | assignment.left.compile_expressions_state.value = right_value | |
| 340 | ||
| 341 | _visitor.check_non_optional(lhs_type, assignment.right, assignment.right.location) | |
| 342 | ||
| 343 | _pure_slots.check_store(assignment.right.location, lhs_type, right_value) | |
| 344 | fi | |
| 345 | ||
| 346 | // Back-feed the RHS type as a constraint onto a | |
| 347 | // placeholder-typed LHS variable (`let l; l = X` etc.). | |
| 348 | // The next retry-loop iteration of the enclosing | |
| 349 | // function-body walk picks up the LUB via | |
| 350 | // try_get_inferred_type at visit(SIMPLE_VARIABLE_LEFT). | |
| 351 | // | |
| 352 | // `!is_settled` (not is_inferred or contains_inferred): a | |
| 353 | // provisional composite LHS like `Func[List[int], | |
| 354 | // INFERRED_RETURN_TYPE]` — recorded on an early iter when | |
| 355 | // the lambda's body was walked before the return type | |
| 356 | // resolved — still needs refining once the placeholder | |
| 357 | // inside settles. Same goes for an ERROR-carrying | |
| 358 | // composite like `(ERROR, int)` left over from an iter | |
| 359 | // where a sub-expression poisoned to DUMMY(ERROR): | |
| 360 | // a later iter may produce a clean `(int, int)` value, | |
| 361 | // and without the match propagation the LUB never picks it up | |
| 362 | // and the per-position merge prefers the stale ERROR | |
| 363 | // slot. `is_settled` ("no placeholders, no errors") | |
| 364 | // captures both senses in one predicate. | |
| 365 | if | |
| 366 | lhs_type? /\ | |
| 367 | (!lhs_type.is_settled \/ _is_joining_local(assignment.left)) /\ | |
| 368 | right_value.type? /\ | |
| 369 | _try_propagate_assignment_lhs(assignment.left, right_value.type) | |
| 370 | then | |
| 371 | _logger.mark_consumed_any() | |
| 372 | fi | |
| 373 | ||
| 374 | assignment.left.assign_location = assignment.location | |
| 375 | ||
| 376 | assignment.left.walk(_visitor) | |
| 377 | ||
| 378 | // The target walk compiles the target's name, which the | |
| 379 | // narrowing flow sees as a read — but a store target is | |
| 380 | // written, not read, so any load it recorded is moot. | |
| 381 | ||
| 382 | if _flow.release_shield(shield_frame) /\ receiver_field? then | |
| 383 | _flow.forget(receiver_field) | |
| 384 | fi | |
| 385 | ||
| 386 | // A store to anything but a local can change what a | |
| 387 | // property getter returns, so property facts must not | |
| 388 | // survive it. Field facts do — a store to one field | |
| 389 | // cannot alter another, and the assignment transfer | |
| 390 | // above already handled the target itself. | |
| 391 | if _is_heap_store_target(assignment.left) then | |
| 392 | _flow.on_heap_store(assignment.location) | |
| 393 | fi | |
| 394 | ||
| 395 | assignment.compile_expressions_state.value = assignment.left.value | |
| 396 | ||
| 397 | return true | |
| 398 | si | |
| 399 | ||
| 400 | // True when an assignment target writes the heap: a member, | |
| 401 | // index, field or property target — anything except a plain | |
| 402 | // local variable or parameter. Destructuring stores to the | |
| 403 | // heap when any element does. | |
| 404 | ||
| 405 | _is_heap_store_target(left: Trees.Expressions.AssignmentLeftExpression?) -> bool is | |
| 406 | if !left? then | |
| 407 | return true | |
| 408 | fi | |
| 409 | ||
| 410 | if isa Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION(left) then | |
| 411 | for element in (cast Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION(left)).elements do | |
| 412 | if _is_heap_store_target(element) then | |
| 413 | return true | |
| 414 | fi | |
| 415 | od | |
| 416 | ||
| 417 | return false | |
| 418 | fi | |
| 419 | ||
| 420 | if !isa Trees.Expressions.SIMPLE_LEFT_EXPRESSION(left) then | |
| 421 | return true | |
| 422 | fi | |
| 423 | ||
| 424 | let expression = (cast Trees.Expressions.SIMPLE_LEFT_EXPRESSION(left)).expression | |
| 425 | ||
| 426 | if !isa Trees.Expressions.IDENTIFIER(expression) then | |
| 427 | return true | |
| 428 | fi | |
| 429 | ||
| 430 | let identifier = (cast Trees.Expressions.IDENTIFIER(expression)).identifier | |
| 431 | ||
| 432 | if identifier.is_qualified then | |
| 433 | return true | |
| 434 | fi | |
| 435 | ||
| 436 | let symbol = _visitor.try_find(identifier) | |
| 437 | ||
| 438 | return | |
| 439 | !symbol? \/ | |
| 440 | !(isa Semantic.Symbols.LOCAL_VARIABLE(symbol) \/ isa Semantic.Symbols.LOCAL_ARGUMENT(symbol)) | |
| 441 | si | |
| 442 | ||
| 443 | // The field variable a member / index assignment target reads | |
| 444 | // as its receiver — the shield subject in `pre_assignment`. | |
| 445 | // Null unless the target is `field.member` / `field[index]` | |
| 446 | // rooted at a bare field identifier. | |
| 447 | _try_get_assignment_receiver_field(left: Trees.Expressions.AssignmentLeftExpression) -> Semantic.Symbols.Symbol? is | |
| 448 | let simple = cast Trees.Expressions.SIMPLE_LEFT_EXPRESSION?(left) | |
| 449 | ||
| 450 | if !simple? then | |
| 451 | return null | |
| 452 | fi | |
| 453 | ||
| 454 | let target = simple.expression | |
| 455 | let receiver = | |
| 456 | if isa Trees.Expressions.MEMBER(target) then | |
| 457 | (cast Trees.Expressions.MEMBER(target)).left | |
| 458 | elif isa Trees.Expressions.INDEX(target) then | |
| 459 | (cast Trees.Expressions.INDEX(target)).left | |
| 460 | else | |
| 461 | null | |
| 462 | fi | |
| 463 | ||
| 464 | if !receiver? then | |
| 465 | return null | |
| 466 | fi | |
| 467 | ||
| 468 | return _visitor.try_get_narrowing_target(receiver) | |
| 469 | si | |
| 470 | ||
| 471 | // If the assignment's LHS is a simple identifier referring | |
| 472 | // to a Variable whose current type is an inference | |
| 473 | // placeholder, push the supplied RHS type onto the | |
| 474 | // variable's LUB accumulator. Returns true when a real | |
| 475 | // constraint actually landed (so the caller can signal | |
| 476 | // progress to the retry loop). | |
| 477 | // A `mut` local whose type is the join of its initializer and its | |
| 478 | // assignments: each assignment is a bound on that join even after | |
| 479 | // the local's type has settled. | |
| 480 | _is_joining_local(left: Trees.Expressions.AssignmentLeftExpression) -> bool is | |
| 481 | if | |
| 482 | let simple: Trees.Expressions.SIMPLE_LEFT_EXPRESSION = left, | |
| 483 | identifier: Trees.Expressions.IDENTIFIER = simple.expression | |
| 484 | then | |
| 485 | if let variable: Semantic.Symbols.Variable = _visitor.find(identifier.identifier) then | |
| 486 | return variable.joins_assignments | |
| 487 | fi | |
| 488 | fi | |
| 489 | ||
| 490 | return false | |
| 491 | si | |
| 492 | ||
| 493 | _try_propagate_assignment_lhs( | |
| 494 | left: Trees.Expressions.AssignmentLeftExpression, | |
| 495 | rhs_type: Semantic.Types.Type | |
| 496 | ) -> bool is | |
| 497 | let simple = cast Trees.Expressions.SIMPLE_LEFT_EXPRESSION?(left) | |
| 498 | ||
| 499 | if !simple? then | |
| 500 | return false | |
| 501 | fi | |
| 502 | ||
| 503 | let identifier = cast Trees.Expressions.IDENTIFIER?(simple.expression) | |
| 504 | ||
| 505 | if !identifier? then | |
| 506 | return false | |
| 507 | fi | |
| 508 | ||
| 509 | let symbol = _visitor.find(identifier.identifier) | |
| 510 | ||
| 511 | if !symbol? \/ !isa Semantic.Symbols.Variable(symbol) then | |
| 512 | return false | |
| 513 | fi | |
| 514 | ||
| 515 | let variable = symbol | |
| 516 | ||
| 517 | // A value typed over another function's type parameter - the | |
| 518 | // `T[]` a `collect_array[T]` call still resolving reports - is | |
| 519 | // no type the variable could take; a later walk supplies the | |
| 520 | // one it settles to. | |
| 521 | if rhs_type.has_function_generic_argument_foreign_to(_symbol_table.current_scope) then | |
| 522 | return false | |
| 523 | fi | |
| 524 | ||
| 525 | return Semantic.INFERENCE_TRACE.add_lower_bound("bindings.assignment_lhs", variable, rhs_type) | |
| 526 | si | |
| 527 | ||
| 528 | // The type a SIMPLE_LEFT_EXPRESSION's target stores. Destructuring | |
| 529 | // lefts are skipped — extracting their effective type needs a | |
| 530 | // tuple-shape construction we don't have yet. | |
| 531 | _try_get_assignment_left_type(left: Trees.Expressions.AssignmentLeftExpression) -> Type? is | |
| 532 | let simple = cast Trees.Expressions.SIMPLE_LEFT_EXPRESSION?(left) | |
| 533 | ||
| 534 | if !simple? then | |
| 535 | return null | |
| 536 | fi | |
| 537 | ||
| 538 | let (resolved, type) = _resolve_assignment_target_type(simple.expression) | |
| 539 | ||
| 540 | if resolved then | |
| 541 | return type | |
| 542 | fi | |
| 543 | ||
| 544 | return _probe_assignment_left_type(simple) | |
| 545 | si | |
| 546 | ||
| 547 | // Read the target's type off its symbol without compiling it, | |
| 548 | // where the target names one: a local, field or readable | |
| 549 | // property, `self.member`, or the pointee of a `ref` local. | |
| 550 | // Resolving the name can report it missing, so the logger is | |
| 551 | // rolled back to keep the lookup invisible, as the probe is. | |
| 552 | _resolve_assignment_target_type(target: Trees.Expressions.Expression) -> (bool, Type?) is | |
| 553 | let mark = _logger.mark() | |
| 554 | ||
| 555 | _logger.speculate() | |
| 556 | ||
| 557 | try | |
| 558 | return _resolve_assignment_target_type_silently(target) | |
| 559 | finally | |
| 560 | _logger.roll_back() | |
| 561 | _logger.release(mark) | |
| 562 | yrt | |
| 563 | si | |
| 564 | ||
| 565 | _resolve_assignment_target_type_silently(target: Trees.Expressions.Expression) -> (bool, Type?) is | |
| 566 | let symbol = _find_assignment_target_symbol(target) | |
| 567 | ||
| 568 | if !symbol? then | |
| 569 | return (false, null) | |
| 570 | fi | |
| 571 | ||
| 572 | let type = _flow.declared_type_of(symbol) | |
| 573 | ||
| 574 | if !type? then | |
| 575 | return (false, null) | |
| 576 | fi | |
| 577 | ||
| 578 | if isa Trees.Expressions.UNWRAP(target) then | |
| 579 | if !type.is_ref then | |
| 580 | return (false, null) | |
| 581 | fi | |
| 582 | ||
| 583 | let element_type = type.get_element_type() | |
| 584 | ||
| 585 | if !element_type? then | |
| 586 | return (false, null) | |
| 587 | fi | |
| 588 | ||
| 589 | return (true, element_type) | |
| 590 | fi | |
| 591 | ||
| 592 | return (true, type) | |
| 593 | si | |
| 594 | ||
| 595 | _find_assignment_target_symbol(target: Trees.Expressions.Expression) -> Semantic.Symbols.Symbol? is | |
| 596 | if let unwrap: Trees.Expressions.UNWRAP = target then | |
| 597 | return _visitor.try_get_narrowing_target(unwrap.left) | |
| 598 | fi | |
| 599 | ||
| 600 | let narrowing_target = _visitor.try_get_narrowing_target(target) | |
| 601 | ||
| 602 | if narrowing_target? then | |
| 603 | return narrowing_target | |
| 604 | fi | |
| 605 | ||
| 606 | let member = cast Trees.Expressions.MEMBER?(target) | |
| 607 | ||
| 608 | if | |
| 609 | !member? \/ | |
| 610 | !isa Trees.Expressions.SELF(member.left) \/ | |
| 611 | member.is_coalesce \/ | |
| 612 | member.identifier.is_qualified | |
| 613 | then | |
| 614 | return null | |
| 615 | fi | |
| 616 | ||
| 617 | let context = _visitor.current_instance_context | |
| 618 | ||
| 619 | if !context? then | |
| 620 | return null | |
| 621 | fi | |
| 622 | ||
| 623 | let symbol = context.find_direct(member.identifier.name) | |
| 624 | ||
| 625 | if isa Semantic.Symbols.Variable(symbol) then | |
| 626 | return symbol | |
| 627 | fi | |
| 628 | ||
| 629 | if let property: Semantic.Symbols.Property = symbol /\ property.read_function? then | |
| 630 | return property | |
| 631 | fi | |
| 632 | ||
| 633 | return null | |
| 634 | si | |
| 635 | ||
| 636 | // For targets whose type depends on compiling them - an indexer, | |
| 637 | // a member reached through anything but `self` - speculatively | |
| 638 | // walk the target as a regular load, read its type, and roll | |
| 639 | // back the logger so the probe is invisible to later passes. | |
| 640 | // The probe walks the live expression rather than a copy: the | |
| 641 | // subsequent normal walk through pre(SIMPLE_LEFT_EXPRESSION) | |
| 642 | // overwrites the inner expression's value with the STORE form | |
| 643 | // anyway, so the probe's read-form value never escapes. | |
| 644 | _probe_assignment_left_type(simple: Trees.Expressions.SIMPLE_LEFT_EXPRESSION) -> Type? is | |
| 645 | let mark = _logger.mark() | |
| 646 | let type: Type? mut = _ | |
| 647 | ||
| 648 | // The probe's walk must be invisible to the symbol-use | |
| 649 | // maps as well as to the logger: it compiles the target | |
| 650 | // as a read, and the read-shaped use it would record | |
| 651 | // out-ranks the store-shaped one the real walk records | |
| 652 | // at the same location (first recorded wins a hover tie). | |
| 653 | _symbol_use_locations.begin_suppress() | |
| 654 | ||
| 655 | try | |
| 656 | let use retry_site = RETRY_SITE_STATS.enter("bindings.assignment_target_probe", RetrySiteKind.ALTERNATIVE) | |
| 657 | _logger.speculate() | |
| 658 | ||
| 659 | simple.expression.walk(_visitor) | |
| 660 | ||
| 661 | let expression_value = simple.expression.value | |
| 662 | ||
| 663 | // is_error left unfiltered here: an ERROR-bearing | |
| 664 | // composite (e.g. `(ERROR, int)` set by an earlier | |
| 665 | // iter on a delayed-init variable whose first-seen | |
| 666 | // RHS poisoned to DUMMY(ERROR)) is exactly the case | |
| 667 | // the assignment match propagation below needs to recognise | |
| 668 | // — without the LHS type, the match propagation gate | |
| 669 | // gets null, no refinement constraint lands, and | |
| 670 | // the LUB stays frozen. Call sites that don't want | |
| 671 | // to pass ERROR onward (the set_constraint push to | |
| 672 | // RHS) gate it themselves. | |
| 673 | if expression_value? /\ expression_value.type? then | |
| 674 | let target_variable = _visitor.try_get_narrowing_target(simple.expression) | |
| 675 | ||
| 676 | type = | |
| 677 | if target_variable? then | |
| 678 | _flow.declared_type_of(target_variable) | |
| 679 | else | |
| 680 | expression_value.type | |
| 681 | fi | |
| 682 | fi | |
| 683 | ||
| 684 | _logger.roll_back() | |
| 685 | _logger.release(mark) | |
| 686 | catch e: Exception | |
| 687 | _logger.release(mark) | |
| 688 | return null | |
| 689 | finally | |
| 690 | _symbol_use_locations.end_suppress() | |
| 691 | yrt | |
| 692 | ||
| 693 | return type | |
| 694 | si | |
| 695 | ||
| 696 | visit_expression_statement(expression: Trees.Statements.EXPRESSION) is | |
| 697 | let inner_value = expression.expression.value | |
| 698 | if inner_value? then | |
| 699 | Value.check_is_consumable_allow_void(_logger, expression.expression.location, inner_value) | |
| 700 | ||
| 701 | // A call whose own type parameter appears only in its | |
| 702 | // return type is left carrying it for whatever consumes | |
| 703 | // the value to settle, and a statement consumes nothing, | |
| 704 | // so nothing will. Reported only on an otherwise clean | |
| 705 | // walk, where it is not the consequence of another error. | |
| 706 | if | |
| 707 | _logger.is_clean /\ | |
| 708 | inner_value.type? /\ | |
| 709 | inner_value.type.has_function_generic_argument_foreign_to(_symbol_table.current_function) | |
| 710 | then | |
| 711 | _logger.error(expression.expression.location, "cannot infer type here") | |
| 712 | fi | |
| 713 | fi | |
| 714 | ||
| 715 | if !expression.want_value then | |
| 716 | if expression.expression.must_be_consumed then | |
| 717 | _logger.error(expression.expression.location, "{expression.expression.description} result is not used") | |
| 718 | fi | |
| 719 | ||
| 720 | // A re-walk that no longer wants a value (e.g. the | |
| 721 | // enclosing literal's return type settled to void | |
| 722 | // between walks) must not leave behind the value an | |
| 723 | // earlier value-wanting walk recorded on the | |
| 724 | // statement — generate-il's val-block tail emission | |
| 725 | // reads the last statement's value even for a void | |
| 726 | // block and would emit that stale IR alongside the | |
| 727 | // fresh one. | |
| 728 | expression.compile_expressions_state.value = null | |
| 729 | ||
| 730 | return | |
| 731 | fi | |
| 732 | ||
| 733 | expression.compile_expressions_state.value = expression.expression.value | |
| 734 | si | |
| 735 | ||
| 736 | pre_return(r: Trees.Statements.RETURN) -> bool is | |
| 737 | // Stamp the innermost-val-block target onto the RETURN | |
| 738 | // AST node so visit_return and generate-il dispatch | |
| 739 | // consistently from a single source of truth, even if the | |
| 740 | // val-block stack is unwound by the time those fire. | |
| 741 | r.val_block_target = _visitor.innermost_val_block | |
| 742 | ||
| 743 | if let target = r.val_block_target then | |
| 744 | target.has_targeted_return = true | |
| 745 | fi | |
| 746 | ||
| 747 | if !r.expression? then | |
| 748 | return false | |
| 749 | fi | |
| 750 | ||
| 751 | if let block = r.val_block_target then | |
| 752 | // Return targets a val-block — push the block's | |
| 753 | // pushed-down expected_type (if any) onto the return | |
| 754 | // expression so it participates in inference / | |
| 755 | // overload resolution against the consumer's context, | |
| 756 | // mirroring the function-return path. | |
| 757 | if let expected = block.expected_type then | |
| 758 | if let error_message = block.expected_type_error_message then | |
| 759 | r.expression.set_expected_type(expected, error_message) | |
| 760 | fi | |
| 761 | fi | |
| 762 | ||
| 763 | return false | |
| 764 | fi | |
| 765 | ||
| 766 | let function = _symbol_table.current_function | |
| 767 | ||
| 768 | assert function? else "return outside a function" | |
| 769 | ||
| 770 | if | |
| 771 | !function.return_type? \/ | |
| 772 | function.return_type.is_sentinel \/ | |
| 773 | function.return_type.matches(_innate_symbol_lookup.get_void_type()) | |
| 774 | then | |
| 775 | return false | |
| 776 | fi | |
| 777 | ||
| 778 | r.expression!.set_expected_type(function.return_type!, "cannot return value of type {{0}} where {{1}} expected") | |
| 779 | ||
| 780 | return false | |
| 781 | si | |
| 782 | ||
| 783 | visit_return(r: Trees.Statements.RETURN) is | |
| 784 | // Control does not fall through a return — the rest of | |
| 785 | // the enclosing block is unreachable. | |
| 786 | _flow.set_unreachable() | |
| 787 | ||
| 788 | if let block = r.val_block_target then | |
| 789 | // Return targets the innermost enclosing val-block: | |
| 790 | // collect the expression's type into the block's LUB | |
| 791 | // pool and stop here. Function-level return-type | |
| 792 | // inference, async-SM rewiring, and the various | |
| 793 | // assignability / wrap rules below are scoped to | |
| 794 | // function-return; a val-targeted return is a | |
| 795 | // local control transfer, not a function exit. | |
| 796 | if let expression = r.expression then | |
| 797 | if let value = expression.value then | |
| 798 | if let value_type = value.type then | |
| 799 | if !value.check_is_consumable(_logger, expression.location) then | |
| 800 | return | |
| 801 | fi | |
| 802 | ||
| 803 | if !value_type.is_error then | |
| 804 | if let expected = block.expected_type then | |
| 805 | // Honour the pushed-down expected_type | |
| 806 | // the same way function-return does: | |
| 807 | // error if the expression's type isn't | |
| 808 | // assignable. | |
| 809 | if !expected.is_void /\ !expected.is_assignable_from(value_type) then | |
| 810 | let error_message mut = block.expected_type_error_message | |
| 811 | if !error_message? then | |
| 812 | error_message = "cannot return value of type {{0}} where {{1}} expected" | |
| 813 | fi | |
| 814 | _logger.error( | |
| 815 | expression.location, | |
| 816 | string.format(error_message, value_type, expected) | |
| 817 | ) | |
| 818 | return | |
| 819 | fi | |
| 820 | fi | |
| 821 | ||
| 822 | block.return_types.add(value_type) | |
| 823 | fi | |
| 824 | fi | |
| 825 | fi | |
| 826 | else | |
| 827 | // Bare `return;` inside a val-block. Diagnose | |
| 828 | // only when an outer expected_type makes the | |
| 829 | // value requirement unambiguous (typed `let` | |
| 830 | // initializer, function argument, value- | |
| 831 | // returning `=> body`). want_value alone reads | |
| 832 | // stale for inferred-return lambdas — the | |
| 833 | // lambda's return type isn't pinned until after | |
| 834 | // the body walks, so a bare return that's | |
| 835 | // consistent with a void inference would error | |
| 836 | // here prematurely. Generate-il stores the | |
| 837 | // default of the result type on the no- | |
| 838 | // diagnostic path so the IL still verifies. | |
| 839 | if let expected = block.expected_type then | |
| 840 | if !expected.is_void then | |
| 841 | _logger.error(r.location, "return without value from val block requiring a value") | |
| 842 | fi | |
| 843 | fi | |
| 844 | fi | |
| 845 | ||
| 846 | return | |
| 847 | fi | |
| 848 | ||
| 849 | let function = _symbol_table.current_function | |
| 850 | ||
| 851 | if !function? \/ !function.return_type? then | |
| 852 | // FIXME: null function happens for properties, null return type happens for anonymous functions | |
| 853 | return | |
| 854 | fi | |
| 855 | ||
| 856 | // State-machine async: `return X` provides X of type T | |
| 857 | // (the Task element type), generate-il stashes X into | |
| 858 | // `_result` and leaves to success_label. | |
| 859 | let async_sm = Semantic.Symbols.async_state_machine_for(function) | |
| 860 | if async_sm? /\ async_sm.frame? then | |
| 861 | _visit_return_state_machine_async(r, function, async_sm) | |
| 862 | return | |
| 863 | fi | |
| 864 | ||
| 865 | // A value whose type is still a placeholder has to fit the | |
| 866 | // declared return type, which bounds it as an argument slot | |
| 867 | // bounds a placeholder passed to it. | |
| 868 | if | |
| 869 | let value = r.expression?.value /\ | |
| 870 | isa Semantic.Types.INFERRED_VARIABLE_TYPE(value.type) /\ | |
| 871 | !function.return_type!.is_sentinel | |
| 872 | then | |
| 873 | _match_propagator.propagate_match(function.return_type!, value.type!) | |
| 874 | fi | |
| 875 | ||
| 876 | if r.expression? then | |
| 877 | if let | |
| 878 | r.expression.value? /\ | |
| 879 | value.type? /\ | |
| 880 | value.check_is_consumable(_logger, r.expression.location) | |
| 881 | then | |
| 882 | let null_join = Semantic.ARM_NULL_JOIN() | |
| 883 | ||
| 884 | // A null says a value can be absent without saying | |
| 885 | // what it holds when present, so it settles an | |
| 886 | // inferred return at nothing: it records that the | |
| 887 | // return is optional, and leaves what it is optional | |
| 888 | // of to the other returns, or to the slot the | |
| 889 | // literal goes into. | |
| 890 | if function.return_type!.is_inferred /\ null_join.is_genuine_null(value.type!) then | |
| 891 | function.returned_genuine_null = true | |
| 892 | elif function.return_type!.is_inferred then | |
| 893 | // Filling in a previously-undeclared return | |
| 894 | // type from this return statement. is_inferred | |
| 895 | // (today only INFERRED_RETURN_TYPE) rather than | |
| 896 | // is_sentinel: a function whose return type was | |
| 897 | // already set to ERROR by an earlier failing | |
| 898 | // return shouldn't get silently overwritten | |
| 899 | // with a concrete type by a later (validly- | |
| 900 | // typed) return — the original error should | |
| 901 | // remain reported and the return-type contract | |
| 902 | // should stay ERROR until the user fixes the | |
| 903 | // source. | |
| 904 | // | |
| 905 | // Async closures: wrap a bare-T body return | |
| 906 | // to Task[T] before pinning, so the closure's | |
| 907 | // signature matches its SM emission shape. | |
| 908 | // Values already typed Task[?] pass through. | |
| 909 | let value_type: Semantic.Types.Type? mut = value.type! | |
| 910 | ||
| 911 | if | |
| 912 | function.wrap_inferred_return_as_task /\ | |
| 913 | value_type.is_settled /\ | |
| 914 | !_task_conversion.is_task_type(value_type) | |
| 915 | then | |
| 916 | let task_type = _innate_symbol_lookup.get_task_type(value_type) | |
| 917 | ||
| 918 | // Wrap as `Tasks.TASK.from_result(orig)`. | |
| 919 | // Guard on `is_settled` so we don't fire | |
| 920 | // during iterative-inference walks with a | |
| 921 | // partially-resolved body type — those | |
| 922 | // would over-wrap to `Task[Task[?]]`. | |
| 923 | if task_type? /\ _task_conversion.try_wrap_value_as_task_return(r, task_type, _visitor) then | |
| 924 | // The wrap replaced r.expression and | |
| 925 | // re-walked it — read the fresh value, | |
| 926 | // not the pre-wrap snapshot. | |
| 927 | value_type = r.expression!.value!.type | |
| 928 | fi | |
| 929 | fi | |
| 930 | ||
| 931 | let pinned = Semantic.MAYBE_RETURN_PIN.of(function, value_type!, _innate_symbol_lookup) | |
| 932 | ||
| 933 | function.set_return_type(Semantic.NULL_RETURN_JOIN.of(pinned, function.returned_genuine_null, _innate_symbol_lookup)) | |
| 934 | elif function.return_type!.matches(_innate_symbol_lookup.get_void_type()) /\ !function.return_type!.is_type_variable then | |
| 935 | _logger | |
| 936 | .error( | |
| 937 | r.expression!.location, | |
| 938 | "cannot return value from function of void type" | |
| 939 | ) | |
| 940 | elif !function.return_type!.is_assignable_from(value.type!) then | |
| 941 | // Implicit T → TASK[T] widening at return position | |
| 942 | // (the C# `async` return-rewrap rule applied to ghūl). | |
| 943 | // Synthesises Tasks.TASK.from_result(orig) around the | |
| 944 | // original expression and re-resolves the wrapper. | |
| 945 | // Restricted to return position — variable init / | |
| 946 | // argument passing slot boundaries don't widen this way. | |
| 947 | if _task_conversion.try_wrap_value_as_task_return(r, function.return_type!, _visitor) then | |
| 948 | // wrap succeeded — type now matches | |
| 949 | elif function.return_type_was_inferred /\ null_join.is_genuine_null(value.type!) then | |
| 950 | // The return settled on an earlier value and | |
| 951 | // this one says it can also be absent. | |
| 952 | function.returned_genuine_null = true | |
| 953 | ||
| 954 | function.set_return_type( | |
| 955 | Semantic.NULL_RETURN_JOIN.of(function.return_type!, function.returned_genuine_null, _innate_symbol_lookup)) | |
| 956 | elif function.return_type_was_inferred then | |
| 957 | let lub = Semantic.LEAST_UPPER_BOUND_MAP() | |
| 958 | lub.add(function.return_type!) | |
| 959 | lub.add(value.type!) | |
| 960 | let widened = lub.get_result() | |
| 961 | ||
| 962 | if widened? then | |
| 963 | function.set_return_type(widened) | |
| 964 | else | |
| 965 | _logger | |
| 966 | .error( | |
| 967 | r.expression!.location, | |
| 968 | "cannot return value of type {value.type} where {function.return_type} expected" | |
| 969 | ) | |
| 970 | fi | |
| 971 | else | |
| 972 | _logger | |
| 973 | .error( | |
| 974 | r.expression!.location, | |
| 975 | "cannot return value of type {value.type} where {function.return_type} expected" | |
| 976 | ) | |
| 977 | fi | |
| 978 | fi | |
| 979 | ||
| 980 | _visitor.check_non_optional(function.return_type, r.expression, r.expression!.location) | |
| 981 | ||
| 982 | _pure_slots.check_store(r.expression!.location, function.return_type, r.expression!.value) | |
| 983 | ||
| 984 | return | |
| 985 | fi | |
| 986 | else | |
| 987 | // Void async: bare `return;` sugar — synthesise | |
| 988 | // `return Tasks.TASK.completed_task;` and re-walk. | |
| 989 | // `is_void_async` is stamped on a body that awaits, so | |
| 990 | // it covers the state-machine lowering; a machine-less | |
| 991 | // Tasks.TASK return completes the same way. | |
| 992 | if | |
| 993 | function.is_void_async \/ | |
| 994 | _task_conversion.is_void_task_return(function.return_type) | |
| 995 | then | |
| 996 | r.expression = Semantic.TASK_CONVERSION.build_completed_task_expression(r.location) | |
| 997 | r.expression!.walk(_visitor) | |
| 998 | ||
| 999 | return | |
| 1000 | fi | |
| 1001 | ||
| 1002 | // In a generator a bare `return` ends the stream, so the | |
| 1003 | // declared `Pipe[T]` is not a value any return carries. | |
| 1004 | if Semantic.Symbols.state_machine_for(function)? then | |
| 1005 | return | |
| 1006 | fi | |
| 1007 | ||
| 1008 | // A machine-less return of some other result-less | |
| 1009 | // task-like completes through its builder too - IL | |
| 1010 | // generation emits the driving sequence for the bare | |
| 1011 | // return - so no value is missing here either. | |
| 1012 | if _task_conversion.completes_without_machine(function.return_type) then | |
| 1013 | return | |
| 1014 | fi | |
| 1015 | ||
| 1016 | if !function.return_type!.matches(_innate_symbol_lookup.get_void_type()) then | |
| 1017 | _logger | |
| 1018 | .warn( | |
| 1019 | r.location, | |
| 1020 | "return-without-value", | |
| 1021 | "return without value from non void function returns default value of type {function.return_type}" | |
| 1022 | ) | |
| 1023 | fi | |
| 1024 | fi | |
| 1025 | si | |
| 1026 | ||
| 1027 | // Type-check `return X` against the element type T (not | |
| 1028 | // Task[T]); bare `return;` is left for generate-il to map to | |
| 1029 | // `leave success_label`. | |
| 1030 | _visit_return_state_machine_async( | |
| 1031 | r: Trees.Statements.RETURN, | |
| 1032 | function: Semantic.Symbols.Function, | |
| 1033 | async_sm: Semantic.Symbols.ASYNC_STATE_MACHINE | |
| 1034 | ) is | |
| 1035 | let frame = async_sm.frame | |
| 1036 | ||
| 1037 | assert frame? else "async state machine has no frame at return" | |
| 1038 | ||
| 1039 | if r.expression? then | |
| 1040 | let expression_value = r.expression.value | |
| 1041 | ||
| 1042 | if | |
| 1043 | !expression_value? \/ | |
| 1044 | !expression_value.type? \/ | |
| 1045 | !expression_value.check_is_consumable(_logger, r.expression.location) | |
| 1046 | then | |
| 1047 | return | |
| 1048 | fi | |
| 1049 | ||
| 1050 | if frame.is_void then | |
| 1051 | if !_try_await_returned_value(r, expression_value.type!, null) then | |
| 1052 | _logger.error( | |
| 1053 | r.expression!.location, | |
| 1054 | "cannot return value from function of void type" | |
| 1055 | ) | |
| 1056 | fi | |
| 1057 | return | |
| 1058 | fi | |
| 1059 | ||
| 1060 | // Async-closure inference: lambdas with | |
| 1061 | // INFERRED_RETURN_TYPE + wrap_inferred_return_as_task | |
| 1062 | // pin from the body's value-returning statement here. | |
| 1063 | // The SM frame's result_type follows via | |
| 1064 | // ensure_result_field. | |
| 1065 | if | |
| 1066 | function.return_type? /\ | |
| 1067 | function.return_type.is_inferred /\ | |
| 1068 | function.wrap_inferred_return_as_task /\ | |
| 1069 | expression_value.type? /\ | |
| 1070 | expression_value.type.is_settled /\ | |
| 1071 | !_task_conversion.is_task_type(expression_value.type!) | |
| 1072 | then | |
| 1073 | let task_type = | |
| 1074 | _task_conversion.async_return_type_for(function, expression_value.type!) | |
| 1075 | if task_type? then | |
| 1076 | function.set_return_type(task_type) | |
| 1077 | fi | |
| 1078 | fi | |
| 1079 | ||
| 1080 | let element_type = frame.result_type | |
| 1081 | ||
| 1082 | if !element_type? then | |
| 1083 | return | |
| 1084 | fi | |
| 1085 | ||
| 1086 | if | |
| 1087 | !element_type.is_assignable_from(expression_value.type!) /\ | |
| 1088 | !_try_await_returned_value(r, expression_value.type!, element_type) | |
| 1089 | then | |
| 1090 | _logger.error( | |
| 1091 | r.expression!.location, | |
| 1092 | "cannot return value of type {expression_value.type} where {element_type} expected" | |
| 1093 | ) | |
| 1094 | return | |
| 1095 | fi | |
| 1096 | ||
| 1097 | _visitor.check_non_optional(element_type, r.expression, r.expression!.location) | |
| 1098 | ||
| 1099 | _pure_slots.check_store(r.expression!.location, element_type, r.expression!.value) | |
| 1100 | else | |
| 1101 | // A literal whose return was left to its body to settle: | |
| 1102 | // a bare `return` is only a return at all where the body | |
| 1103 | // carries no result, so it settles the literal as | |
| 1104 | // void-async here rather than waiting for a tail that | |
| 1105 | // may never deliver one. | |
| 1106 | if let rt = function.return_type then | |
| 1107 | if rt.is_inferred /\ function.wrap_inferred_return_as_task then | |
| 1108 | if let void_task = _innate_symbol_lookup.get_void_task_type() then | |
| 1109 | function.set_return_type(void_task) | |
| 1110 | function.is_void_async = true | |
| 1111 | fi | |
| 1112 | fi | |
| 1113 | fi | |
| 1114 | ||
| 1115 | let settled_frame = async_sm.frame ?? frame | |
| 1116 | ||
| 1117 | // Bare `return;`. For value-async this is an | |
| 1118 | // error — the result slot is non-void. For | |
| 1119 | // void-async it's the usual no-op. | |
| 1120 | if !settled_frame.is_void then | |
| 1121 | _logger.warn( | |
| 1122 | r.location, | |
| 1123 | "return-without-value", | |
| 1124 | "return without value from value-async function returns default value of type {settled_frame.result_type}" | |
| 1125 | ) | |
| 1126 | fi | |
| 1127 | fi | |
| 1128 | si | |
| 1129 | ||
| 1130 | // A returned value that does not fit the result slot but awaits | |
| 1131 | // to something that does is returned as its awaited result: the | |
| 1132 | // caller already holds this function's own task, so the | |
| 1133 | // returned task can only ever complete it. A void slot takes a | |
| 1134 | // value that awaits to nothing. Rewrites `r` to return the | |
| 1135 | // await and answers false, leaving `r` alone, when the value is | |
| 1136 | // not awaitable or its result does not fit either. | |
| 1137 | _try_await_returned_value( | |
| 1138 | r: Trees.Statements.RETURN, | |
| 1139 | value_type: Semantic.Types.Type, | |
| 1140 | element_type: Semantic.Types.Type? | |
| 1141 | ) -> bool is | |
| 1142 | let awaitable = _awaitable_resolver.try_resolve(value_type) | |
| 1143 | ||
| 1144 | if !awaitable? then | |
| 1145 | return false | |
| 1146 | fi | |
| 1147 | ||
| 1148 | let result_type = awaitable.result_type | |
| 1149 | ||
| 1150 | if element_type? then | |
| 1151 | if !element_type.is_assignable_from(result_type) then | |
| 1152 | return false | |
| 1153 | fi | |
| 1154 | elif !result_type.is_void then | |
| 1155 | return false | |
| 1156 | fi | |
| 1157 | ||
| 1158 | let awaited = Trees.Expressions.AWAIT(r.expression!.location, r.expression!) | |
| 1159 | ||
| 1160 | r.expression = awaited | |
| 1161 | ||
| 1162 | awaited.accept(_visitor) | |
| 1163 | ||
| 1164 | return true | |
| 1165 | si | |
| 1166 | ||
| 1167 | si | |
| 1168 | si |