Appearance
| 1 | namespace Semantic is | |
| 2 | use System.NotImplementedException; | |
| 3 | ||
| 4 | use Logging; | |
| 5 | ||
| 6 | use IR.Values; | |
| 7 | use IR.VALUE_BOXER; | |
| 8 | ||
| 9 | use Types.Type; | |
| 10 | ||
| 11 | class SYMBOL_LOADER is | |
| 12 | _null_find_symbol: (string) -> Symbols.Symbol? static; | |
| 13 | ||
| 14 | _logger: Logger; | |
| 15 | _symbol_table: SYMBOL_TABLE; | |
| 16 | _function_caller: FUNCTION_CALLER; | |
| 17 | _value_boxer: IR.VALUE_BOXER; | |
| 18 | ||
| 19 | _innate_symbol_lookup: Lookups.InnateSymbolLookup; | |
| 20 | ||
| 21 | find_symbol: (string) -> Symbols.Symbol? public; | |
| 22 | ||
| 23 | init( | |
| 24 | logger: Logger, | |
| 25 | symbol_table: SYMBOL_TABLE, | |
| 26 | function_caller: FUNCTION_CALLER, | |
| 27 | value_boxer: VALUE_BOXER, | |
| 28 | innate_symbol_lookup: Lookups.InnateSymbolLookup | |
| 29 | ) is | |
| 30 | super.init(); | |
| 31 | ||
| 32 | // calls to null anon functions don't always produce a sane stack trace | |
| 33 | if _null_find_symbol == null then | |
| 34 | _null_find_symbol = | |
| 35 | (name: string) -> Symbols.Symbol? is | |
| 36 | throw NotImplementedException("find_symbol is not set"); | |
| 37 | si; | |
| 38 | fi | |
| 39 | ||
| 40 | _logger = logger; | |
| 41 | _symbol_table = symbol_table; | |
| 42 | _function_caller = function_caller; | |
| 43 | _value_boxer = value_boxer; | |
| 44 | _innate_symbol_lookup = innate_symbol_lookup; | |
| 45 | ||
| 46 | find_symbol = _null_find_symbol; | |
| 47 | si | |
| 48 | ||
| 49 | load_self(location: Source.LOCATION) -> Value is | |
| 50 | let context = _symbol_table.current_function; | |
| 51 | ||
| 52 | if context? then | |
| 53 | let result = context.load_self(location, self); | |
| 54 | ||
| 55 | return result; | |
| 56 | fi | |
| 57 | ||
| 58 | IoC.CONTAINER.instance.logger.error(location, "cannot access instance member from non-instance context"); | |
| 59 | ||
| 60 | return IR.Values.DUMMY(Types.ERROR(), Source.LOCATION.internal); | |
| 61 | si | |
| 62 | ||
| 63 | load_outer_self(location: Source.LOCATION) -> Value is | |
| 64 | let context = _symbol_table.current_function; | |
| 65 | ||
| 66 | if context? then | |
| 67 | let result = context.load_outer_self(location, self); | |
| 68 | ||
| 69 | assert result? else "load outer self did not return a value: {context}"; | |
| 70 | ||
| 71 | return result; | |
| 72 | fi | |
| 73 | ||
| 74 | IoC.CONTAINER.instance.logger.error(location, "cannot access instance member from non-instance context"); | |
| 75 | ||
| 76 | return IR.Values.DUMMY(Types.ERROR(), Source.LOCATION.internal); | |
| 77 | si | |
| 78 | ||
| 79 | load_namespace(symbol: Symbols.NAMESPACE) -> Value => | |
| 80 | Load.SYMBOL(null, symbol); | |
| 81 | ||
| 82 | load_class(symbol: Symbols.CLASS) -> Value => | |
| 83 | TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal); | |
| 84 | ||
| 85 | load_trait(symbol: Symbols.TRAIT) -> Value => | |
| 86 | TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal); | |
| 87 | ||
| 88 | load_struct(symbol: Symbols.STRUCT) -> Value => | |
| 89 | TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal); | |
| 90 | ||
| 91 | load_union(symbol: Symbols.UNION) -> Value => | |
| 92 | TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal); | |
| 93 | ||
| 94 | load_variant(symbol: Symbols.VARIANT) -> Value => | |
| 95 | TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal); | |
| 96 | ||
| 97 | load_enum_struct_member(symbol: Symbols.ENUM_STRUCT_MEMBER) -> Value => | |
| 98 | Literal.NUMBER("{symbol.value}", symbol.type, "i4"); | |
| 99 | ||
| 100 | load_instance_anonymous_function(symbol: Symbols.Symbol, func_type: Type) -> Value => | |
| 101 | Load.INSTANCE_ANONYMOUS_FUNCTION(symbol, func_type); | |
| 102 | ||
| 103 | load_static_anonymous_function(symbol: Symbols.Symbol, func_type: Type) -> Value => | |
| 104 | Load.STATIC_ANONYMOUS_FUNCTION(symbol, func_type); | |
| 105 | ||
| 106 | load_global_anonymous_function(symbol: Symbols.Symbol, func_type: Type) -> Value => | |
| 107 | Load.GLOBAL_ANONYMOUS_FUNCTION(symbol, func_type); | |
| 108 | ||
| 109 | load_function_group(from: Value?, symbol: Symbols.Symbol) -> Value => | |
| 110 | Load.SYMBOL(from, symbol); | |
| 111 | ||
| 112 | load_global_function(symbol: Symbols.Symbol) -> Value => | |
| 113 | Load.SYMBOL(null, symbol); | |
| 114 | ||
| 115 | load_instance_method(location: Source.LOCATION, from: Value?, symbol: Symbols.Symbol) -> Value | |
| 116 | => Load.SYMBOL(from, symbol); | |
| 117 | ||
| 118 | load_struct_method(location: Source.LOCATION, from: Value?, symbol: Symbols.Symbol) -> Value | |
| 119 | => Load.SYMBOL(from, symbol); | |
| 120 | ||
| 121 | // FIXME: this needs to create a pointer to the function: | |
| 122 | load_static_method(symbol: Symbols.Symbol) -> Value | |
| 123 | => Load.SYMBOL(null, symbol); | |
| 124 | ||
| 125 | load_local_variable(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is | |
| 126 | let function = _symbol_table.current_function; | |
| 127 | ||
| 128 | if function? /\ symbol.owner != function then | |
| 129 | let raw = function.load_captured_value(location, symbol, self); | |
| 130 | ||
| 131 | // Captured boxed locals: the frame field holds | |
| 132 | // BOX[T]; the user-code-side read wants T, so | |
| 133 | // unwrap via `.value`. Inter-frame transfers | |
| 134 | // (load_outer_captured_value chaining for | |
| 135 | // nested closures) call load_captured_value | |
| 136 | // directly and don't hit this unwrap — they | |
| 137 | // need the box reference to pass to the next | |
| 138 | // frame's constructor. | |
| 139 | if symbol.is_boxed then | |
| 140 | let value_member = resolve_box_value_member(symbol); | |
| 141 | ||
| 142 | if value_member? then | |
| 143 | return value_member.load(location, raw, self); | |
| 144 | fi | |
| 145 | fi | |
| 146 | ||
| 147 | return raw; | |
| 148 | fi | |
| 149 | ||
| 150 | if symbol.is_boxed then | |
| 151 | return _load_boxed_local(location, symbol); | |
| 152 | fi | |
| 153 | ||
| 154 | return Load.LOCAL_VARIABLE(symbol); | |
| 155 | si | |
| 156 | ||
| 157 | load_outer_local_variable(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is | |
| 158 | let function = _symbol_table.current_function; | |
| 159 | ||
| 160 | if function? /\ symbol.owner != function then | |
| 161 | let result = function.load_outer_captured_value(location, symbol, self); | |
| 162 | ||
| 163 | assert result? else "load outer captured value did not return a value: {function} {symbol}"; | |
| 164 | ||
| 165 | return result; | |
| 166 | fi | |
| 167 | ||
| 168 | let raw = Load.LOCAL_VARIABLE(symbol); | |
| 169 | ||
| 170 | if symbol.is_boxed then | |
| 171 | // The slot value IS the BOX[T] reference; tell the | |
| 172 | // IR layer its type is BOX[T] rather than T (the | |
| 173 | // load IR derives its type from `symbol.type`). | |
| 174 | // Used by `closure.find_or_add_capture` -> frame | |
| 175 | // construction at closure-build time. | |
| 176 | return TYPE_WRAPPER(symbol.storage_type!, raw); | |
| 177 | fi | |
| 178 | ||
| 179 | return raw; | |
| 180 | si | |
| 181 | ||
| 182 | store_local_variable(location: Source.LOCATION, symbol: Symbols.Variable, value: Value, is_initialize: bool) -> Value is | |
| 183 | let function = _symbol_table.current_function; | |
| 184 | ||
| 185 | if !is_initialize then | |
| 186 | // Assignability is a static property of the declaration: | |
| 187 | // a mut local is always assignable - captured mut locals | |
| 188 | // are stored through a shared BOX[T] cell - and a non-mut | |
| 189 | // local never is. Whether the box exists is a code | |
| 190 | // generation concern, not a legality one. | |
| 191 | if symbol.is_disposed then | |
| 192 | IoC.CONTAINER.instance.logger.error(location, "scoped disposal value may not be assigned to"); | |
| 193 | elif !symbol.is_mutable_marked then | |
| 194 | IoC.CONTAINER.instance.logger.error(location, "local value cannot be reassigned"); | |
| 195 | elif | |
| 196 | !symbol.is_boxed /\ | |
| 197 | (symbol.is_captured \/ !function? \/ symbol.owner != function) /\ | |
| 198 | _wants_il | |
| 199 | then | |
| 200 | // mark-boxed-locals boxes every captured, reassigned | |
| 201 | // mut local before compile-expressions on an IL-bound | |
| 202 | // build, so reaching this store unboxed means the | |
| 203 | // boxing analysis missed the assignment - erroring | |
| 204 | // beats emitting a store the capture cannot observe. | |
| 205 | IoC.CONTAINER.instance.logger.error(location, "captured value may not be assigned to"); | |
| 206 | fi | |
| 207 | fi | |
| 208 | ||
| 209 | // Boxed store from inside the closure body that | |
| 210 | // captured this local: route through the closure's | |
| 211 | // frame so we hit the shared BOX[T] cell, not the | |
| 212 | // (invisible-from-here) outer slot. | |
| 213 | if symbol.is_boxed /\ !is_initialize /\ function? /\ symbol.owner != function then | |
| 214 | let result = function.store_captured_value(location, symbol, value, self); | |
| 215 | ||
| 216 | return result; | |
| 217 | fi | |
| 218 | ||
| 219 | if symbol.is_boxed then | |
| 220 | return _store_boxed_local(location, symbol, value, is_initialize); | |
| 221 | fi | |
| 222 | ||
| 223 | return Store.LOCAL_VARIABLE(symbol, _value_boxer.box_if_needed(value, symbol.type!)); | |
| 224 | si | |
| 225 | ||
| 226 | // Boxed-local read: load slot (gives BOX[T] reference) | |
| 227 | // and field-load `.value`. The TYPE_WRAPPER re-types the | |
| 228 | // slot load as BOX[T] for the benefit of the follow-on | |
| 229 | // instance-field access; emitted IL is just ldloc. | |
| 230 | // The `.value` access goes through `member.load` | |
| 231 | // polymorphically — works whether the resolved member | |
| 232 | // is a Field, INSTANCE_FIELD, or auto-generated property | |
| 233 | // shape that wraps a public field. | |
| 234 | _load_boxed_local(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is | |
| 235 | let member = resolve_box_value_member(symbol); | |
| 236 | ||
| 237 | if !member? then | |
| 238 | return Load.LOCAL_VARIABLE(symbol); | |
| 239 | fi | |
| 240 | ||
| 241 | let raw_load = Load.LOCAL_VARIABLE(symbol); | |
| 242 | let typed_box = TYPE_WRAPPER(symbol.storage_type!, raw_load); | |
| 243 | ||
| 244 | return member.load(location, typed_box, self); | |
| 245 | si | |
| 246 | ||
| 247 | // Boxed-local write: on declaration init, construct a | |
| 248 | // fresh `Ghul.BOX[T](value)` (or `Ghul.BOX[T]()` when no | |
| 249 | // initializer is supplied) and store the reference; on | |
| 250 | // reassignment, load the slot's BOX[T] and field-store to | |
| 251 | // `.value`. Allocating the empty box at declaration time — | |
| 252 | // not lazily on first write — is what makes by-reference | |
| 253 | // capture work for a forward-declared mutable (`let f mut; | |
| 254 | // let g = () => f(); f = ...`): the closure-frame ctor | |
| 255 | // receives the same heap cell the later assignment writes | |
| 256 | // to. | |
| 257 | _store_boxed_local(location: Source.LOCATION, symbol: Symbols.Variable, value: Value?, is_initialize: bool) -> Value is | |
| 258 | if is_initialize then | |
| 259 | // `box_type.symbol` is the specialised | |
| 260 | // Symbols.GENERIC (BOX[T]), constructed by | |
| 261 | // `Types.GENERIC.init`. find_member on it | |
| 262 | // returns the specialised init constructor(s) | |
| 263 | // with the concrete T substituted in — but | |
| 264 | // BOX has two overloads (`init()` and | |
| 265 | // `init(value: T)`), so we get a FUNCTION_GROUP | |
| 266 | // back. Pick the overload matching what we have. | |
| 267 | let box_type = symbol.storage_type!; | |
| 268 | let arity = if value? then 1 else 0 fi; | |
| 269 | let ctor = _resolve_box_constructor(box_type, arity); | |
| 270 | ||
| 271 | assert ctor? else "no box constructor of arity {arity} for {box_type}"; | |
| 272 | ||
| 273 | let arguments = | |
| 274 | if value? then | |
| 275 | Collections.LIST[Value]([value]) | |
| 276 | else | |
| 277 | Collections.LIST[Value]() | |
| 278 | fi; | |
| 279 | let new_box = NEW(box_type, ctor, arguments); | |
| 280 | ||
| 281 | return Store.LOCAL_VARIABLE(symbol, new_box); | |
| 282 | fi | |
| 283 | ||
| 284 | let member = resolve_box_value_member(symbol); | |
| 285 | ||
| 286 | if !member? then | |
| 287 | return Store.LOCAL_VARIABLE(symbol, _value_boxer.box_if_needed(value!, symbol.type!)); | |
| 288 | fi | |
| 289 | ||
| 290 | let raw_load = Load.LOCAL_VARIABLE(symbol); | |
| 291 | let typed_box = TYPE_WRAPPER(symbol.storage_type!, raw_load); | |
| 292 | ||
| 293 | return member.store(location, typed_box, value!, self, false); | |
| 294 | si | |
| 295 | ||
| 296 | // Constructor of `Ghul.BOX[T]` taking `arity` arguments — | |
| 297 | // 1 for the value-carrying overload, 0 for the empty one. | |
| 298 | // `find_member("init")` on the specialised GENERIC | |
| 299 | // returns a FUNCTION_GROUP (BOX has both); pick the | |
| 300 | // matching overload. Returns null if box_type is unresolved | |
| 301 | // or the constructor can't be located — callers null-check | |
| 302 | // before use. | |
| 303 | _resolve_box_constructor(box_type: Type?, arity: int) -> Symbols.Function? is | |
| 304 | if !box_type? then | |
| 305 | return null; | |
| 306 | fi | |
| 307 | ||
| 308 | let member = box_type.symbol.find_member("init"); | |
| 309 | ||
| 310 | if !member? then | |
| 311 | return null; | |
| 312 | fi | |
| 313 | ||
| 314 | let direct = cast Symbols.Function?(member); | |
| 315 | ||
| 316 | if direct? then | |
| 317 | if direct.are_arguments_declared /\ direct.arguments.count == arity then | |
| 318 | return direct; | |
| 319 | else | |
| 320 | return null; | |
| 321 | fi | |
| 322 | fi | |
| 323 | ||
| 324 | let group = cast Symbols.FUNCTION_GROUP?(member); | |
| 325 | ||
| 326 | if !group? then | |
| 327 | return null; | |
| 328 | fi | |
| 329 | ||
| 330 | for f in group.functions do | |
| 331 | if f.are_arguments_declared /\ f.arguments.count == arity then | |
| 332 | return f; | |
| 333 | fi | |
| 334 | od | |
| 335 | ||
| 336 | return null; | |
| 337 | si | |
| 338 | ||
| 339 | // The `value` member of `Ghul.BOX[T]` — what reads and | |
| 340 | // writes ultimately touch when `symbol.is_boxed`. | |
| 341 | // Returned as a generic Symbol so polymorphic | |
| 342 | // `.load(location, from, loader)` / | |
| 343 | // `.store(location, from, value, loader, is_initialize)` | |
| 344 | // dispatch handles Field vs INSTANCE_FIELD vs | |
| 345 | // auto-generated property shapes uniformly. | |
| 346 | resolve_box_value_member(symbol: Symbols.Variable) -> Symbols.Symbol? is | |
| 347 | let box_type = symbol.storage_type; | |
| 348 | ||
| 349 | if !box_type? then | |
| 350 | return null; | |
| 351 | fi | |
| 352 | ||
| 353 | return box_type.symbol.find_direct("value"); | |
| 354 | si | |
| 355 | ||
| 356 | load_local_argument(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is | |
| 357 | let function = _symbol_table.current_function; | |
| 358 | ||
| 359 | if function? /\ symbol.owner != function then | |
| 360 | let result = function.load_captured_value(location, symbol, self); | |
| 361 | ||
| 362 | return result; | |
| 363 | fi | |
| 364 | ||
| 365 | return Load.LOCAL_ARGUMENT(symbol); | |
| 366 | si | |
| 367 | ||
| 368 | load_outer_local_argument(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is | |
| 369 | let function = _symbol_table.current_function; | |
| 370 | ||
| 371 | if function? /\ symbol.owner != function then | |
| 372 | let result = function.load_outer_captured_value(location, symbol, self); | |
| 373 | ||
| 374 | assert result? else "load outer captured value did not return a value: {function} {symbol}"; | |
| 375 | ||
| 376 | return result; | |
| 377 | fi | |
| 378 | ||
| 379 | return Load.LOCAL_VARIABLE(symbol); | |
| 380 | si | |
| 381 | ||
| 382 | store_local_argument(location: Source.LOCATION, symbol: Symbols.Variable, value: Value, is_initialize: bool) -> Value is | |
| 383 | let function = _symbol_table.current_function; | |
| 384 | ||
| 385 | if !is_initialize then | |
| 386 | // FIXME: need proper dataflow analysis | |
| 387 | if !symbol.is_mutable_marked then | |
| 388 | IoC.CONTAINER.instance.logger.error(location, "local value cannot be reassigned"); | |
| 389 | elif symbol.is_captured \/ !function? \/ symbol.owner != function then | |
| 390 | // Captured arguments are not yet routed through a | |
| 391 | // shared BOX[T] cell the way captured mut locals | |
| 392 | // are - the closure frame copies the argument value | |
| 393 | // when it is constructed. Until they are boxed, an | |
| 394 | // assignment on either side of the capture would be | |
| 395 | // invisible to the other, so it is rejected even | |
| 396 | // for a mut argument. | |
| 397 | IoC.CONTAINER.instance.logger.error(location, "captured argument may not be assigned to"); | |
| 398 | fi | |
| 399 | fi | |
| 400 | ||
| 401 | return Store.LOCAL_ARGUMENT(symbol, _value_boxer.box_if_needed(value, symbol.type!)); | |
| 402 | si | |
| 403 | ||
| 404 | // True when this build lowers the IR to IL - the only case where | |
| 405 | // boxing decisions have observable consequences. | |
| 406 | _wants_il: bool => | |
| 407 | IoC.CONTAINER.instance.build_flags.want_assembler \/ | |
| 408 | IoC.CONTAINER.instance.build_flags.want_executable; | |
| 409 | ||
| 410 | load_global_variable(symbol: Symbols.Variable) -> Value => | |
| 411 | Load.GLOBAL_FIELD(symbol); | |
| 412 | ||
| 413 | store_global_variable(symbol: Symbols.Variable, value: Value) -> Value => | |
| 414 | Store.GLOBAL_FIELD(symbol, _value_boxer.box_if_needed(value, symbol.type!)); | |
| 415 | ||
| 416 | load_instance_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable) -> Value is | |
| 417 | if !from? then | |
| 418 | from = load_self(location); | |
| 419 | fi | |
| 420 | ||
| 421 | return Load.INSTANCE_FIELD(from, symbol); | |
| 422 | si | |
| 423 | ||
| 424 | store_instance_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable, value: Value) -> Value is | |
| 425 | if !from? then | |
| 426 | from = load_self(location); | |
| 427 | fi | |
| 428 | ||
| 429 | return Store.INSTANCE_FIELD(from, symbol, _value_boxer.box_if_needed(value, symbol.type!)); | |
| 430 | si | |
| 431 | ||
| 432 | load_struct_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable) -> Value is | |
| 433 | if !from? then | |
| 434 | from = load_self(location); | |
| 435 | elif from.has_address then | |
| 436 | from = ADDRESS(from); | |
| 437 | fi | |
| 438 | ||
| 439 | return Load.INSTANCE_FIELD(from, symbol); | |
| 440 | si | |
| 441 | ||
| 442 | store_struct_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable, value: Value) -> Value is | |
| 443 | if !from? then | |
| 444 | from = load_self(location); | |
| 445 | elif from.has_address then | |
| 446 | from = ADDRESS(from); | |
| 447 | else | |
| 448 | _logger.info(location, "member updated in discarded value"); | |
| 449 | fi | |
| 450 | ||
| 451 | return Store.INSTANCE_FIELD(from, symbol, _value_boxer.box_if_needed(value, symbol.type!)); | |
| 452 | si | |
| 453 | ||
| 454 | ||
| 455 | load_static_field(symbol: Symbols.Variable) -> Value => | |
| 456 | Load.STATIC_FIELD(symbol); | |
| 457 | ||
| 458 | store_static_field(symbol: Symbols.Variable, value: Value) -> Value => | |
| 459 | Store.STATIC_FIELD(symbol, _value_boxer.box_if_needed(value, symbol.type!)); | |
| 460 | ||
| 461 | // The `load_*_property` wrappers always return non-null when called | |
| 462 | // with a real Property — the only null paths are when symbol or | |
| 463 | // read_function is null, which the wrappers' callers (Property.load) | |
| 464 | // never hit because they pass `self`. Asserting non-null here lets | |
| 465 | // those callers consume a `Value` while load_property stays honestly | |
| 466 | // `Value?` for internal use. | |
| 467 | load_instance_property(location: Source.LOCATION, from: Value?, symbol: Symbols.Property) -> Value is | |
| 468 | let result = load_property(location, from, symbol, false); | |
| 469 | assert result? else "load_instance_property returned null for {symbol}"; | |
| 470 | return result; | |
| 471 | si | |
| 472 | ||
| 473 | load_static_property(location: Source.LOCATION, symbol: Symbols.Property) -> Value is | |
| 474 | let result = load_property(location, null, symbol, true); | |
| 475 | assert result? else "load_static_property returned null for {symbol}"; | |
| 476 | return result; | |
| 477 | si | |
| 478 | ||
| 479 | load_global_property(location: Source.LOCATION, symbol: Symbols.Property) -> Value is | |
| 480 | let result = load_property(location, null, symbol, true); | |
| 481 | assert result? else "load_global_property returned null for {symbol}"; | |
| 482 | return result; | |
| 483 | si | |
| 484 | ||
| 485 | load_property(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Property, is_static: bool) -> Value? is | |
| 486 | if !from? /\ !is_static then | |
| 487 | from = load_self(location); | |
| 488 | fi | |
| 489 | ||
| 490 | if !symbol.read_function? then | |
| 491 | _logger.poison(location, "property {symbol.qualified_name} does not have a read function"); | |
| 492 | return null; | |
| 493 | fi | |
| 494 | ||
| 495 | let read_function = symbol.read_function; | |
| 496 | ||
| 497 | find_symbol = _null_find_symbol; | |
| 498 | ||
| 499 | // Flow narrowing reconciles a narrowed property's | |
| 500 | // `Property.type` the same way it does `Variable.type`; | |
| 501 | // the load's value must carry that narrowed view rather | |
| 502 | // than the getter's declared return type. Outside a | |
| 503 | // narrow the two are the same type instance, so this is | |
| 504 | // inert. | |
| 505 | let narrowed_type = | |
| 506 | if symbol.type? /\ symbol.type != read_function.return_type then | |
| 507 | symbol.type | |
| 508 | else | |
| 509 | null | |
| 510 | fi; | |
| 511 | ||
| 512 | return read_function.call(location, from, Collections.LIST[Value](0), narrowed_type, _function_caller); | |
| 513 | si | |
| 514 | ||
| 515 | store_instance_property(location: Source.LOCATION, from: Value?, symbol: Symbols.Property, value: Value) -> Value is | |
| 516 | let result = store_property(location, from, symbol, value, false); | |
| 517 | return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi; | |
| 518 | si | |
| 519 | store_static_property(location: Source.LOCATION, symbol: Symbols.Property, value: Value) -> Value is | |
| 520 | let result = store_property(location, null, symbol, value, true); | |
| 521 | return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi; | |
| 522 | si | |
| 523 | store_global_property(location: Source.LOCATION, symbol: Symbols.Property, value: Value) -> Value is | |
| 524 | let result = store_property(location, null, symbol, value, true); | |
| 525 | return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi; | |
| 526 | si | |
| 527 | ||
| 528 | store_property(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Property, value: Value, is_static: bool) -> Value? is | |
| 529 | if symbol == null then | |
| 530 | return null; | |
| 531 | fi | |
| 532 | ||
| 533 | if !from? /\ !is_static then | |
| 534 | from = load_self(location); | |
| 535 | fi | |
| 536 | ||
| 537 | if symbol.assign_function == null then | |
| 538 | _logger.error(location, "property {symbol} is not assignable"); | |
| 539 | ||
| 540 | assert !symbol.is_assignable else "property {symbol} is assignable but does not have an assign accessor function"; | |
| 541 | ||
| 542 | return null; | |
| 543 | fi | |
| 544 | ||
| 545 | find_symbol = _null_find_symbol; | |
| 546 | ||
| 547 | return TYPE_WRAPPER( | |
| 548 | cast Types.Typed(symbol).type!, | |
| 549 | symbol.assign_function!.call(location, from, Collections.LIST([value]), null, _function_caller) | |
| 550 | ); | |
| 551 | si | |
| 552 | si | |
| 553 | si |