Appearance
| 1 | namespace Semantic is | |
| 2 | use Source | |
| 3 | use Trees = Syntax.Trees | |
| 4 | ||
| 5 | use Types.Type | |
| 6 | ||
| 7 | // Owns the implicit T → TASK[T] / void → TASK conversion | |
| 8 | // machinery used by the let-await desugaring and the inferred- | |
| 9 | // return paths. Concentrates dispersed predicates and AST | |
| 10 | // builders in one place so the rules are easy to read and the | |
| 11 | // mechanism is easy to extend. | |
| 12 | // | |
| 13 | // Predicates: | |
| 14 | // `is_task_type` — type is non-generic Task or constructed Task[T] | |
| 15 | // `try_get_task_element_type` — returns T for constructed Task[T], null otherwise | |
| 16 | // `is_void_task_return` — type is the non-generic Tasks.TASK | |
| 17 | // `completes_without_machine` — a machine-less body returning this completes at fall-through | |
| 18 | // `builder_completion_for` — the task-like whose builder that completion drives | |
| 19 | // | |
| 20 | // Conversion attempts: | |
| 21 | // `try_wrap_value_as_task_expression` — wrap bare-T expression as Tasks.TASK.from_result(orig) | |
| 22 | // `try_wrap_value_as_task_return` — same, but applied to a RETURN statement's expression | |
| 23 | // | |
| 24 | // AST builders (static — also callable from parser paths): | |
| 25 | // `build_completed_task_expression` — `Tasks.TASK.completed_task` (void async terminator) | |
| 26 | // `build_from_result_expression` — `Tasks.TASK.from_result(arg)` | |
| 27 | // | |
| 28 | // See `docs/claude/let-await-task-conversion.md` for the rule | |
| 29 | // table — when each conversion fires and what shape it produces. | |
| 30 | class TASK_CONVERSION(_innate_symbol_lookup: Lookups.InnateSymbolLookup) is | |
| 31 | _task_like_resolver: TASK_LIKE_RESOLVER? | |
| 32 | ||
| 33 | init(..) is | |
| 34 | _task_like_resolver = null | |
| 35 | si | |
| 36 | ||
| 37 | // Resolved lazily so construction stays cheap for the callers | |
| 38 | // that never ask about task-likes. | |
| 39 | resolver() -> TASK_LIKE_RESOLVER is | |
| 40 | if !_task_like_resolver? then | |
| 41 | _task_like_resolver = TASK_LIKE_RESOLVER(IoC.CONTAINER.instance.logger) | |
| 42 | fi | |
| 43 | ||
| 44 | return _task_like_resolver | |
| 45 | si | |
| 46 | ||
| 47 | // True iff `type` is the Task class — constructed Task[T] | |
| 48 | // or bare unspecialized Task (the latter shows up during | |
| 49 | // iterative inference before the element argument settles). | |
| 50 | // Used to gate the inferred-return wrap so we don't double- | |
| 51 | // wrap a body that already produces a Task. | |
| 52 | is_task_type(type: Type?) -> bool is | |
| 53 | if !type? then | |
| 54 | return false | |
| 55 | fi | |
| 56 | ||
| 57 | if try_get_task_element_type(type)? then | |
| 58 | return true | |
| 59 | fi | |
| 60 | ||
| 61 | let unspec = _innate_symbol_lookup.get_unspecialized_task_type() | |
| 62 | ||
| 63 | if !unspec? then | |
| 64 | return false | |
| 65 | fi | |
| 66 | ||
| 67 | // Compares the underlying Classy symbol against the | |
| 68 | // innate-known open `System.Threading.Tasks.Task`1` — name- | |
| 69 | // based comparison wouldn't survive the namespace remap. | |
| 70 | return type.symbol.unspecialized_symbol =~ unspec.symbol.unspecialized_symbol | |
| 71 | si | |
| 72 | ||
| 73 | // True iff `type` is the non-generic Tasks.TASK - the task-like | |
| 74 | // whose fall-through completion is Tasks.TASK.completed_task. | |
| 75 | is_void_task_return(type: Type?) -> bool is | |
| 76 | // An unresolved type is not known to be anything: a | |
| 77 | // sentinel matches whatever it is asked about, so a rule | |
| 78 | // that fired on one would commit before the answer exists. | |
| 79 | // A closure's return type is a sentinel while its body is | |
| 80 | // first walked, and a bare `return` rewritten here then | |
| 81 | // emits a completed task into a void method. | |
| 82 | if !type? \/ type.is_sentinel then | |
| 83 | return false | |
| 84 | fi | |
| 85 | ||
| 86 | let void_task = _innate_symbol_lookup.get_void_task_type() | |
| 87 | ||
| 88 | return void_task? /\ type.matches(void_task) | |
| 89 | si | |
| 90 | ||
| 91 | // True iff a machine-less body returning `type` completes at | |
| 92 | // fall-through rather than owing a value: the non-generic | |
| 93 | // Tasks.TASK (completed_task), or a task-like whose builder | |
| 94 | // can be driven without a machine. | |
| 95 | completes_without_machine(type: Type?) -> bool => | |
| 96 | is_void_task_return(type) \/ builder_completion_for(type)? | |
| 97 | ||
| 98 | // The task-like whose builder a machine-less body drives to | |
| 99 | // complete at fall-through, or null when `type` completes some | |
| 100 | // other way or not at all. The one place the contract is | |
| 101 | // stated: not Task, which completes through completed_task | |
| 102 | // instead; resolves as a task-like, which means its builder | |
| 103 | // has already been checked complete; carries no result, since | |
| 104 | // there is no value to deliver; and has a non-generic builder, | |
| 105 | // because only the state-machine lowering knows a generic | |
| 106 | // one's type arguments. | |
| 107 | builder_completion_for(type: Type?) -> TASK_LIKE? is | |
| 108 | if !type? \/ is_void_task_return(type) then | |
| 109 | return null | |
| 110 | fi | |
| 111 | ||
| 112 | let task_like = resolver().try_resolve(type) | |
| 113 | ||
| 114 | if !task_like? \/ !task_like.is_void_like then | |
| 115 | return null | |
| 116 | fi | |
| 117 | ||
| 118 | if let builder_classy = cast Symbols.Classy?(task_like.builder_type.symbol.unspecialized_symbol) then | |
| 119 | if builder_classy.is_generic then | |
| 120 | return null | |
| 121 | fi | |
| 122 | fi | |
| 123 | ||
| 124 | return task_like | |
| 125 | si | |
| 126 | ||
| 127 | // The return type an async closure with an inferred return | |
| 128 | // settles to when its body produces `element`: the generic | |
| 129 | // task-like the closure's slot named, applied to the element, | |
| 130 | // and `Tasks.TASK[element]` where no other task-like was named. | |
| 131 | async_return_type_for(function: Symbols.Function, element: Type) -> Type? is | |
| 132 | if let template = function.async_task_like_template then | |
| 133 | return | |
| 134 | Types.GENERIC( | |
| 135 | Source.LOCATION.internal, | |
| 136 | template, | |
| 137 | Collections.LIST[Type]([element]) | |
| 138 | ) | |
| 139 | fi | |
| 140 | ||
| 141 | return _innate_symbol_lookup.get_task_type(element) | |
| 142 | si | |
| 143 | ||
| 144 | // For a constructed `Tasks.TASK[T]` return T; null otherwise. | |
| 145 | // Compares the underlying Classy symbol against the | |
| 146 | // innate-known open `System.Threading.Tasks.Task`1` — name- | |
| 147 | // based comparison wouldn't survive the namespace remap. | |
| 148 | try_get_task_element_type(type: Type?) -> Type? is | |
| 149 | if !type? then | |
| 150 | return null | |
| 151 | fi | |
| 152 | ||
| 153 | let task_unspec = _innate_symbol_lookup.get_unspecialized_task_type() | |
| 154 | ||
| 155 | if !task_unspec? then | |
| 156 | return null | |
| 157 | fi | |
| 158 | ||
| 159 | if !isa Types.GENERIC(type) \/ !isa Types.GENERIC(task_unspec) then | |
| 160 | return null | |
| 161 | fi | |
| 162 | ||
| 163 | let constructed = type | |
| 164 | let unspec = task_unspec | |
| 165 | ||
| 166 | let constructed_sym = cast Symbols.GENERIC?(constructed.symbol) | |
| 167 | let unspec_sym = cast Symbols.GENERIC?(unspec.symbol) | |
| 168 | ||
| 169 | if !constructed_sym? \/ !unspec_sym? then | |
| 170 | return null | |
| 171 | fi | |
| 172 | ||
| 173 | if constructed_sym.symbol !~ unspec_sym.symbol then | |
| 174 | return null | |
| 175 | fi | |
| 176 | ||
| 177 | if constructed.arguments.count != 1 then | |
| 178 | return null | |
| 179 | fi | |
| 180 | ||
| 181 | return constructed.arguments[0] | |
| 182 | si | |
| 183 | ||
| 184 | // Attempt to wrap `orig` as `Tasks.TASK.from_result(orig)`. | |
| 185 | // Fires when `target_type` is a constructed `Task[T]` AND T | |
| 186 | // is assignable from `orig`'s type. The synthesised wrap | |
| 187 | // call is walked via `visitor` so its value/type fields get | |
| 188 | // populated before being returned. Returns the wrap | |
| 189 | // expression on success; null otherwise (target not Task[?], | |
| 190 | // element type mismatch, or post-walk resolution failure). | |
| 191 | try_wrap_value_as_task_expression( | |
| 192 | orig: Trees.Expressions.Expression?, | |
| 193 | target_type: Type, | |
| 194 | visitor: Syntax.Visitor | |
| 195 | ) -> Trees.Expressions.Expression? is | |
| 196 | if !orig? then | |
| 197 | return null | |
| 198 | fi | |
| 199 | ||
| 200 | let value = orig.value | |
| 201 | ||
| 202 | if !value? \/ !value.type? then | |
| 203 | return null | |
| 204 | fi | |
| 205 | ||
| 206 | let element_type = try_get_task_element_type(target_type) | |
| 207 | ||
| 208 | if !element_type? then | |
| 209 | return null | |
| 210 | fi | |
| 211 | ||
| 212 | if !element_type.is_assignable_from(value.type!) then | |
| 213 | return null | |
| 214 | fi | |
| 215 | ||
| 216 | let wrap_call = build_from_result_expression(orig.location, orig) | |
| 217 | ||
| 218 | wrap_call.walk(visitor) | |
| 219 | ||
| 220 | let wrap_value = wrap_call.value | |
| 221 | ||
| 222 | if !wrap_value? \/ !wrap_value.type? \/ !target_type.is_assignable_from(wrap_value.type) then | |
| 223 | return null | |
| 224 | fi | |
| 225 | ||
| 226 | return wrap_call | |
| 227 | si | |
| 228 | ||
| 229 | // Convenience: attempt the wrap and mutate `r.expression` in | |
| 230 | // place. Returns true on success. | |
| 231 | try_wrap_value_as_task_return( | |
| 232 | r: Trees.Statements.RETURN, | |
| 233 | target_type: Type, | |
| 234 | visitor: Syntax.Visitor | |
| 235 | ) -> bool is | |
| 236 | let wrapped = try_wrap_value_as_task_expression(r.expression, target_type, visitor) | |
| 237 | ||
| 238 | if !wrapped? then | |
| 239 | return false | |
| 240 | fi | |
| 241 | ||
| 242 | r.expression = wrapped | |
| 243 | ||
| 244 | return true | |
| 245 | si | |
| 246 | ||
| 247 | // `Tasks.TASK.completed_task` member-access AST. Used as the | |
| 248 | // return value for void async bodies — bare `return;` is | |
| 249 | // rewritten to `return Tasks.TASK.completed_task;` and a | |
| 250 | // synthesised continuation lambda's end-of-body fallthrough | |
| 251 | // appends the same. | |
| 252 | // | |
| 253 | // Outer nodes carry `loc` so resolver diagnostics anchor on | |
| 254 | // the user's source. Inner identifiers (`Tasks`, `TASK`, | |
| 255 | // `completed_task`) use `LOCATION.internal` so the analyser's | |
| 256 | // hover suppression drops them — the user didn't type them. | |
| 257 | build_completed_task_expression(loc: LOCATION) -> Trees.Expressions.Expression static is | |
| 258 | let internal_loc = LOCATION.internal | |
| 259 | ||
| 260 | let tasks_id = | |
| 261 | Trees.Expressions.IDENTIFIER(loc, Trees.Identifiers.Identifier(internal_loc, "Tasks")) | |
| 262 | let task_member = | |
| 263 | Trees.Expressions.MEMBER(loc, tasks_id, Trees.Identifiers.Identifier(internal_loc, "TASK"), loc) | |
| 264 | let completed_member = | |
| 265 | Trees.Expressions.MEMBER(loc, task_member, Trees.Identifiers.Identifier(internal_loc, "completed_task"), loc) | |
| 266 | ||
| 267 | return completed_member | |
| 268 | si | |
| 269 | ||
| 270 | // `Tasks.TASK.from_result(arg)` member-access + call AST. | |
| 271 | // Used internally by `try_wrap_value_as_task_expression`; also | |
| 272 | // exposed so paths that synthesise the wrap without going | |
| 273 | // through the assignability check (rare) can share the same | |
| 274 | // builder shape and hover-suppression conventions. | |
| 275 | build_from_result_expression( | |
| 276 | loc: LOCATION, | |
| 277 | arg: Trees.Expressions.Expression | |
| 278 | ) -> Trees.Expressions.Expression static is | |
| 279 | let internal_loc = LOCATION.internal | |
| 280 | ||
| 281 | let tasks_id = | |
| 282 | Trees.Expressions.IDENTIFIER(loc, Trees.Identifiers.Identifier(internal_loc, "Tasks")) | |
| 283 | let task_member = | |
| 284 | Trees.Expressions.MEMBER(loc, tasks_id, Trees.Identifiers.Identifier(internal_loc, "TASK"), loc) | |
| 285 | let from_result_member = | |
| 286 | Trees.Expressions.MEMBER(loc, task_member, Trees.Identifiers.Identifier(internal_loc, "from_result"), loc) | |
| 287 | ||
| 288 | let wrap_args = | |
| 289 | Trees.Expressions.LIST( | |
| 290 | loc, | |
| 291 | Collections.LIST[Trees.Expressions.Expression]([arg]: Trees.Expressions.Expression) | |
| 292 | ) | |
| 293 | ||
| 294 | return Trees.Expressions.CALL(loc, from_result_member, wrap_args) | |
| 295 | si | |
| 296 | si | |
| 297 | si |