Skip to content
← Back

src/semantic/task_conversion.ghul

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