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
//
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