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 | // | |
| 17 | // Conversion attempts: | |
| 18 | // `try_wrap_value_as_task_expression` — wrap bare-T expression as Tasks.TASK.from_result(orig) | |
| 19 | // `try_wrap_value_as_task_return` — same, but applied to a RETURN statement's expression | |
| 20 | // | |
| 21 | // AST builders (static — also callable from parser paths): | |
| 22 | // `build_completed_task_expression` — `Tasks.TASK.completed_task` (void async terminator) | |
| 23 | // `build_from_result_expression` — `Tasks.TASK.from_result(arg)` | |
| 24 | // | |
| 25 | // See `docs/claude/let-await-task-conversion.md` for the rule | |
| 26 | // table — when each conversion fires and what shape it produces. | |
| 27 | class TASK_CONVERSION(_innate_symbol_lookup: Lookups.InnateSymbolLookup) is | |
| 28 | ||
| 29 | // True iff `type` is the Task class — constructed Task[T] | |
| 30 | // or bare unspecialized Task (the latter shows up during | |
| 31 | // iterative inference before the element argument settles). | |
| 32 | // Used to gate the inferred-return wrap so we don't double- | |
| 33 | // wrap a body that already produces a Task. | |
| 34 | is_task_type(type: Type?) -> bool is | |
| 35 | if !type? then | |
| 36 | return false; | |
| 37 | fi | |
| 38 | ||
| 39 | if try_get_task_element_type(type)? then | |
| 40 | return true; | |
| 41 | fi | |
| 42 | ||
| 43 | let unspec = _innate_symbol_lookup.get_unspecialized_task_type(); | |
| 44 | ||
| 45 | if !unspec? then | |
| 46 | return false; | |
| 47 | fi | |
| 48 | ||
| 49 | let unspec_classy = unspec.symbol.unspecialized_symbol; | |
| 50 | let value_classy = type.symbol.unspecialized_symbol; | |
| 51 | ||
| 52 | return value_classy =~ unspec_classy \/ value_classy.qualified_name =~ unspec_classy.qualified_name; | |
| 53 | si | |
| 54 | ||
| 55 | // For a constructed `Tasks.TASK[T]` return T; null otherwise. | |
| 56 | // Compares the underlying Classy symbol against the | |
| 57 | // innate-known open `System.Threading.Tasks.Task`1` — name- | |
| 58 | // based comparison wouldn't survive the namespace remap. | |
| 59 | try_get_task_element_type(type: Type?) -> Type? is | |
| 60 | if !type? then | |
| 61 | return null; | |
| 62 | fi | |
| 63 | ||
| 64 | let task_unspec = _innate_symbol_lookup.get_unspecialized_task_type(); | |
| 65 | ||
| 66 | if !task_unspec? then | |
| 67 | return null; | |
| 68 | fi | |
| 69 | ||
| 70 | if !isa Types.GENERIC(type) \/ !isa Types.GENERIC(task_unspec) then | |
| 71 | return null; | |
| 72 | fi | |
| 73 | ||
| 74 | let constructed = type; | |
| 75 | let unspec = task_unspec; | |
| 76 | ||
| 77 | let constructed_sym = cast Symbols.GENERIC?(constructed.symbol); | |
| 78 | let unspec_sym = cast Symbols.GENERIC?(unspec.symbol); | |
| 79 | ||
| 80 | if !constructed_sym? \/ !unspec_sym? then | |
| 81 | return null; | |
| 82 | fi | |
| 83 | ||
| 84 | if constructed_sym.symbol !~ unspec_sym.symbol then | |
| 85 | return null; | |
| 86 | fi | |
| 87 | ||
| 88 | if constructed.arguments.count != 1 then | |
| 89 | return null; | |
| 90 | fi | |
| 91 | ||
| 92 | return constructed.arguments[0]; | |
| 93 | si | |
| 94 | ||
| 95 | // Attempt to wrap `orig` as `Tasks.TASK.from_result(orig)`. | |
| 96 | // Fires when `target_type` is a constructed `Task[T]` AND T | |
| 97 | // is assignable from `orig`'s type. The synthesised wrap | |
| 98 | // call is walked via `visitor` so its value/type fields get | |
| 99 | // populated before being returned. Returns the wrap | |
| 100 | // expression on success; null otherwise (target not Task[?], | |
| 101 | // element type mismatch, or post-walk resolution failure). | |
| 102 | try_wrap_value_as_task_expression( | |
| 103 | orig: Trees.Expressions.Expression?, | |
| 104 | target_type: Type, | |
| 105 | visitor: Syntax.Visitor | |
| 106 | ) -> Trees.Expressions.Expression? is | |
| 107 | if !orig? then | |
| 108 | return null; | |
| 109 | fi | |
| 110 | ||
| 111 | let value = orig.value; | |
| 112 | ||
| 113 | if !value? \/ !value.type? then | |
| 114 | return null; | |
| 115 | fi | |
| 116 | ||
| 117 | let element_type = try_get_task_element_type(target_type); | |
| 118 | ||
| 119 | if !element_type? then | |
| 120 | return null; | |
| 121 | fi | |
| 122 | ||
| 123 | if !element_type.is_assignable_from(value.type!) then | |
| 124 | return null; | |
| 125 | fi | |
| 126 | ||
| 127 | let wrap_call = build_from_result_expression(orig.location, orig); | |
| 128 | ||
| 129 | wrap_call.walk(visitor); | |
| 130 | ||
| 131 | let wrap_value = wrap_call.value; | |
| 132 | ||
| 133 | if !wrap_value? \/ !wrap_value.type? \/ !target_type.is_assignable_from(wrap_value.type!) then | |
| 134 | return null; | |
| 135 | fi | |
| 136 | ||
| 137 | return wrap_call; | |
| 138 | si | |
| 139 | ||
| 140 | // Convenience: attempt the wrap and mutate `r.expression` in | |
| 141 | // place. Returns true on success. | |
| 142 | try_wrap_value_as_task_return( | |
| 143 | r: Trees.Statements.RETURN, | |
| 144 | target_type: Type, | |
| 145 | visitor: Syntax.Visitor | |
| 146 | ) -> bool is | |
| 147 | let wrapped = try_wrap_value_as_task_expression(r.expression, target_type, visitor); | |
| 148 | ||
| 149 | if !wrapped? then | |
| 150 | return false; | |
| 151 | fi | |
| 152 | ||
| 153 | r.expression = wrapped; | |
| 154 | ||
| 155 | return true; | |
| 156 | si | |
| 157 | ||
| 158 | // `Tasks.TASK.completed_task` member-access AST. Used as the | |
| 159 | // return value for void async bodies — bare `return;` is | |
| 160 | // rewritten to `return Tasks.TASK.completed_task;` and a | |
| 161 | // synthesised continuation lambda's end-of-body fallthrough | |
| 162 | // appends the same. | |
| 163 | // | |
| 164 | // Outer nodes carry `loc` so resolver diagnostics anchor on | |
| 165 | // the user's source. Inner identifiers (`Tasks`, `TASK`, | |
| 166 | // `completed_task`) use `LOCATION.internal` so the analyser's | |
| 167 | // hover suppression drops them — the user didn't type them. | |
| 168 | build_completed_task_expression(loc: LOCATION) -> Trees.Expressions.Expression static is | |
| 169 | let internal_loc = LOCATION.internal; | |
| 170 | ||
| 171 | let tasks_id = | |
| 172 | Trees.Expressions.IDENTIFIER(loc, Trees.Identifiers.Identifier(internal_loc, "Tasks")); | |
| 173 | let task_member = | |
| 174 | Trees.Expressions.MEMBER(loc, tasks_id, Trees.Identifiers.Identifier(internal_loc, "TASK"), loc); | |
| 175 | let completed_member = | |
| 176 | Trees.Expressions.MEMBER(loc, task_member, Trees.Identifiers.Identifier(internal_loc, "completed_task"), loc); | |
| 177 | ||
| 178 | return completed_member; | |
| 179 | si | |
| 180 | ||
| 181 | // `Tasks.TASK.from_result(arg)` member-access + call AST. | |
| 182 | // Used internally by `try_wrap_value_as_task_expression`; also | |
| 183 | // exposed so paths that synthesise the wrap without going | |
| 184 | // through the assignability check (rare) can share the same | |
| 185 | // builder shape and hover-suppression conventions. | |
| 186 | build_from_result_expression( | |
| 187 | loc: LOCATION, | |
| 188 | arg: Trees.Expressions.Expression | |
| 189 | ) -> Trees.Expressions.Expression static is | |
| 190 | let internal_loc = LOCATION.internal; | |
| 191 | ||
| 192 | let tasks_id = | |
| 193 | Trees.Expressions.IDENTIFIER(loc, Trees.Identifiers.Identifier(internal_loc, "Tasks")); | |
| 194 | let task_member = | |
| 195 | Trees.Expressions.MEMBER(loc, tasks_id, Trees.Identifiers.Identifier(internal_loc, "TASK"), loc); | |
| 196 | let from_result_member = | |
| 197 | Trees.Expressions.MEMBER(loc, task_member, Trees.Identifiers.Identifier(internal_loc, "from_result"), loc); | |
| 198 | ||
| 199 | let wrap_args = | |
| 200 | Trees.Expressions.LIST( | |
| 201 | loc, | |
| 202 | Collections.LIST[Trees.Expressions.Expression]([arg]: Trees.Expressions.Expression) | |
| 203 | ); | |
| 204 | ||
| 205 | return Trees.Expressions.CALL(loc, from_result_member, wrap_args); | |
| 206 | si | |
| 207 | si | |
| 208 | si |