Skip to content
← Back

src/semantic/types/named.ghul

1
namespace Semantic.Types is
2
use IO.Std;
3
4
use Logging;
5
6
class NAMED: Type is
7
_compare_count: int static;
8
_same_count: int static;
9
_named_count: int static;
10
_hit_count: int static;
11
_miss_count: int static;
12
13
_cache: Collections.MutableMap[(int,int), Types.MATCH] static;
14
15
name: string => symbol.name;
16
scope: Scope => symbol;
17
18
_symbol: Symbols.Symbol;
19
20
// Set when this reference type was written with a `?`
21
// nullability annotation. Directional: a `T?` slot accepts a
22
// `T` value (widening); a `T` slot does not accept a `T?`
23
// value (would lose the discriminator).
24
_is_optional: bool;
25
26
symbol: Symbols.Symbol => _symbol;
27
28
is_optional: bool => _is_optional;
29
30
optional_inner_type: Type? =>
31
if _is_optional then as_non_optional() else null fi;
32
33
short_description: string =>
34
if _is_optional then
35
"{symbol.name}?";
36
else
37
symbol.name;
38
fi;
39
40
is_type_variable: bool =>
41
symbol.is_type_variable;
42
43
is_named: bool => true;
44
45
is_value_type: bool => symbol.is_value_type;
46
is_trait: bool => symbol.is_trait;
47
is_inheritable: bool => symbol.is_inheritable;
48
is_class: bool => symbol.is_class;
49
is_object: bool => symbol.is_object;
50
is_root_value_type: bool => symbol.is_root_value_type;
51
is_void: bool => symbol.is_void;
52
53
init(symbol: Symbols.Symbol) is
54
super.init();
55
56
_symbol = symbol;
57
si
58
59
matches(other: Type) -> bool =>
60
if other.is_sentinel then
61
true;
62
elif isa ONE_OF(other) /\ !isa ONE_OF(self) then
63
// ONE_OF carries the underlying union's symbol but
64
// narrows to a proper variant subset — never matches
65
// the plain union it was built over. ONE_OF's own `matches`
66
// override handles the ONE_OF-against-ONE_OF case.
67
false;
68
elif isa INTERSECTION(other) /\ !isa INTERSECTION(self) then
69
// INTERSECTION inherits NAMED with members[0].symbol
70
// as its "primary" symbol, but the intersection is
71
// not the same type as a plain NAMED holding that
72
// symbol — `Symbol` does not match `Symbol & Trait`.
73
// INTERSECTION's own `matches` override handles the
74
// INTERSECTION-against-INTERSECTION case.
75
false;
76
elif isa NAMED(other) then
77
let other_symbol = other;
78
symbol == other_symbol.symbol;
79
else
80
false;
81
fi;
82
83
is_equivalent_to(other: Type) -> bool =>
84
self.matches(other) /\ (other.is_sentinel \/ is_optional == other.is_optional);
85
86
specialize(type_map: Collections.Map[string,Type]) -> Type =>
87
if type_map.contains_key(name) then
88
// A flagged `T?` keeps its nullability when `T` is
89
// substituted — `T?` with `T := Foo` is `Foo?`.
90
if _is_optional then
91
type_map[name].as_optional();
92
else
93
type_map[name];
94
fi;
95
else
96
self;
97
fi;
98
99
_cache_result(other: Type, result: Types.MATCH) -> Types.MATCH is
100
_cache[(self.symbol.get_hash_code(), other.symbol.get_hash_code())] = result;
101
102
return result;
103
si
104
105
dump_stats() static is
106
debug_always("cache size: {_cache.count} named ratio: {cast double(_hit_count) / cast double(_named_count)}");
107
108
debug_always("total: {_compare_count} named: {_named_count} same: {_same_count} misses: {_miss_count} hits: {_hit_count}");
109
si
110
111
clear_cache() static is
112
if _cache == null then
113
_cache = Collections.MAP[(int,int), Types.MATCH]();
114
fi
115
116
_cache.clear();
117
si
118
119
compare(other: Type) -> Types.MATCH is
120
_compare_count = _compare_count + 1;
121
if other.is_sentinel then
122
return Types.MATCH.ASSIGNABLE;
123
fi
124
125
// `null` is assignable to any reference type, and to a
126
// type written `T?` — including a `class`-constrained
127
// type variable, which otherwise reports is_value_type.
128
if other.is_null /\ (!is_value_type \/ _is_optional) then
129
return Types.MATCH.ASSIGNABLE;
130
fi
131
132
// Ghul.MAYBE[T] → reference-T? widening. The carrier's
133
// `value` field already holds the reference (null when
134
// absent, the user's reference when present), so a single
135
// property load at the slot boundary is the whole coercion.
136
if _is_optional /\ !is_value_type /\ other.is_maybe then
137
let other_inner = other.optional_inner_type;
138
139
if other_inner? /\ self.as_non_optional().is_assignable_from(other_inner) then
140
return Types.MATCH.ASSIGNABLE;
141
fi
142
fi
143
144
if symbol == null then
145
return Types.MATCH.DIFFERENT;
146
elif other.is_named then
147
_named_count = _named_count + 1;
148
149
// Strict non-nullable-by-default: a `T` slot never
150
// accepts a `T?` value. The caller must narrow first
151
// (`x!`, `if let`, `if x?`). This applies uniformly
152
// — symbol equality and subtype relationships below
153
// don't loosen it. Without this check, the ancestor
154
// walk below silently drops the optional flag because
155
// ancestor types are bare (see `Symbol.ancestors`).
156
//
157
// Wild placeholders (unbound type variables) skip
158
// this — a wild `T` matches anything during overload
159
// resolution's first pass, and binding picks up the
160
// optional flag from the actual.
161
if !is_wild /\ !_is_optional /\ other.is_optional then
162
return Types.MATCH.DIFFERENT;
163
fi
164
165
if symbol == other.symbol then
166
if _is_optional == other.is_optional then
167
_same_count = _same_count + 1;
168
return Types.MATCH.SAME;
169
else
170
// `T?` slot accepts `T` value via the implicit
171
// widening — null becomes the absent marker, a
172
// non-null reference flows through unchanged.
173
// (The `T` slot accepting `T?` case is already
174
// rejected by the strict check above.)
175
return Types.MATCH.ASSIGNABLE;
176
fi
177
fi
178
179
let result: Types.MATCH mut = _;
180
181
if _cache == null then
182
_cache = Collections.MAP[(int,int),Types.MATCH]();
183
fi
184
185
if _cache.try_get_value((self.symbol.get_hash_code(), other.symbol.get_hash_code()), result ref) then
186
_hit_count = _hit_count + 1;
187
return result;
188
fi
189
190
_miss_count = _miss_count + 1;
191
192
for a in other.symbol.ancestors do
193
let match = self.compare(a);
194
195
if cast int(match) <= cast int(Types.MATCH.ASSIGNABLE) then
196
return _cache_result(other, Types.MATCH.ASSIGNABLE);
197
elif match == Types.MATCH.CONVERTABLE then
198
return _cache_result(other, Types.MATCH.CONVERTABLE);
199
fi
200
od
201
202
if is_wild \/ other.is_wild then
203
return _cache_result(other, Types.MATCH.WILD);
204
fi
205
206
return _cache_result(other, Types.MATCH.DIFFERENT);
207
fi
208
209
// FIXME: should we be caching this result?
210
return Types.MATCH.DIFFERENT;
211
si
212
213
find_member(name: string) -> Symbols.Symbol? => symbol.find_member(name);
214
215
get_destructure_member_name(index: int) -> string? => symbol.get_destructure_member_name(index);
216
217
find_ancestor(type: Type) -> Type? => symbol.find_ancestor(type);
218
219
freeze() -> Type? =>
220
let result = symbol.freeze() in
221
if result? then result.type else null fi;
222
223
gen_class_name(buffer: System.Text.StringBuilder) is
224
symbol.gen_class_name(buffer);
225
si
226
227
gen_type(buffer: System.Text.StringBuilder) is
228
symbol.gen_type(buffer);
229
si
230
231
walk(action: (Type) -> void) is
232
action(self);
233
si
234
235
as_optional() -> Type is
236
// A genuine value type carries optionality as NULLABLE[T],
237
// never the reference flag. A type variable reports
238
// is_value_type spuriously (every GenericArgument does),
239
// so exclude it — a `class`-constrained `T?` is flagged.
240
if _is_optional \/ (is_value_type /\ !is_type_variable) then
241
return self;
242
fi
243
244
return as_optional_unchecked();
245
si
246
247
// Reflected-import variant of `as_optional` that skips the
248
// is_value_type / is_type_variable guards. Those guards are
249
// about preventing source-side `as_optional()` from flagging a
250
// value type as a reference optional. At reflection time the
251
// caller already knows the slot was marked by the emitter,
252
// which only emits NullableAttribute on reference-`?` slots
253
// (Semantic.DotNet.NULLABILITY.needs_attribute filters value
254
// types out). Skipping the guards avoids forcing materialization
255
// of a lazy TYPE_WRAPPER's symbol during bootstrap — the
256
// re-entrant `get_array_type` lookups it triggers would crash
257
// before the innate-symbol table is populated.
258
as_optional_unchecked() -> Type is
259
let result = cast NAMED?(memberwise_clone())!;
260
261
result._is_optional = true;
262
263
return result;
264
si
265
266
as_non_optional() -> Type is
267
if !_is_optional then
268
return self;
269
fi
270
271
let result = cast NAMED?(memberwise_clone())!;
272
273
result._is_optional = false;
274
275
return result;
276
si
277
278
to_string() -> string =>
279
if _is_optional then
280
"{IoC.CONTAINER.instance.name_display.name_for(symbol)}?";
281
else
282
IoC.CONTAINER.instance.name_display.name_for(symbol);
283
fi;
284
si
285
si