Appearance
| 1 | namespace Semantic.Symbols is | |
| 2 | use IO.Std | |
| 3 | ||
| 4 | use System.Text.StringBuilder | |
| 5 | ||
| 6 | use IoC | |
| 7 | use Logging | |
| 8 | use Source | |
| 9 | ||
| 10 | use IR.Values.Value | |
| 11 | ||
| 12 | use Types.Type | |
| 13 | ||
| 14 | // Per-`let await` bookkeeping recorded by await IL emission. | |
| 15 | // The resume label is placed immediately after the per-await | |
| 16 | // `leave` that suspends MoveNext; entry-dispatch jumps here | |
| 17 | // when the builder re-enters MoveNext on awaiter completion. | |
| 18 | class ASYNC_STATE_LABEL is | |
| 19 | state: int public | |
| 20 | awaiter_field: Field public | |
| 21 | label: IR.LABEL public | |
| 22 | ||
| 23 | init(state: int, awaiter_field: Field, label: IR.LABEL) is | |
| 24 | self.state = state | |
| 25 | self.awaiter_field = awaiter_field | |
| 26 | self.label = label | |
| 27 | si | |
| 28 | si | |
| 29 | ||
| 30 | // State held on an async function symbol: the synthesised frame | |
| 31 | // class, the running state-number counter, the recorded | |
| 32 | // (state, awaiter_field, resume_label) triples for entry-dispatch | |
| 33 | // emission. Lives as a non-null field on each *_ASYNC_* | |
| 34 | // function; checked-for via `async_state_machine_for(...)`. | |
| 35 | class ASYNC_STATE_MACHINE is | |
| 36 | function: Function public | |
| 37 | ||
| 38 | _frame: ASYNC_STATE_MACHINE_FRAME? | |
| 39 | _next_state: int | |
| 40 | _await_labels: Collections.LIST[ASYNC_STATE_LABEL] | |
| 41 | ||
| 42 | init(function: Function) is | |
| 43 | self.function = function | |
| 44 | ||
| 45 | _next_state = 1 | |
| 46 | _await_labels = Collections.LIST[ASYNC_STATE_LABEL]() | |
| 47 | si | |
| 48 | ||
| 49 | // Drops the frame so the next access builds it afresh from the | |
| 50 | // function's current return type, returning the frame dropped | |
| 51 | // so its owner can forget whatever was recorded against it. A closure is walked more | |
| 52 | // than once while its slot is being resolved, and a frame built | |
| 53 | // on the first walk carries that walk's builder and result | |
| 54 | // types - wrong once a later walk settles the closure on a | |
| 55 | // different task-like. | |
| 56 | reset() -> ASYNC_STATE_MACHINE_FRAME? is | |
| 57 | let dropped = _frame | |
| 58 | ||
| 59 | _frame = null | |
| 60 | _next_state = 1 | |
| 61 | _await_labels.clear() | |
| 62 | ||
| 63 | return dropped | |
| 64 | si | |
| 65 | ||
| 66 | // Lazy: the frame class is materialised on first access, | |
| 67 | // pulling its result type from the (now-resolved) function | |
| 68 | // return type (Tasks.TASK[T] → T, Tasks.TASK → void). Returns | |
| 69 | // null only when the return type isn't a Task — which is an | |
| 70 | // upstream diagnostic, not something the frame can recover | |
| 71 | // from. | |
| 72 | frame: ASYNC_STATE_MACHINE_FRAME? is | |
| 73 | let lookup = IoC.CONTAINER.instance.innate_symbol_lookup | |
| 74 | ||
| 75 | if !_frame? then | |
| 76 | // A return type that failed to resolve has been reported | |
| 77 | // where it was written; a frame built on it would only | |
| 78 | // fail again, less legibly, when declared. | |
| 79 | if !function.return_type? \/ function.return_type.is_error then | |
| 80 | return null | |
| 81 | fi | |
| 82 | ||
| 83 | let result_type: Type? mut = null | |
| 84 | let is_void mut = false | |
| 85 | ||
| 86 | // A task-like return type settles both at once: the | |
| 87 | // result is its single type argument, and a non-generic | |
| 88 | // task-like has none. Task is itself a task-like through | |
| 89 | // its AsyncMethodBuilderAttribute, so this covers | |
| 90 | // Tasks.TASK the same way it covers any other. Lambdas | |
| 91 | // with INFERRED_RETURN_TYPE can't resolve (an inferred | |
| 92 | // placeholder matches anything) so they fall through to | |
| 93 | // the AST flag `is_void_async`; `result_type` fills in | |
| 94 | // later via `ensure_result_field`. | |
| 95 | let task_like = | |
| 96 | TASK_LIKE_RESOLVER(IoC.CONTAINER.instance.logger) | |
| 97 | .resolve(function.location, function.return_type) | |
| 98 | ||
| 99 | if task_like? then | |
| 100 | if let task_result = task_like.result_type then | |
| 101 | result_type = task_result | |
| 102 | else | |
| 103 | is_void = true | |
| 104 | fi | |
| 105 | else | |
| 106 | let extracted = _extract_result_type(lookup) | |
| 107 | ||
| 108 | if extracted? then | |
| 109 | result_type = extracted | |
| 110 | elif function.return_type!.is_inferred then | |
| 111 | is_void = function.is_void_async | |
| 112 | else | |
| 113 | let void_task = lookup.get_void_task_type() | |
| 114 | if void_task? /\ function.return_type!.matches(void_task) then | |
| 115 | is_void = true | |
| 116 | else | |
| 117 | return null | |
| 118 | fi | |
| 119 | fi | |
| 120 | fi | |
| 121 | ||
| 122 | _frame = ASYNC_STATE_MACHINE_FRAME( | |
| 123 | function.owner!, | |
| 124 | function, | |
| 125 | result_type, | |
| 126 | is_void | |
| 127 | ) | |
| 128 | else | |
| 129 | // Re-poll on each access: a value-async lambda whose | |
| 130 | // return type wasn't yet settled when the frame was | |
| 131 | // first realised needs its _result_type filled in | |
| 132 | // once the type pins. Idempotent. | |
| 133 | let frame = _frame | |
| 134 | frame.ensure_result_field(lookup) | |
| 135 | return frame | |
| 136 | fi | |
| 137 | ||
| 138 | return _frame | |
| 139 | si | |
| 140 | ||
| 141 | _extract_result_type(lookup: Lookups.InnateSymbolLookup) -> Type? => | |
| 142 | TYPE_ARGUMENT_EXTRACTOR.extract(function.return_type, lookup.get_unspecialized_task_type()) | |
| 143 | ||
| 144 | allocate_state() -> int is | |
| 145 | let n = _next_state | |
| 146 | ||
| 147 | _next_state = _next_state + 1 | |
| 148 | ||
| 149 | return n | |
| 150 | si | |
| 151 | ||
| 152 | record_label(state: int, awaiter_field: Field, label: IR.LABEL) is | |
| 153 | _await_labels.add(ASYNC_STATE_LABEL(state, awaiter_field, label)) | |
| 154 | si | |
| 155 | ||
| 156 | await_labels: Collections.Iterable[ASYNC_STATE_LABEL] => _await_labels | |
| 157 | ||
| 158 | // See `STATE_MACHINE_TYPE_PARAMS` — generator + async use | |
| 159 | // the same capture order so the helper handles both. | |
| 160 | install_body_emission_overrides() is | |
| 161 | STATE_MACHINE_TYPE_PARAMS.walk_install(function, true) | |
| 162 | si | |
| 163 | ||
| 164 | uninstall_body_emission_overrides() is | |
| 165 | STATE_MACHINE_TYPE_PARAMS.walk_install(function, false) | |
| 166 | si | |
| 167 | ||
| 168 | get_construction_type_arguments() -> Collections.List[Type]? => | |
| 169 | STATE_MACHINE_TYPE_PARAMS.construction_type_arguments(function) | |
| 170 | si | |
| 171 | ||
| 172 | // Frame class synthesised for each async function. Implements | |
| 173 | // `IAsyncStateMachine` (MoveNext, SetStateMachine); holds the | |
| 174 | // current state, a builder, per-await awaiter fields, captured | |
| 175 | // arguments + locals + outer-self, and a result slot for | |
| 176 | // value-async (omitted for void-async). The class itself is | |
| 177 | // generic in any type parameters visible inside the owning | |
| 178 | // function (mirrors `STATE_MACHINE_FRAME`). | |
| 179 | class ASYNC_STATE_MACHINE_FRAME: STATE_MACHINE_FRAME_BASE is | |
| 180 | _next_id: int static | |
| 181 | ||
| 182 | // Null for void-async, and for a value-async lambda whose | |
| 183 | // inferred return type hasn't pinned yet (filled in by | |
| 184 | // ensure_result_field). | |
| 185 | _result_type: Type? | |
| 186 | _is_void: bool | |
| 187 | ||
| 188 | // Null until declare() runs. Builder, result and outer-self | |
| 189 | // stay null past declare() on some paths: builder when the | |
| 190 | // builder type cannot be resolved, result for void-async, | |
| 191 | // outer-self for statics and globals. | |
| 192 | _state_field: Field? | |
| 193 | _builder_field: Field? | |
| 194 | _result_field: Field? | |
| 195 | ||
| 196 | result_type: Type? => _result_type | |
| 197 | is_void: bool => _is_void | |
| 198 | ||
| 199 | class_result_type: Type? => | |
| 200 | if _result_type? then | |
| 201 | _class_relative(_result_type) | |
| 202 | else | |
| 203 | null | |
| 204 | fi | |
| 205 | ||
| 206 | // Non-null once declare() has run. | |
| 207 | state_field: Field => _state_field! | |
| 208 | ||
| 209 | builder_field: Field? => _builder_field | |
| 210 | result_field: Field? => _result_field | |
| 211 | ||
| 212 | next_id: int static is | |
| 213 | let result = _next_id | |
| 214 | _next_id = _next_id + 1 | |
| 215 | return result | |
| 216 | si | |
| 217 | ||
| 218 | init(owner: Scope, owning_function: Function, result_type: Type?, is_void: bool) is | |
| 219 | let owner_owner: Scope mut | |
| 220 | ||
| 221 | if isa Symbol(owner) then | |
| 222 | let owner_symbol = owner | |
| 223 | owner_owner = owner_symbol.owner! | |
| 224 | else | |
| 225 | owner_owner = owner | |
| 226 | fi | |
| 227 | ||
| 228 | super.init( | |
| 229 | LOCATION.internal, | |
| 230 | LOCATION.internal, | |
| 231 | owner_owner, | |
| 232 | "$AsyncStateMachine_{owning_function.name}_{next_id}", | |
| 233 | owner, | |
| 234 | owning_function | |
| 235 | ) | |
| 236 | ||
| 237 | _result_type = result_type | |
| 238 | _is_void = is_void | |
| 239 | ||
| 240 | _awaiter_fields = Collections.LIST[Field]() | |
| 241 | ||
| 242 | set_type(Types.NAMED(self)) | |
| 243 | si | |
| 244 | ||
| 245 | _awaiter_fields: Collections.LIST[Field] | |
| 246 | ||
| 247 | awaiter_fields: Collections.Iterable[Field] => _awaiter_fields | |
| 248 | ||
| 249 | // The type parameters and the `$outer_self` field, declared | |
| 250 | // apart from the members that wait on the return type. A | |
| 251 | // literal whose return is inferred declares nothing else until | |
| 252 | // that return settles, and a capture load built in the meantime | |
| 253 | // has to find the field: without it the load falls back to a | |
| 254 | // plain self reference, which inside MoveNext is the state | |
| 255 | // machine rather than the captures frame. | |
| 256 | ensure_outer_self_field() is | |
| 257 | let listener = IoC.CONTAINER.instance.symbol_definition_locations | |
| 258 | ||
| 259 | // A closure's captured type arguments arrive while its body | |
| 260 | // is walked, so the set is empty at the first call and | |
| 261 | // complete only by emission. Asking again each time is what | |
| 262 | // lets the parameters that arrive late be declared at all; | |
| 263 | // the declaration itself only ever adds. | |
| 264 | declare_captured_type_params(listener) | |
| 265 | ||
| 266 | let outer_classy = _outer_self_target() | |
| 267 | ||
| 268 | if !outer_classy? then | |
| 269 | return | |
| 270 | fi | |
| 271 | ||
| 272 | let self_type = _target_type(outer_classy) | |
| 273 | ||
| 274 | // The frame is a class of its own, and the type parameters | |
| 275 | // it carries are installed as its captures are emitted, so | |
| 276 | // the type naming it is only complete once that has | |
| 277 | // happened. Re-derive rather than keep what the first call | |
| 278 | // produced, which named the frame with no arguments at all. | |
| 279 | if let existing = _outer_self_field then | |
| 280 | existing.set_type(self_type) | |
| 281 | ||
| 282 | return | |
| 283 | fi | |
| 284 | ||
| 285 | let outer_self_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$outer_self") | |
| 286 | outer_self_field.set_type(self_type) | |
| 287 | declare(LOCATION.internal, outer_self_field, listener) | |
| 288 | _outer_self_field = outer_self_field | |
| 289 | si | |
| 290 | ||
| 291 | // The target named over this machine's own parameters. The | |
| 292 | // target carries the same set this machine mirrors - a frame is | |
| 293 | // built from the captures, and an enclosing class supplies them | |
| 294 | // through the receiver - so pairing them off by position, on | |
| 295 | // the symbols rather than on their names, gives the target the | |
| 296 | // arguments a reference from inside MoveNext resolves against. | |
| 297 | // Falls back to the name-matched form while the two disagree, | |
| 298 | // which is every call before the target has its own. | |
| 299 | _target_type(outer_classy: Classy) -> Type is | |
| 300 | let parameters = STATE_MACHINE_TYPE_PARAMS.captured_parameters(owning_function) | |
| 301 | ||
| 302 | if | |
| 303 | parameters.count == 0 \/ | |
| 304 | !outer_classy.is_generic \/ | |
| 305 | outer_classy.argument_names.count != parameters.count | |
| 306 | then | |
| 307 | return outer_self_type(outer_classy) | |
| 308 | fi | |
| 309 | ||
| 310 | let arguments = Collections.LIST[Type]() | |
| 311 | ||
| 312 | for parameter in parameters do | |
| 313 | if !mirrors(parameter) then | |
| 314 | return outer_self_type(outer_classy) | |
| 315 | fi | |
| 316 | ||
| 317 | arguments.add(mirror_of(parameter)) | |
| 318 | od | |
| 319 | ||
| 320 | return Types.GENERIC(LOCATION.internal, outer_classy, arguments) | |
| 321 | si | |
| 322 | ||
| 323 | // What `$outer_self` points at: the captures frame for a | |
| 324 | // closure that has one, since the launch is emitted as a method | |
| 325 | // on that frame, and otherwise the enclosing class for an | |
| 326 | // instance method. A closure's own owner is the enclosing class | |
| 327 | // until emission repoints it at the frame, so asking the | |
| 328 | // closure first is what keeps the two apart. | |
| 329 | _outer_self_target() -> Classy? is | |
| 330 | if let closure: Closure = owning_function then | |
| 331 | if let closure.frame? then | |
| 332 | return frame | |
| 333 | fi | |
| 334 | fi | |
| 335 | ||
| 336 | if owning_function.is_instance /\ isa Classy(owning_function.owner) then | |
| 337 | return cast Classy?(owning_function.owner)! | |
| 338 | fi | |
| 339 | ||
| 340 | return null | |
| 341 | si | |
| 342 | ||
| 343 | // Lazy + idempotent realisation of frame members. Value-async | |
| 344 | // frames short-circuit until `_result_type` is available | |
| 345 | // (otherwise we'd resolve `AsyncTaskMethodBuilder<null>`). | |
| 346 | declare() is | |
| 347 | ensure_outer_self_field() | |
| 348 | ||
| 349 | if _state_field? then | |
| 350 | // A closure's first declare() runs mid-inference, so an | |
| 351 | // inferred parameter group's field captured a settled- | |
| 352 | // later INFERRED_VARIABLE_TYPE placeholder. Recompute | |
| 353 | // from the locals on every subsequent call - generate-il | |
| 354 | // declares again before emitting the frame, by which | |
| 355 | // point inference has settled. | |
| 356 | refresh_argument_field_types() | |
| 357 | return | |
| 358 | fi | |
| 359 | ||
| 360 | if !_is_void /\ !_result_type? then | |
| 361 | return | |
| 362 | fi | |
| 363 | ||
| 364 | let listener = IoC.CONTAINER.instance.symbol_definition_locations | |
| 365 | ||
| 366 | let int_type = IoC.CONTAINER.instance.innate_symbol_lookup.get_int_type() | |
| 367 | ||
| 368 | let state_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$state") | |
| 369 | state_field.set_type(int_type) | |
| 370 | declare(LOCATION.internal, state_field, listener) | |
| 371 | _state_field = state_field | |
| 372 | ||
| 373 | // Builder field — `AsyncTaskMethodBuilder<T>` for | |
| 374 | // value-async, non-generic for void-async. Omitted when | |
| 375 | // the type cannot be resolved; IL emission supplies it | |
| 376 | // literally. | |
| 377 | let builder_type = _resolve_builder_type() | |
| 378 | if builder_type? then | |
| 379 | let builder_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$builder") | |
| 380 | builder_field.set_type(builder_type) | |
| 381 | declare(LOCATION.internal, builder_field, listener) | |
| 382 | _builder_field = builder_field | |
| 383 | fi | |
| 384 | ||
| 385 | // `$result` only for value-async. Lambdas with | |
| 386 | // unresolved result_type defer to `ensure_result_field`. | |
| 387 | if !_is_void /\ _result_type? then | |
| 388 | let result_type = _result_type | |
| 389 | ||
| 390 | let result_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$result") | |
| 391 | result_field.set_type(_class_relative(result_type)) | |
| 392 | declare(LOCATION.internal, result_field, listener) | |
| 393 | _result_field = result_field | |
| 394 | fi | |
| 395 | ||
| 396 | let ctor_argument_names = Collections.LIST[string]() | |
| 397 | let ctor_argument_types = Collections.LIST[Type]() | |
| 398 | ||
| 399 | if let outer_self_field = _outer_self_field then | |
| 400 | ctor_argument_names.add("$outer_self") | |
| 401 | ctor_argument_types.add(outer_self_field.type!) | |
| 402 | fi | |
| 403 | ||
| 404 | if owning_function.argument_names.count > 0 then | |
| 405 | for arg_name in owning_function.argument_names do | |
| 406 | let local = cast Symbols.LOCAL_ARGUMENT?(owning_function.find_direct(arg_name)) | |
| 407 | ||
| 408 | if !local? then | |
| 409 | continue | |
| 410 | fi | |
| 411 | ||
| 412 | // An async closure's parameter group with no | |
| 413 | // written aggregate type carries an | |
| 414 | // INFERRED_VARIABLE_TYPE placeholder - settled by | |
| 415 | // the body-retry walk, but still held as the | |
| 416 | // Variable's type. Collapse it to the concrete type | |
| 417 | // here: a placeholder in a field signature reaches | |
| 418 | // the encoder and is refused. | |
| 419 | let arg_field_type = _class_relative( | |
| 420 | SETTLED_PLACEHOLDER_RESOLVER.instance.resolve(local.type ?? Types.ERROR())) | |
| 421 | ||
| 422 | let arg_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$arg_{arg_name}") | |
| 423 | arg_field.set_type(arg_field_type) | |
| 424 | declare(LOCATION.internal, arg_field, listener) | |
| 425 | ||
| 426 | _argument_fields.add(arg_field) | |
| 427 | ||
| 428 | local.state_machine_field = arg_field | |
| 429 | ||
| 430 | ctor_argument_names.add(arg_name) | |
| 431 | ctor_argument_types.add(arg_field_type) | |
| 432 | od | |
| 433 | fi | |
| 434 | ||
| 435 | let ctor = Symbols.INSTANCE_METHOD(LOCATION.internal, LOCATION.internal, self, "init", self) | |
| 436 | ctor.set_arguments(ctor_argument_names, ctor_argument_types) | |
| 437 | ctor.set_void_return_type() | |
| 438 | declare(LOCATION.internal, ctor, listener) | |
| 439 | _constructor = ctor | |
| 440 | ||
| 441 | _ctor_argument_names = ctor_argument_names | |
| 442 | ||
| 443 | // Ancestors: Object as base class, IAsyncStateMachine as | |
| 444 | // implemented interface. The frame doesn't expose | |
| 445 | // Iterable/Iterator like the generator frame; the runtime | |
| 446 | // calls MoveNext via the builder. | |
| 447 | let lookup = IoC.CONTAINER.instance.innate_symbol_lookup | |
| 448 | ||
| 449 | add_ancestor(lookup.get_object_type()) | |
| 450 | ||
| 451 | let async_state_machine_type = _resolve_async_state_machine_type() | |
| 452 | if async_state_machine_type? then | |
| 453 | add_ancestor(async_state_machine_type) | |
| 454 | fi | |
| 455 | si | |
| 456 | ||
| 457 | // The builder the owning function's return type names through its | |
| 458 | // AsyncMethodBuilderAttribute - `AsyncTaskMethodBuilder<T>` for | |
| 459 | // Task[T], `AsyncValueTaskMethodBuilder<T>` for ValueTask[T], or | |
| 460 | // whatever a source-declared task-like names. Falls back to the | |
| 461 | // Task builders when the return type resolves no task-like, which | |
| 462 | // keeps the Task path answering even where the attribute read | |
| 463 | // cannot run. | |
| 464 | _resolve_builder_type() -> Type? is | |
| 465 | let lookup = IoC.CONTAINER.instance.innate_symbol_lookup | |
| 466 | ||
| 467 | let task_like = | |
| 468 | TASK_LIKE_RESOLVER(IoC.CONTAINER.instance.logger) | |
| 469 | .resolve(owning_function.location, owning_function.return_type) | |
| 470 | ||
| 471 | if !task_like? then | |
| 472 | if _is_void then | |
| 473 | return lookup.get_async_task_method_builder_void_type() | |
| 474 | else | |
| 475 | if !_result_type? then | |
| 476 | return null | |
| 477 | fi | |
| 478 | let class_result = _class_relative(_result_type) | |
| 479 | return lookup.get_async_task_method_builder_type(class_result) | |
| 480 | fi | |
| 481 | fi | |
| 482 | ||
| 483 | let builder = task_like.builder_type | |
| 484 | let builder_classy = cast Symbols.Classy?(builder.symbol.unspecialized_symbol) | |
| 485 | ||
| 486 | if !builder_classy? then | |
| 487 | return builder | |
| 488 | fi | |
| 489 | ||
| 490 | // A generic builder takes the task-like's result as its own | |
| 491 | // type argument, at the frame's class-relative spelling. | |
| 492 | if builder_classy.is_generic then | |
| 493 | if !_result_type? then | |
| 494 | return null | |
| 495 | fi | |
| 496 | ||
| 497 | let class_result = _class_relative(_result_type) | |
| 498 | ||
| 499 | return | |
| 500 | Types.GENERIC( | |
| 501 | Source.LOCATION.internal, | |
| 502 | builder_classy, | |
| 503 | Collections.LIST[Type]([class_result]) | |
| 504 | ) | |
| 505 | fi | |
| 506 | ||
| 507 | return builder | |
| 508 | si | |
| 509 | ||
| 510 | _resolve_async_state_machine_type() -> Type? is | |
| 511 | let lookup = IoC.CONTAINER.instance.innate_symbol_lookup | |
| 512 | return lookup.get_async_state_machine_interface_type() | |
| 513 | si | |
| 514 | ||
| 515 | // Late-fill `_result_type` once the owning lambda's | |
| 516 | // wrap-as-task pin settles. Unblocks declare() for value- | |
| 517 | // async closures whose return type was inferred. Idempotent. | |
| 518 | ensure_result_field(lookup: Lookups.InnateSymbolLookup) is | |
| 519 | if _is_void \/ _result_type? then | |
| 520 | return | |
| 521 | fi | |
| 522 | if !owning_function.return_type? then | |
| 523 | return | |
| 524 | fi | |
| 525 | ||
| 526 | let extracted = TYPE_ARGUMENT_EXTRACTOR.extract( | |
| 527 | owning_function.return_type, | |
| 528 | lookup.get_unspecialized_task_type() | |
| 529 | ) | |
| 530 | ||
| 531 | if extracted? then | |
| 532 | _result_type = extracted | |
| 533 | return | |
| 534 | fi | |
| 535 | ||
| 536 | // A closure pinned to a task-like other than Task carries | |
| 537 | // its result the same way, as the single type argument. | |
| 538 | let task_like = | |
| 539 | TASK_LIKE_RESOLVER(IoC.CONTAINER.instance.logger) | |
| 540 | .resolve(owning_function.location, owning_function.return_type) | |
| 541 | ||
| 542 | if let result = task_like?.result_type then | |
| 543 | _result_type = result | |
| 544 | return | |
| 545 | fi | |
| 546 | ||
| 547 | // A literal whose return was left to its body and settled | |
| 548 | // result-less is void-async after all: the frame realised | |
| 549 | // while the return was still inferred holds the value-async | |
| 550 | // shape, and follows the settled return here. An inferred | |
| 551 | // return says nothing yet - a placeholder matches every | |
| 552 | // type, this one included - so it waits for a later poll. | |
| 553 | if owning_function.return_type!.is_inferred then | |
| 554 | return | |
| 555 | fi | |
| 556 | ||
| 557 | if task_like? then | |
| 558 | _is_void = true | |
| 559 | return | |
| 560 | fi | |
| 561 | ||
| 562 | if let void_task = lookup.get_void_task_type() then | |
| 563 | if owning_function.return_type!.matches(void_task) then | |
| 564 | _is_void = true | |
| 565 | fi | |
| 566 | fi | |
| 567 | si | |
| 568 | ||
| 569 | // Per-await awaiter field. Each call site gets its own slot | |
| 570 | // since the awaiter is typically a value type | |
| 571 | // (`TaskAwaiter<T>` / its non-generic sibling). | |
| 572 | // The awaiter field for an `await` a body re-walk reaches | |
| 573 | // again. Same reasoning as the frame base's anonymous fields: | |
| 574 | // reuse the field rather than leaving a dead one behind, and | |
| 575 | // retype it so an awaiter type the first walk had not settled | |
| 576 | // still reaches the emitter. | |
| 577 | declare_or_retype_awaiter_field(existing: Field?, awaiter_type: Type) -> Field is | |
| 578 | if let `field = existing /\ `field.owner == self then | |
| 579 | `field.set_type(_class_relative(awaiter_type)) | |
| 580 | ||
| 581 | return `field | |
| 582 | fi | |
| 583 | ||
| 584 | return declare_awaiter_field(awaiter_type) | |
| 585 | si | |
| 586 | ||
| 587 | declare_awaiter_field(awaiter_type: Type) -> Field is | |
| 588 | let id = next_local_id() | |
| 589 | ||
| 590 | let listener = IoC.CONTAINER.instance.symbol_definition_locations | |
| 591 | ||
| 592 | let `field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$awaiter_{id}") | |
| 593 | `field.set_type(_class_relative(awaiter_type)) | |
| 594 | declare(LOCATION.internal, `field, listener) | |
| 595 | ||
| 596 | _awaiter_fields.add(`field) | |
| 597 | ||
| 598 | return `field | |
| 599 | si | |
| 600 | ||
| 601 | si | |
| 602 | ||
| 603 | // Free helper: returns the ASYNC_STATE_MACHINE held by any of | |
| 604 | // the async function or closure forms, or null if `f` is a plain | |
| 605 | // function / generator / sync closure. | |
| 606 | async_state_machine_for(f: Function?) -> ASYNC_STATE_MACHINE? is | |
| 607 | if !f? then | |
| 608 | return null | |
| 609 | fi | |
| 610 | ||
| 611 | if isa STATIC_ASYNC_METHOD(f) then | |
| 612 | return f.async_state_machine | |
| 613 | fi | |
| 614 | ||
| 615 | if isa INSTANCE_ASYNC_METHOD(f) then | |
| 616 | return f.async_state_machine | |
| 617 | fi | |
| 618 | ||
| 619 | if isa GLOBAL_ASYNC_FUNCTION(f) then | |
| 620 | return f.async_state_machine | |
| 621 | fi | |
| 622 | ||
| 623 | if isa INSTANCE_ASYNC_CLOSURE(f) then | |
| 624 | return f.async_state_machine | |
| 625 | fi | |
| 626 | ||
| 627 | if isa STATIC_ASYNC_CLOSURE(f) then | |
| 628 | return f.async_state_machine | |
| 629 | fi | |
| 630 | ||
| 631 | if isa GLOBAL_ASYNC_CLOSURE(f) then | |
| 632 | return f.async_state_machine | |
| 633 | fi | |
| 634 | ||
| 635 | return null | |
| 636 | si | |
| 637 | ||
| 638 | // Concrete async-function forms — thin extensions of the | |
| 639 | // STATIC_METHOD / INSTANCE_METHOD / GLOBAL_FUNCTION classes | |
| 640 | // that additionally carry an ASYNC_STATE_MACHINE. | |
| 641 | ||
| 642 | class STATIC_ASYNC_METHOD: STATIC_METHOD is | |
| 643 | async_state_machine: ASYNC_STATE_MACHINE public | |
| 644 | ||
| 645 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => | |
| 646 | "class async" | |
| 647 | ||
| 648 | is_accessible_to(accessor: Classy?) -> bool => | |
| 649 | _underscore_is_accessible_to(accessor) | |
| 650 | ||
| 651 | init(location: LOCATION, span: LOCATION, owner: Scope, name: string, enclosing_scope: Scope) is | |
| 652 | super.init(location, span, owner, name, enclosing_scope) | |
| 653 | ||
| 654 | async_state_machine = ASYNC_STATE_MACHINE(self) | |
| 655 | si | |
| 656 | si | |
| 657 | ||
| 658 | class INSTANCE_ASYNC_METHOD: INSTANCE_METHOD is | |
| 659 | async_state_machine: ASYNC_STATE_MACHINE public | |
| 660 | ||
| 661 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => | |
| 662 | "async" | |
| 663 | ||
| 664 | is_accessible_to(accessor: Classy?) -> bool => | |
| 665 | _underscore_is_accessible_to(accessor) | |
| 666 | ||
| 667 | init(location: LOCATION, span: LOCATION, owner: Scope, name: string, enclosing_scope: Scope) is | |
| 668 | super.init(location, span, owner, name, enclosing_scope) | |
| 669 | ||
| 670 | async_state_machine = ASYNC_STATE_MACHINE(self) | |
| 671 | si | |
| 672 | ||
| 673 | // Inside the body, `self` and instance-member access go via | |
| 674 | // the async state-machine frame's _outer_self field rather | |
| 675 | // than ldarg.0 (which inside MoveNext refers to the state | |
| 676 | // machine itself). Mirrors INSTANCE_GENERATOR_METHOD.load_self. | |
| 677 | load_self(location: LOCATION, loader: SYMBOL_LOADER) -> Value is | |
| 678 | let frame = async_state_machine.frame | |
| 679 | ||
| 680 | if frame? then | |
| 681 | frame.declare() | |
| 682 | ||
| 683 | let outer_self_field = frame.outer_self_field | |
| 684 | ||
| 685 | if outer_self_field? then | |
| 686 | let context = IoC.CONTAINER.instance.symbol_table.current_instance_context | |
| 687 | if context? then | |
| 688 | return IR.Values.Load.OUTER_SELF(context, context.type, outer_self_field) | |
| 689 | fi | |
| 690 | fi | |
| 691 | fi | |
| 692 | ||
| 693 | return super.load_self(location, loader) | |
| 694 | si | |
| 695 | si | |
| 696 | ||
| 697 | // An async method declared in a trait body. Always has a body — the | |
| 698 | // async classification comes from finding `await` in one — so there | |
| 699 | // is no abstract counterpart, and it is a default trait method for | |
| 700 | // inheritance purposes like any other bodied trait member. | |
| 701 | class DEFAULT_TRAIT_ASYNC_METHOD: INSTANCE_ASYNC_METHOD is | |
| 702 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => | |
| 703 | "default trait async" | |
| 704 | ||
| 705 | is_default_trait_method: bool => true | |
| 706 | ||
| 707 | init(location: LOCATION, span: LOCATION, owner: Scope, name: string, enclosing_scope: Scope) is | |
| 708 | super.init(location, span, owner, name, enclosing_scope) | |
| 709 | si | |
| 710 | ||
| 711 | try_instance_override_me(into: Classy, overrider: Function, logger: Logger) is | |
| 712 | super.try_instance_override_me(into, overrider, logger) | |
| 713 | ||
| 714 | _check_ineffective_trait_override(into, overrider, logger) | |
| 715 | si | |
| 716 | si | |
| 717 | ||
| 718 | class GLOBAL_ASYNC_FUNCTION: GLOBAL_FUNCTION is | |
| 719 | async_state_machine: ASYNC_STATE_MACHINE public | |
| 720 | ||
| 721 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => | |
| 722 | "global async" | |
| 723 | ||
| 724 | init(location: LOCATION, span: LOCATION, owner: Scope, name: string, enclosing_scope: Scope) is | |
| 725 | super.init(location, span, owner, name, enclosing_scope) | |
| 726 | ||
| 727 | async_state_machine = ASYNC_STATE_MACHINE(self) | |
| 728 | si | |
| 729 | si | |
| 730 | ||
| 731 | // Async closure forms — parallel to the *_ASYNC_METHOD / | |
| 732 | // GLOBAL_ASYNC_FUNCTION trio, but for user-written async lambdas. | |
| 733 | // Each is a thin extension of the corresponding sync closure that | |
| 734 | // additionally carries an ASYNC_STATE_MACHINE; generate-il dispatches | |
| 735 | // on `async_state_machine_for(symbol)` to route lambda emission down | |
| 736 | // the state-machine path. Nested lambdas inside an async closure | |
| 737 | // are sync by default — `declare_closure` returns the regular | |
| 738 | // form, matching the named-function convention. | |
| 739 | ||
| 740 | class INSTANCE_ASYNC_CLOSURE: INSTANCE_CLOSURE is | |
| 741 | async_state_machine: ASYNC_STATE_MACHINE public | |
| 742 | ||
| 743 | init(location: LOCATION, owner: Scope, name: string, enclosing_scope: Scope, is_recursive: bool) is | |
| 744 | super.init(location, owner, name, enclosing_scope, is_recursive) | |
| 745 | ||
| 746 | async_state_machine = ASYNC_STATE_MACHINE(self) | |
| 747 | si | |
| 748 | ||
| 749 | to_string() -> string => "[instance async closure {name}]" | |
| 750 | si | |
| 751 | ||
| 752 | class STATIC_ASYNC_CLOSURE: STATIC_CLOSURE is | |
| 753 | async_state_machine: ASYNC_STATE_MACHINE public | |
| 754 | ||
| 755 | init(location: LOCATION, owner: Scope, name: string, enclosing_scope: Scope, is_recursive: bool) is | |
| 756 | super.init(location, owner, name, enclosing_scope, is_recursive) | |
| 757 | ||
| 758 | async_state_machine = ASYNC_STATE_MACHINE(self) | |
| 759 | si | |
| 760 | ||
| 761 | to_string() -> string => "[static async closure {name}]" | |
| 762 | si | |
| 763 | ||
| 764 | class GLOBAL_ASYNC_CLOSURE: GLOBAL_CLOSURE is | |
| 765 | async_state_machine: ASYNC_STATE_MACHINE public | |
| 766 | ||
| 767 | init(location: LOCATION, owner: Scope, name: string, enclosing_scope: Scope, is_recursive: bool) is | |
| 768 | super.init(location, owner, name, enclosing_scope, is_recursive) | |
| 769 | ||
| 770 | async_state_machine = ASYNC_STATE_MACHINE(self) | |
| 771 | si | |
| 772 | ||
| 773 | to_string() -> string => "[global async closure {name}]" | |
| 774 | si | |
| 775 | si |