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