Appearance
| 1 | namespace IR is | |
| 2 | use Semantic.Types.Type | |
| 3 | ||
| 4 | use IR.Values.Value | |
| 5 | ||
| 6 | class VALUE_BOXER(_logger: Logging.Logger) is | |
| 7 | want_boxing: bool public | |
| 8 | ||
| 9 | super() | |
| 10 | ||
| 11 | box_if_value(value: Value) -> Value => | |
| 12 | if want_boxing /\ value.is_value_type then | |
| 13 | Values.BOX(value) | |
| 14 | else | |
| 15 | value | |
| 16 | fi | |
| 17 | ||
| 18 | box_if_needed(value: Value, target_type: Type) -> Value is | |
| 19 | // Try the T → T? wrap first — covers the value-type | |
| 20 | // optional widening (Nullable<T>::.ctor at the slot | |
| 21 | // boundary). The target stays a value type, so no | |
| 22 | // subsequent BOX fires. | |
| 23 | let wrapped = wrap_if_needed(value, target_type)! | |
| 24 | ||
| 25 | if wrapped != value then | |
| 26 | return wrapped | |
| 27 | fi | |
| 28 | ||
| 29 | if want_boxing /\ value.is_value_type /\ !target_type.is_value_type then | |
| 30 | return Values.BOX(value) | |
| 31 | else | |
| 32 | return value | |
| 33 | fi | |
| 34 | si | |
| 35 | ||
| 36 | // Implicit T → T? widening at slot boundaries. A non-optional | |
| 37 | // value-type T flowing into a value-type optional target gets | |
| 38 | // wrapped via Nullable<T>::.ctor — the value-type analogue of | |
| 39 | // the box-on-assignment-to-object path. Reference T → T? needs | |
| 40 | // no wrap (the bits are identical, only the type-system flag | |
| 41 | // differs) so this helper only fires when the target is a | |
| 42 | // value-type optional. | |
| 43 | wrap_if_needed(value: Value?, target_type: Type?) -> Value? is | |
| 44 | if !value? \/ !target_type? \/ !value.type? then | |
| 45 | return value | |
| 46 | fi | |
| 47 | ||
| 48 | if let repacked = _repack_tuple(value, target_type) then | |
| 49 | return repacked | |
| 50 | fi | |
| 51 | ||
| 52 | if !value.type? then | |
| 53 | return value | |
| 54 | fi | |
| 55 | ||
| 56 | // MAYBE[T] → reference T?: `MAYBE[T].value` throws when | |
| 57 | // absent (it is a checked unwrap in its own right), so the | |
| 58 | // coercion has to test `has_value` first and only read | |
| 59 | // `value` on the present path — mirroring the `??` | |
| 60 | // value-shape lowering in COMPILE_OPERATORS. The absent | |
| 61 | // arm is a bare `null`: a reference T? represents absence | |
| 62 | // that way, with no exception involved. | |
| 63 | if | |
| 64 | value.type.is_maybe /\ | |
| 65 | target_type.is_optional /\ | |
| 66 | !target_type.is_value_type | |
| 67 | then | |
| 68 | let has_value_member = value.type!.find_member("has_value") | |
| 69 | let value_member = value.type!.find_member("value") | |
| 70 | ||
| 71 | if has_value_member? /\ value_member? then | |
| 72 | let symbol_loader = IoC.CONTAINER.instance.symbol_loader | |
| 73 | let address_stand_in = Values.STACK_TOP_ADDRESS(value.type!) | |
| 74 | ||
| 75 | let presence_test = has_value_member.load(value.location, address_stand_in, symbol_loader) | |
| 76 | let value_extract = value_member.load(value.location, address_stand_in, symbol_loader) | |
| 77 | ||
| 78 | // The payload reaches a reference target, so a | |
| 79 | // value-type payload — or a type-variable one that | |
| 80 | // may instantiate as a value type — arrives boxed; | |
| 81 | // the extract alone would leave a bare struct on | |
| 82 | // the stack where the slot expects a reference. | |
| 83 | let payload_type = value_extract.type | |
| 84 | ||
| 85 | let present_arm: Values.Value = | |
| 86 | if payload_type? /\ (payload_type.is_value_type \/ payload_type.is_type_variable) then | |
| 87 | Values.BOX(Values.STACK_TOP(payload_type)) | |
| 88 | else | |
| 89 | Values.STACK_TOP(target_type) | |
| 90 | fi | |
| 91 | ||
| 92 | return Values.NULL_COALESCE_VALUE( | |
| 93 | value, | |
| 94 | presence_test, | |
| 95 | value_extract, | |
| 96 | present_arm, | |
| 97 | Values.DEFAULT(target_type), | |
| 98 | target_type | |
| 99 | ) | |
| 100 | fi | |
| 101 | fi | |
| 102 | ||
| 103 | if !target_type.is_value_type \/ !target_type.is_optional then | |
| 104 | return value | |
| 105 | fi | |
| 106 | ||
| 107 | // A bare `null` flowing into a value-type optional slot is the | |
| 108 | // empty optional — a zeroed value, not a null reference. Lower | |
| 109 | // it like `default`, mirroring the `null`-into-NULLABLE path in | |
| 110 | // compile_expressions. The constraint that would otherwise drive | |
| 111 | // that lowering does not reach a call argument, so the coercion | |
| 112 | // happens here at the boxing boundary instead. | |
| 113 | if value.type!.is_null then | |
| 114 | return Values.DEFAULT(target_type) | |
| 115 | fi | |
| 116 | ||
| 117 | // Any other `T?` lowering flowing into a MAYBE[T] slot. | |
| 118 | // The three optional carriers are intercompatible in the | |
| 119 | // type system but distinct CLR types, so the coercion is | |
| 120 | // explicit: test the source for presence, and either wrap | |
| 121 | // its payload with MAYBE<T>::.ctor or leave the empty | |
| 122 | // MAYBE. A reference source needs no local — the null test | |
| 123 | // is a dup-and-branch on the value itself. | |
| 124 | if target_type.is_maybe /\ !target_type.is_equivalent_to(value.type!) then | |
| 125 | if value.type!.is_optional /\ !value.type!.is_value_type then | |
| 126 | return Values.WRAP_MAYBE(target_type, value) | |
| 127 | fi | |
| 128 | ||
| 129 | if value.type!.is_optional then | |
| 130 | let has_value_member = value.type!.find_member("has_value") | |
| 131 | let value_member = value.type!.find_member("value") | |
| 132 | ||
| 133 | if has_value_member? /\ value_member? then | |
| 134 | let symbol_loader = IoC.CONTAINER.instance.symbol_loader | |
| 135 | let address_stand_in = Values.STACK_TOP_ADDRESS(value.type!) | |
| 136 | ||
| 137 | let presence_test = has_value_member.load(value.location, address_stand_in, symbol_loader) | |
| 138 | let value_extract = value_member.load(value.location, address_stand_in, symbol_loader) | |
| 139 | ||
| 140 | return Values.NULL_COALESCE_VALUE( | |
| 141 | value, | |
| 142 | presence_test, | |
| 143 | value_extract, | |
| 144 | Values.WRAP_OPTIONAL(target_type, Values.STACK_TOP(value_extract.type!)), | |
| 145 | Values.DEFAULT(target_type), | |
| 146 | target_type | |
| 147 | ) | |
| 148 | fi | |
| 149 | fi | |
| 150 | fi | |
| 151 | ||
| 152 | if value.type!.is_optional then | |
| 153 | return value | |
| 154 | fi | |
| 155 | ||
| 156 | // Value-type optional — source-side NULLABLE or a reflected | |
| 157 | // Nullable<T> wrapper; both are GENERICs carrying the inner | |
| 158 | // T as their single argument. | |
| 159 | if isa Semantic.Types.GENERIC(target_type) then | |
| 160 | let optional = target_type | |
| 161 | ||
| 162 | if optional.arguments.count != 1 then | |
| 163 | return value | |
| 164 | fi | |
| 165 | ||
| 166 | if !optional.arguments[0].is_assignable_from(value.type!) then | |
| 167 | return value | |
| 168 | fi | |
| 169 | ||
| 170 | return Values.WRAP_OPTIONAL(target_type, value) | |
| 171 | fi | |
| 172 | ||
| 173 | return value | |
| 174 | si | |
| 175 | ||
| 176 | // A tuple is assignable to a tuple type whose elements each | |
| 177 | // accept its own, but the CLR type is invariant: the target is | |
| 178 | // rebuilt from the source's elements, each converted in turn. | |
| 179 | _repack_tuple(value: Value, target_type: Type) -> Value? is | |
| 180 | if | |
| 181 | let source = value.type /\ | |
| 182 | (source.is_value_tuple \/ isa Semantic.Types.TUPLE(source)) /\ | |
| 183 | (target_type.is_value_tuple \/ isa Semantic.Types.TUPLE(target_type)) | |
| 184 | then | |
| 185 | let target = target_type | |
| 186 | ||
| 187 | if | |
| 188 | source.is_optional \/ | |
| 189 | target.is_optional \/ | |
| 190 | source.arguments.count != target.arguments.count \/ | |
| 191 | target.is_equivalent_to(source) | |
| 192 | then | |
| 193 | return null | |
| 194 | fi | |
| 195 | ||
| 196 | // A tuple literal is built at the target type directly from its | |
| 197 | // own element values. Spilling it first would build the source | |
| 198 | // tuple, whose element types - a bare `null`, say - may have no | |
| 199 | // runtime representation. | |
| 200 | if let rebuilt = _rebuild_tuple_literal(value, target) then | |
| 201 | return rebuilt | |
| 202 | fi | |
| 203 | ||
| 204 | let symbol_loader = IoC.CONTAINER.instance.symbol_loader | |
| 205 | let temp_name = ".repack.{IR.TEMP.get_next_id()}" | |
| 206 | let elements = Collections.LIST[Value]() | |
| 207 | ||
| 208 | for i in 0..source.arguments.count do | |
| 209 | let member = source.find_destructure_member(i) | |
| 210 | ||
| 211 | if !member? then | |
| 212 | return null | |
| 213 | fi | |
| 214 | ||
| 215 | let element = member.load(value.location, Values.Load.TEMP(temp_name, source), symbol_loader) | |
| 216 | ||
| 217 | elements.add(box_if_needed(element, target.arguments[i])) | |
| 218 | od | |
| 219 | ||
| 220 | return Values.TUPLE_REPACK(value, temp_name, source, Values.TUPLE(target, elements)) | |
| 221 | fi | |
| 222 | ||
| 223 | return null | |
| 224 | si | |
| 225 | ||
| 226 | // `value` rebuilt at `target` when it is a tuple literal, or a block | |
| 227 | // whose result is one, as an `if` or `case` arm is; null otherwise. | |
| 228 | _rebuild_tuple_literal(value: Value, target: Type) -> Value? is | |
| 229 | if let literal: Values.TUPLE = value then | |
| 230 | if literal.values.count != target.arguments.count then | |
| 231 | return null | |
| 232 | fi | |
| 233 | ||
| 234 | let converted = Collections.LIST[Value]() | |
| 235 | ||
| 236 | for i in 0..literal.values.count do | |
| 237 | converted.add(box_if_needed(literal.values[i], target.arguments[i])) | |
| 238 | od | |
| 239 | ||
| 240 | return Values.TUPLE(target, converted) | |
| 241 | fi | |
| 242 | ||
| 243 | if let block: Values.BLOCK = value, result = block.result_value then | |
| 244 | if let rebuilt_result = _rebuild_tuple_literal(result, target) then | |
| 245 | let rebuilt = Values.BLOCK(target) | |
| 246 | ||
| 247 | for i in 0..block.values.count - 1 do | |
| 248 | rebuilt.add(block.values[i]) | |
| 249 | od | |
| 250 | ||
| 251 | rebuilt.add(rebuilt_result) | |
| 252 | ||
| 253 | return rebuilt | |
| 254 | fi | |
| 255 | fi | |
| 256 | ||
| 257 | return null | |
| 258 | si | |
| 259 | ||
| 260 | box_arguments(arguments: Collections.List[Value], argument_types: Collections.List[Type]) -> Collections.List[Value] is | |
| 261 | if arguments.count != argument_types.count then | |
| 262 | _logger.poison(Source.LOCATION.internal, "boxed incomplete arguments") | |
| 263 | return arguments | |
| 264 | fi | |
| 265 | ||
| 266 | let any_need_change mut = false | |
| 267 | ||
| 268 | for i in 0..arguments.count do | |
| 269 | ||
| 270 | // Apply the wrap first — a value-typed source that | |
| 271 | // coerces to a reference-typed target (e.g. MAYBE[T] → | |
| 272 | // T?) emerges from `wrap_if_needed` as the .value | |
| 273 | // reference, so the boxing branch below must see the | |
| 274 | // already-coerced value or it boxes the wrong thing. | |
| 275 | let wrapped = wrap_if_needed(arguments[i], argument_types[i])! | |
| 276 | ||
| 277 | if wrapped != arguments[i] then | |
| 278 | any_need_change = true | |
| 279 | break | |
| 280 | fi | |
| 281 | ||
| 282 | if want_boxing /\ !argument_types[i].is_value_type /\ wrapped.type!.is_value_type then | |
| 283 | any_need_change = true | |
| 284 | break | |
| 285 | fi | |
| 286 | od | |
| 287 | ||
| 288 | if !any_need_change then | |
| 289 | return arguments | |
| 290 | fi | |
| 291 | ||
| 292 | let result = Collections.LIST[Value](arguments.count) | |
| 293 | ||
| 294 | for i in 0..arguments.count do | |
| 295 | let wrapped = wrap_if_needed(arguments[i], argument_types[i])! | |
| 296 | ||
| 297 | if want_boxing /\ !argument_types[i].is_value_type /\ wrapped.type!.is_value_type then | |
| 298 | result.add(Values.BOX(wrapped)) | |
| 299 | else | |
| 300 | result.add(wrapped) | |
| 301 | fi | |
| 302 | od | |
| 303 | ||
| 304 | return result | |
| 305 | si | |
| 306 | si | |
| 307 | si |