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
// Eliminating every alternative but one leaves a type
66
// indistinguishable from the one a positive test would have
67
// produced, and nothing checked the value on the way. The
68
// mark is what a later phase reads to guard a use of it; a
69
// multi-member ONE_OF needs none, since only the root's own
70
// members are reachable through it.
71
//
72
// Marked here rather than in build_singleton_subtype, which
73
// the positive-narrow path calls too, and which must stay
74
// unmarked: that path has checked the value.
75
if subtypes.count == 1 then
76
let singleton = build_singleton_subtype(underlying_type, subtypes[0])
77
78
// Only a session can gain an alternative after the fact,
79
// so only a session records this; an ordinary build does
80
// not reach the set at all.
81
if IoC.CONTAINER.instance.build_flags.submission_name? then
82
IoC.CONTAINER.instance.complement_singletons.mark(singleton)
83
fi
84
85
return singleton
86
fi
87
88
return ONE_OF(underlying_type, subtypes)
89
si
90
91
// Build the subtype carrying the underlying receiver's generic
92
// args. For a variant subtype of a generic union, args are
93
// shared with the union slot-for-slot and the singleton
94
// collapses to a GENERIC over the variant. For a closed-class
95
// subclass the receiver's generic args don't lift uniformly,
96
// so we emit the bare subtype NAMED — the narrowing
97
// complement path already rejects generic-class receivers.
98
build_singleton_subtype(
99
underlying_type: NAMED,
100
subtype: Symbols.Classy
101
) -> NAMED? static =>
102
if isa GENERIC(underlying_type) /\ subtype.is_variant then
103
let generic = underlying_type in
104
GENERIC(subtype.location, subtype, generic.arguments)
105
else
106
NAMED(subtype)
107
fi
108
109
contains_subtype(v: Symbols.Classy?) -> bool is
110
if !v? then
111
return false
112
fi
113
114
for u in _subtypes do
115
if u == v then
116
return true
117
fi
118
od
119
120
return false
121
si
122
123
// Join two flow-narrow types over the same closed root by
124
// subtype-set union — the exact join at a control-flow merge
125
// where each edge narrowed the same variable to a subset of
126
// the root's alternatives. Applicable when at least one side
127
// is a ONE_OF and the other is a ONE_OF over the same root or
128
// a single alternative of it. Returns the underlying root
129
// type when the union covers every alternative (the merge
130
// learns nothing beyond the declared type), a ONE_OF or
131
// single subtype otherwise, and null when the shapes don't
132
// line up — the caller falls back to the general LUB.
133
//
134
// Optionality is unioned too: an edge that can be null keeps
135
// the null in the merged type.
136
try_join(a: Type?, b: Type?) -> Type? static is
137
if !a? \/ !b? then
138
return null
139
fi
140
141
if !isa ONE_OF(a) /\ !isa ONE_OF(b) then
142
return null
143
fi
144
145
let result_is_optional = a.is_optional \/ b.is_optional
146
147
let one_of mut = cast ONE_OF?(a.as_non_optional())
148
let other mut = b.as_non_optional()
149
150
if !one_of? then
151
one_of = cast ONE_OF?(b.as_non_optional())
152
other = a.as_non_optional()
153
fi
154
155
if !one_of? then
156
return null
157
fi
158
159
let underlying = cast NAMED?(one_of.underlying_type.as_non_optional())
160
let root_classy = _try_drill_to_classy(underlying)
161
162
if !underlying? \/ !root_classy? then
163
return null
164
fi
165
166
let members = Collections.LIST[Symbols.Classy]()
167
168
for s in one_of.subtypes do
169
members.add(s)
170
od
171
172
let other_one_of = cast ONE_OF?(other)
173
174
if other_one_of? then
175
if !underlying.matches(other_one_of.underlying_type) then
176
return null
177
fi
178
179
for s in other_one_of.subtypes do
180
if !one_of.contains_subtype(s) then
181
members.add(s)
182
fi
183
od
184
else
185
let classy = _try_drill_to_classy(other)
186
187
if !classy? then
188
return null
189
fi
190
191
if classy == root_classy then
192
// The other edge holds the whole root — the merge
193
// is the root itself.
194
return _flag_optional(underlying, result_is_optional)
195
fi
196
197
let is_alternative mut = false
198
199
for s in root_classy.closed_alternatives do
200
if s == classy then
201
is_alternative = true
202
fi
203
od
204
205
if !is_alternative then
206
return null
207
fi
208
209
if !one_of.contains_subtype(classy) then
210
members.add(classy)
211
fi
212
fi
213
214
if _covers_every_alternative(root_classy, members) then
215
return _flag_optional(underlying, result_is_optional)
216
fi
217
218
return _flag_optional(create(underlying, members), result_is_optional)
219
si
220
221
// True when `members` includes every alternative of the
222
// closed root — the same universe rule the complement
223
// builder uses: the root's closed alternatives, plus the
224
// root itself when it is a concrete class.
225
_covers_every_alternative(
226
root_classy: Symbols.Classy,
227
members: Collections.LIST[Symbols.Classy]
228
) -> bool static is
229
if !root_classy.is_closed_root then
230
return false
231
fi
232
233
for s in root_classy.closed_alternatives do
234
if !members.contains(s) then
235
return false
236
fi
237
od
238
239
if
240
root_classy.is_class /\
241
!root_classy.is_abstract /\
242
!members.contains(root_classy)
243
then
244
return false
245
fi
246
247
return true
248
si
249
250
_flag_optional(type: Type?, is_optional: bool) -> Type? static =>
251
if type? /\ is_optional then
252
type.as_optional()
253
else
254
type
255
fi
256
257
// The Classy behind `type`, peeling Symbols.GENERIC wrapping
258
// for specialized generics. Null when `type` isn't a NAMED
259
// of a Classy.
260
_try_drill_to_classy(type: Type?) -> Symbols.Classy? static is
261
if !type? then
262
return null
263
fi
264
265
if !isa NAMED(type) then
266
return null
267
fi
268
269
let symbol mut = type.symbol
270
271
if isa Symbols.GENERIC(symbol) then
272
symbol = symbol.symbol
273
fi
274
275
return cast Symbols.Classy?(symbol)
276
si
277
278
matches(other: Type) -> bool is
279
if !isa ONE_OF(other) then
280
return false
281
fi
282
283
let o = other
284
285
if !_underlying_type.matches(o._underlying_type) then
286
return false
287
fi
288
289
if _subtypes.count != o._subtypes.count then
290
return false
291
fi
292
293
for v in _subtypes do
294
if !o.contains_subtype(v) then
295
return false
296
fi
297
od
298
299
return true
300
si
301
302
short_description: string is
303
let buffer = System.Text.StringBuilder()
304
305
buffer.append(_underlying_type.short_description)
306
buffer.append('{')
307
308
let seen_any mut = false
309
310
for v in _subtypes do
311
if seen_any then
312
buffer.append('|')
313
fi
314
315
buffer.append(v.name)
316
317
seen_any = true
318
od
319
320
buffer.append('}')
321
322
if is_optional then
323
buffer.append('?')
324
fi
325
326
return buffer.to_string()
327
si
328
329
to_string() -> string => short_description
330
331
walk(action: (Type) -> void) is
332
_underlying_type.walk(action)
333
334
action(self)
335
si
336
si
337
si