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 | // A call through a null anon function does not always produce a | |
| 24 | // sane stack trace, so the unset state is a function that says so. | |
| 25 | init() static is | |
| 26 | _null_find_symbol = | |
| 27 | (name: string) -> Symbols.Symbol? is | |
| 28 | throw NotImplementedException("find_symbol is not set") | |
| 29 | si | |
| 30 | si | |
| 31 | ||
| 32 | init( | |
| 33 | logger: Logger, | |
| 34 | symbol_table: SYMBOL_TABLE, | |
| 35 | function_caller: FUNCTION_CALLER, | |
| 36 | value_boxer: VALUE_BOXER, | |
| 37 | innate_symbol_lookup: Lookups.InnateSymbolLookup | |
| 38 | ) is | |
| 39 | super.init() | |
| 40 | ||
| 41 | _logger = logger | |
| 42 | _symbol_table = symbol_table | |
| 43 | _function_caller = function_caller | |
| 44 | _value_boxer = value_boxer | |
| 45 | _innate_symbol_lookup = innate_symbol_lookup | |
| 46 | ||
| 47 | find_symbol = _null_find_symbol | |
| 48 | si | |
| 49 | ||
| 50 | load_self(location: Source.LOCATION) -> Value is | |
| 51 | let context = _symbol_table.current_function | |
| 52 | ||
| 53 | if context? then | |
| 54 | let result = context.load_self(location, self) | |
| 55 | ||
| 56 | return result | |
| 57 | fi | |
| 58 | ||
| 59 | IoC.CONTAINER.instance.logger.error(location, "cannot access instance member from non-instance context") | |
| 60 | ||
| 61 | return IR.Values.DUMMY(Types.ERROR(), Source.LOCATION.internal) | |
| 62 | si | |
| 63 | ||
| 64 | load_outer_self(location: Source.LOCATION) -> Value is | |
| 65 | let context = _symbol_table.current_function | |
| 66 | ||
| 67 | if context? then | |
| 68 | let result = context.load_outer_self(location, self) | |
| 69 | ||
| 70 | assert result? else "load outer self did not return a value: {context}" | |
| 71 | ||
| 72 | return result | |
| 73 | fi | |
| 74 | ||
| 75 | IoC.CONTAINER.instance.logger.error(location, "cannot access instance member from non-instance context") | |
| 76 | ||
| 77 | return IR.Values.DUMMY(Types.ERROR(), Source.LOCATION.internal) | |
| 78 | si | |
| 79 | ||
| 80 | load_namespace(symbol: Symbols.NAMESPACE) -> Value => | |
| 81 | Load.SYMBOL(null, symbol) | |
| 82 | ||
| 83 | load_class(symbol: Symbols.CLASS) -> Value => | |
| 84 | TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal) | |
| 85 | ||
| 86 | load_trait(symbol: Symbols.TRAIT) -> Value => | |
| 87 | TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal) | |
| 88 | ||
| 89 | load_struct(symbol: Symbols.STRUCT) -> Value => | |
| 90 | TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal) | |
| 91 | ||
| 92 | load_union(symbol: Symbols.UNION) -> Value => | |
| 93 | TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal) | |
| 94 | ||
| 95 | load_variant(symbol: Symbols.VARIANT) -> Value => | |
| 96 | TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal) | |
| 97 | ||
| 98 | load_enum_struct_member(symbol: Symbols.ENUM_STRUCT_MEMBER) -> Value => | |
| 99 | if let number = symbol.numeric_value then | |
| 100 | Symbols.ENUM_CONSTANT.from_number(symbol.type, number, _innate_symbol_lookup) | |
| 101 | else | |
| 102 | Symbols.ENUM_CONSTANT.from_pattern( | |
| 103 | symbol.type, | |
| 104 | _wide_enum_pattern(symbol.emitted_value), | |
| 105 | _innate_symbol_lookup) | |
| 106 | fi | |
| 107 | ||
| 108 | // A member of an enum wider than int32 has no int32 value to | |
| 109 | // read and arrives as the text it was declared with. What the | |
| 110 | // load takes is the bit pattern, so both signednesses are tried | |
| 111 | // before giving up: `[Flags] enum : uint` with the high bit set | |
| 112 | // reads as unsigned, a negative `enum : long` as signed. | |
| 113 | _wide_enum_pattern(text: string) -> ulong static is | |
| 114 | let unsigned: ulong mut = _ | |
| 115 | ||
| 116 | if ulong.try_parse(text, unsigned ref) then | |
| 117 | return unsigned | |
| 118 | fi | |
| 119 | ||
| 120 | let signed: long mut = _ | |
| 121 | ||
| 122 | if long.try_parse(text, signed ref) then | |
| 123 | return cast ulong(signed) | |
| 124 | fi | |
| 125 | ||
| 126 | return 0UL | |
| 127 | si | |
| 128 | ||
| 129 | load_instance_anonymous_function(symbol: Symbols.Symbol, func_type: Type) -> Value => | |
| 130 | Load.INSTANCE_ANONYMOUS_FUNCTION(symbol, func_type) | |
| 131 | ||
| 132 | load_static_anonymous_function(symbol: Symbols.Symbol, func_type: Type) -> Value => | |
| 133 | Load.STATIC_ANONYMOUS_FUNCTION(symbol, func_type) | |
| 134 | ||
| 135 | load_global_anonymous_function(symbol: Symbols.Symbol, func_type: Type) -> Value => | |
| 136 | Load.GLOBAL_ANONYMOUS_FUNCTION(symbol, func_type) | |
| 137 | ||
| 138 | load_function_group(from: Value?, symbol: Symbols.Symbol) -> Value => | |
| 139 | Load.SYMBOL(from, symbol) | |
| 140 | ||
| 141 | load_global_function(symbol: Symbols.Symbol) -> Value => | |
| 142 | Load.SYMBOL(null, symbol) | |
| 143 | ||
| 144 | load_instance_method(location: Source.LOCATION, from: Value?, symbol: Symbols.Symbol) -> Value | |
| 145 | => Load.SYMBOL(from, symbol) | |
| 146 | ||
| 147 | load_struct_method(location: Source.LOCATION, from: Value?, symbol: Symbols.Symbol) -> Value | |
| 148 | => Load.SYMBOL(from, symbol) | |
| 149 | ||
| 150 | // FIXME: this needs to create a pointer to the function: | |
| 151 | load_static_method(symbol: Symbols.Symbol) -> Value | |
| 152 | => Load.SYMBOL(null, symbol) | |
| 153 | ||
| 154 | load_local_variable(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is | |
| 155 | let function = _symbol_table.current_function | |
| 156 | ||
| 157 | if function? /\ symbol.owner != function then | |
| 158 | let raw = function.load_captured_value(location, symbol, self) | |
| 159 | ||
| 160 | // Captured boxed locals: the frame field holds | |
| 161 | // BOX[T]; the user-code-side read wants T, so | |
| 162 | // unwrap via `.value`. Inter-frame transfers | |
| 163 | // (load_outer_captured_value chaining for | |
| 164 | // nested closures) call load_captured_value | |
| 165 | // directly and don't hit this unwrap — they | |
| 166 | // need the box reference to pass to the next | |
| 167 | // frame's constructor. | |
| 168 | if symbol.is_boxed then | |
| 169 | let value_member = resolve_box_value_member(symbol) | |
| 170 | ||
| 171 | if value_member? then | |
| 172 | return value_member.load(location, raw, self) | |
| 173 | fi | |
| 174 | fi | |
| 175 | ||
| 176 | return raw | |
| 177 | fi | |
| 178 | ||
| 179 | if symbol.is_boxed then | |
| 180 | return _load_boxed_local(location, symbol) | |
| 181 | fi | |
| 182 | ||
| 183 | return Load.LOCAL_VARIABLE(symbol) | |
| 184 | si | |
| 185 | ||
| 186 | load_outer_local_variable(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is | |
| 187 | let function = _symbol_table.current_function | |
| 188 | ||
| 189 | if function? /\ symbol.owner != function then | |
| 190 | let result = function.load_outer_captured_value(location, symbol, self) | |
| 191 | ||
| 192 | assert result? else "load outer captured value did not return a value: {function} {symbol}" | |
| 193 | ||
| 194 | return result | |
| 195 | fi | |
| 196 | ||
| 197 | let raw = Load.LOCAL_VARIABLE(symbol) | |
| 198 | ||
| 199 | if symbol.is_boxed then | |
| 200 | // The slot value IS the BOX[T] reference; tell the | |
| 201 | // IR layer its type is BOX[T] rather than T (the | |
| 202 | // load IR derives its type from `symbol.type`). | |
| 203 | // Used by `closure.find_or_add_capture` -> frame | |
| 204 | // construction at closure-build time. | |
| 205 | return TYPE_WRAPPER(symbol.storage_type!, raw) | |
| 206 | fi | |
| 207 | ||
| 208 | return raw | |
| 209 | si | |
| 210 | ||
| 211 | store_local_variable(location: Source.LOCATION, symbol: Symbols.Variable, value: Value, is_initialize: bool) -> Value is | |
| 212 | // A local written to before the declaration that types it | |
| 213 | // has been compiled - an assignment inside the local's own | |
| 214 | // initializer, which the walk reaches first - has no type to | |
| 215 | // store through. The read that got there is already | |
| 216 | // reported, so this is the consequence of a diagnostic | |
| 217 | // rather than one of its own; report only where nothing | |
| 218 | // else has, so that no store reaches code generation | |
| 219 | // unexplained. | |
| 220 | if !symbol.type? then | |
| 221 | if IoC.CONTAINER.instance.logger.is_clean then | |
| 222 | IoC.CONTAINER.instance.logger.error(location, "cannot assign to {symbol.name} here") | |
| 223 | fi | |
| 224 | ||
| 225 | return DUMMY(Types.ERROR(), location) | |
| 226 | fi | |
| 227 | ||
| 228 | let function = _symbol_table.current_function | |
| 229 | ||
| 230 | if !is_initialize then | |
| 231 | // Assignability is a static property of the declaration: | |
| 232 | // a mut local is always assignable - captured mut locals | |
| 233 | // are stored through a shared BOX[T] cell - and a non-mut | |
| 234 | // local never is. Whether the box exists is a code | |
| 235 | // generation concern, not a legality one. | |
| 236 | if symbol.is_disposed then | |
| 237 | IoC.CONTAINER.instance.logger.error(location, "scoped disposal value may not be assigned to") | |
| 238 | elif !symbol.is_mutable_marked then | |
| 239 | IoC.CONTAINER.instance.logger.error(location, "local value cannot be reassigned") | |
| 240 | elif | |
| 241 | !symbol.is_boxed /\ | |
| 242 | (symbol.is_captured \/ !function? \/ symbol.owner != function) /\ | |
| 243 | _wants_il | |
| 244 | then | |
| 245 | // mark-boxed-locals boxes every captured, reassigned | |
| 246 | // mut local before compile-expressions on an IL-bound | |
| 247 | // build, so reaching this store unboxed means the | |
| 248 | // boxing analysis missed the assignment - erroring | |
| 249 | // beats emitting a store the capture cannot observe. | |
| 250 | IoC.CONTAINER.instance.logger.error(location, "captured value may not be assigned to") | |
| 251 | fi | |
| 252 | fi | |
| 253 | ||
| 254 | // Boxed store from inside the closure body that | |
| 255 | // captured this local: route through the closure's | |
| 256 | // frame so we hit the shared BOX[T] cell, not the | |
| 257 | // (invisible-from-here) outer slot. | |
| 258 | if symbol.is_boxed /\ !is_initialize /\ function? /\ symbol.owner != function then | |
| 259 | let result = function.store_captured_value(location, symbol, value, self) | |
| 260 | ||
| 261 | return result | |
| 262 | fi | |
| 263 | ||
| 264 | if symbol.is_boxed then | |
| 265 | return _store_boxed_local(location, symbol, value, is_initialize) | |
| 266 | fi | |
| 267 | ||
| 268 | return Store.LOCAL_VARIABLE(symbol, _value_boxer.box_if_needed(value, symbol.type!)) | |
| 269 | si | |
| 270 | ||
| 271 | // Boxed-local read: load slot (gives BOX[T] reference) | |
| 272 | // and field-load `.value`. The TYPE_WRAPPER re-types the | |
| 273 | // slot load as BOX[T] for the benefit of the follow-on | |
| 274 | // instance-field access; emitted IL is just ldloc. | |
| 275 | // The `.value` access goes through `member.load` | |
| 276 | // polymorphically — works whether the resolved member | |
| 277 | // is a Field, INSTANCE_FIELD, or auto-generated property | |
| 278 | // shape that wraps a public field. | |
| 279 | _load_boxed_local(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is | |
| 280 | let member = resolve_box_value_member(symbol) | |
| 281 | ||
| 282 | if !member? then | |
| 283 | return Load.LOCAL_VARIABLE(symbol) | |
| 284 | fi | |
| 285 | ||
| 286 | let raw_load = Load.LOCAL_VARIABLE(symbol) | |
| 287 | let typed_box = TYPE_WRAPPER(symbol.storage_type!, raw_load) | |
| 288 | ||
| 289 | return member.load(location, typed_box, self) | |
| 290 | si | |
| 291 | ||
| 292 | // Boxed-local write: on declaration init, construct a | |
| 293 | // fresh `Ghul.BOX[T](value)` (or `Ghul.BOX[T]()` when no | |
| 294 | // initializer is supplied) and store the reference; on | |
| 295 | // reassignment, load the slot's BOX[T] and field-store to | |
| 296 | // `.value`. Allocating the empty box at declaration time — | |
| 297 | // not lazily on first write — is what makes by-reference | |
| 298 | // capture work for a forward-declared mutable (`let f mut; | |
| 299 | // let g = () => f(); f = ...`): the closure-frame ctor | |
| 300 | // receives the same heap cell the later assignment writes | |
| 301 | // to. | |
| 302 | // Declaring a captured, reassigned local that has no initializer: | |
| 303 | // there is no value to store, only the empty box to allocate. | |
| 304 | store_empty_boxed_local(location: Source.LOCATION, symbol: Symbols.Variable) -> Value => | |
| 305 | _store_boxed_local(location, symbol, null, true) | |
| 306 | ||
| 307 | _store_boxed_local(location: Source.LOCATION, symbol: Symbols.Variable, value: Value?, is_initialize: bool) -> Value is | |
| 308 | if is_initialize then | |
| 309 | // `box_type.symbol` is the specialised | |
| 310 | // Symbols.GENERIC (BOX[T]), constructed by | |
| 311 | // `Types.GENERIC.init`. find_member on it | |
| 312 | // returns the specialised init constructor(s) | |
| 313 | // with the concrete T substituted in — but | |
| 314 | // BOX has two overloads (`init()` and | |
| 315 | // `init(value: T)`), so we get a FUNCTION_GROUP | |
| 316 | // back. Pick the overload matching what we have. | |
| 317 | let box_type = symbol.storage_type! | |
| 318 | let arity = if value? then 1 else 0 fi | |
| 319 | let ctor = _resolve_box_constructor(box_type, arity) | |
| 320 | ||
| 321 | assert ctor? else "no box constructor of arity {arity} for {box_type}" | |
| 322 | ||
| 323 | let arguments = | |
| 324 | if value? then | |
| 325 | Collections.LIST[Value]([value]) | |
| 326 | else | |
| 327 | Collections.LIST[Value]() | |
| 328 | fi | |
| 329 | let new_box = NEW(box_type, ctor, arguments) | |
| 330 | ||
| 331 | return Store.LOCAL_VARIABLE(symbol, new_box) | |
| 332 | fi | |
| 333 | ||
| 334 | let member = resolve_box_value_member(symbol) | |
| 335 | ||
| 336 | if !member? then | |
| 337 | return Store.LOCAL_VARIABLE(symbol, _value_boxer.box_if_needed(value!, symbol.type!)) | |
| 338 | fi | |
| 339 | ||
| 340 | let raw_load = Load.LOCAL_VARIABLE(symbol) | |
| 341 | let typed_box = TYPE_WRAPPER(symbol.storage_type!, raw_load) | |
| 342 | ||
| 343 | return member.store(location, typed_box, value!, self, false) | |
| 344 | si | |
| 345 | ||
| 346 | // Constructor of `Ghul.BOX[T]` taking `arity` arguments — | |
| 347 | // 1 for the value-carrying overload, 0 for the empty one. | |
| 348 | // `find_member("init")` on the specialised GENERIC | |
| 349 | // returns a FUNCTION_GROUP (BOX has both); pick the | |
| 350 | // matching overload. Returns null if box_type is unresolved | |
| 351 | // or the constructor can't be located — callers null-check | |
| 352 | // before use. | |
| 353 | _resolve_box_constructor(box_type: Type?, arity: int) -> Symbols.Function? is | |
| 354 | if !box_type? then | |
| 355 | return null | |
| 356 | fi | |
| 357 | ||
| 358 | let member = box_type.symbol.find_member("init") | |
| 359 | ||
| 360 | if !member? then | |
| 361 | return null | |
| 362 | fi | |
| 363 | ||
| 364 | let direct = cast Symbols.Function?(member) | |
| 365 | ||
| 366 | if direct? then | |
| 367 | if direct.are_arguments_declared /\ direct.arguments.count == arity then | |
| 368 | return direct | |
| 369 | else | |
| 370 | return null | |
| 371 | fi | |
| 372 | fi | |
| 373 | ||
| 374 | let group = cast Symbols.FUNCTION_GROUP?(member) | |
| 375 | ||
| 376 | if !group? then | |
| 377 | return null | |
| 378 | fi | |
| 379 | ||
| 380 | for f in group.functions do | |
| 381 | if f.are_arguments_declared /\ f.arguments.count == arity then | |
| 382 | return f | |
| 383 | fi | |
| 384 | od | |
| 385 | ||
| 386 | return null | |
| 387 | si | |
| 388 | ||
| 389 | // The `value` member of `Ghul.BOX[T]` — what reads and | |
| 390 | // writes ultimately touch when `symbol.is_boxed`. | |
| 391 | // Returned as a generic Symbol so polymorphic | |
| 392 | // `.load(location, from, loader)` / | |
| 393 | // `.store(location, from, value, loader, is_initialize)` | |
| 394 | // dispatch handles Field vs INSTANCE_FIELD vs | |
| 395 | // auto-generated property shapes uniformly. | |
| 396 | resolve_box_value_member(symbol: Symbols.Variable) -> Symbols.Symbol? is | |
| 397 | let box_type = symbol.storage_type | |
| 398 | ||
| 399 | if !box_type? then | |
| 400 | return null | |
| 401 | fi | |
| 402 | ||
| 403 | return box_type.symbol.find_direct("value") | |
| 404 | si | |
| 405 | ||
| 406 | load_local_argument(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is | |
| 407 | let function = _symbol_table.current_function | |
| 408 | ||
| 409 | if function? /\ symbol.owner != function then | |
| 410 | let result = function.load_captured_value(location, symbol, self) | |
| 411 | ||
| 412 | return result | |
| 413 | fi | |
| 414 | ||
| 415 | return Load.LOCAL_ARGUMENT(symbol) | |
| 416 | si | |
| 417 | ||
| 418 | load_outer_local_argument(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is | |
| 419 | let function = _symbol_table.current_function | |
| 420 | ||
| 421 | if function? /\ symbol.owner != function then | |
| 422 | let result = function.load_outer_captured_value(location, symbol, self) | |
| 423 | ||
| 424 | assert result? else "load outer captured value did not return a value: {function} {symbol}" | |
| 425 | ||
| 426 | return result | |
| 427 | fi | |
| 428 | ||
| 429 | return Load.LOCAL_VARIABLE(symbol) | |
| 430 | si | |
| 431 | ||
| 432 | store_local_argument(location: Source.LOCATION, symbol: Symbols.Variable, value: Value, is_initialize: bool) -> Value is | |
| 433 | let function = _symbol_table.current_function | |
| 434 | ||
| 435 | if !is_initialize then | |
| 436 | // FIXME: need proper dataflow analysis | |
| 437 | if !symbol.is_mutable_marked then | |
| 438 | IoC.CONTAINER.instance.logger.error(location, "local value cannot be reassigned") | |
| 439 | elif symbol.is_captured \/ !function? \/ symbol.owner != function then | |
| 440 | // Captured arguments are not yet routed through a | |
| 441 | // shared BOX[T] cell the way captured mut locals | |
| 442 | // are - the closure frame copies the argument value | |
| 443 | // when it is constructed. Until they are boxed, an | |
| 444 | // assignment on either side of the capture would be | |
| 445 | // invisible to the other, so it is rejected even | |
| 446 | // for a mut argument. | |
| 447 | IoC.CONTAINER.instance.logger.error(location, "captured argument may not be assigned to") | |
| 448 | fi | |
| 449 | fi | |
| 450 | ||
| 451 | return Store.LOCAL_ARGUMENT(symbol, _value_boxer.box_if_needed(value, symbol.type!)) | |
| 452 | si | |
| 453 | ||
| 454 | // True when this build lowers the IR to IL - the only case where | |
| 455 | // boxing decisions have observable consequences. | |
| 456 | _wants_il: bool => | |
| 457 | IoC.CONTAINER.instance.build_flags.want_assembler \/ | |
| 458 | IoC.CONTAINER.instance.build_flags.want_executable | |
| 459 | ||
| 460 | load_global_variable(symbol: Symbols.Variable) -> Value => | |
| 461 | Load.GLOBAL_FIELD(symbol) | |
| 462 | ||
| 463 | // Whether the value wants boxing is a question about the | |
| 464 | // variable's type, and a top-level `let` assigned above its own | |
| 465 | // declaration has none - the statement that types it has not | |
| 466 | // been walked. The assignment is reported at its own site, so | |
| 467 | // the unboxed value stands in rather than forcing an absent | |
| 468 | // type here. | |
| 469 | store_global_variable(symbol: Symbols.Variable, value: Value) -> Value => | |
| 470 | Store.GLOBAL_FIELD( | |
| 471 | symbol, | |
| 472 | if let variable_type = symbol.type then | |
| 473 | _value_boxer.box_if_needed(value, variable_type) | |
| 474 | else | |
| 475 | value | |
| 476 | fi | |
| 477 | ) | |
| 478 | ||
| 479 | load_instance_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable) -> Value is | |
| 480 | if !from? then | |
| 481 | from = load_self(location) | |
| 482 | fi | |
| 483 | ||
| 484 | return Load.INSTANCE_FIELD(from, symbol) | |
| 485 | si | |
| 486 | ||
| 487 | store_instance_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable, value: Value) -> Value is | |
| 488 | if !from? then | |
| 489 | from = load_self(location) | |
| 490 | fi | |
| 491 | ||
| 492 | return Store.INSTANCE_FIELD(from, symbol, _value_boxer.box_if_needed(value, symbol.type!)) | |
| 493 | si | |
| 494 | ||
| 495 | load_struct_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable) -> Value is | |
| 496 | if !from? then | |
| 497 | from = load_self(location) | |
| 498 | elif from.has_address then | |
| 499 | from = ADDRESS(from) | |
| 500 | fi | |
| 501 | ||
| 502 | return Load.INSTANCE_FIELD(from, symbol) | |
| 503 | si | |
| 504 | ||
| 505 | store_struct_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable, value: Value) -> Value is | |
| 506 | if !from? then | |
| 507 | from = load_self(location) | |
| 508 | elif from.has_address then | |
| 509 | from = ADDRESS(from) | |
| 510 | else | |
| 511 | _logger.info(location, "member updated in discarded value") | |
| 512 | fi | |
| 513 | ||
| 514 | return Store.INSTANCE_FIELD(from, symbol, _value_boxer.box_if_needed(value, symbol.type!)) | |
| 515 | si | |
| 516 | ||
| 517 | ||
| 518 | load_static_field(symbol: Symbols.Variable) -> Value => | |
| 519 | Load.STATIC_FIELD(symbol) | |
| 520 | ||
| 521 | store_static_field(symbol: Symbols.Variable, value: Value) -> Value => | |
| 522 | Store.STATIC_FIELD(symbol, _value_boxer.box_if_needed(value, symbol.type!)) | |
| 523 | ||
| 524 | // A constant is carried in metadata as text, in the same form a | |
| 525 | // reflected argument default is, so the same conversion reads it | |
| 526 | // back at the type the constant was declared with - through the | |
| 527 | // entry that takes the text as a value, because a constant string | |
| 528 | // can hold any text and so cannot reserve one to mean null. | |
| 529 | load_constant_field(symbol: Symbols.CONSTANT_FIELD) -> Value => | |
| 530 | if let text = symbol.constant_value then | |
| 531 | Syntax.Process.DEFAULT_ARGUMENT_VALUES.build_value( | |
| 532 | text, | |
| 533 | symbol.type!, | |
| 534 | _innate_symbol_lookup | |
| 535 | ) | |
| 536 | else | |
| 537 | IR.Values.DEFAULT(symbol.type!) | |
| 538 | fi | |
| 539 | ||
| 540 | // Reached when a constant is written to through a name brought | |
| 541 | // into scope by `use`, which stores without asking whether the | |
| 542 | // symbol accepts one. The member-access form is turned away | |
| 543 | // earlier, as a field only its declaring type may assign to, so | |
| 544 | // the two spellings report differently - but both have to | |
| 545 | // report, because a constant has no field for a write to reach. | |
| 546 | store_constant_field(location: Source.LOCATION, symbol: Symbols.CONSTANT_FIELD) -> Value is | |
| 547 | _logger.error(location, "{symbol} is not assignable") | |
| 548 | ||
| 549 | return IR.Values.DUMMY(Types.ERROR(), location) | |
| 550 | si | |
| 551 | ||
| 552 | // A property error recovery declared without building its read | |
| 553 | // function has nothing to load, and the diagnostic for it is already | |
| 554 | // reported, so the wrappers load an ERROR value there, as the | |
| 555 | // `store_*_property` wrappers below do for a store. | |
| 556 | load_instance_property(location: Source.LOCATION, from: Value?, symbol: Symbols.Property) -> Value is | |
| 557 | let result = load_property(location, from, symbol, false) | |
| 558 | return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi | |
| 559 | si | |
| 560 | ||
| 561 | load_static_property(location: Source.LOCATION, symbol: Symbols.Property) -> Value is | |
| 562 | let result = load_property(location, null, symbol, true) | |
| 563 | return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi | |
| 564 | si | |
| 565 | ||
| 566 | load_global_property(location: Source.LOCATION, symbol: Symbols.Property) -> Value is | |
| 567 | let result = load_property(location, null, symbol, true) | |
| 568 | return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi | |
| 569 | si | |
| 570 | ||
| 571 | load_property(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Property, is_static: bool) -> Value? is | |
| 572 | if !from? /\ !is_static then | |
| 573 | from = load_self(location) | |
| 574 | fi | |
| 575 | ||
| 576 | // Error recovery can declare a property from a malformed | |
| 577 | // declaration without a read function; the syntax error is the | |
| 578 | // diagnosis, and reading the property is an ordinary error | |
| 579 | // after it rather than the end of the compile. | |
| 580 | if !symbol.read_function? then | |
| 581 | _logger.error(location, "property {symbol.name} is not readable") | |
| 582 | return null | |
| 583 | fi | |
| 584 | ||
| 585 | let read_function = symbol.read_function | |
| 586 | ||
| 587 | find_symbol = _null_find_symbol | |
| 588 | ||
| 589 | // Flow narrowing reconciles a narrowed property's | |
| 590 | // `Property.type` the same way it does `Variable.type`; | |
| 591 | // the load's value must carry that narrowed view rather | |
| 592 | // than the getter's declared return type. Outside a | |
| 593 | // narrow the two are the same type instance, so this is | |
| 594 | // inert. | |
| 595 | let narrowed_type = | |
| 596 | if symbol.type? /\ symbol.type != read_function.return_type then | |
| 597 | symbol.type | |
| 598 | else | |
| 599 | null | |
| 600 | fi | |
| 601 | ||
| 602 | return read_function.call(location, from, Collections.LIST[Value](0), narrowed_type, _function_caller) | |
| 603 | si | |
| 604 | ||
| 605 | store_instance_property(location: Source.LOCATION, from: Value?, symbol: Symbols.Property, value: Value) -> Value is | |
| 606 | let result = store_property(location, from, symbol, value, false) | |
| 607 | return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi | |
| 608 | si | |
| 609 | store_static_property(location: Source.LOCATION, symbol: Symbols.Property, value: Value) -> Value is | |
| 610 | let result = store_property(location, null, symbol, value, true) | |
| 611 | return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi | |
| 612 | si | |
| 613 | store_global_property(location: Source.LOCATION, symbol: Symbols.Property, value: Value) -> Value is | |
| 614 | let result = store_property(location, null, symbol, value, true) | |
| 615 | return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi | |
| 616 | si | |
| 617 | ||
| 618 | store_property(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Property, value: Value, is_static: bool) -> Value? is | |
| 619 | if !from? /\ !is_static then | |
| 620 | from = load_self(location) | |
| 621 | fi | |
| 622 | ||
| 623 | if symbol.assign_function == null then | |
| 624 | _logger.error(location, "property {symbol} is not assignable") | |
| 625 | ||
| 626 | assert !symbol.is_assignable else "property {symbol} is assignable but does not have an assign accessor function" | |
| 627 | ||
| 628 | return null | |
| 629 | fi | |
| 630 | ||
| 631 | find_symbol = _null_find_symbol | |
| 632 | ||
| 633 | return TYPE_WRAPPER( | |
| 634 | cast Types.Typed(symbol).type!, | |
| 635 | symbol.assign_function!.call(location, from, Collections.LIST([value]), null, _function_caller) | |
| 636 | ) | |
| 637 | si | |
| 638 | si | |
| 639 | si |