Skip to content
← Back

src/semantic/types/type.ghul

1
namespace Semantic.Types is
2
use IO.Std;
3
4
use System.Text.StringBuilder;
5
6
use Source;
7
8
trait SettableTyped: Typed is
9
set_type(value: Type);
10
si
11
12
trait Typed is
13
type: Type?;
14
si
15
16
enum MATCH is
17
SAME = 0,
18
ASSIGNABLE = 1,
19
CONVERTABLE = 2,
20
PARTIAL = 3,
21
WILD = 4,
22
DIFFERENT = 100000
23
si
24
25
class Type: Typed abstract is
26
type: Type? => self;
27
28
name: string? => null;
29
depth: int => symbol.depth;
30
31
scope: Scope? => null;
32
33
symbol: Symbols.Symbol =>
34
if scope? /\ isa Symbols.Symbol(scope) then
35
cast Symbols.Symbol?(scope)!;
36
else
37
Symbols.NONE.instance;
38
fi;
39
40
ancestors: Collections.List[Type] => symbol.ancestors;
41
arguments: Collections.List[Type] => Collections.LIST[Type](0);
42
43
// The declared upper bound of a type variable, or null for any
44
// other type and for an unbounded variable. A value of a bounded
45
// type variable can be used as its bound; narrowing, destructuring,
46
// and operator resolution peel to this so a bounded `T` behaves as
47
// its bound the way member access already does.
48
bound_type: Type? =>
49
if is_type_variable /\ ancestors.count > 0 then
50
ancestors[0]
51
else
52
null
53
fi;
54
55
short_description: string => to_string() ?? "";
56
57
unspecialized_symbol: Symbols.Symbol? =>
58
let s = scope in
59
if s? then
60
s.unspecialized_symbol
61
else
62
null
63
fi;
64
65
// FIXME: better than isa XXXX, but still should not need these:
66
is_none: bool => false;
67
is_null: bool => false;
68
is_consumable: bool => !is_sentinel /\ !is_error /\ !is_wild;
69
is_error: bool => false;
70
is_wild: bool => false;
71
is_inferred: bool => false;
72
73
// ===== Inference-state predicates =====
74
//
75
// Three states a type can be in during iterative
76
// inference, captured by two predicates:
77
//
78
// is_sentinel is_settled
79
// sentinel singleton true false e.g. INFERRED_VARIABLE_TYPE, ERROR
80
// provisional composite false false e.g. Function[INFERRED_VARIABLE_TYPE, int]
81
// settled false true e.g. Function[int, int]
82
//
83
// The canonical answers for "what state is this type
84
// in?". Prefer these to ad-hoc combinations like
85
// `!is_inferred /\ !is_error` or `is_error \/
86
// is_inferred` — and if you find yourself editing near
87
// such a combination, migrate it to the named
88
// predicate. The point is to have one spelling per
89
// concept across the codebase; without that every new
90
// call-site reinvents the question and gets the
91
// top/deep distinction subtly wrong.
92
93
// True if this type is one of the three singleton
94
// inference markers: INFERRED_VARIABLE_TYPE,
95
// INFERRED_RETURN_TYPE, or ERROR. Sentinels aren't types
96
// a user could write; the first two stand in for slots
97
// the inference machinery hasn't yet filled, the third
98
// for a slot that failed to fill. Use !is_sentinel as
99
// the "this is a real type I can work with" test — it's
100
// true for every named/composite type, including
101
// provisional composites like Function[placeholder,int]
102
// whose outer shape is real even if inner slots are
103
// unresolved.
104
//
105
// Prefer this over `!is_inferred /\ !is_error` — the
106
// conjunction is just the unfactored spelling of this
107
// predicate.
108
is_sentinel: bool => false;
109
110
// True if self or any nested type argument is an
111
// inference placeholder (INFERRED_VARIABLE_TYPE,
112
// INFERRED_RETURN_TYPE). Use this in preference to
113
// is_inferred whenever the type might be composite —
114
// a Function[INFERRED_VARIABLE_TYPE, int] has
115
// is_inferred=false at the top level but is *not* yet
116
// resolved. Default looks only at self.is_inferred;
117
// composite types (NAMED etc.) override to recurse.
118
contains_inferred: bool => is_inferred;
119
120
// True if self or any nested type argument is a method-level
121
// generic type parameter that has not yet been bound by the
122
// in-flight overload resolution (FUNCTION_GENERIC_ARGUMENT) -
123
// a bare `U` or a composite like `Tasks.TASK[U]` where `U` is
124
// the callee's own unresolved type argument. Distinct from
125
// is_wild, which also answers true for a class-level type
126
// parameter that is already bound in its own context (e.g. a
127
// generic class's own `T` referenced from inside a method) -
128
// that case must not be treated as unresolved. Default looks
129
// only at self.is_function_generic_argument; composite types
130
// (NAMED etc.) override to recurse.
131
contains_function_generic_argument: bool => is_function_generic_argument;
132
133
// True if this type's tree contains a method-level generic
134
// type-parameter reference (see contains_function_generic_argument)
135
// that does not belong to `owner`. Such a reference is legitimate
136
// only while it names one of the generic parameters of the
137
// function currently being compiled - anywhere else its index
138
// has meaning only inside the (possibly already-discarded)
139
// overload specialization that produced it, and committing a
140
// type carrying it (e.g. as a local variable's declared type)
141
// leaks an unbound !!N into the emitted IL.
142
has_function_generic_argument_foreign_to(owner: Scope?) -> bool is
143
if !contains_function_generic_argument then
144
return false;
145
fi
146
147
for a in get_type_arguments() do
148
if a.is_function_generic_argument /\ a.symbol.owner != owner then
149
return true;
150
fi
151
od
152
153
return false;
154
si
155
156
// Fully resolved: no inference placeholder, no ERROR,
157
// anywhere in the type tree. The canonical "ready to
158
// commit / push as a constraint" test; describes the
159
// universal end-state every inferred slot is expected
160
// to converge to. !is_settled means a slot holds either
161
// a sentinel or a provisional composite that the body-
162
// retry loop may overwrite on the next iter.
163
//
164
// Stronger than !is_sentinel — provisional composites
165
// pass !is_sentinel but fail is_settled. Pick the
166
// weaker predicate (!is_sentinel) when you can work
167
// with any real type; pick is_settled when you need
168
// the inner slots filled too.
169
//
170
// Prefer this over `!is_error /\ !contains_inferred`
171
// or any other recombination of the underlying flags;
172
// if you spot one while editing, migrate it.
173
is_settled: bool => !contains_inferred /\ !is_error;
174
is_named: bool => false; // FIXME: what would it mean not to be named?
175
is_object: bool => false;
176
is_root_value_type: bool => false;
177
is_void: bool => false;
178
is_type_variable: bool => false;
179
is_classy_generic_argument: bool => false;
180
is_function_generic_argument: bool => false;
181
is_value_type: bool => false;
182
is_inheritable: bool => false;
183
is_class: bool => false;
184
is_trait: bool => false;
185
is_action: bool => false;
186
is_function: bool => false;
187
188
// True for a function type marked `pure` — values are trusted
189
// store-free. Not part of type identity or assignability; see
190
// PURE_FUNCTION.
191
is_pure_function: bool => false;
192
is_function_with_any_implicit_argument_types: bool => false;
193
is_ref: bool => false; // specifically 'ref', not just a reference type
194
is_value_tuple: bool => false;
195
is_unsafe_constraints: bool => symbol.is_unsafe_constraints;
196
197
// The element names of a value tuple, or null when this is not
198
// a tuple or carries no names. The .NET ValueTuple type holds
199
// no names — they ride on a TupleElementNamesAttribute at the
200
// declaration site — so a reflected tuple starts nameless and
201
// is rebuilt with `apply_tuple_element_names`.
202
tuple_element_names: Collections.List[string?]? => null;
203
204
// Return an equivalent value-tuple type carrying `names` (one
205
// per element, null for an unnamed element). A no-op for any
206
// type that is not a value tuple.
207
apply_tuple_element_names(names: Collections.List[string?]) -> Type => self;
208
209
// True for a reference type carrying an explicit `?`
210
// nullability annotation, and for the value-type NULLABLE[T].
211
is_optional: bool => false;
212
213
// True for `Ghul.MAYBE[T]`, the runtime's unconstrained-T
214
// optional carrier. `T?` slot boundaries accept it via an
215
// implicit coercion.
216
is_maybe: bool => false;
217
218
init() is
219
si
220
221
// The `T?` form of this type. A value type yields NULLABLE[T];
222
// a reference type yields itself flagged optional. Overridden
223
// by NAMED; the base covers sentinels, which are left as-is.
224
as_optional() -> Type => self;
225
226
// Reflected-import variant of `as_optional`. NAMED overrides
227
// to skip the is_value_type / is_type_variable guards (those
228
// would force premature materialization of a TYPE_WRAPPER's
229
// symbol during bootstrap). Other types fall back to plain
230
// `as_optional` — sentinels stay as-is, NULLABLE / MAYBE are
231
// already optional.
232
as_optional_unchecked() -> Type => as_optional();
233
234
// The non-optional form of this type. For a reference type
235
// carrying `?` this drops the flag; a no-op for everything
236
// else — value-type optionality is the distinct NULLABLE[T],
237
// and sentinels have no `?` form. Overridden by NAMED. Used
238
// when flow-sensitive narrowing establishes a variable is
239
// non-null at a use site.
240
as_non_optional() -> Type => self;
241
242
// The `T` of a `T?` carrier, regardless of which lowering
243
// produced it (reference-T flagged `NAMED`, value-T
244
// `NULLABLE[T]`, or unconstrained-T `MAYBE[T]`). Null when
245
// this type is not optional-shaped. The single accessor lets
246
// compare/box sites ask "what's inside?" without having to
247
// know which optional flavour they're looking at.
248
optional_inner_type: Type? => null;
249
250
// Erased type identity: same type with the reference-`?`
251
// annotation ignored, so `cat?` matches `cat`. This is the CLR's
252
// view — the flag has no runtime existence — and it is what
253
// override matching, reflected-signature comparison, and
254
// synthesised-member wiring need. It is NOT safe for
255
// assignability decisions; those go through `compare` /
256
// `is_equivalent_to`, where the flag participates.
257
// (`NULLABLE[T]` and `MAYBE[T]` are distinct CLR types, so
258
// value-type and unconstrained optionals never erase.)
259
//
260
// Not an equivalence relation, and deliberately not spelled as
261
// one: sentinel types match anything, so it is neither
262
// symmetric (`NULL` matches `cat`, `cat` does not match `NULL`)
263
// nor transitive (`cat` matches `ERROR` matches `dog`), and the
264
// base returns false rather than true. Do not route it through
265
// `equals` — .NET requires all three of those properties from
266
// anything it uses as a dictionary key.
267
matches(other: Type) -> bool => false;
268
269
// Full type equivalence: `matches` plus the optional flag, at
270
// every nesting depth. Invariant generic-argument positions
271
// compare with this — `box_like[cat?]` must not unify with
272
// `box_like[cat]`, and a `cat?` local must not satisfy a
273
// `cat ref` parameter. Sentinel tolerance follows `matches`.
274
is_equivalent_to(other: Type) -> bool
275
=> self.matches(other);
276
277
is_assignable_from(other: Type) -> bool
278
=> cast int(compare(other)) <= cast int (MATCH.ASSIGNABLE);
279
280
compare(other: Type) -> MATCH
281
=> MATCH.DIFFERENT;
282
283
find_member(name: string) -> Symbols.Symbol?
284
=> null;
285
286
find_destructure_member(index: int) -> Symbols.Symbol? is
287
// A bounded type variable destructures through its bound the
288
// way member access already resolves through it.
289
if let bound = bound_type then
290
return bound.find_destructure_member(index);
291
fi
292
293
let name = get_destructure_member_name(index);
294
295
if !name? then
296
return null;
297
fi
298
299
let result = find_member(name);
300
301
if result? then
302
return result;
303
fi
304
305
if Symbols.Symbol.is_positional_member_name(name) then
306
// Some assemblies name positional members with a leading
307
// backtick (`0, `1, ...) rather than a bare index; retry
308
// with that spelling before giving up.
309
return find_member("`{name}");
310
fi
311
312
return null;
313
si
314
315
get_destructure_member_name(index: int) -> string?
316
=> null;
317
318
find_ancestor(type: Type) -> Type? => null;
319
320
specialize(type_map: Collections.Map[string,Type]) -> Type => throw System.NotImplementedException("not implemented by {self.get_type()}");
321
bind_type_variables(other: Type, results: GENERIC_ARGUMENT_BIND_RESULTS) -> bool =>
322
true;
323
324
get_type_arguments_into(results: Collections.LIST[GenericArgument]) is
325
si
326
327
get_type_arguments() -> Collections.LIST[GenericArgument] =>
328
val
329
let result = Collections.LIST[GenericArgument]();
330
get_type_arguments_into(result);
331
result
332
lav;
333
334
freeze() -> Type? => null;
335
336
walk(action: (Type) -> void) => throw System.NotImplementedException("not implemented by {self.get_type()}");
337
get_element_type() -> Type? => null;
338
339
get_il_type() -> string =>
340
val
341
let result = StringBuilder();
342
gen_type(result);
343
result.to_string()
344
lav;
345
346
get_il_class_name() -> string =>
347
val
348
let result = StringBuilder();
349
gen_class_name(result);
350
result.to_string()
351
lav;
352
353
// output IL name for this type in a normal context:
354
gen_type(buffer: StringBuilder) => throw System.NotImplementedException("not implemented by {self.get_type()}");
355
// output IL name for this type in a context that requires a 'class name' (i.e. a type without 'class' or 'valuetype' prefix)
356
gen_class_name(buffer: StringBuilder) => throw System.NotImplementedException("not implemented by {self.get_type()}");
357
format(result: StringBuilder) is
358
result.append(self);
359
si
360
361
get_hash_code() -> int => symbol.get_hash_code();
362
si
363
si