Skip to content
← Back

src/semantic/types/one_of.ghul

1
namespace Semantic.Types is
2
use IO.Std;
3
4
use Source.LOCATION;
5
6
use Logging;
7
8
// Internal narrowing-only sum type. Represents "value of closed
9
// root R restricted to a non-empty proper subset of R's
10
// alternatives" — built only by the narrowing path in
11
// compile_expressions when an if/elif chain rules out one or
12
// more alternatives, never produced by user-written type
13
// expressions and never reaches IL gen (reads inside a narrowed
14
// scope compile against the underlying root; release_scope
15
// restores the declared type). Two closed-root shapes feed
16
// this: a union (alternatives are its variants) and a closed
17
// class (alternatives are its in-assembly direct subclasses).
18
//
19
// Subclasses NAMED with the root's own symbol so find_member,
20
// ancestors, scope, gen_* and qualified_name all delegate to
21
// the root — the issue's "members common to every subtype"
22
// requirement is met by exposing the root's own members as a
23
// strict lower bound. Construction is by Types.ONE_OF.create
24
// which degenerates |subtypes| = 1 to a plain subtype and
25
// rejects |subtypes| = 0 (returns null — the caller's signal
26
// that the chain is exhaustive).
27
//
28
// Identity is by underlying type + subtype set: the `matches` override
29
// refuses equality against the plain underlying root so that
30
// NARROWING.try_push sees the type as changed. Comparison is
31
// handled symmetrically: NAMED.compare(ONE_OF) already produces
32
// SAME because the symbols match (assignability-to-root holds
33
// by construction), GENERIC.compare(ONE_OF) delegates to the
34
// underlying type via a small guard so the same identity falls
35
// out for generic roots.
36
class ONE_OF: NAMED is
37
_underlying_type: NAMED;
38
_subtypes: Collections.LIST[Symbols.Classy];
39
40
underlying_type: NAMED => _underlying_type;
41
subtypes: Collections.Iterable[Symbols.Classy] => _subtypes;
42
subtypes_count: int => _subtypes.count;
43
44
init(underlying_type: NAMED, subtypes: Collections.LIST[Symbols.Classy]) is
45
super.init(underlying_type.symbol);
46
47
_underlying_type = underlying_type;
48
_subtypes = subtypes;
49
si
50
51
// Build the narrowed type for `subtypes` over the given
52
// underlying root type. Returns null when `subtypes` is
53
// empty (the caller's exhaustiveness signal). Returns a
54
// plain subtype for the singleton case so the size-1
55
// ONE_OF edge case is never representable. Returns a
56
// ONE_OF for |subtypes| >= 2.
57
create(
58
underlying_type: NAMED?,
59
subtypes: Collections.LIST[Symbols.Classy]?
60
) -> NAMED? static is
61
if !underlying_type? \/ !subtypes? \/ subtypes.count == 0 then
62
return null;
63
fi
64
65
if subtypes.count == 1 then
66
return build_singleton_subtype(underlying_type, subtypes[0]);
67
fi
68
69
return ONE_OF(underlying_type, subtypes);
70
si
71
72
// Build the subtype carrying the underlying receiver's generic
73
// args. For a variant subtype of a generic union, args are
74
// shared with the union slot-for-slot and the singleton
75
// collapses to a GENERIC over the variant. For a closed-class
76
// subclass the receiver's generic args don't lift uniformly,
77
// so we emit the bare subtype NAMED — the narrowing
78
// complement path already rejects generic-class receivers.
79
build_singleton_subtype(
80
underlying_type: NAMED,
81
subtype: Symbols.Classy
82
) -> NAMED? static =>
83
if isa GENERIC(underlying_type) /\ subtype.is_variant then
84
let generic = underlying_type in
85
GENERIC(subtype.location, subtype, generic.arguments)
86
else
87
NAMED(subtype)
88
fi;
89
90
contains_subtype(v: Symbols.Classy?) -> bool is
91
if !v? then
92
return false;
93
fi
94
95
for u in _subtypes do
96
if u == v then
97
return true;
98
fi
99
od
100
101
return false;
102
si
103
104
// Join two flow-narrow types over the same closed root by
105
// subtype-set union — the exact join at a control-flow merge
106
// where each edge narrowed the same variable to a subset of
107
// the root's alternatives. Applicable when at least one side
108
// is a ONE_OF and the other is a ONE_OF over the same root or
109
// a single alternative of it. Returns the underlying root
110
// type when the union covers every alternative (the merge
111
// learns nothing beyond the declared type), a ONE_OF or
112
// single subtype otherwise, and null when the shapes don't
113
// line up — the caller falls back to the general LUB.
114
//
115
// Optionality is unioned too: an edge that can be null keeps
116
// the null in the merged type.
117
try_join(a: Type?, b: Type?) -> Type? static is
118
if !a? \/ !b? then
119
return null;
120
fi
121
122
if !isa ONE_OF(a) /\ !isa ONE_OF(b) then
123
return null;
124
fi
125
126
let result_is_optional = a.is_optional \/ b.is_optional;
127
128
let one_of mut = cast ONE_OF?(a.as_non_optional());
129
let other mut = b.as_non_optional();
130
131
if !one_of? then
132
one_of = cast ONE_OF?(b.as_non_optional());
133
other = a.as_non_optional();
134
fi
135
136
if !one_of? then
137
return null;
138
fi
139
140
let underlying = cast NAMED?(one_of.underlying_type.as_non_optional());
141
let root_classy = _try_drill_to_classy(underlying);
142
143
if !underlying? \/ !root_classy? then
144
return null;
145
fi
146
147
let members = Collections.LIST[Symbols.Classy]();
148
149
for s in one_of.subtypes do
150
members.add(s);
151
od
152
153
let other_one_of = cast ONE_OF?(other);
154
155
if other_one_of? then
156
if !underlying.matches(other_one_of.underlying_type) then
157
return null;
158
fi
159
160
for s in other_one_of.subtypes do
161
if !one_of.contains_subtype(s) then
162
members.add(s);
163
fi
164
od
165
else
166
let classy = _try_drill_to_classy(other);
167
168
if !classy? then
169
return null;
170
fi
171
172
if classy == root_classy then
173
// The other edge holds the whole root — the merge
174
// is the root itself.
175
return _flag_optional(underlying, result_is_optional);
176
fi
177
178
let is_alternative mut = false;
179
180
for s in root_classy.closed_alternatives do
181
if s == classy then
182
is_alternative = true;
183
fi
184
od
185
186
if !is_alternative then
187
return null;
188
fi
189
190
if !one_of.contains_subtype(classy) then
191
members.add(classy);
192
fi
193
fi
194
195
if _covers_every_alternative(root_classy, members) then
196
return _flag_optional(underlying, result_is_optional);
197
fi
198
199
return _flag_optional(create(underlying, members), result_is_optional);
200
si
201
202
// True when `members` includes every alternative of the
203
// closed root — the same universe rule the complement
204
// builder uses: the root's closed alternatives, plus the
205
// root itself when it is a concrete class.
206
_covers_every_alternative(
207
root_classy: Symbols.Classy,
208
members: Collections.LIST[Symbols.Classy]
209
) -> bool static is
210
if !root_classy.is_closed_root then
211
return false;
212
fi
213
214
for s in root_classy.closed_alternatives do
215
if !members.contains(s) then
216
return false;
217
fi
218
od
219
220
if
221
root_classy.is_class /\
222
!root_classy.is_abstract /\
223
!members.contains(root_classy)
224
then
225
return false;
226
fi
227
228
return true;
229
si
230
231
_flag_optional(type: Type?, is_optional: bool) -> Type? static =>
232
if type? /\ is_optional then
233
type.as_optional();
234
else
235
type;
236
fi;
237
238
// The Classy behind `type`, peeling Symbols.GENERIC wrapping
239
// for specialized generics. Null when `type` isn't a NAMED
240
// of a Classy.
241
_try_drill_to_classy(type: Type?) -> Symbols.Classy? static is
242
if !type? then
243
return null;
244
fi
245
246
if !isa NAMED(type) then
247
return null;
248
fi
249
250
let symbol mut = type.symbol;
251
252
if isa Symbols.GENERIC(symbol) then
253
symbol = symbol.symbol;
254
fi
255
256
return cast Symbols.Classy?(symbol);
257
si
258
259
matches(other: Type) -> bool is
260
if !isa ONE_OF(other) then
261
return false;
262
fi
263
264
let o = other;
265
266
if !_underlying_type.matches(o._underlying_type) then
267
return false;
268
fi
269
270
if _subtypes.count != o._subtypes.count then
271
return false;
272
fi
273
274
for v in _subtypes do
275
if !o.contains_subtype(v) then
276
return false;
277
fi
278
od
279
280
return true;
281
si
282
283
short_description: string is
284
let buffer = System.Text.StringBuilder();
285
286
buffer.append(_underlying_type.short_description);
287
buffer.append('{');
288
289
let seen_any mut = false;
290
291
for v in _subtypes do
292
if seen_any then
293
buffer.append('|');
294
fi
295
296
buffer.append(v.name);
297
298
seen_any = true;
299
od
300
301
buffer.append('}');
302
303
if is_optional then
304
buffer.append('?');
305
fi
306
307
return buffer.to_string();
308
si
309
310
to_string() -> string => short_description;
311
312
walk(action: (Type) -> void) is
313
_underlying_type.walk(action);
314
315
action(self);
316
si
317
si
318
si