Appearance
| 1 | namespace Semantic.Symbols is | |
| 2 | use IO.Std; | |
| 3 | ||
| 4 | use System.Text.StringBuilder; | |
| 5 | ||
| 6 | use IoC; | |
| 7 | use Logging; | |
| 8 | use Source; | |
| 9 | ||
| 10 | use Types.Type; | |
| 11 | ||
| 12 | use IR.Values.Value; | |
| 13 | use IR.Values.DUMMY; | |
| 14 | ||
| 15 | class Variable: Symbol, Types.SettableTyped abstract is | |
| 16 | type: Type?; | |
| 17 | set_type(value: Type) is type = value; si | |
| 18 | ||
| 19 | short_description: string => "{name}: {if type? then type!.short_description else "?" fi}"; | |
| 20 | ||
| 21 | // Concrete Variable / Field describe overrides delegate | |
| 22 | // straight through to Symbol._describe_typed with the live | |
| 23 | // declared type — the dual `declared → narrowed` rule lives | |
| 24 | // there, shared with Property so a narrowed member access | |
| 25 | // hovers the same shape as a narrowed local. | |
| 26 | ||
| 27 | symbol_kind: SymbolKind => SymbolKind.VARIABLE; | |
| 28 | completion_kind: CompletionKind => CompletionKind.VARIABLE; | |
| 29 | ||
| 30 | is_defined: bool public; | |
| 31 | is_variable: bool => true; | |
| 32 | is_assigned: bool public; | |
| 33 | is_reassigned: bool public; | |
| 34 | is_mutable_marked: bool public; | |
| 35 | is_captured: bool public; | |
| 36 | is_disposed: bool public; | |
| 37 | ||
| 38 | // Set by the `mark-boxed-locals` analysis pass when a | |
| 39 | // local is both captured by a closure and reassigned. | |
| 40 | // Storage becomes `Ghul.BOX[type]`; reads/writes | |
| 41 | // dispatch through the box's `.value` field; the closure | |
| 42 | // body and the enclosing scope share one heap cell. A | |
| 43 | // flag rather than a subclass so symbol identity stays | |
| 44 | // stable across the analysis-pass marking — IDE caches | |
| 45 | // (`SYMBOL_DEFINITION_LOCATIONS`, `SymbolUseListener`) | |
| 46 | // hold direct pointers from declare-symbols time and | |
| 47 | // mustn't be invalidated. See | |
| 48 | // `docs/claude/boxed-captured-mutables.md`. | |
| 49 | is_boxed: bool public; | |
| 50 | ||
| 51 | // When this variable lives on the synthesised state-machine | |
| 52 | // frame of a generator function, this points at the matching | |
| 53 | // `Field` on that class. Load/Store IR for the variable then | |
| 54 | // routes through `ldarg.0; ldfld/stfld <state_machine_field>` | |
| 55 | // instead of `ldarg`/`ldloc`/`starg`/`stloc`. Populated by | |
| 56 | // STATE_MACHINE_FRAME for parameters at declare-time and | |
| 57 | // for body locals by generate_il during the body walk. Null | |
| 58 | // for variables in plain (non-generator) functions. | |
| 59 | state_machine_field: Field? public; | |
| 60 | ||
| 61 | // The actual IL slot type for this Variable. Defaults to | |
| 62 | // `type`; switches to `Ghul.BOX[type]` when `is_boxed`. | |
| 63 | // Queried at IL emission time and by | |
| 64 | // `closure.find_or_add_capture` when declaring the | |
| 65 | // frame field. User-visible queries (HOVER, completion, | |
| 66 | // type-checking) continue to use `type` directly so the | |
| 67 | // box is invisible above IL level. | |
| 68 | storage_type: Type? => | |
| 69 | if is_boxed /\ type? then | |
| 70 | IoC.CONTAINER.instance.innate_symbol_lookup.get_box_type(type!) | |
| 71 | else | |
| 72 | type | |
| 73 | fi; | |
| 74 | ||
| 75 | // While `type` is an INFERRED_VARIABLE_TYPE placeholder, | |
| 76 | // a Variable holds constraints in three shapes — all | |
| 77 | // accumulated by the body-retry walk and consulted by | |
| 78 | // `try_get_inferred_type` when collapsing to a resolved | |
| 79 | // type. | |
| 80 | // | |
| 81 | // `_lub_map` — lower-bound type candidates. Records | |
| 82 | // "this placeholder IS-A T" from sites that produce a | |
| 83 | // concrete value typed T to be held in the placeholder | |
| 84 | // (assignment RHS `v = expr`, lambda call-site actual | |
| 85 | // passed to a placeholder formal). The LUB-map collapses | |
| 86 | // these to a single widest-needed candidate type — the | |
| 87 | // classical least-upper-bound operation. | |
| 88 | // | |
| 89 | // `_upper_bounds` — upper-bound type candidates. Records | |
| 90 | // "this placeholder MUST FIT INTO T" from sites that | |
| 91 | // consume the placeholder via a concretely-typed slot | |
| 92 | // (passing the placeholder as an argument to `g(a: T)`). | |
| 93 | // These are validated against the LUB candidate but | |
| 94 | // never widen it. Distinct from `_lub_map` so that an | |
| 95 | // upper-bound `object` (e.g. from `g(v: object)`) does | |
| 96 | // not dominate the LUB and force v→object when an | |
| 97 | // assignment said v=bool. | |
| 98 | // | |
| 99 | // `_constraints` — operation/structural constraints. | |
| 100 | // Records "this placeholder is consumed by `.foo`" / | |
| 101 | // "this placeholder is called with (int)" etc. from | |
| 102 | // sites that exercise the placeholder without | |
| 103 | // producing a candidate type. The LUB candidate is | |
| 104 | // accepted only if every accumulated constraint | |
| 105 | // discharges against it. | |
| 106 | // | |
| 107 | // All three are lazily constructed; null when nothing | |
| 108 | // has been recorded yet. | |
| 109 | ||
| 110 | _lub_map: Semantic.LEAST_UPPER_BOUND_MAP?; | |
| 111 | ||
| 112 | // True when the type-bound LUB has at least one | |
| 113 | // candidate. Callers gate speculative match propagation (the | |
| 114 | // call-site-synthesized function-type-shape constraint | |
| 115 | // used by mutual-recursion inference) on this to avoid | |
| 116 | // polluting the LUB with a synthesised shape when an | |
| 117 | // explicit assignment already supplied a candidate — | |
| 118 | // the synthesised arg types come from the call site and | |
| 119 | // may not match the assigned shape, leaving the per- | |
| 120 | // position merge unable to fold the two entries. | |
| 121 | has_lub_candidate: bool => _lub_map? /\ _lub_map.types.count > 0; | |
| 122 | ||
| 123 | // Linear list with `matches`-based dedup. A SET would need | |
| 124 | // Type to override `equals`/`get_hash_code` (it doesn't, and | |
| 125 | // `matches` isn't an equivalence relation so couldn't back a | |
| 126 | // SET anyway). In practice this list is tiny (0-2 entries) so | |
| 127 | // a linear contains-check is cheap. | |
| 128 | _upper_bounds: Collections.LIST[Type]?; | |
| 129 | ||
| 130 | // Stored as a SET to deduplicate constraints emitted | |
| 131 | // from multiple equivalent operation sites (e.g. two | |
| 132 | // `.foo` accesses on the same placeholder collapse to a | |
| 133 | // single MEMBER_CONSTRAINT("foo")). Constraint subclasses | |
| 134 | // override `equals` and `get_hash_code` to make that work. | |
| 135 | _constraints: Collections.SET[Semantic.Constraint]?; | |
| 136 | ||
| 137 | init(location: LOCATION, owner: Scope, name: string) is | |
| 138 | super.init(location, owner, name); | |
| 139 | si | |
| 140 | ||
| 141 | // Records a lower-bound type candidate ("placeholder IS-A | |
| 142 | // this type") into `_lub_map`. Returns true if the bound | |
| 143 | // was actually added (i.e. the symbol is unresolved AND | |
| 144 | // the bound carries information). Callers use this to | |
| 145 | // signal progress to the retry loop via | |
| 146 | // _logger.mark_consumed_any so the body retry kicks in | |
| 147 | // even for cases where the body walk itself didn't fire | |
| 148 | // mark_consumed_any (e.g. member access on placeholder | |
| 149 | // receiver poisons silently to ERROR without consuming | |
| 150 | // the receiver). | |
| 151 | add_lower_bound(bound: Type?) -> bool is | |
| 152 | // Skip only once the symbol's type is fully settled | |
| 153 | // (no placeholders, no ERROR). Provisional composites | |
| 154 | // — e.g. a `Func[List[int], INFERRED_RETURN_TYPE]` | |
| 155 | // recorded on an early iter before the lambda's return | |
| 156 | // resolved — still need refining; without further LUB | |
| 157 | // entries the per-position merge has nothing to fold | |
| 158 | // the resolved arity into. The earlier `!is_sentinel` | |
| 159 | // gate stopped accepting refinement constraints in | |
| 160 | // exactly the case where they were most needed (and | |
| 161 | // produced the survey §4.24 / fuzz finding 04 IL | |
| 162 | // placeholder leak). | |
| 163 | if type? /\ type.is_settled then | |
| 164 | return false; | |
| 165 | fi | |
| 166 | ||
| 167 | if !bound? \/ bound.is_sentinel then | |
| 168 | return false; | |
| 169 | fi | |
| 170 | ||
| 171 | if !_lub_map? then | |
| 172 | _lub_map = Semantic.LEAST_UPPER_BOUND_MAP(); | |
| 173 | fi | |
| 174 | ||
| 175 | _lub_map.add(bound); | |
| 176 | return true; | |
| 177 | si | |
| 178 | ||
| 179 | // Re-entrancy latch for try_get_inferred_type. Resolving a | |
| 180 | // candidate composite recurses into the origins of any | |
| 181 | // placeholders it carries; two unresolved variables whose | |
| 182 | // candidates reference each other would recurse forever. | |
| 183 | // Answering null on re-entry treats the cycle as | |
| 184 | // still-unresolved, which is what it is. | |
| 185 | _resolving_inferred_type: bool; | |
| 186 | ||
| 187 | try_get_inferred_type() -> Type? is | |
| 188 | if _resolving_inferred_type then | |
| 189 | return null; | |
| 190 | fi | |
| 191 | ||
| 192 | _resolving_inferred_type = true; | |
| 193 | let result = _try_get_inferred_type(); | |
| 194 | _resolving_inferred_type = false; | |
| 195 | ||
| 196 | return result; | |
| 197 | si | |
| 198 | ||
| 199 | _try_get_inferred_type() -> Type? is | |
| 200 | let candidate: Type? mut = null; | |
| 201 | ||
| 202 | if _lub_map? then | |
| 203 | candidate = _lub_map.get_result(); | |
| 204 | fi | |
| 205 | ||
| 206 | // No lower-bound candidate — fall back to upper bounds. | |
| 207 | // Single upper bound: return it (preserves the bare | |
| 208 | // "only signal is g(v: object)" case as v->object). | |
| 209 | // Multiple upper bounds: keep deferred — no general | |
| 210 | // narrowest-of-uppers heuristic yet. Returning null | |
| 211 | // here leaves the placeholder unresolved and lets the | |
| 212 | // existing "cannot infer" diagnostic surface if no | |
| 213 | // further info appears. | |
| 214 | if !candidate? then | |
| 215 | if _upper_bounds? /\ _upper_bounds.count == 1 then | |
| 216 | candidate = _upper_bounds[0]; | |
| 217 | else | |
| 218 | return null; | |
| 219 | fi | |
| 220 | fi | |
| 221 | ||
| 222 | // The candidate may be a composite that captured other | |
| 223 | // variables' placeholders before their origins settled | |
| 224 | // (a lambda type recorded at a call site, a tuple LUB | |
| 225 | // entry). Nothing rewrites the stored composite when | |
| 226 | // those origins settle, so collapse the settled slots | |
| 227 | // here - the chokepoint every consumer reads through - | |
| 228 | // rather than letting the stale placeholder propagate | |
| 229 | // into committed types and eventually IL. | |
| 230 | candidate = SETTLED_PLACEHOLDER_RESOLVER.instance.resolve(candidate!); | |
| 231 | ||
| 232 | // Upper-bound validation: the chosen candidate must | |
| 233 | // be assignable to every recorded upper bound. If | |
| 234 | // not, the placeholder is being asked to be both | |
| 235 | // wider (assignment / lower bound) and narrower | |
| 236 | // (passed to a too-narrow slot). Return null so the | |
| 237 | // placeholder stays unresolved; downstream | |
| 238 | // assignability errors surface at the offending sites. | |
| 239 | if _upper_bounds? then | |
| 240 | for upper in _upper_bounds do | |
| 241 | if !upper.is_assignable_from(candidate) then | |
| 242 | return null; | |
| 243 | fi | |
| 244 | od | |
| 245 | fi | |
| 246 | ||
| 247 | // Operation-side filter: the candidate is only | |
| 248 | // valid if every accumulated constraint discharges | |
| 249 | // against it. A rejection here means the LUB picked | |
| 250 | // a type that doesn't expose an operation the user's | |
| 251 | // code performs on the placeholder — return null so | |
| 252 | // the placeholder stays unresolved and the retry | |
| 253 | // loop has another iteration to accumulate more | |
| 254 | // information. If no candidate ever discharges, the | |
| 255 | // slot stays unresolved and the non-convergence sweep | |
| 256 | // reports it as cannot infer type here rather than | |
| 257 | // silently producing bad IL. | |
| 258 | if _constraints? then | |
| 259 | for c in _constraints do | |
| 260 | if !c.try_discharge(candidate) then | |
| 261 | return null; | |
| 262 | fi | |
| 263 | od | |
| 264 | fi | |
| 265 | ||
| 266 | return candidate; | |
| 267 | si | |
| 268 | ||
| 269 | // Record an upper bound on this placeholder's eventual | |
| 270 | // type. The chosen LUB candidate (from lower bounds) | |
| 271 | // must be assignable to every upper bound to be accepted | |
| 272 | // by `try_get_inferred_type`. | |
| 273 | // | |
| 274 | // Returns true iff the bound carries new information — | |
| 275 | // wasn't already recorded by `matches`. The | |
| 276 | // retry loop uses this signal via `mark_consumed_any`. | |
| 277 | // | |
| 278 | // Skipped when the placeholder already has a concrete | |
| 279 | // resolved type (constraints accumulate only while | |
| 280 | // unresolved) and when the bound is itself a sentinel | |
| 281 | // or type variable (no information). | |
| 282 | add_upper_bound(bound: Type?) -> bool is | |
| 283 | if type? /\ !type.is_sentinel then | |
| 284 | return false; | |
| 285 | fi | |
| 286 | ||
| 287 | if !bound? \/ bound.is_sentinel \/ bound.is_type_variable then | |
| 288 | return false; | |
| 289 | fi | |
| 290 | ||
| 291 | let upper_bounds mut = _upper_bounds; | |
| 292 | ||
| 293 | if !upper_bounds? then | |
| 294 | upper_bounds = Collections.LIST[Type](); | |
| 295 | _upper_bounds = upper_bounds; | |
| 296 | else | |
| 297 | for existing in upper_bounds do | |
| 298 | if existing.matches(bound) then | |
| 299 | return false; | |
| 300 | fi | |
| 301 | od | |
| 302 | fi | |
| 303 | ||
| 304 | upper_bounds.add(bound); | |
| 305 | return true; | |
| 306 | si | |
| 307 | ||
| 308 | // Record an operation/structural constraint against this | |
| 309 | // placeholder origin. Returns true iff the constraint | |
| 310 | // carries information that wasn't already recorded — i.e. | |
| 311 | // wasn't already in the set keyed on its `equals` / | |
| 312 | // `get_hash_code`. The retry loop uses this signal to | |
| 313 | // drive `_logger.mark_consumed_any`. | |
| 314 | // | |
| 315 | // Like `add_lower_bound`, skipped only when the symbol's | |
| 316 | // type is fully settled — provisional composites are | |
| 317 | // still legitimately refining and should keep accumulating | |
| 318 | // operation evidence too. | |
| 319 | add_constraint(constraint: Semantic.Constraint?) -> bool is | |
| 320 | if type? /\ type.is_settled then | |
| 321 | return false; | |
| 322 | fi | |
| 323 | ||
| 324 | if !constraint? then | |
| 325 | return false; | |
| 326 | fi | |
| 327 | ||
| 328 | if !_constraints? then | |
| 329 | _constraints = Collections.SET[Semantic.Constraint](); | |
| 330 | elif _constraints.contains(constraint) then | |
| 331 | return false; | |
| 332 | fi | |
| 333 | ||
| 334 | _constraints.add(constraint); | |
| 335 | return true; | |
| 336 | si | |
| 337 | ||
| 338 | specialize(type_map: Collections.Map[string,Type], owner: GENERIC) -> Symbol is | |
| 339 | let result = cast Variable?(memberwise_clone())!; | |
| 340 | ||
| 341 | result.specialized_from = self; | |
| 342 | ||
| 343 | if type? then | |
| 344 | let specialized_type = type.specialize(type_map); | |
| 345 | ||
| 346 | result.type = specialized_type; | |
| 347 | fi | |
| 348 | ||
| 349 | result.owner = owner; | |
| 350 | ||
| 351 | return result; | |
| 352 | si | |
| 353 | ||
| 354 | gen_reference(buffer: StringBuilder) is | |
| 355 | gen_name(buffer); | |
| 356 | si | |
| 357 | ||
| 358 | gen_definition_header(buffer: StringBuilder) is | |
| 359 | gen_directive(buffer); | |
| 360 | ||
| 361 | gen_access(buffer); | |
| 362 | ||
| 363 | gen_flags(buffer); | |
| 364 | ||
| 365 | gen_open_paren(buffer); | |
| 366 | ||
| 367 | storage_type!.gen_type(buffer); | |
| 368 | ||
| 369 | gen_owner_name(buffer); | |
| 370 | ||
| 371 | gen_name(buffer); | |
| 372 | ||
| 373 | gen_close_paren(buffer); | |
| 374 | si | |
| 375 | ||
| 376 | gen_directive(buffer: StringBuilder) is si | |
| 377 | ||
| 378 | gen_access(buffer: StringBuilder) is | |
| 379 | si | |
| 380 | ||
| 381 | gen_flags(buffer: StringBuilder) is | |
| 382 | si | |
| 383 | ||
| 384 | gen_owner_name(buffer: StringBuilder) is | |
| 385 | si | |
| 386 | ||
| 387 | gen_open_paren(buffer: StringBuilder) is | |
| 388 | si | |
| 389 | ||
| 390 | gen_close_paren(buffer: StringBuilder) is | |
| 391 | si | |
| 392 | si | |
| 393 | ||
| 394 | // Minimal Variable subclass used as the origin symbol for an | |
| 395 | // unbound owner type-arg placeholder at a constructor expression. | |
| 396 | // Carries the inherited `_lub_map` accumulator so overload back- | |
| 397 | // feed can push concrete types from downstream usage. Never | |
| 398 | // appears in IL — it's an inference-time-only sentinel that | |
| 399 | // identifies "the T slot of this specific construction". | |
| 400 | class INFERRED_TYPE_ARG_ORIGIN: Variable is | |
| 401 | init(location: LOCATION, owner: Scope, name: string) is | |
| 402 | super.init(location, owner, name); | |
| 403 | si | |
| 404 | ||
| 405 | gen_directive(buffer: System.Text.StringBuilder) is | |
| 406 | si | |
| 407 | si | |
| 408 | ||
| 409 | // FIXME: pull up common code into a local + argument superclass: | |
| 410 | class LOCAL_VARIABLE: Variable, Types.SettableTyped is | |
| 411 | is_local: bool => true; | |
| 412 | ||
| 413 | describe(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 414 | _describe_typed(context, PARTS.literal(name), type); | |
| 415 | ||
| 416 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => | |
| 417 | _local_kind(); | |
| 418 | ||
| 419 | _local_kind() -> string => | |
| 420 | if is_boxed then "captured variable" | |
| 421 | elif is_captured then "captured value" | |
| 422 | elif is_disposed then "scoped disposal value" | |
| 423 | elif is_reassigned then "local variable" | |
| 424 | else "local value" | |
| 425 | fi; | |
| 426 | ||
| 427 | init(location: LOCATION, owner: Scope, name: string) is | |
| 428 | super.init(location, owner, name); | |
| 429 | ||
| 430 | il_name_override = IoC.CONTAINER.instance.local_id_generator.get_unique_il_name_for(name); | |
| 431 | si | |
| 432 | ||
| 433 | define() is | |
| 434 | is_defined = true; | |
| 435 | si | |
| 436 | ||
| 437 | check_is_defined(location: LOCATION) is | |
| 438 | if !is_defined then | |
| 439 | IoC.CONTAINER.instance.logger.error(location, "variable is not defined here"); | |
| 440 | fi | |
| 441 | si | |
| 442 | ||
| 443 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value is | |
| 444 | assert !from?; | |
| 445 | ||
| 446 | check_is_defined(location); | |
| 447 | ||
| 448 | return loader.load_local_variable(location, self); | |
| 449 | si | |
| 450 | ||
| 451 | load_outer(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value is | |
| 452 | assert !from?; | |
| 453 | ||
| 454 | // A closure capturing this variable can only see a meaningful | |
| 455 | // value if the let-binding has already completed by the time | |
| 456 | // the closure is created. If is_defined is false the closure | |
| 457 | // is being constructed inside the variable's own initializer | |
| 458 | // (or before it), so the slot it captures is null. Without | |
| 459 | // this check, the silent ERROR-typed reference escapes all | |
| 460 | // the way to IL emission and crashes Type.gen_type. | |
| 461 | check_is_defined(location); | |
| 462 | ||
| 463 | return loader.load_outer_local_variable(location, self); | |
| 464 | si | |
| 465 | ||
| 466 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value is | |
| 467 | assert !from?; | |
| 468 | ||
| 469 | check_is_defined(location); | |
| 470 | ||
| 471 | is_assigned = true; | |
| 472 | ||
| 473 | if !is_initialize then | |
| 474 | is_reassigned = true; | |
| 475 | fi | |
| 476 | ||
| 477 | return loader.store_local_variable(location, self, value, is_initialize); | |
| 478 | si | |
| 479 | ||
| 480 | gen_directive(buffer: StringBuilder) is | |
| 481 | buffer.append(".locals "); | |
| 482 | si | |
| 483 | ||
| 484 | gen_access(buffer: StringBuilder) is | |
| 485 | si | |
| 486 | ||
| 487 | gen_flags(buffer: StringBuilder) is | |
| 488 | buffer.append("init "); | |
| 489 | si | |
| 490 | ||
| 491 | gen_open_paren(buffer: StringBuilder) is | |
| 492 | buffer.append("("); | |
| 493 | si | |
| 494 | ||
| 495 | gen_close_paren(buffer: StringBuilder) is | |
| 496 | buffer.append(")"); | |
| 497 | si | |
| 498 | si | |
| 499 | ||
| 500 | // FIXME: pull up common code into a local + argument superclass: | |
| 501 | class LOCAL_ARGUMENT: Variable, Types.SettableTyped is | |
| 502 | is_argument: bool => true; | |
| 503 | is_local: bool => true; | |
| 504 | ||
| 505 | describe(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 506 | _describe_typed(context, PARTS.literal(name), type); | |
| 507 | ||
| 508 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => | |
| 509 | _argument_kind(); | |
| 510 | ||
| 511 | _argument_kind() -> string => | |
| 512 | if is_captured then "captured value" | |
| 513 | elif is_reassigned then "local variable" | |
| 514 | else "local argument" | |
| 515 | fi; | |
| 516 | ||
| 517 | init(location: LOCATION, owner: Scope, name: string) is | |
| 518 | super.init(location, owner, name); | |
| 519 | si | |
| 520 | ||
| 521 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value is | |
| 522 | assert !from?; | |
| 523 | return loader.load_local_argument(location, self); | |
| 524 | si | |
| 525 | ||
| 526 | load_outer(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value is | |
| 527 | assert !from?; | |
| 528 | return loader.load_outer_local_argument(location, self); | |
| 529 | si | |
| 530 | ||
| 531 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value is | |
| 532 | assert !from?; | |
| 533 | ||
| 534 | is_assigned = true; | |
| 535 | ||
| 536 | if !is_initialize then | |
| 537 | is_reassigned = true; | |
| 538 | fi | |
| 539 | ||
| 540 | return loader.store_local_argument(location, self, value, is_initialize); | |
| 541 | si | |
| 542 | ||
| 543 | gen_definition_header(buffer: StringBuilder) is | |
| 544 | si | |
| 545 | si | |
| 546 | ||
| 547 | class Field: Variable, Types.SettableTyped abstract is | |
| 548 | unspecialized_type: Type? public; | |
| 549 | ||
| 550 | symbol_kind: SymbolKind => SymbolKind.FIELD; | |
| 551 | completion_kind: CompletionKind => CompletionKind.FIELD; | |
| 552 | ||
| 553 | is_private: bool; | |
| 554 | is_field: bool => true; | |
| 555 | is_public_readable: bool => !is_private; | |
| 556 | is_workspace_visible: bool => !is_private; | |
| 557 | ||
| 558 | is_accessible_to(accessor: Classy?) -> bool is | |
| 559 | if !is_private then | |
| 560 | return true; | |
| 561 | fi | |
| 562 | ||
| 563 | let policy = IoC.CONTAINER.instance.build_flags.underscore_access; | |
| 564 | let o = cast Classy?(owner); | |
| 565 | ||
| 566 | if policy == Compiler.UnderscoreAccess.PRIVATE then | |
| 567 | return accessor? /\ o? /\ o == accessor; | |
| 568 | elif policy == Compiler.UnderscoreAccess.PROTECTED then | |
| 569 | return accessor? /\ o? /\ o.type? /\ accessor.type? /\ o.type.is_assignable_from(accessor.type); | |
| 570 | fi | |
| 571 | ||
| 572 | // LEGACY: the pre-existing is_public_readable rule remains the gate. | |
| 573 | return true; | |
| 574 | si | |
| 575 | ||
| 576 | access_prefix: string => | |
| 577 | if !is_private then | |
| 578 | "" | |
| 579 | elif IoC.CONTAINER.instance.build_flags.underscore_access == Compiler.UnderscoreAccess.PRIVATE then | |
| 580 | "private " | |
| 581 | elif IoC.CONTAINER.instance.build_flags.underscore_access == Compiler.UnderscoreAccess.PROTECTED then | |
| 582 | "protected " | |
| 583 | else | |
| 584 | "" | |
| 585 | fi; | |
| 586 | ||
| 587 | // Shared body for the concrete Field kinds. Delegates to | |
| 588 | // `_describe_typed` so the narrowed / declared dual display | |
| 589 | // rule stays in one place — a hover on a field whose observed | |
| 590 | // type differs from its declared shape shows both. | |
| 591 | _describe_field(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 592 | _describe_typed(context, PARTS.name(self), type); | |
| 593 | ||
| 594 | init(location: LOCATION, owner: Scope, name: string) is | |
| 595 | super.init(location, owner, name); | |
| 596 | ||
| 597 | self.is_private = name.starts_with('_'); | |
| 598 | si | |
| 599 | ||
| 600 | specialize(type_map: Collections.Map[string,Type], owner: GENERIC) -> Symbol is | |
| 601 | let result = cast Field?(super.specialize(type_map, owner))!; | |
| 602 | ||
| 603 | result.unspecialized_type = type!; | |
| 604 | ||
| 605 | return result; | |
| 606 | si | |
| 607 | ||
| 608 | gen_reference(buffer: StringBuilder) is | |
| 609 | let t mut = unspecialized_type; | |
| 610 | ||
| 611 | if !t? then | |
| 612 | t = type!; | |
| 613 | fi | |
| 614 | ||
| 615 | t.gen_type(buffer); | |
| 616 | owner!.gen_reference(buffer); | |
| 617 | gen_dot(buffer); | |
| 618 | gen_name(buffer); | |
| 619 | si | |
| 620 | ||
| 621 | gen_access(buffer: StringBuilder) is | |
| 622 | if is_private then | |
| 623 | buffer.append("assembly "); | |
| 624 | else | |
| 625 | buffer.append("public "); | |
| 626 | fi | |
| 627 | si | |
| 628 | ||
| 629 | gen_directive(buffer: StringBuilder) is | |
| 630 | buffer.append(".field "); | |
| 631 | si | |
| 632 | ||
| 633 | gen_dot(buffer: StringBuilder) is | |
| 634 | buffer.append("::"); | |
| 635 | si | |
| 636 | si | |
| 637 | ||
| 638 | class GLOBAL_VARIABLE: Field, Types.SettableTyped is | |
| 639 | // Set by the .NET importer when the symbol is read back from a referenced | |
| 640 | // assembly's $globals class so field references get an [asm] prefix. | |
| 641 | il_assembly_name: string? public; | |
| 642 | ||
| 643 | // Globals live on the synthetic $globals class; declaring-class-private | |
| 644 | // is meaningless for them. An underscore global variable is | |
| 645 | // assembly-internal (emitted assembly, hidden from other assemblies) | |
| 646 | // and freely reachable within the assembly. | |
| 647 | is_accessible_to(accessor: Classy?) -> bool => true; | |
| 648 | access_prefix: string => ""; | |
| 649 | ||
| 650 | init(location: LOCATION, owner: Scope, name: string) is | |
| 651 | super.init(location, owner, name); | |
| 652 | si | |
| 653 | ||
| 654 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value is | |
| 655 | assert !from? else "load global variable from instance context"; | |
| 656 | return loader.load_global_variable(self); | |
| 657 | si | |
| 658 | ||
| 659 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value is | |
| 660 | assert !from? else "load global variable from instance context"; | |
| 661 | return loader.store_global_variable(self, value); | |
| 662 | si | |
| 663 | ||
| 664 | gen_flags(buffer: StringBuilder) is | |
| 665 | buffer.append("static "); | |
| 666 | si | |
| 667 | ||
| 668 | // Field definition lives inside `.class 'NS'.'$globals' { ... }` block. | |
| 669 | gen_owner_name(buffer: StringBuilder) is | |
| 670 | si | |
| 671 | ||
| 672 | gen_reference(buffer: StringBuilder) is | |
| 673 | let t mut = unspecialized_type; | |
| 674 | ||
| 675 | if !t? then | |
| 676 | t = type!; | |
| 677 | fi | |
| 678 | ||
| 679 | t.gen_type(buffer); | |
| 680 | ||
| 681 | cast Symbols.NAMESPACE?(owner)!.gen_globals_class_reference(buffer, il_assembly_name); | |
| 682 | ||
| 683 | buffer.append("::"); | |
| 684 | ||
| 685 | gen_name(buffer); | |
| 686 | si | |
| 687 | si | |
| 688 | ||
| 689 | class INSTANCE_FIELD: Field is | |
| 690 | describe(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 691 | _describe_field(context); | |
| 692 | ||
| 693 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => "{access_prefix}field"; | |
| 694 | ||
| 695 | is_instance: bool => true; | |
| 696 | ||
| 697 | init(location: LOCATION, owner: Scope, name: string) is | |
| 698 | super.init(location, owner, name); | |
| 699 | si | |
| 700 | ||
| 701 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value => | |
| 702 | loader.load_instance_variable(location, from, self); | |
| 703 | ||
| 704 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value => | |
| 705 | loader.store_instance_variable(location, from, self, value); | |
| 706 | si | |
| 707 | ||
| 708 | class VARIANT_FIELD: Field is | |
| 709 | describe(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 710 | _describe_field(context); | |
| 711 | ||
| 712 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => "variant field"; | |
| 713 | ||
| 714 | is_instance: bool => true; | |
| 715 | ||
| 716 | // it's OK for variant fields to hide symbols in the base union | |
| 717 | can_hide_inherited: bool => true; | |
| 718 | ||
| 719 | init(location: LOCATION, owner: Scope, name: string) is | |
| 720 | super.init(location, owner, name); | |
| 721 | si | |
| 722 | ||
| 723 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value => | |
| 724 | loader.load_instance_variable(location, from, self); | |
| 725 | ||
| 726 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value => | |
| 727 | loader.store_instance_variable(location, from, self, value); | |
| 728 | si | |
| 729 | ||
| 730 | class STRUCT_FIELD: Field is | |
| 731 | describe(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 732 | _describe_field(context); | |
| 733 | ||
| 734 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => "{access_prefix}field"; | |
| 735 | ||
| 736 | is_instance: bool => true; | |
| 737 | ||
| 738 | init(location: LOCATION, owner: Scope, name: string) is | |
| 739 | super.init(location, owner, name); | |
| 740 | si | |
| 741 | ||
| 742 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value => | |
| 743 | loader.load_struct_variable(location, from, self); | |
| 744 | ||
| 745 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value => | |
| 746 | loader.store_struct_variable(location, from, self, value); | |
| 747 | si | |
| 748 | ||
| 749 | class STATIC_FIELD: Field is | |
| 750 | describe(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 751 | _describe_field(context); | |
| 752 | ||
| 753 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => "{access_prefix}class field"; | |
| 754 | ||
| 755 | init(location: LOCATION, owner: Scope, name: string) is | |
| 756 | super.init(location, owner, name); | |
| 757 | si | |
| 758 | ||
| 759 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value => | |
| 760 | loader.load_static_field(self); | |
| 761 | ||
| 762 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value => | |
| 763 | loader.store_static_field(self, value); | |
| 764 | ||
| 765 | gen_flags(buffer: StringBuilder) is | |
| 766 | buffer.append("static "); | |
| 767 | si | |
| 768 | si | |
| 769 | si | |
| 770 |