Skip to content
← Back

src/syntax/process/destructure_resolver.ghul

1
namespace Syntax.Process is
2
use Logging
3
use Source.LOCATION
4
5
use Semantic.Symbols.Function
6
use Semantic.Symbols.FUNCTION_GROUP
7
use Semantic.Symbols.Symbol
8
use Semantic.Types.Type
9
10
// The shape decided for a destructure of `from_type` into a
11
// fixed list of target elements. Exactly one of `members` and
12
// `deconstruct_function` is populated.
13
//
14
// `members` carries one entry per element (null where it could
15
// not be resolved); the source for each element is loaded by
16
// calling the corresponding symbol's `load`.
17
//
18
// `deconstruct_function` carries the resolved `deconstruct(...)`
19
// instance method; the call shape is one INSTANCE call passing
20
// ADDRESS args for each `ref` parameter, with the temps then
21
// bound to the elements in order.
22
//
23
// Each constructor populates its own half, so `is_deconstruct`
24
// says which one to read.
25
@suppress("field-definite-assignment")
26
class DESTRUCTURE_STRATEGY is
27
// Per-element symbol; null entries mark slots that have no
28
// resolvable member (the consumer logs an error and treats
29
// the destructure as failed).
30
members: Collections.List[Symbol?] public
31
deconstruct_function: Function? public
32
33
is_deconstruct: bool => deconstruct_function?
34
35
init(members: Collections.List[Symbol?]) is
36
super.init()
37
self.members = members
38
si
39
40
init(deconstruct_function: Function) is
41
super.init()
42
self.deconstruct_function = deconstruct_function
43
si
44
si
45
46
// Decides, for a destructure of a source of `from_type` into a
47
// fixed list of target elements, how each element is supplied.
48
//
49
// Two entry points, matching the surface syntax split:
50
//
51
// - `resolve_strategy(from_type, element_count)` — positional
52
// `(a, b) = source`. Tries in order: value-tuple positional;
53
// `deconstruct(...)` instance method with all-`T ref` params;
54
// conventionally-named positional members `` `0 ``, `` `1 ``,
55
// ... A type without one of those shapes is not destructurable
56
// positionally — there is no by-name fallback.
57
//
58
// - `resolve_strategy_by_name(from_type, field_names)` — named
59
// `(local = field, …) = source`. Each element resolves to
60
// `from_type.find_member(field_name)`.
61
//
62
// Pure and shared: COMPILE_EXPRESSIONS resolves here and logs an
63
// error for any unresolved element; GENERATE_IL resolves the same
64
// way but only ever runs for an already-valid destructure.
65
// Keeping the decision in one place stops the two passes
66
// drifting apart.
67
class DESTRUCTURE_RESOLVER is
68
// Resolve the destructure strategy and log an error against
69
// `location` for any unresolvable case. When `field_names` is
70
// null the destructure is positional and the failure is
71
// reported as a count mismatch if the source has positional
72
// members, otherwise as a not-destructurable source. When
73
// `field_names` is non-null the destructure is by-name and
74
// each missed name is reported individually.
75
resolve_strategy_reporting(
76
logger: Logger,
77
location: LOCATION,
78
from_type: Type?,
79
element_count: int,
80
field_names: Collections.List[string?]?
81
) -> DESTRUCTURE_STRATEGY static
82
is
83
let strategy =
84
if field_names? then
85
resolve_strategy_by_name(from_type, field_names)
86
else
87
resolve_strategy(from_type, element_count)
88
fi
89
90
if !from_type? \/ from_type.is_error then
91
return strategy
92
fi
93
94
if strategy.is_deconstruct then
95
return strategy
96
fi
97
98
let members = strategy.members
99
let resolved mut = 0
100
101
for member in members do
102
if member? then
103
resolved = resolved + 1
104
fi
105
od
106
107
if resolved == element_count then
108
return strategy
109
fi
110
111
if field_names? then
112
// Named-group failure: pinpoint the missed field
113
// names individually.
114
for i in 0..field_names.count do
115
if !members[i]? then
116
let name = field_names[i]
117
118
logger.error(location, "{from_type} has no member {name}")
119
fi
120
od
121
122
return strategy
123
fi
124
125
let positional_arity =
126
if from_type.is_value_tuple then
127
from_type.arguments.count
128
else
129
positional_member_count(from_type)
130
fi
131
132
if positional_arity > 0 then
133
logger.error(location, "expected {element_count} destructuring elements but found {positional_arity}")
134
else
135
logger.error(location, "cannot destructure {from_type}")
136
fi
137
138
return strategy
139
si
140
141
resolve_strategy(
142
from_type: Type?,
143
element_count: int
144
) -> DESTRUCTURE_STRATEGY static is
145
if !from_type? \/ from_type.is_error then
146
return DESTRUCTURE_STRATEGY(_nulls(element_count))
147
fi
148
149
if
150
from_type.is_value_tuple /\
151
from_type.arguments.count == element_count
152
then
153
return DESTRUCTURE_STRATEGY(_positional_members(from_type, element_count))
154
fi
155
156
let deconstruct = try_resolve_deconstruct(from_type, element_count)
157
158
if deconstruct? then
159
return DESTRUCTURE_STRATEGY(deconstruct)
160
fi
161
162
let pos_arity = positional_member_count(from_type)
163
164
if pos_arity > 0 /\ pos_arity == element_count then
165
return DESTRUCTURE_STRATEGY(_positional_members(from_type, element_count))
166
fi
167
168
return DESTRUCTURE_STRATEGY(_nulls(element_count))
169
si
170
171
// `field_names` carries one entry per element naming the
172
// source member that backs that element; a null entry marks
173
// a nested destructuring group (which holds its own
174
// resolver state). A null member result on a non-null name
175
// means the source has no such member — the caller reports
176
// the diagnostic.
177
resolve_strategy_by_name(
178
from_type: Type?,
179
field_names: Collections.List[string?]
180
) -> DESTRUCTURE_STRATEGY static is
181
if !from_type? \/ from_type.is_error then
182
return DESTRUCTURE_STRATEGY(_nulls(field_names.count))
183
fi
184
185
let result = Collections.LIST[Symbol?]()
186
for name in field_names do
187
if !name? then
188
result.add(null)
189
else
190
result.add(from_type.find_member(name))
191
fi
192
od
193
return DESTRUCTURE_STRATEGY(result)
194
si
195
196
_nulls(count: int) -> Collections.List[Symbol?] static is
197
let result = Collections.LIST[Symbol?]()
198
for i in 0..count do
199
result.add(null)
200
od
201
return result
202
si
203
204
// The number of leading `0 `1 ... conventionally-named
205
// destructure members the type exposes.
206
positional_member_count(type: Type) -> int static is
207
let count mut = 0
208
let member mut = type.find_destructure_member(count)
209
210
while member? do
211
count = count + 1
212
member = type.find_destructure_member(count)
213
od
214
215
return count
216
si
217
218
// Returns the resolved `deconstruct(...)` instance method
219
// whose arity equals `element_count` and whose parameters
220
// are all `T ref`, or null if no such method exists. When
221
// more than one overload matches the arity, returns null —
222
// the caller falls through to the next destructure strategy.
223
try_resolve_deconstruct(
224
from_type: Type?,
225
element_count: int
226
) -> Function? static is
227
if !from_type? then
228
return null
229
fi
230
231
let member = from_type.find_member("deconstruct")
232
233
if !member? then
234
return null
235
fi
236
237
let candidates = Collections.LIST[Function]()
238
239
if let group: FUNCTION_GROUP = member then
240
for f in group.functions do
241
if is_viable_deconstruct(f, element_count) then
242
candidates.add(f)
243
fi
244
od
245
elif let function: Function = member then
246
if is_viable_deconstruct(function, element_count) then
247
candidates.add(function)
248
fi
249
fi
250
251
if candidates.count == 1 then
252
return candidates[0]
253
fi
254
255
return null
256
si
257
258
is_viable_deconstruct(f: Function?, element_count: int) -> bool static is
259
if !f? \/ !f.is_instance then
260
return false
261
fi
262
263
if !f.are_arguments_declared \/ f.arguments.count != element_count then
264
return false
265
fi
266
267
for a in f.arguments do
268
if !a.is_ref then
269
return false
270
fi
271
od
272
273
return true
274
si
275
276
_positional_members(from_type: Type, element_count: int) -> Collections.List[Symbol?] static is
277
let result = Collections.LIST[Symbol?]()
278
for i in 0..element_count do
279
result.add(from_type.find_destructure_member(i))
280
od
281
return result
282
si
283
si
284
si