Appearance
| 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 |