v4 Semantics: loop rules and scope

Work through examples, try the reasoning yourself, then put it into practice.

View Slides

Before you start

  • Trace source-order scopes and v4 for AST fields.

What you will practice

  • Track loop legality within the current function only.
  • Separate initializer scope from body block scope.
  • Analyze both operands and unreachable-looking statements before execution.
In this lesson

Lesson

3 min read

v4 Semantics: loop rules and scope

The v4 analyzer adds loop context alongside function context. A break or continue needs an enclosing loop in the same function. A nested function inside a loop cannot jump into its caller's loop. Save/reset loop depth while checking a function body, then restore the outer function's loop context.

Worked reconstruction

while (true) {
  fn inner() { break; }
  continue;
}
Traversal point Function context Loop depth Decision
outer while body top-level 1 loop control allowed
enter inner function inner 0 reset loop context
inner break inner 0 break outside loop
leave inner top-level 1 restore outer context
outer continue top-level 1 valid

Merely counting lexical loop nodes without respecting function boundaries would accept the invalid break. A loop declared inside inner would give inner its own valid target.

Scope and visit order

Open a scope for the for initializer, analyze init, condition, and post, then analyze the body as a nested block while loop depth is positive. Close both scopes when their constructs end. Header names are visible in the body and post; body-only names are unavailable in post. A body declaration may shadow an initializer name without redeclaring it in the same scope.

The analyzer's deterministic visit order (post before body) is not execution order. Runtime runs body before post. Static traversal checks every expression; it does not run the program to decide which names deserve validation. For false && missing, the missing name remains an error. A constant-looking infinite loop does not permit undefined names in its post clause.

Preserved boundaries

Const, declared-before-use, return placement, and known-function arity remain active. Type checking is still dynamic: v4 permits truthy numeric conditions. The compiler assumes these structural and placement checks already passed; letting an illegal cross-function break through can otherwise turn it into a missing or incorrectly targeted jump.

Debugging lab

An analyzer uses one loopDepth counter across nested function calls during traversal. It accepts the worked example, then rejects the outer continue after setting depth to zero. What two operations must be paired?

Compare your diagnosis after attempting the lab

Save the enclosing depth, reset to zero for the function, and restore it afterward. Reset without restoration breaks valid enclosing code; no reset incorrectly allows cross-function control transfer.

Transfer task

Construct a for loop whose body shadows its initializer variable and a post clause that uses a body-only variable. Predict acceptance of the first and the undefined-name diagnostic for the second without executing either program.

Carry forward

Carry a placement-valid AST into lowering. Preserve tests for function boundaries, scope shadowing, and static traversal of skipped runtime branches.

Build semantic-analysis-v4. Keep a before/after trace of one failing test. Use the checkpoint to diagnose specific skills; passing it does not replace implementing and testing the artifact.


Practice sequence

Use the order below for a guided path, or choose an item to revisit. A pass records a successful attempt; try a fresh example later to check what you can reconstruct.

Join Discord