Skip to content
← Back

src/semantic/type_caster.ghul

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