Skip to content
← Back

src/syntax/process/case_pattern_matrix.ghul

1
namespace Syntax.Process is
2
use Semantic.Types.Type
3
use Semantic.Symbols.Classy
4
5
// A pattern occupying one column: a wildcard, which matches every
6
// value of that column, or a constructor with one sub-pattern per
7
// field.
8
class CASE_PATTERN is
9
constructor: PATTERN_CONSTRUCTOR?
10
fields: Collections.List[CASE_PATTERN]
11
12
is_wildcard: bool => !constructor?
13
14
init() is
15
super.init()
16
17
fields = Collections.LIST[CASE_PATTERN]()
18
si
19
20
init(constructor: PATTERN_CONSTRUCTOR, fields: Collections.List[CASE_PATTERN]) is
21
super.init()
22
23
self.constructor = constructor
24
self.fields = fields
25
si
26
27
// How a missing case reads in a diagnostic. A constructor
28
// whose fields are all wildcards is named on its own, so a
29
// union variant reads as `WHOLE` rather than `WHOLE(_)`
30
// unless a field is what distinguishes the missing case.
31
describe() -> string is
32
if let c = constructor then
33
if c.kind == ConstructorKind.PRODUCT then
34
return "({_describe_fields()})"
35
fi
36
37
if fields.count == 0 \/ (fields |> Ghul.Pipes.all(f => f.is_wildcard)) then
38
return c.name
39
fi
40
41
return "{c.name}({_describe_fields()})"
42
fi
43
44
return "_"
45
si
46
47
_describe_fields() -> string =>
48
fields |> Ghul.Pipes.map(f => f.describe()) |> Ghul.Pipes.join(", ")
49
si
50
51
// Builds the pattern rows a `case`'s arms contribute.
52
//
53
// An arm whose shape cannot be modelled contributes no rows at
54
// all rather than an approximation. Dropping a row can only leave
55
// the domain looking less covered than it is, which costs a
56
// warning; crediting one that does not match would let a gap
57
// close and leave the no-match path unreachable in the checker's
58
// view alone.
59
class CASE_PATTERN_BUILDER is
60
init() is
61
super.init()
62
si
63
64
// The rows this arm contributes, or null when its shape
65
// cannot be modelled. An arm carrying several labels, or an
66
// optional ascription, contributes one row each.
67
build_rows(
68
arm: Trees.Statements.CASE_MATCH,
69
domain: PATTERN_DOMAIN?
70
) -> Collections.List[Collections.List[CASE_PATTERN]]? is
71
// A guarded arm can decline to match a value its pattern
72
// admits, so it never covers anything.
73
if arm.guard? then
74
return null
75
fi
76
77
let rows = Collections.LIST[Collections.List[CASE_PATTERN]]()
78
79
if let expressions = arm.expressions then
80
for expression in expressions.expressions do
81
let pattern = _from_expression(expression, domain)
82
83
if !pattern? then
84
return null
85
fi
86
87
rows.add(_row(pattern))
88
od
89
90
return rows
91
fi
92
93
if let pattern = arm.pattern then
94
let patterns = _from_variable(pattern, domain)
95
96
if !patterns? then
97
return null
98
fi
99
100
for p in patterns do
101
rows.add(_row(p))
102
od
103
104
return rows
105
fi
106
107
rows.add(_row(CASE_PATTERN()))
108
109
return rows
110
si
111
112
_row(pattern: CASE_PATTERN) -> Collections.List[CASE_PATTERN] is
113
let row = Collections.LIST[CASE_PATTERN]()
114
115
row.add(pattern)
116
117
return row
118
si
119
120
_from_variable(
121
pattern: Trees.Variables.VARIABLE,
122
domain: PATTERN_DOMAIN?
123
) -> Collections.List[CASE_PATTERN]? is
124
if !pattern.is_explicit_type then
125
let single = _from_left(pattern.left, domain)
126
127
if !single? then
128
return null
129
fi
130
131
let result = Collections.LIST[CASE_PATTERN]()
132
133
result.add(single)
134
135
return result
136
fi
137
138
return _from_ascription(
139
pattern.type_expression.type,
140
_elements_of(pattern.left),
141
domain
142
)
143
si
144
145
// An ascribed pattern selects the alternative its target
146
// names. An optional target additionally admits absence, so
147
// it contributes a second row rather than being approximated
148
// by either half alone.
149
_from_ascription(
150
target: Type?,
151
elements: Collections.List[Trees.Variables.VariableLeft]?,
152
domain: PATTERN_DOMAIN?
153
) -> Collections.List[CASE_PATTERN]? is
154
if !target? \/ !target.is_settled \/ !domain? then
155
return null
156
fi
157
158
let target_optional = target.is_optional
159
let core = if target_optional then target.as_non_optional() else target fi
160
161
let constructor = _find_classy_constructor(domain, core)
162
163
if !constructor? then
164
return null
165
fi
166
167
let fields = _fields(elements, constructor)
168
169
if !fields? then
170
return null
171
fi
172
173
let result = Collections.LIST[CASE_PATTERN]()
174
175
result.add(CASE_PATTERN(constructor, fields))
176
177
if target_optional then
178
let null_constructor = _find_kind(domain, ConstructorKind.NULL)
179
180
if !null_constructor? then
181
return null
182
fi
183
184
result.add(CASE_PATTERN(null_constructor, Collections.LIST[CASE_PATTERN]()))
185
fi
186
187
return result
188
si
189
190
_from_left(
191
left: Trees.Variables.VariableLeft,
192
domain: PATTERN_DOMAIN?
193
) -> CASE_PATTERN? is
194
// A pattern with no runtime test of its own matches every
195
// value it is given, whatever shape it destructures into.
196
if !left.has_intrinsic_refutability then
197
return CASE_PATTERN()
198
fi
199
200
if let literal: Trees.Variables.LITERAL_VARIABLE_LEFT = left then
201
return _from_expression(literal.expression, domain)
202
fi
203
204
let elements = _elements_of(left)
205
206
if elements? /\ _has_named_group(elements) then
207
return null
208
fi
209
210
if let ascription = left.type_expression then
211
let patterns = _from_ascription(ascription.type, elements, domain)
212
213
// An optional ascription below the top level would
214
// need the column to hold two shapes at once, which a
215
// single row cannot say.
216
if !patterns? \/ patterns.count != 1 then
217
return null
218
fi
219
220
return patterns[0]
221
fi
222
223
if !elements? \/ !domain? then
224
return null
225
fi
226
227
let constructor = _find_kind(domain, ConstructorKind.PRODUCT)
228
229
if !constructor? then
230
return null
231
fi
232
233
let fields = _fields(elements, constructor)
234
235
if !fields? then
236
return null
237
fi
238
239
return CASE_PATTERN(constructor, fields)
240
si
241
242
_from_expression(
243
expression: Trees.Expressions.Expression,
244
domain: PATTERN_DOMAIN?
245
) -> CASE_PATTERN? is
246
if isa Trees.Expressions.NULL(expression) then
247
return _leaf(domain, ConstructorKind.NULL)
248
fi
249
250
if let boolean: Trees.Expressions.Literals.BOOLEAN = expression then
251
if boolean.value_string =~ "true" then
252
return _leaf(domain, ConstructorKind.TRUE)
253
fi
254
255
if boolean.value_string =~ "false" then
256
return _leaf(domain, ConstructorKind.FALSE)
257
fi
258
259
return null
260
fi
261
262
let enum_constructor = _try_get_enum_constructor(expression, domain)
263
264
if enum_constructor? then
265
return CASE_PATTERN(enum_constructor, Collections.LIST[CASE_PATTERN]())
266
fi
267
268
let unit_constructor = _try_get_unit_variant_constructor(expression, domain)
269
270
if unit_constructor? then
271
return CASE_PATTERN(unit_constructor, Collections.LIST[CASE_PATTERN]())
272
fi
273
274
// A value drawn from a domain too large to enumerate. It
275
// is distinct from every other alternative, including any
276
// other literal, so it can never complete a domain.
277
return CASE_PATTERN(
278
PATTERN_CONSTRUCTOR(ConstructorKind.OPAQUE, "_", Collections.LIST[PATTERN_DOMAIN?]()),
279
Collections.LIST[CASE_PATTERN]()
280
)
281
si
282
283
_leaf(domain: PATTERN_DOMAIN?, kind: ConstructorKind) -> CASE_PATTERN? is
284
if !domain? then
285
return null
286
fi
287
288
let constructor = _find_kind(domain, kind)
289
290
if !constructor? then
291
return null
292
fi
293
294
return CASE_PATTERN(constructor, Collections.LIST[CASE_PATTERN]())
295
si
296
297
// Recognises an expression compile-expressions has resolved to
298
// a numeric literal of the scrutinee's own enum type, and
299
// returns the member it names. The value is matched by value —
300
// that is what distinguishes one member from another — and the
301
// member symbol is what the resulting pattern is keyed on.
302
_try_get_enum_constructor(
303
expression: Trees.Expressions.Expression,
304
domain: PATTERN_DOMAIN?
305
) -> PATTERN_CONSTRUCTOR? is
306
if !domain? \/ !domain.enum_root? then
307
return null
308
fi
309
310
if !expression.value? then
311
return null
312
fi
313
314
let number = cast IR.Values.Literal.NUMBER?(expression.value)
315
316
if !number? then
317
return null
318
fi
319
320
let number_classy = _classy_of(number.type)
321
322
if !number_classy? \/ !(number_classy =~ domain.enum_root!) then
323
return null
324
fi
325
326
for constructor in domain.constructors do
327
if let member = constructor.enum_member then
328
if member.emitted_value =~ number.rendered then
329
return constructor
330
fi
331
fi
332
od
333
334
return null
335
si
336
337
// Recognises a value whose type is a variant carrying no
338
// fields. Such a variant has a single shared instance, so a
339
// value of that type is the whole of the alternative and
340
// matching it covers what a pattern ascribing the variant
341
// would.
342
_try_get_unit_variant_constructor(
343
expression: Trees.Expressions.Expression,
344
domain: PATTERN_DOMAIN?
345
) -> PATTERN_CONSTRUCTOR? is
346
if !domain? \/ !expression.value? then
347
return null
348
fi
349
350
let type = expression.value.type
351
352
if !type? \/ !type.is_settled \/ type.is_optional then
353
return null
354
fi
355
356
let classy = _classy_of(type)
357
358
if !classy? \/ !classy.is_unit_variant then
359
return null
360
fi
361
362
return _find_classy_constructor(domain, type)
363
si
364
365
_classy_of(type: Type?) -> Classy? is
366
if !type? then
367
return null
368
fi
369
370
if let named = cast Semantic.Types.NAMED?(type) then
371
return cast Classy?(named.symbol.unspecialized_symbol)
372
fi
373
374
return null
375
si
376
377
_find_classy_constructor(domain: PATTERN_DOMAIN, core: Type) -> PATTERN_CONSTRUCTOR? is
378
let target = _classy_of(core)
379
380
if !target? then
381
return null
382
fi
383
384
for constructor in domain.constructors do
385
if let classy = constructor.classy then
386
if classy =~ target then
387
return constructor
388
fi
389
fi
390
od
391
392
return null
393
si
394
395
_find_kind(domain: PATTERN_DOMAIN, kind: ConstructorKind) -> PATTERN_CONSTRUCTOR? is
396
for constructor in domain.constructors do
397
if constructor.kind == kind then
398
return constructor
399
fi
400
od
401
402
return null
403
si
404
405
// One sub-pattern per field of `constructor`. A pattern that
406
// writes no elements binds the whole alternative and so
407
// matches every field; one whose element count disagrees with
408
// the alternative's shape cannot be modelled.
409
_fields(
410
elements: Collections.List[Trees.Variables.VariableLeft]?,
411
constructor: PATTERN_CONSTRUCTOR
412
) -> Collections.List[CASE_PATTERN]? is
413
let field_domains = constructor.field_domains
414
415
if !elements? then
416
return wildcards(field_domains.count)
417
fi
418
419
if elements.count != field_domains.count then
420
return null
421
fi
422
423
let result = Collections.LIST[CASE_PATTERN]()
424
425
for i in 0..elements.count do
426
let element = _from_left(elements[i], field_domains[i])
427
428
if !element? then
429
return null
430
fi
431
432
result.add(element)
433
od
434
435
return result
436
si
437
438
_elements_of(left: Trees.Variables.VariableLeft) -> Collections.List[Trees.Variables.VariableLeft]? is
439
if isa Trees.Variables.DESTRUCTURING_VARIABLE_LEFT(left) then
440
return left.elements
441
fi
442
443
return null
444
si
445
446
_has_named_group(elements: Collections.List[Trees.Variables.VariableLeft]) -> bool static is
447
for element in elements do
448
if element.source_field_name? then
449
return true
450
fi
451
od
452
453
return false
454
si
455
456
wildcards(count: int) -> Collections.List[CASE_PATTERN] static is
457
let result = Collections.LIST[CASE_PATTERN]()
458
459
for i in 0..count do
460
result.add(CASE_PATTERN())
461
od
462
463
return result
464
si
465
si
466
si