Skip to main content

Module flow

Module flow 

Source
Expand description

Per-body control-flow narrowing (Phase-6 Workstream 2).

A pure forward dataflow over a lowered Body (no engine API, no &dyn Db, no types — types are layered by the checker in crate::infer consulting these facts). For each reachable statement it records the FlowFacts that hold before it; the checker installs those as the active narrowing environment, replacing the old lexical narrowing map. It also computes reachability — statements after a return/break/continue (or where every branch diverges) are dead, feeding UNREACHABLE_CODE (Workstream 1 owns the emission).

Soundness over precision (the 1.0 invariant). When unsure we widen (drop a fact), never narrow wrongly: a join is an intersection, a reassignment / opaque call invalidates, a loop body is entered with its assignments widened (no back-edge fixpoint). A wrong narrowing would hide a real UNSAFE_* or assert an absent member — both worse than over-warning. The checker keeps the load-bearing is_uninformative + widen-only gate when it consumes a fact (M1).

GDScript’s control flow is fully structured (reducible: the only non-local edges are break/continue), so this recursive dataflow is equivalent to — and simpler + less error-prone than — an explicit basic-block graph. It produces exactly the contract the checker (M1) and the warning layer (M3 UNREACHABLE_*) consume: per-statement entry facts + reachability.

Structs§

AssignedAnalysis
The locals definitely assigned a value at each statement’s entry — the intersection over all incoming control-flow paths. A typed-no-initializer local read while absent here is a read-before-assign. The sound dual of narrowing: grow-only + intersect-at-merge, and simpler — a callee cannot assign a caller’s function-scoped local, so there is no opaque-call / aliasing / self-rooted handling (do not copy those arms from the narrowing analyzer).
FlowAnalysis
The result of flowing a body: per-statement entry facts + the statements proven unreachable.
FlowFacts
The narrowing facts in force at a program point (a Place → NarrowedTy environment).

Enums§

NarrowedTy
A narrowing fact that holds at a program point (a place narrowed to a type or proven non-null). The type-test variants carry an AstPtr to the TypeRef, resolved lazily by the checker against the engine model — exactly like the old apply_narrowing resolved its ptr.
Place
A narrowable place: a local/param, or a (shallow) dotted access rooted at a local or self. Deliberately shallow — we narrow x, x.y, self.y but not arbitrary call results (f().y), array indices (a[i].y), or anything whose identity isn’t stable under re-evaluation. Shallowness is what keeps narrowing sound under mutation/aliasing (the 1.0 cut).

Functions§

analyze
Run the forward dataflow over a lowered body, producing per-statement entry facts + reachability.
analyze_assigned
Run definite-assignment over body’s top-level statements (recursing into if/for/while/ match blocks but not lambda bodies — those get a fresh scope and are left unchecked), seeded with the function’s params (always assigned).
condition_facts
The narrowing facts a condition establishes on its truthy (or falsy) edge — exposed so the checker can apply and/or short-circuit narrowing within a condition expression (the RHS of a and b is typed under a’s then-facts, a or b’s under a’s else-facts).