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