Skip to content
← Back

src/semantic/type_caster.ghul

1
namespace Semantic is
2
use System.Reflection.Metadata.ILOpCode
3
use IO.Std
4
5
use Logging
6
7
use Types.Type
8
9
use IR.Values
10
11
use IR.TEMP
12
use IR.BRANCH
13
14
class TYPE_CASTER(
15
_brancher: IR.BRANCHER,
16
_block_context: IR.BlockContext,
17
_value_boxer: IR.VALUE_BOXER,
18
_value_converter: IR.VALUE_CONVERTER,
19
_innate_symbol_lookup: Lookups.InnateSymbolLookup,
20
_conversion_lookup: USER_DEFINED_CONVERSION_LOOKUP,
21
_logger: Logger
22
) is
23
_bridge: POINTER_SIZED_DECIMAL_BRIDGE
24
25
super()
26
27
init(..) is
28
_bridge = POINTER_SIZED_DECIMAL_BRIDGE(_innate_symbol_lookup)
29
si
30
31
check_cast_is_valid(location: Source.LOCATION, source_type: Type, target_type: Type) -> bool is
32
if source_type.is_type_variable /\ target_type.is_type_variable then
33
_logger.error(location, "cannot convert {source_type} to {target_type}")
34
35
return false
36
fi
37
38
if source_type.is_type_variable \/ target_type.is_type_variable then
39
return true
40
fi
41
42
if source_type.is_value_type /\ target_type.is_value_type then
43
let decimal_type = _innate_symbol_lookup.get_decimal_type()
44
45
// Reached in two steps - see the matching branch in
46
// cast_value.
47
if _bridge.find(source_type, target_type)? then
48
return true
49
fi
50
51
if source_type.matches(decimal_type) \/ target_type.matches(decimal_type) then
52
if
53
!_value_converter.get_decimal_conversion(source_type, target_type)? /\
54
!_conversion_lookup.find(source_type, target_type)?
55
then
56
_logger.error(location, "cannot convert {source_type} to {target_type}")
57
return false
58
fi
59
60
return true
61
fi
62
63
let instruction = _value_converter.get_instruction(target_type)
64
let enum_type = _innate_symbol_lookup.get_enum_type()
65
66
if instruction? then
67
// A `conv.*` opcode only accepts a source already on
68
// the evaluation stack in a compatible primitive
69
// shape - one of the built-in scalars, or an enum
70
// (which decays to its underlying integral type on
71
// the stack per ECMA-335). A multi-field value type
72
// like Half or Int128 is neither, so `get_instruction`
73
// being keyed on the target alone isn't enough here.
74
if
75
!_value_converter.get_instruction(source_type)? /\
76
!enum_type.is_assignable_from(source_type) /\
77
!_conversion_lookup.find(source_type, target_type)?
78
then
79
_logger.error(location, "cannot convert {source_type} to {target_type}")
80
return false
81
fi
82
elif
83
// Only a target enum is accepted here, matching
84
// cast_value below: `conv.i4` re-annotates the
85
// stack value's type as `target_type`, which is
86
// only sound when that target itself decays to
87
// int32 the same way a source enum does. An enum
88
// source with some other non-scalar target is the
89
// same unsound-IL shape the check above rejects
90
// for a struct source.
91
!enum_type.is_assignable_from(target_type) /\
92
!_conversion_lookup.find(source_type, target_type)?
93
then
94
_logger.error(location, "cannot convert {source_type} to {target_type}")
95
return false
96
fi
97
fi
98
99
return true
100
si
101
102
// `is_checked` says what a failed cast should do: fault, which is
103
// what the program asked for by writing `cast T(v)`, or decline,
104
// which is what a refutable test and the compiler's own coercions
105
// want. See IR.Values.CAST.
106
cast_value(
107
location: Source.LOCATION,
108
value: IR.Values.Value,
109
target_type: Type,
110
quiet: bool,
111
is_checked: bool
112
) -> IR.Values.Value is
113
let need_conditional_unbox = false
114
115
if value.type!.is_type_variable /\ target_type.is_type_variable then
116
if !quiet then
117
_logger.error(location, "cannot convert {value.type} to {target_type}")
118
fi
119
120
return
121
DUMMY(
122
target_type,
123
location
124
)
125
fi
126
127
if value.type!.is_type_variable \/ target_type.is_type_variable then
128
let result: IR.Values.Value mut =
129
CAST(
130
target_type,
131
value,
132
is_checked
133
)
134
135
if target_type.is_value_type then
136
result = _conditionally_unbox(result, target_type)
137
fi
138
139
return result
140
fi
141
142
if value.type!.is_value_type /\ target_type.is_value_type then
143
let decimal_type = _innate_symbol_lookup.get_decimal_type()
144
145
// System.Decimal declares a conversion for each of the
146
// fixed-width integers but none for a pointer-sized one,
147
// so that pair goes through the 64-bit integer of the
148
// same signedness, which it does declare.
149
if let bridge = _bridge.find(value.type!, target_type) then
150
return
151
cast_value(
152
location,
153
cast_value(location, value, bridge, quiet, is_checked),
154
target_type,
155
quiet,
156
is_checked
157
)
158
fi
159
160
if value.type!.matches(decimal_type) \/ target_type.matches(decimal_type) then
161
let decimal_conversion = _value_converter.get_decimal_conversion(value.type!, target_type)
162
163
if !decimal_conversion? then
164
let conversion_function = _conversion_lookup.find(value.type!, target_type)
165
166
if conversion_function? then
167
return _build_conversion_call(value, conversion_function, target_type)
168
fi
169
170
if !quiet then
171
_logger.error(location, "cannot convert {value.type} to {target_type}")
172
fi
173
174
return
175
DUMMY(
176
target_type,
177
location
178
)
179
fi
180
181
// Decimal to decimal is not a conversion at all;
182
// the value on the stack is already what it needs
183
// to be.
184
if decimal_conversion.is_identity then
185
return value
186
fi
187
188
return
189
IR.Values.DECIMAL_CONVERT(
190
target_type,
191
value,
192
decimal_conversion.method_name,
193
decimal_conversion.parameter_type
194
)
195
fi
196
197
let instruction = _value_converter.get_instruction(target_type)
198
let enum_type = _innate_symbol_lookup.get_enum_type()
199
200
if instruction? then
201
// See the matching comment in check_cast_is_valid:
202
// `get_instruction` is keyed on the target alone, so
203
// it says nothing about whether `value` is actually
204
// stack-compatible with the resulting `conv.*`
205
// opcode. A multi-field value type like Half or
206
// Int128 isn't, and reaching the JIT with one gives
207
// an InvalidProgramException rather than a compile
208
// error.
209
if
210
_value_converter.get_instruction(value.type!)? \/
211
enum_type.is_assignable_from(value.type!)
212
then
213
return
214
CONVERT(
215
target_type,
216
value,
217
instruction
218
)
219
fi
220
elif enum_type.is_assignable_from(target_type) then
221
return
222
CONVERT(
223
target_type,
224
value,
225
ILOpCode.CONV_I4
226
)
227
fi
228
229
let conversion_function = _conversion_lookup.find(value.type!, target_type)
230
231
if conversion_function? then
232
return _build_conversion_call(value, conversion_function, target_type)
233
fi
234
235
if !quiet then
236
_logger.error(location, "cannot convert {value.type} to {target_type}")
237
fi
238
239
return
240
DUMMY(
241
target_type,
242
location
243
)
244
fi
245
246
let target_non_optional = target_type.optional_inner_type ?? target_type
247
248
if
249
!target_non_optional.is_assignable_from(value.type!) /\
250
!value.type!.is_assignable_from(target_non_optional)
251
then
252
let conversion_function = _conversion_lookup.find(value.type!, target_type)
253
254
if conversion_function? then
255
return _build_conversion_call(value, conversion_function, target_type)
256
fi
257
fi
258
259
let result: Value mut =
260
CAST(
261
target_type,
262
value,
263
is_checked
264
)
265
266
if !value.type!.is_value_type /\ target_type.is_value_type then
267
result = _conditionally_unbox(result, target_type)
268
fi
269
270
return result
271
si
272
273
// Public so compile_expressions can exclude a cast covered by a
274
// user-defined conversion from the impossible-cast warning.
275
find_user_defined_conversion(source_type: Type, target_type: Type) -> Symbols.Function? =>
276
_conversion_lookup.find(source_type, target_type)
277
278
_build_conversion_call(value: Value, function: Symbols.Function, target_type: Type) -> Value is
279
let arguments = Collections.LIST[Value]()
280
arguments.add(value)
281
282
let call: Value =
283
Call.STATIC(
284
function,
285
_value_boxer.box_arguments(arguments, function.arguments),
286
function.return_type,
287
null
288
)
289
290
if !target_type.is_optional then
291
return call
292
fi
293
294
return _guard_against_conversion_failure(call, target_type)
295
si
296
297
// A user-defined conversion is arbitrary code and can throw;
298
// `cast T?(...)` promises never to, for the two failure kinds
299
// the C# conversion-operator guidelines specify. Any other
300
// exception is a bug and propagates.
301
_guard_against_conversion_failure(call: Value, target_type: Type) -> Value is
302
let block = _block_context.enter_block(target_type)
303
let brancher = _brancher.get_for(block)
304
305
let result = TEMP(block, "user_convert", target_type)
306
307
let done = IR.LABEL()
308
309
let extent = IR.Values.TRY_EXTENT()
310
311
block.add(IR.Values.TRY_START(extent))
312
result.store(_value_boxer.box_if_needed(call, target_type))
313
brancher.leave(done)
314
block.add(IR.Values.TRY_END(extent))
315
316
for name in ["InvalidCastException", "OverflowException"] do
317
let region =
318
IR.Values.PROTECTED_REGION(extent, "System.Runtime", "System", name)
319
320
block.add(IR.Values.HANDLER_START(region))
321
block.add(IR.Values.INSTRUCTION(ILOpCode.POP))
322
result.store(IR.Values.DEFAULT(target_type))
323
brancher.leave(done)
324
block.add(IR.Values.HANDLER_END(region))
325
od
326
327
brancher.label(done)
328
block.add(result.load())
329
330
_block_context.leave_block()
331
332
return block
333
si
334
335
_conditionally_unbox(
336
value: IR.Values.Value,
337
target_type: Types.Type
338
) -> IR.Values.Value
339
is
340
// value is the result of a cast instruction ('isinsnt'). we now need
341
// to check if the result is not null before we attempt to unbox, or
342
// if the result is null, we return the value type's default value instead
343
344
let block = _block_context.enter_block(target_type)
345
let brancher = _brancher.get_for(block)
346
347
let temp = TYPE_WRAPPER(_innate_symbol_lookup.get_object_type(), value).get_temp_copier(block, "cast")
348
349
let want_default_value = IR.LABEL()
350
let done = IR.LABEL()
351
352
// is the `isinst` result null?
353
brancher.branch(BRANCH.Z, temp(), want_default_value)
354
355
// `isinst` result is not null, so safe to unbox:
356
block.add(
357
UNBOX(
358
TYPE_WRAPPER(target_type, temp())
359
)
360
)
361
362
brancher.branch(done)
363
364
brancher.label(want_default_value)
365
366
// 'isinst' result is null, so attempting unbox would throw
367
// NullReferenceException. return default value instead:
368
block.add(IR.Values.DEFAULT(target_type))
369
370
brancher.label(done)
371
372
_block_context.leave_block()
373
374
return block
375
si
376
si
377
si