docs on updated scopes
This commit is contained in:
parent
bcabfc8962
commit
b953282251
@ -2,6 +2,11 @@
|
||||
|
||||
### Unreleased
|
||||
|
||||
- Docs: Scopes and Closures guidance
|
||||
- New page: `docs/scopes_and_closures.md` detailing `ClosureScope` resolution order, recursion‑safe helpers (`chainLookupIgnoreClosure`, `chainLookupWithMembers`, `baseGetIgnoreClosure`), cycle prevention, and capturing lexical environments for callbacks (`snapshotForClosure`).
|
||||
- Updated: `docs/advanced_topics.md` (link to the new page), `docs/parallelism.md` (closures in `launch`/`flow`), `docs/OOP.md` (visibility from closures with preserved `currentClassCtx`), `docs/exceptions_handling.md` (compatibility alias `SymbolNotFound`).
|
||||
- Tutorial: added quick link to Scopes and Closures.
|
||||
|
||||
- IDEA plugin: Lightweight autocompletion (experimental)
|
||||
- Global completion: local declarations, in‑scope parameters, imported modules, and stdlib symbols.
|
||||
- Member completion: after a dot, suggests only members of the inferred receiver type (incl. chained calls like `Path(".." ).lines().` → `Iterator` methods). No global identifiers appear after a dot.
|
||||
|
||||
@ -603,4 +603,9 @@ Regular methods are called on instances as usual `instance.method()`. The method
|
||||
|
||||
TBD
|
||||
|
||||
[argument list](declaring_arguments.md)
|
||||
[argument list](declaring_arguments.md)
|
||||
### Visibility from within closures and instance scopes
|
||||
|
||||
When a closure executes within a method, the closure retains the lexical class context of its creation site. This means private/protected members of that class remain accessible where expected (subject to usual visibility rules). Field resolution checks the declaring class and validates access using the preserved `currentClassCtx`.
|
||||
|
||||
See also: [Scopes and Closures: resolution and safety](scopes_and_closures.md)
|
||||
|
||||
@ -158,3 +158,9 @@ Function annotation can have more args specified at call time. There arguments m
|
||||
>>> void
|
||||
|
||||
[parallelism]: parallelism.md
|
||||
|
||||
## Scopes and Closures: resolution and safety
|
||||
|
||||
Closures and dynamic scope graphs require care to avoid accidental recursion and to keep name resolution predictable. See the dedicated page for detailed rules, helper APIs, and best practices:
|
||||
|
||||
- Scopes and Closures: resolution and safety → [scopes_and_closures.md](scopes_and_closures.md)
|
||||
|
||||
@ -169,3 +169,18 @@ _this functionality is not yet released_
|
||||
| UnknownException | unexpected kotlin exception caught |
|
||||
| | |
|
||||
|
||||
|
||||
### Symbol resolution errors
|
||||
|
||||
For compatibility, `SymbolNotFound` is an alias of `SymbolNotDefinedException`. You can catch either name in examples and tests.
|
||||
|
||||
Example:
|
||||
|
||||
```lyng
|
||||
try {
|
||||
nonExistingMethod()
|
||||
}
|
||||
catch(e: SymbolNotFound) {
|
||||
// handle
|
||||
}
|
||||
```
|
||||
|
||||
@ -221,3 +221,22 @@ Lyng includes an optional optimization for function/method calls on JVM: scope f
|
||||
- Expected effect (from our JVM micro‑benchmarks): in deep call loops, enabling pooling reduced total time by about 1.38× in a dedicated pooling benchmark; mileage may vary depending on workload.
|
||||
|
||||
Future work: introduce thread‑safe pooling (e.g., per‑thread pools or confinement strategies) before considering enabling it by default in multi‑threaded environments.
|
||||
|
||||
### Closures inside coroutine helpers (launch/flow)
|
||||
|
||||
Closures executed by `launch { ... }` and `flow { ... }` resolve names using the `ClosureScope` rules:
|
||||
|
||||
1. Closure frame locals/arguments
|
||||
2. Captured receiver instance/class members
|
||||
3. Closure ancestry locals + each frame’s `this` members (cycle‑safe)
|
||||
4. Caller `this` members
|
||||
5. Caller ancestry locals + each frame’s `this` members (cycle‑safe)
|
||||
6. Module pseudo‑symbols (e.g., `__PACKAGE__`)
|
||||
7. Direct module/global fallback (nearest `ModuleScope` and its parent/root)
|
||||
|
||||
Implications:
|
||||
- Outer locals (e.g., `counter`) stay visible across suspension points.
|
||||
- Global helpers like `delay(ms)` and `yield()` are available from inside closures.
|
||||
- If you write your own async helpers, execute user lambdas under `ClosureScope(callScope, capturedCreatorScope)` and avoid manual ancestry walking.
|
||||
|
||||
See also: [Scopes and Closures: resolution and safety](scopes_and_closures.md)
|
||||
|
||||
75
docs/scopes_and_closures.md
Normal file
75
docs/scopes_and_closures.md
Normal file
@ -0,0 +1,75 @@
|
||||
# Scopes and Closures: resolution and safety
|
||||
|
||||
This page documents how name resolution works with `ClosureScope`, how to avoid recursion pitfalls, and how to safely capture and execute callbacks that need access to outer locals.
|
||||
|
||||
## Why this matters
|
||||
Name lookup across nested scopes and closures can accidentally form recursive resolution paths or hide expected symbols (outer locals, module/global functions). The rules below ensure predictable resolution and prevent infinite recursion.
|
||||
|
||||
## Resolution order in ClosureScope
|
||||
When evaluating an identifier `name` inside a closure, `ClosureScope.get(name)` resolves in this order:
|
||||
|
||||
1. Closure frame locals and arguments
|
||||
2. Captured receiver (`closureScope.thisObj`) instance/class members
|
||||
3. Closure ancestry locals + each frame’s `thisObj` members (cycle‑safe)
|
||||
4. Caller `this` members
|
||||
5. Caller ancestry locals + each frame’s `thisObj` members (cycle‑safe)
|
||||
6. Module pseudo‑symbols (e.g., `__PACKAGE__`) from the nearest `ModuleScope`
|
||||
7. Direct module/global fallback (nearest `ModuleScope` and its parent/root scope)
|
||||
8. Final fallback: base local/parent lookup for the current frame
|
||||
|
||||
This preserves intuitive visibility (locals → captured receiver → closure chain → caller members → caller chain → module/root) while preventing infinite recursion between scope types.
|
||||
|
||||
## Use raw‑chain helpers for ancestry walks
|
||||
When authoring new scope types or advanced lookups, avoid calling virtual `get` while walking parents. Instead, use the non‑dispatch helpers on `Scope`:
|
||||
|
||||
- `chainLookupIgnoreClosure(name)`
|
||||
- Walk raw `parent` chain and check only per‑frame locals/bindings/slots.
|
||||
- Ignores overridden `get` (e.g., in `ClosureScope`). Cycle‑safe.
|
||||
- `chainLookupWithMembers(name)`
|
||||
- Like above, but after locals/bindings it also checks each frame’s `thisObj` members.
|
||||
- Ignores overridden `get`. Cycle‑safe.
|
||||
- `baseGetIgnoreClosure(name)`
|
||||
- For the current frame only: check locals/bindings, then walk raw parents (locals/bindings), then fallback to this frame’s `thisObj` members.
|
||||
|
||||
These helpers avoid ping‑pong recursion and make structural cycles harmless (lookups terminate).
|
||||
|
||||
## Preventing structural cycles
|
||||
- Don’t construct parent chains that can point back to a descendant.
|
||||
- A debug‑time guard throws if assigning a parent would create a cycle; keep it enabled for development builds.
|
||||
- Even with a cycle, chain helpers break out via a small `visited` set keyed by `frameId`.
|
||||
|
||||
## Capturing lexical environments for callbacks
|
||||
For dynamic objects or custom builders, capture the creator’s lexical scope so callbacks can see outer locals/parameters:
|
||||
|
||||
1. Use `snapshotForClosure()` on the caller scope to capture locals/bindings/slots and parent.
|
||||
2. Store this snapshot and run callbacks under `ClosureScope(callScope, captured)`.
|
||||
|
||||
Kotlin sketch:
|
||||
```kotlin
|
||||
val captured = scope.snapshotForClosure()
|
||||
val execScope = ClosureScope(currentCallScope, captured)
|
||||
callback.execute(execScope)
|
||||
```
|
||||
|
||||
This ensures expressions like `contractName` used inside dynamic `get { name -> ... }` resolve to outer variables defined at the creation site.
|
||||
|
||||
## Closures in coroutines (launch/flow)
|
||||
- The closure frame still prioritizes its own locals/args.
|
||||
- Outer locals declared before suspension points remain visible through slot‑aware ancestry lookups.
|
||||
- Global functions like `delay(ms)` and `yield()` are resolved via module/root fallbacks from within closures.
|
||||
|
||||
Tip: If a closure unexpectedly cannot see an outer local, check whether an intermediate runtime helper introduced an extra call frame; the built‑in lookup already traverses caller ancestry, so prefer the standard helpers rather than custom dispatch.
|
||||
|
||||
## Local variable references and missing symbols
|
||||
- Unqualified identifier resolution first prefers locals/bindings/slots before falling back to `this` members.
|
||||
- If neither locals nor members contain the symbol, missing field lookups map to `SymbolNotFound` (compatibility alias for `SymbolNotDefinedException`).
|
||||
|
||||
## Performance notes
|
||||
- The `visited` sets used for cycle detection are tiny and short‑lived; in typical scripts the overhead is negligible.
|
||||
- If profiling shows hotspots, consider limiting ancestry depth in your custom helpers or using small fixed arrays instead of hash sets—only for extremely hot code paths.
|
||||
|
||||
## Dos and Don’ts
|
||||
- Do use `chainLookupIgnoreClosure` / `chainLookupWithMembers` for ancestry traversals.
|
||||
- Do maintain the resolution order above for predictable behavior.
|
||||
- Don’t call virtual `get` while walking parents; it risks recursion across scope types.
|
||||
- Don’t attach instance scopes to transient/pool frames; bind to a stable parent scope instead.
|
||||
@ -6,7 +6,7 @@ In other word, the code usually works as expected when you see it. So, nothing u
|
||||
|
||||
__Other documents to read__ maybe after this one:
|
||||
|
||||
- [Advanced topics](advanced_topics.md), [declaring arguments](declaring_arguments.md)
|
||||
- [Advanced topics](advanced_topics.md), [declaring arguments](declaring_arguments.md), [Scopes and Closures](scopes_and_closures.md)
|
||||
- [OOP notes](OOP.md), [exception handling](exceptions_handling.md)
|
||||
- [math in Lyng](math.md), [the `when` statement](when.md)
|
||||
- [time](time.md) and [parallelism](parallelism.md)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user