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 | // MAYBE[T] → reference T?: read the .value property. The | |
| 49 | // field already carries the reference — null when absent, | |
| 50 | // the user's reference when present — so a single property | |
| 51 | // load is the entire coercion. | |
| 52 | if | |
| 53 | value.type!.is_maybe /\ | |
| 54 | target_type.is_optional /\ | |
| 55 | !target_type.is_value_type | |
| 56 | then | |
| 57 | let value_member = value.type!.find_member("value"); | |
| 58 | ||
| 59 | if value_member? then | |
| 60 | return value_member.load( | |
| 61 | value.location, | |
| 62 | value, | |
| 63 | IoC.CONTAINER.instance.symbol_loader); | |
| 64 | fi | |
| 65 | fi | |
| 66 | ||
| 67 | if !target_type.is_value_type \/ !target_type.is_optional then | |
| 68 | return value; | |
| 69 | fi | |
| 70 | ||
| 71 | // A bare `null` flowing into a value-type optional slot is the | |
| 72 | // empty optional — a zeroed value, not a null reference. Lower | |
| 73 | // it like `default`, mirroring the `null`-into-NULLABLE path in | |
| 74 | // compile_expressions. The constraint that would otherwise drive | |
| 75 | // that lowering does not reach a call argument, so the coercion | |
| 76 | // happens here at the boxing boundary instead. | |
| 77 | if value.type!.is_null then | |
| 78 | return Values.DEFAULT(target_type); | |
| 79 | fi | |
| 80 | ||
| 81 | if value.type!.is_optional then | |
| 82 | return value; | |
| 83 | fi | |
| 84 | ||
| 85 | // Value-type optional — source-side NULLABLE or a reflected | |
| 86 | // Nullable<T> wrapper; both are GENERICs carrying the inner | |
| 87 | // T as their single argument. | |
| 88 | if isa Semantic.Types.GENERIC(target_type) then | |
| 89 | let optional = target_type; | |
| 90 | ||
| 91 | if optional.arguments.count != 1 then | |
| 92 | return value; | |
| 93 | fi | |
| 94 | ||
| 95 | if !optional.arguments[0].is_assignable_from(value.type!) then | |
| 96 | return value; | |
| 97 | fi | |
| 98 | ||
| 99 | return Values.WRAP_OPTIONAL(target_type, value); | |
| 100 | fi | |
| 101 | ||
| 102 | return value; | |
| 103 | si | |
| 104 | ||
| 105 | box_arguments(arguments: Collections.List[Value], argument_types: Collections.List[Type]) -> Collections.List[Value] is | |
| 106 | if arguments.count != argument_types.count then | |
| 107 | _logger.poison(Source.LOCATION.internal, "boxed incomplete arguments"); | |
| 108 | return arguments; | |
| 109 | fi | |
| 110 | ||
| 111 | let any_need_change mut = false; | |
| 112 | ||
| 113 | for i in 0..arguments.count do | |
| 114 | ||
| 115 | // Apply the wrap first — a value-typed source that | |
| 116 | // coerces to a reference-typed target (e.g. MAYBE[T] → | |
| 117 | // T?) emerges from `wrap_if_needed` as the .value | |
| 118 | // reference, so the boxing branch below must see the | |
| 119 | // already-coerced value or it boxes the wrong thing. | |
| 120 | let wrapped = wrap_if_needed(arguments[i], argument_types[i])!; | |
| 121 | ||
| 122 | if wrapped != arguments[i] then | |
| 123 | any_need_change = true; | |
| 124 | break; | |
| 125 | fi | |
| 126 | ||
| 127 | if want_boxing /\ !argument_types[i].is_value_type /\ wrapped.type!.is_value_type then | |
| 128 | any_need_change = true; | |
| 129 | break; | |
| 130 | fi | |
| 131 | od | |
| 132 | ||
| 133 | if !any_need_change then | |
| 134 | return arguments; | |
| 135 | fi | |
| 136 | ||
| 137 | let result = Collections.LIST[Value](arguments.count); | |
| 138 | ||
| 139 | for i in 0..arguments.count do | |
| 140 | let wrapped = wrap_if_needed(arguments[i], argument_types[i])!; | |
| 141 | ||
| 142 | if want_boxing /\ !argument_types[i].is_value_type /\ wrapped.type!.is_value_type then | |
| 143 | result.add(Values.BOX(wrapped)); | |
| 144 | else | |
| 145 | result.add(wrapped); | |
| 146 | fi | |
| 147 | od | |
| 148 | ||
| 149 | return result; | |
| 150 | si | |
| 151 | si | |
| 152 | si |