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 | // Set by `mark-boxed-locals` when the local is assigned | |
| 52 | // from inside a nested function literal. Such a local | |
| 53 | // shares one heap cell with the closure body, so invoking | |
| 54 | // the closure rewrites it behind the enclosing scope's | |
| 55 | // back, and flow narrowing forms no facts on it. A local | |
| 56 | // the enclosing scope alone assigns is not marked: those | |
| 57 | // writes are on the path the narrowing walk already sees. | |
| 58 | // | |
| 59 | // Derived on every build, unlike `is_boxed`, which asks the | |
| 60 | // same question of the *storage strategy* and is only worth | |
| 61 | // answering once the IR is lowered to IL. | |
| 62 | is_closure_assigned: bool public | |
| 63 | ||
| 64 | // When this variable lives on the synthesised state-machine | |
| 65 | // frame of a generator function, this points at the matching | |
| 66 | // `Field` on that class. Load/Store IR for the variable then | |
| 67 | // routes through `ldarg.0; ldfld/stfld <state_machine_field>` | |
| 68 | // instead of `ldarg`/`ldloc`/`starg`/`stloc`. Populated by | |
| 69 | // STATE_MACHINE_FRAME for parameters at declare-time and | |
| 70 | // for body locals by generate_il during the body walk. Null | |
| 71 | // for variables in plain (non-generator) functions. | |
| 72 | state_machine_field: Field? public | |
| 73 | ||
| 74 | // The actual IL slot type for this Variable. Defaults to | |
| 75 | // `type`; switches to `Ghul.BOX[type]` when `is_boxed`. | |
| 76 | // Queried at IL emission time and by | |
| 77 | // `closure.find_or_add_capture` when declaring the | |
| 78 | // frame field. User-visible queries (HOVER, completion, | |
| 79 | // type-checking) continue to use `type` directly so the | |
| 80 | // box is invisible above IL level. | |
| 81 | storage_type: Type? => | |
| 82 | if is_boxed /\ type? then | |
| 83 | IoC.CONTAINER.instance.innate_symbol_lookup.get_box_type(type!) | |
| 84 | else | |
| 85 | type | |
| 86 | fi | |
| 87 | ||
| 88 | // While `type` is an INFERRED_VARIABLE_TYPE placeholder, | |
| 89 | // a Variable holds constraints in three shapes — all | |
| 90 | // accumulated by the body-retry walk and consulted by | |
| 91 | // `try_get_inferred_type` when collapsing to a resolved | |
| 92 | // type. | |
| 93 | // | |
| 94 | // `_lub_map` — lower-bound type candidates. Records | |
| 95 | // "this placeholder IS-A T" from sites that produce a | |
| 96 | // concrete value typed T to be held in the placeholder | |
| 97 | // (assignment RHS `v = expr`, lambda call-site actual | |
| 98 | // passed to a placeholder formal). The LUB-map collapses | |
| 99 | // these to a single widest-needed candidate type — the | |
| 100 | // classical least-upper-bound operation. | |
| 101 | // | |
| 102 | // `_upper_bounds` — upper-bound type candidates. Records | |
| 103 | // "this placeholder MUST FIT INTO T" from sites that | |
| 104 | // consume the placeholder via a concretely-typed slot | |
| 105 | // (passing the placeholder as an argument to `g(a: T)`). | |
| 106 | // These are validated against the LUB candidate but | |
| 107 | // never widen it. Distinct from `_lub_map` so that an | |
| 108 | // upper-bound `object` (e.g. from `g(v: object)`) does | |
| 109 | // not dominate the LUB and force v→object when an | |
| 110 | // assignment said v=bool. | |
| 111 | // | |
| 112 | // `_constraints` — operation/structural constraints. | |
| 113 | // Records "this placeholder is consumed by `.foo`" / | |
| 114 | // "this placeholder is called with (int)" etc. from | |
| 115 | // sites that exercise the placeholder without | |
| 116 | // producing a candidate type. The LUB candidate is | |
| 117 | // accepted only if every accumulated constraint | |
| 118 | // discharges against it. | |
| 119 | // | |
| 120 | // All three are lazily constructed; null when nothing | |
| 121 | // has been recorded yet. | |
| 122 | ||
| 123 | _lub_map: Semantic.LEAST_UPPER_BOUND_MAP? | |
| 124 | ||
| 125 | // A `null` lower bound names no type of its own: it says only | |
| 126 | // that the type the other bounds settle on must hold absence. | |
| 127 | _seen_null: bool | |
| 128 | ||
| 129 | has_seen_null: bool => _seen_null | |
| 130 | ||
| 131 | // Set for a `mut` local with an initializer: its type is the join | |
| 132 | // of the initializer and every value assigned to it, so assignments | |
| 133 | // keep recording lower bounds after the type first settles. | |
| 134 | joins_assignments: bool public | |
| 135 | ||
| 136 | lower_bounds: Collections.List[Type] => | |
| 137 | if _lub_map? then _lub_map.types else Collections.LIST[Type]() fi | |
| 138 | ||
| 139 | // True when the type-bound LUB has at least one | |
| 140 | // candidate. Callers gate speculative match propagation (the | |
| 141 | // call-site-synthesized function-type-shape constraint | |
| 142 | // used by mutual-recursion inference) on this to avoid | |
| 143 | // polluting the LUB with a synthesised shape when an | |
| 144 | // explicit assignment already supplied a candidate — | |
| 145 | // the synthesised arg types come from the call site and | |
| 146 | // may not match the assigned shape, leaving the per- | |
| 147 | // position merge unable to fold the two entries. | |
| 148 | has_lub_candidate: bool => _lub_map? /\ _lub_map.types.count > 0 | |
| 149 | ||
| 150 | // Linear list with `matches`-based dedup. A SET would need | |
| 151 | // Type to override `equals`/`get_hash_code` (it doesn't, and | |
| 152 | // `matches` isn't an equivalence relation so couldn't back a | |
| 153 | // SET anyway). In practice this list is tiny (0-2 entries) so | |
| 154 | // a linear contains-check is cheap. | |
| 155 | _upper_bounds: Collections.LIST[Type]? | |
| 156 | ||
| 157 | // Stored as a SET to deduplicate constraints emitted | |
| 158 | // from multiple equivalent operation sites (e.g. two | |
| 159 | // `.foo` accesses on the same placeholder collapse to a | |
| 160 | // single MEMBER_CONSTRAINT("foo")). Constraint subclasses | |
| 161 | // override `equals` and `get_hash_code` to make that work. | |
| 162 | _constraints: Collections.SET[Semantic.Constraint]? | |
| 163 | ||
| 164 | init(location: LOCATION, owner: Scope, name: string) is | |
| 165 | super.init(location, owner, name) | |
| 166 | si | |
| 167 | ||
| 168 | // Records a lower-bound type candidate ("placeholder IS-A | |
| 169 | // this type") into `_lub_map`. Returns true if the bound | |
| 170 | // was actually added (i.e. the symbol is unresolved AND | |
| 171 | // the bound carries information). Callers use this to | |
| 172 | // signal progress to the retry loop via | |
| 173 | // _logger.mark_consumed_any so the body retry kicks in | |
| 174 | // even for cases where the body walk itself didn't fire | |
| 175 | // mark_consumed_any (e.g. member access on placeholder | |
| 176 | // receiver poisons silently to ERROR without consuming | |
| 177 | // the receiver). | |
| 178 | add_lower_bound(bound: Type?) -> bool is | |
| 179 | // Skip only once the symbol's type is fully settled | |
| 180 | // (no placeholders, no ERROR). Provisional composites | |
| 181 | // — e.g. a `Func[List[int], INFERRED_RETURN_TYPE]` | |
| 182 | // recorded on an early iter before the lambda's return | |
| 183 | // resolved — still need refining; without further LUB | |
| 184 | // entries the per-position merge has nothing to fold | |
| 185 | // the resolved arity into. The earlier `!is_sentinel` | |
| 186 | // gate stopped accepting refinement constraints in | |
| 187 | // exactly the case where they were most needed (and | |
| 188 | // produced the survey §4.24 / fuzz finding 04 IL | |
| 189 | // placeholder leak). | |
| 190 | if type? /\ type.is_settled /\ !joins_assignments then | |
| 191 | return false | |
| 192 | fi | |
| 193 | ||
| 194 | if !bound? \/ bound.is_sentinel then | |
| 195 | return false | |
| 196 | fi | |
| 197 | ||
| 198 | // A composite with ERROR inside says nothing about the element | |
| 199 | // that failed, and a lower bound is never withdrawn, so it would | |
| 200 | // outlive the walk that produced it and spoil the join. | |
| 201 | if Types.ERROR_ELEMENT.within(bound) then | |
| 202 | return false | |
| 203 | fi | |
| 204 | ||
| 205 | // A local that joins its assignments sees the same bounds on | |
| 206 | // every walk; only one it has not seen is progress. | |
| 207 | if joins_assignments /\ _lub_map? then | |
| 208 | for existing in _lub_map.types do | |
| 209 | if existing.is_equivalent_to(bound) then | |
| 210 | return false | |
| 211 | fi | |
| 212 | od | |
| 213 | fi | |
| 214 | ||
| 215 | if bound.is_null then | |
| 216 | if bound.is_error \/ _seen_null then | |
| 217 | return false | |
| 218 | fi | |
| 219 | ||
| 220 | _seen_null = true | |
| 221 | return true | |
| 222 | fi | |
| 223 | ||
| 224 | if !_lub_map? then | |
| 225 | _lub_map = Semantic.LEAST_UPPER_BOUND_MAP() | |
| 226 | fi | |
| 227 | ||
| 228 | _lub_map.add(bound) | |
| 229 | return true | |
| 230 | si | |
| 231 | ||
| 232 | // Re-entrancy latch for try_get_inferred_type. Resolving a | |
| 233 | // candidate composite recurses into the origins of any | |
| 234 | // placeholders it carries; two unresolved variables whose | |
| 235 | // candidates reference each other would recurse forever. | |
| 236 | // Answering null on re-entry treats the cycle as | |
| 237 | // still-unresolved, which is what it is. | |
| 238 | _resolving_inferred_type: bool | |
| 239 | ||
| 240 | try_get_inferred_type() -> Type? is | |
| 241 | if _resolving_inferred_type then | |
| 242 | return null | |
| 243 | fi | |
| 244 | ||
| 245 | _resolving_inferred_type = true | |
| 246 | let result = _try_get_inferred_type() | |
| 247 | _resolving_inferred_type = false | |
| 248 | ||
| 249 | return result | |
| 250 | si | |
| 251 | ||
| 252 | _try_get_inferred_type() -> Type? is | |
| 253 | let candidate: Type? mut = null | |
| 254 | ||
| 255 | if _lub_map? then | |
| 256 | candidate = _lub_map.get_result() | |
| 257 | fi | |
| 258 | ||
| 259 | // No lower-bound candidate — fall back to upper bounds. | |
| 260 | // Single upper bound: return it (preserves the bare | |
| 261 | // "only signal is g(v: object)" case as v->object). | |
| 262 | // Multiple upper bounds: keep deferred — no general | |
| 263 | // narrowest-of-uppers heuristic yet. Returning null | |
| 264 | // here leaves the placeholder unresolved and lets the | |
| 265 | // existing "cannot infer" diagnostic surface if no | |
| 266 | // further info appears. | |
| 267 | if !candidate? then | |
| 268 | // A bound over another function's type parameter names | |
| 269 | // no type the placeholder could take, so it is left to | |
| 270 | // the lower bounds a later walk records. A type parameter | |
| 271 | // of the function this variable is declared in is a | |
| 272 | // definite type here. | |
| 273 | let uppers = _upper_bounds | |
| 274 | ||
| 275 | if uppers? /\ uppers.count == 1 /\ !uppers[0].has_function_generic_argument_foreign_to(owner) then | |
| 276 | candidate = uppers[0] | |
| 277 | else | |
| 278 | return null | |
| 279 | fi | |
| 280 | fi | |
| 281 | ||
| 282 | // The candidate may be a composite that captured other | |
| 283 | // variables' placeholders before their origins settled | |
| 284 | // (a lambda type recorded at a call site, a tuple LUB | |
| 285 | // entry). Nothing rewrites the stored composite when | |
| 286 | // those origins settle, so collapse the settled slots | |
| 287 | // here - the chokepoint every consumer reads through - | |
| 288 | // rather than letting the stale placeholder propagate | |
| 289 | // into committed types and eventually IL. | |
| 290 | candidate = SETTLED_PLACEHOLDER_RESOLVER.instance.resolve(candidate) | |
| 291 | ||
| 292 | if _seen_null /\ !candidate.is_optional then | |
| 293 | if candidate.is_type_variable then | |
| 294 | return null | |
| 295 | elif candidate.is_value_type then | |
| 296 | candidate = IoC.CONTAINER.instance.innate_symbol_lookup.get_optional_type(candidate) | |
| 297 | else | |
| 298 | candidate = candidate.as_optional() | |
| 299 | fi | |
| 300 | fi | |
| 301 | ||
| 302 | // Upper-bound validation: the chosen candidate must | |
| 303 | // be assignable to every recorded upper bound. If | |
| 304 | // not, the placeholder is being asked to be both | |
| 305 | // wider (assignment / lower bound) and narrower | |
| 306 | // (passed to a too-narrow slot). Return null so the | |
| 307 | // placeholder stays unresolved; downstream | |
| 308 | // assignability errors surface at the offending sites. | |
| 309 | if _upper_bounds? then | |
| 310 | for upper in _upper_bounds do | |
| 311 | // A bound over a function's type parameter - the | |
| 312 | // `Iterable[T]` of a `count[T]` the placeholder was | |
| 313 | // passed to - accepts whatever that parameter binds | |
| 314 | // to, and no candidate is assignable to the unbound | |
| 315 | // parameter itself, so it cannot reject a settled | |
| 316 | // candidate. A candidate still holding placeholders | |
| 317 | // stays held back, for a later walk to settle. | |
| 318 | if upper.contains_function_generic_argument /\ candidate.is_settled then | |
| 319 | continue | |
| 320 | fi | |
| 321 | ||
| 322 | if !upper.is_assignable_from(candidate) then | |
| 323 | return null | |
| 324 | fi | |
| 325 | od | |
| 326 | fi | |
| 327 | ||
| 328 | // Operation-side filter: the candidate is only | |
| 329 | // valid if every accumulated constraint discharges | |
| 330 | // against it. A rejection here means the LUB picked | |
| 331 | // a type that doesn't expose an operation the user's | |
| 332 | // code performs on the placeholder — return null so | |
| 333 | // the placeholder stays unresolved and the retry | |
| 334 | // loop has another iteration to accumulate more | |
| 335 | // information. If no candidate ever discharges, the | |
| 336 | // slot stays unresolved and the non-convergence sweep | |
| 337 | // reports it as cannot infer type here rather than | |
| 338 | // silently producing bad IL. | |
| 339 | if _constraints? then | |
| 340 | for c in _constraints do | |
| 341 | if !c.try_discharge(candidate) then | |
| 342 | return null | |
| 343 | fi | |
| 344 | od | |
| 345 | fi | |
| 346 | ||
| 347 | return candidate | |
| 348 | si | |
| 349 | ||
| 350 | // Record an upper bound on this placeholder's eventual | |
| 351 | // type. The chosen LUB candidate (from lower bounds) | |
| 352 | // must be assignable to every upper bound to be accepted | |
| 353 | // by `try_get_inferred_type`. | |
| 354 | // | |
| 355 | // Returns true iff the bound carries new information — | |
| 356 | // wasn't already recorded by `matches`. The | |
| 357 | // retry loop uses this signal via `mark_consumed_any`. | |
| 358 | // | |
| 359 | // Skipped when the placeholder already has a concrete | |
| 360 | // resolved type (constraints accumulate only while | |
| 361 | // unresolved) and when the bound is itself a sentinel | |
| 362 | // or type variable (no information). | |
| 363 | add_upper_bound(bound: Type?) -> bool is | |
| 364 | if type? /\ !type.is_sentinel then | |
| 365 | return false | |
| 366 | fi | |
| 367 | ||
| 368 | if !bound? \/ bound.is_sentinel \/ bound.is_type_variable then | |
| 369 | return false | |
| 370 | fi | |
| 371 | ||
| 372 | let upper_bounds mut = _upper_bounds | |
| 373 | ||
| 374 | if !upper_bounds? then | |
| 375 | upper_bounds = Collections.LIST[Type]() | |
| 376 | _upper_bounds = upper_bounds | |
| 377 | else | |
| 378 | for existing in upper_bounds do | |
| 379 | if existing.matches(bound) then | |
| 380 | return false | |
| 381 | fi | |
| 382 | od | |
| 383 | fi | |
| 384 | ||
| 385 | upper_bounds.add(bound) | |
| 386 | return true | |
| 387 | si | |
| 388 | ||
| 389 | // Record an operation/structural constraint against this | |
| 390 | // placeholder origin. Returns true iff the constraint | |
| 391 | // carries information that wasn't already recorded — i.e. | |
| 392 | // wasn't already in the set keyed on its `equals` / | |
| 393 | // `get_hash_code`. The retry loop uses this signal to | |
| 394 | // drive `_logger.mark_consumed_any`. | |
| 395 | // | |
| 396 | // Like `add_lower_bound`, skipped only when the symbol's | |
| 397 | // type is fully settled — provisional composites are | |
| 398 | // still legitimately refining and should keep accumulating | |
| 399 | // operation evidence too. | |
| 400 | add_constraint(constraint: Semantic.Constraint?) -> bool is | |
| 401 | if type? /\ type.is_settled then | |
| 402 | return false | |
| 403 | fi | |
| 404 | ||
| 405 | if !constraint? then | |
| 406 | return false | |
| 407 | fi | |
| 408 | ||
| 409 | if !_constraints? then | |
| 410 | _constraints = Collections.SET[Semantic.Constraint]() | |
| 411 | elif _constraints.contains(constraint) then | |
| 412 | return false | |
| 413 | fi | |
| 414 | ||
| 415 | _constraints.add(constraint) | |
| 416 | return true | |
| 417 | si | |
| 418 | ||
| 419 | specialize(type_map: Collections.Map[Symbol,Type], owner: GENERIC) -> Symbol is | |
| 420 | let result = cast Variable?(memberwise_clone())! | |
| 421 | ||
| 422 | result.specialized_from = self | |
| 423 | ||
| 424 | if type? then | |
| 425 | let specialized_type = type.specialize(type_map) | |
| 426 | ||
| 427 | result.type = specialized_type | |
| 428 | fi | |
| 429 | ||
| 430 | result.owner = owner | |
| 431 | ||
| 432 | return result | |
| 433 | si | |
| 434 | si | |
| 435 | ||
| 436 | // Minimal Variable subclass used as the origin symbol for an | |
| 437 | // unbound owner type-arg placeholder at a constructor expression. | |
| 438 | // Carries the inherited `_lub_map` accumulator so overload back- | |
| 439 | // feed can push concrete types from downstream usage. Never | |
| 440 | // appears in IL — it's an inference-time-only sentinel that | |
| 441 | // identifies "the T slot of this specific construction". | |
| 442 | class INFERRED_TYPE_ARG_ORIGIN: Variable is | |
| 443 | // True when the slot this phantom stands for is an argument | |
| 444 | // pack: a value called through the phantom takes the call's | |
| 445 | // arguments as the tuple the pack binds to. | |
| 446 | is_argument_pack: bool public | |
| 447 | ||
| 448 | init(location: LOCATION, owner: Scope, name: string) is | |
| 449 | super.init(location, owner, name) | |
| 450 | si | |
| 451 | ||
| 452 | si | |
| 453 | ||
| 454 | // FIXME: pull up common code into a local + argument superclass: | |
| 455 | class LOCAL_VARIABLE: Variable, Types.SettableTyped is | |
| 456 | is_local: bool => true | |
| 457 | ||
| 458 | // Set on the local a nested named function statement declares, so | |
| 459 | // a reference from above the definition - a call written earlier | |
| 460 | // in the body, or a sibling function reaching for this one - is | |
| 461 | // reported as what it is rather than as an undefined variable. | |
| 462 | is_nested_function: bool public | |
| 463 | ||
| 464 | describe(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 465 | _describe_typed(context, PARTS.literal(name), type) | |
| 466 | ||
| 467 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => | |
| 468 | _local_kind() | |
| 469 | ||
| 470 | _local_kind() -> string => | |
| 471 | if is_nested_function then "nested function" | |
| 472 | elif is_boxed then "captured variable" | |
| 473 | elif is_captured then "captured value" | |
| 474 | elif is_disposed then "scoped disposal value" | |
| 475 | elif is_reassigned then "local variable" | |
| 476 | else "local value" | |
| 477 | fi | |
| 478 | ||
| 479 | init(location: LOCATION, owner: Scope, name: string) is | |
| 480 | super.init(location, owner, name) | |
| 481 | ||
| 482 | il_name_override = IoC.CONTAINER.instance.local_id_generator.get_unique_il_name_for(name) | |
| 483 | si | |
| 484 | ||
| 485 | define() is | |
| 486 | is_defined = true | |
| 487 | si | |
| 488 | ||
| 489 | check_is_defined(location: LOCATION) is | |
| 490 | if !is_defined then | |
| 491 | if is_nested_function then | |
| 492 | IoC.CONTAINER.instance.logger.error(location, "nested function {name} is used before its definition") | |
| 493 | else | |
| 494 | IoC.CONTAINER.instance.logger.error(location, "variable is not defined here") | |
| 495 | fi | |
| 496 | fi | |
| 497 | si | |
| 498 | ||
| 499 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value is | |
| 500 | assert !from? | |
| 501 | ||
| 502 | check_is_defined(location) | |
| 503 | ||
| 504 | return loader.load_local_variable(location, self) | |
| 505 | si | |
| 506 | ||
| 507 | load_outer(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value is | |
| 508 | assert !from? | |
| 509 | ||
| 510 | // A closure capturing this variable can only see a meaningful | |
| 511 | // value if the let-binding has already completed by the time | |
| 512 | // the closure is created. If is_defined is false the closure | |
| 513 | // is being constructed inside the variable's own initializer | |
| 514 | // (or before it), so the slot it captures is null. Without | |
| 515 | // this check, the silent ERROR-typed reference escapes all | |
| 516 | // the way to IL emission and crashes Type.gen_type. | |
| 517 | check_is_defined(location) | |
| 518 | ||
| 519 | return loader.load_outer_local_variable(location, self) | |
| 520 | si | |
| 521 | ||
| 522 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value is | |
| 523 | assert !from? | |
| 524 | ||
| 525 | check_is_defined(location) | |
| 526 | ||
| 527 | is_assigned = true | |
| 528 | ||
| 529 | if !is_initialize then | |
| 530 | is_reassigned = true | |
| 531 | fi | |
| 532 | ||
| 533 | return loader.store_local_variable(location, self, value, is_initialize) | |
| 534 | si | |
| 535 | ||
| 536 | // Declaring a captured, reassigned local that has no initializer: | |
| 537 | // there is no value to store, only the empty box to allocate. | |
| 538 | store_empty_box(location: LOCATION, loader: SYMBOL_LOADER) -> Value is | |
| 539 | check_is_defined(location) | |
| 540 | ||
| 541 | is_assigned = true | |
| 542 | ||
| 543 | return loader.store_empty_boxed_local(location, self) | |
| 544 | si | |
| 545 | si | |
| 546 | ||
| 547 | // FIXME: pull up common code into a local + argument superclass: | |
| 548 | class LOCAL_ARGUMENT: Variable, Types.SettableTyped is | |
| 549 | is_argument: bool => true | |
| 550 | is_local: bool => true | |
| 551 | ||
| 552 | describe(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 553 | _describe_typed(context, PARTS.literal(name), type) | |
| 554 | ||
| 555 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => | |
| 556 | _argument_kind() | |
| 557 | ||
| 558 | _argument_kind() -> string => | |
| 559 | if is_captured then "captured value" | |
| 560 | elif is_reassigned then "local variable" | |
| 561 | else "local argument" | |
| 562 | fi | |
| 563 | ||
| 564 | init(location: LOCATION, owner: Scope, name: string) is | |
| 565 | super.init(location, owner, name) | |
| 566 | si | |
| 567 | ||
| 568 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value is | |
| 569 | assert !from? | |
| 570 | return loader.load_local_argument(location, self) | |
| 571 | si | |
| 572 | ||
| 573 | load_outer(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value is | |
| 574 | assert !from? | |
| 575 | return loader.load_outer_local_argument(location, self) | |
| 576 | si | |
| 577 | ||
| 578 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value is | |
| 579 | assert !from? | |
| 580 | ||
| 581 | is_assigned = true | |
| 582 | ||
| 583 | if !is_initialize then | |
| 584 | is_reassigned = true | |
| 585 | fi | |
| 586 | ||
| 587 | return loader.store_local_argument(location, self, value, is_initialize) | |
| 588 | si | |
| 589 | ||
| 590 | si | |
| 591 | ||
| 592 | class Field: Variable, Types.SettableTyped abstract is | |
| 593 | unspecialized_type: Type? public | |
| 594 | ||
| 595 | // Set on a closure frame's `$recurse` capture: a load of this | |
| 596 | // field is the enclosing recursive literal referring to | |
| 597 | // itself, so the value it denotes is the frame's own closure. | |
| 598 | is_recurse_capture: bool public | |
| 599 | ||
| 600 | // For a frame field capturing a local variable, the local it | |
| 601 | // captures — an immutable one can only ever hold its | |
| 602 | // initializer's value, so a load of the field denotes | |
| 603 | // whatever that local does. | |
| 604 | captured_symbol: Symbol? public | |
| 605 | ||
| 606 | symbol_kind: SymbolKind => SymbolKind.FIELD | |
| 607 | completion_kind: CompletionKind => CompletionKind.FIELD | |
| 608 | ||
| 609 | is_private: bool | |
| 610 | is_field: bool => true | |
| 611 | is_public_readable: bool => !is_private | |
| 612 | is_workspace_visible: bool => !is_private | |
| 613 | ||
| 614 | is_accessible_to(accessor: Classy?) -> bool is | |
| 615 | if !is_private then | |
| 616 | return true | |
| 617 | fi | |
| 618 | ||
| 619 | let policy = IoC.CONTAINER.instance.build_flags.underscore_access | |
| 620 | ||
| 621 | if policy == Compiler.UnderscoreAccess.PRIVATE then | |
| 622 | return is_accessible_to_declaring_type(accessor) | |
| 623 | elif policy == Compiler.UnderscoreAccess.PROTECTED then | |
| 624 | // Normalised through unspecialized_symbol so a member | |
| 625 | // reached through a specialization matches its declaring | |
| 626 | // type - see is_accessible_to_declaring_type. | |
| 627 | let o = cast Classy?(owner?.unspecialized_symbol) | |
| 628 | return accessor? /\ o? /\ o.type? /\ accessor.type? /\ o.type.is_assignable_from(accessor.type) | |
| 629 | fi | |
| 630 | ||
| 631 | // LEGACY: the pre-existing is_public_readable rule remains the gate. | |
| 632 | return true | |
| 633 | si | |
| 634 | ||
| 635 | access_prefix: string => | |
| 636 | if !is_private then | |
| 637 | "" | |
| 638 | elif IoC.CONTAINER.instance.build_flags.underscore_access == Compiler.UnderscoreAccess.PRIVATE then | |
| 639 | "private " | |
| 640 | elif IoC.CONTAINER.instance.build_flags.underscore_access == Compiler.UnderscoreAccess.PROTECTED then | |
| 641 | "protected " | |
| 642 | else | |
| 643 | "" | |
| 644 | fi | |
| 645 | ||
| 646 | // Shared body for the concrete Field kinds. Delegates to | |
| 647 | // `_describe_typed` so the narrowed / declared dual display | |
| 648 | // rule stays in one place — a hover on a field whose observed | |
| 649 | // type differs from its declared shape shows both. | |
| 650 | _describe_field(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 651 | _describe_typed(context, PARTS.name(self), type) | |
| 652 | ||
| 653 | init(location: LOCATION, owner: Scope, name: string) is | |
| 654 | super.init(location, owner, name) | |
| 655 | ||
| 656 | self.is_private = name.starts_with('_') | |
| 657 | si | |
| 658 | ||
| 659 | specialize(type_map: Collections.Map[Symbol,Type], owner: GENERIC) -> Symbol is | |
| 660 | let result = cast Field?(super.specialize(type_map, owner))! | |
| 661 | ||
| 662 | result.unspecialized_type = type! | |
| 663 | ||
| 664 | return result | |
| 665 | si | |
| 666 | ||
| 667 | si | |
| 668 | ||
| 669 | class GLOBAL_VARIABLE: Field, Types.SettableTyped is | |
| 670 | // Set by the .NET importer when the symbol is read back from a | |
| 671 | // referenced assembly: the carrier class the field is a static | |
| 672 | // member of, which a field reference hangs off. A namespace can | |
| 673 | // have several carriers, so the carrier is recorded rather than | |
| 674 | // derived from the namespace. | |
| 675 | il_carrier: Classy? public | |
| 676 | ||
| 677 | // Globals live on the synthetic $globals class; declaring-class-private | |
| 678 | // is meaningless for them. An underscore global variable is | |
| 679 | // assembly-internal (emitted assembly, hidden from other assemblies) | |
| 680 | // and freely reachable within the assembly. | |
| 681 | is_accessible_to(accessor: Classy?) -> bool => true | |
| 682 | access_prefix: string => "" | |
| 683 | ||
| 684 | describe(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 685 | _describe_field(context) | |
| 686 | ||
| 687 | init(location: LOCATION, owner: Scope, name: string) is | |
| 688 | super.init(location, owner, name) | |
| 689 | si | |
| 690 | ||
| 691 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value is | |
| 692 | // `from` is the namespace when the variable is named through | |
| 693 | // it (`demo.count`), and nothing otherwise: a global variable | |
| 694 | // is reached the same way either way. | |
| 695 | return loader.load_global_variable(self) | |
| 696 | si | |
| 697 | ||
| 698 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value is | |
| 699 | return loader.store_global_variable(self, value) | |
| 700 | si | |
| 701 | ||
| 702 | // Field definition lives inside `.class 'NS'.'$globals' { ... }` block. | |
| 703 | si | |
| 704 | ||
| 705 | // A top-level `let` local promoted to a static field on the globals | |
| 706 | // container, so global functions in the same file can read it. Declared | |
| 707 | // by declare-members from a `let` that is a direct top-level statement; | |
| 708 | // the synthesised entry's own store initialises it, and a bare (non-mut) | |
| 709 | // `let` stays unassignable everywhere else. | |
| 710 | class TOP_LEVEL_VARIABLE: GLOBAL_VARIABLE is | |
| 711 | init(location: LOCATION, owner: Scope, name: string) is | |
| 712 | super.init(location, owner, name) | |
| 713 | si | |
| 714 | ||
| 715 | // "global variable" rather than "top-level variable": the kind is | |
| 716 | // what it is - namespace-scope mutable state with no accessors - | |
| 717 | // rather than where it was written, and it is distinct from the | |
| 718 | // `global property` a declared `name: type` produces, which has a | |
| 719 | // backing field and accessor functions. | |
| 720 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => "global variable" | |
| 721 | ||
| 722 | // A top-level `let` is declared into the namespace, but it is | |
| 723 | // still written as a statement and takes its value where it is | |
| 724 | // written, so it carries a local's notion of the point it | |
| 725 | // becomes readable. The walk already calls this on the `let`'s | |
| 726 | // left and clears the flag before every walk of that `let`; | |
| 727 | // without the override both land on the base no-op and the | |
| 728 | // variable reads as defined nowhere. | |
| 729 | define() is | |
| 730 | is_defined = true | |
| 731 | si | |
| 732 | ||
| 733 | // A reported read has no type to load - the `let` that gives | |
| 734 | // the variable one has not been walked yet - so it recovers as | |
| 735 | // an error-typed value. That is the carrier the rest of | |
| 736 | // compile-expressions already reads as "diagnosed, do not | |
| 737 | // report again"; loading the variable itself instead hands | |
| 738 | // member access, indexing and operator resolution a value with | |
| 739 | // no type at all, which each of them reports in its own words. | |
| 740 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value is | |
| 741 | if let earlier = _earlier_submission_variable(location) then | |
| 742 | return earlier.load(location, from, loader) | |
| 743 | fi | |
| 744 | ||
| 745 | if _check_textual_order(location) then | |
| 746 | return DUMMY(Types.ERROR(), location) | |
| 747 | fi | |
| 748 | ||
| 749 | return super.load(location, from, loader) | |
| 750 | si | |
| 751 | ||
| 752 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value is | |
| 753 | if !is_initialize then | |
| 754 | if !is_mutable_marked then | |
| 755 | IoC.CONTAINER.instance.logger.error(location, "top-level value cannot be reassigned") | |
| 756 | fi | |
| 757 | ||
| 758 | // Reported or not, the store still stands: an | |
| 759 | // assignment has a value to write and a field to write | |
| 760 | // it to, neither of which the diagnostic changes. | |
| 761 | let _ = _check_textual_order(location) | |
| 762 | fi | |
| 763 | ||
| 764 | return super.store(location, from, value, loader, is_initialize) | |
| 765 | si | |
| 766 | ||
| 767 | // One step of an interactive session redefines a name by declaring | |
| 768 | // it again, and the natural way to write the new value is in terms | |
| 769 | // of the old: `let x = x + 1`. Inside its own initializer this | |
| 770 | // variable does not exist yet, so where an earlier step's variable | |
| 771 | // of the same name was imported, that is the one the name means | |
| 772 | // there. Anywhere but a submission there is no earlier one to | |
| 773 | // mean, and the read stays the error it is. | |
| 774 | _earlier_submission_variable(use_location: LOCATION) -> Symbol? is | |
| 775 | let container = IoC.CONTAINER.instance | |
| 776 | ||
| 777 | if | |
| 778 | !container.build_flags.submission_name? \/ | |
| 779 | is_defined \/ | |
| 780 | !container.symbol_table.is_within_top_level_entry \/ | |
| 781 | use_location.file_name !~ location.file_name \/ | |
| 782 | use_location.start < location.start | |
| 783 | then | |
| 784 | return null | |
| 785 | fi | |
| 786 | ||
| 787 | if let block = container.symbol_table.current_namespace_block then | |
| 788 | let earlier = block.get_used_symbol(name) | |
| 789 | ||
| 790 | if earlier? /\ earlier != self then | |
| 791 | return earlier | |
| 792 | fi | |
| 793 | fi | |
| 794 | ||
| 795 | return null | |
| 796 | si | |
| 797 | ||
| 798 | // A `let` among the top-level statements is a statement, and | |
| 799 | // takes its value in the order the statements run, so it is in | |
| 800 | // scope from where it is written and no earlier. A read above it | |
| 801 | // gets the field's default value rather than one the program | |
| 802 | // assigned, wherever that read is written: a function defined | |
| 803 | // above the `let` can still be called from below it, so nothing | |
| 804 | // here makes the read safe, but holding every reader to the | |
| 805 | // declaration's position is consistent and says what the `let` | |
| 806 | // was for. A namespace-scope declaration - `count: int;` - is | |
| 807 | // the way to ask for state the whole file sees regardless of | |
| 808 | // order. | |
| 809 | _check_textual_order(use_location: LOCATION) -> bool is | |
| 810 | if use_location.file_name !~ location.file_name then | |
| 811 | // Another file's use is not ordered against this | |
| 812 | // declaration: its statements are a different entry's, | |
| 813 | // and run in their own order rather than this one's. | |
| 814 | return false | |
| 815 | fi | |
| 816 | ||
| 817 | if use_location.start < location.start then | |
| 818 | IoC.CONTAINER.instance.logger.error( | |
| 819 | use_location, | |
| 820 | "global variable {name} is used before its declaration", | |
| 821 | location, | |
| 822 | "{name} is declared here" | |
| 823 | ) | |
| 824 | ||
| 825 | return true | |
| 826 | elif | |
| 827 | IoC.CONTAINER.instance.symbol_table.is_within_top_level_entry /\ | |
| 828 | !is_defined | |
| 829 | then | |
| 830 | // A read from inside the `let`'s own initializer, which | |
| 831 | // is textually after the name it declares and so is not | |
| 832 | // caught above. `pre_let` clears `is_defined` at the | |
| 833 | // start of every walk of the `let`, so this holds on a | |
| 834 | // re-walk as well as on the first. Asked only within the | |
| 835 | // entry, where that clearing and the matching define() | |
| 836 | // are what the walk is doing; elsewhere the flag says | |
| 837 | // nothing about the reading position. | |
| 838 | IoC.CONTAINER.instance.logger.error( | |
| 839 | use_location, | |
| 840 | "variable is not defined here", | |
| 841 | location, | |
| 842 | "{name} is declared here" | |
| 843 | ) | |
| 844 | ||
| 845 | return true | |
| 846 | fi | |
| 847 | ||
| 848 | return false | |
| 849 | si | |
| 850 | si | |
| 851 | ||
| 852 | class INSTANCE_FIELD: Field is | |
| 853 | describe(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 854 | _describe_field(context) | |
| 855 | ||
| 856 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => "{access_prefix}field" | |
| 857 | ||
| 858 | is_instance: bool => true | |
| 859 | ||
| 860 | init(location: LOCATION, owner: Scope, name: string) is | |
| 861 | super.init(location, owner, name) | |
| 862 | si | |
| 863 | ||
| 864 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value => | |
| 865 | loader.load_instance_variable(location, from, self) | |
| 866 | ||
| 867 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value => | |
| 868 | loader.store_instance_variable(location, from, self, value) | |
| 869 | si | |
| 870 | ||
| 871 | class VARIANT_FIELD: Field is | |
| 872 | describe(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 873 | _describe_field(context) | |
| 874 | ||
| 875 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => "variant field" | |
| 876 | ||
| 877 | is_instance: bool => true | |
| 878 | ||
| 879 | // it's OK for variant fields to hide symbols in the base union | |
| 880 | can_hide_inherited: bool => true | |
| 881 | ||
| 882 | init(location: LOCATION, owner: Scope, name: string) is | |
| 883 | super.init(location, owner, name) | |
| 884 | si | |
| 885 | ||
| 886 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value => | |
| 887 | loader.load_instance_variable(location, from, self) | |
| 888 | ||
| 889 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value => | |
| 890 | loader.store_instance_variable(location, from, self, value) | |
| 891 | si | |
| 892 | ||
| 893 | class STRUCT_FIELD: Field is | |
| 894 | describe(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 895 | _describe_field(context) | |
| 896 | ||
| 897 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => "{access_prefix}field" | |
| 898 | ||
| 899 | is_instance: bool => true | |
| 900 | ||
| 901 | init(location: LOCATION, owner: Scope, name: string) is | |
| 902 | super.init(location, owner, name) | |
| 903 | si | |
| 904 | ||
| 905 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value => | |
| 906 | loader.load_struct_variable(location, from, self) | |
| 907 | ||
| 908 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value => | |
| 909 | loader.store_struct_variable(location, from, self, value) | |
| 910 | si | |
| 911 | ||
| 912 | class STATIC_FIELD: Field is | |
| 913 | describe(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 914 | _describe_field(context) | |
| 915 | ||
| 916 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => "{access_prefix}class field" | |
| 917 | ||
| 918 | init(location: LOCATION, owner: Scope, name: string) is | |
| 919 | super.init(location, owner, name) | |
| 920 | si | |
| 921 | ||
| 922 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value => | |
| 923 | loader.load_static_field(self) | |
| 924 | ||
| 925 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value => | |
| 926 | loader.store_static_field(self, value) | |
| 927 | ||
| 928 | si | |
| 929 | ||
| 930 | // An imported compile-time constant. Metadata carries the value | |
| 931 | // rather than a storage slot - a language that inlines a constant at | |
| 932 | // each use leaves no field behind for anyone to load - so a read is | |
| 933 | // the value written out where the read is, and there is nothing for a | |
| 934 | // write to write to. | |
| 935 | class CONSTANT_FIELD: Field is | |
| 936 | // The value as invariant-culture text. Absent when the constant | |
| 937 | // itself is null, which is the only constant of a reference type | |
| 938 | // metadata can hold - a case of its own rather than a reserved | |
| 939 | // spelling, since a constant string can hold any text at all. | |
| 940 | constant_value: string? | |
| 941 | ||
| 942 | describe(context: DESCRIBE_CONTEXT) -> SignaturePart => | |
| 943 | _describe_field(context) | |
| 944 | ||
| 945 | describe_kind(context: DESCRIBE_CONTEXT) -> string? => "{access_prefix}constant" | |
| 946 | ||
| 947 | init(location: LOCATION, owner: Scope, name: string, constant_value: string?) is | |
| 948 | super.init(location, owner, name) | |
| 949 | ||
| 950 | self.constant_value = constant_value | |
| 951 | si | |
| 952 | ||
| 953 | load(location: LOCATION, from: Value?, loader: SYMBOL_LOADER) -> Value => | |
| 954 | loader.load_constant_field(self) | |
| 955 | ||
| 956 | store(location: LOCATION, from: Value?, value: Value, loader: SYMBOL_LOADER, is_initialize: bool) -> Value => | |
| 957 | loader.store_constant_field(location, self) | |
| 958 | si | |
| 959 | si | |
| 960 |