Skip to content
← Back

src/semantic/task_like.ghul

1
namespace Semantic is
2
use Source.LOCATION
3
4
use Types.Type
5
6
// What an async function returns, in the shape the lowering needs:
7
// the result its body produces (the type the builder's `set_result`
8
// delivers, void for a task-like with no type argument), and the
9
// unspecialized builder an async function returning the task-like
10
// drives.
11
//
12
// A task-like is a type carrying AsyncMethodBuilderAttribute naming
13
// its builder - `Tasks.TASK[T]` / `Tasks.ValueTask[T]` from the BCL,
14
// or a type declared in ghūl source. The convention, matching the
15
// BCL's own task-likes: a generic task-like has exactly one type
16
// argument and it is the result type; a non-generic one carries no
17
// result.
18
class TASK_LIKE is
19
task_type: Type
20
result_type: Type?
21
builder_type: Type
22
23
is_void_like: bool => !result_type?
24
25
// The one home for locating a builder member: an overload of the
26
// name taking exactly `arity` arguments. Both the completeness
27
// check above and the emission-time member resolution route
28
// through here, so the contract they enforce is the same one.
29
member_of_arity(type: Type, name: string, arity: int) -> Symbols.Function? static is
30
if let group: Symbols.FUNCTION_GROUP = type.find_member(name) then
31
for f in group.functions do
32
if f.arguments.count == arity then
33
return f
34
fi
35
od
36
fi
37
38
return null
39
si
40
41
init(task_type: Type, result_type: Type?, builder_type: Type) is
42
self.task_type = task_type
43
self.result_type = result_type
44
self.builder_type = builder_type
45
si
46
si
47
48
// Recognises a task-like return type. The builder is read from the
49
// type's symbol: for an imported type, `Classy.async_builder` was
50
// populated at import from the attribute; for a source-declared
51
// type, from its attribute pragma during resolve-type-expressions -
52
// both before any body is compiled, so the answer does not depend
53
// on file order.
54
class TASK_LIKE_RESOLVER(_logger: Logging.Logger) is
55
// The same answer with nothing reported: for probing a type
56
// that may not be a task-like at all, where a malformed one
57
// is somebody else's to diagnose, at a location of its own.
58
try_resolve(type: Type?) -> TASK_LIKE? =>
59
TASK_LIKE_RESOLVER(Logging.DIAGNOSTICS_STORE()).resolve(LOCATION.internal, type)
60
61
resolve(location: LOCATION, return_type: Type?) -> TASK_LIKE? is
62
if !return_type? \/ return_type.is_error then
63
return null
64
fi
65
66
let classy = cast Symbols.Classy?(return_type.symbol.unspecialized_symbol)
67
68
if !classy? then
69
return null
70
fi
71
72
if classy.is_generic then
73
// Only the single-argument convention is supported; a
74
// generic whose argument is not the result type has no
75
// way to say which argument is.
76
if !isa Types.GENERIC(return_type) then
77
return null
78
fi
79
80
let generic = cast Types.GENERIC(return_type)
81
82
if generic.arguments.count != 1 then
83
_logger.error(
84
location,
85
"a generic task-like type must have exactly one type argument, but {return_type} has {generic.arguments.count}"
86
)
87
return null
88
fi
89
fi
90
91
let builder_type = classy.async_builder
92
93
if !builder_type? then
94
return null
95
fi
96
97
let result_type: Type? =
98
if isa Types.GENERIC(return_type) then
99
(cast Types.GENERIC(return_type)).arguments[0]
100
else
101
null
102
fi
103
104
if !_builder_is_complete(location, builder_type, result_type) then
105
return null
106
fi
107
108
return TASK_LIKE(return_type, result_type, builder_type)
109
si
110
111
// The members the async lowering drives a builder through. Every
112
// one is checked here so a malformed builder is reported at the
113
// function that returns its task-like, not as an emission-time
114
// contract failure.
115
_builder_is_complete(location: LOCATION, builder_type: Type, result_type: Type?) -> bool is
116
let set_result_arity: int = if result_type? then 1 else 0 fi
117
118
let required: Collections.List[(string, int)] =
119
Collections.LIST[(string, int)]([
120
("create", 0),
121
("start", 1),
122
("set_result", set_result_arity),
123
("set_exception", 1),
124
("await_on_completed", 2),
125
("await_unsafe_on_completed", 2)
126
])
127
128
let task_property = cast Symbols.Property?(builder_type.find_member("task"))
129
130
if !(task_property? /\ task_property.read_function?) then
131
_logger.error(
132
location,
133
"builder {builder_type} for a task-like return type has no readable task property")
134
return false
135
fi
136
137
for (name, arity) in required do
138
let member = TASK_LIKE.member_of_arity(builder_type, name, arity)
139
140
if !member? then
141
_logger.error(
142
location,
143
"builder {builder_type} for a task-like return type has no {name} method taking {arity} argument(s)")
144
return false
145
fi
146
147
// The members the lowering passes by address: `start`
148
// receives the state machine by reference, and both
149
// await registrations receive the awaiter by reference.
150
// An arity match alone would let a by-value declaration
151
// through, and the emission always passes the address.
152
if
153
name =~ "start" \/ name.starts_with("await_")
154
then
155
if !isa Types.REFERENCE(member.arguments[0]) then
156
_logger.error(
157
location,
158
"builder {builder_type} for a task-like return type: {name} must take its first argument by reference")
159
return false
160
fi
161
fi
162
od
163
164
return true
165
si
166
si
167
si