Walk a sequence of statements top-to-bottom, updating `scope` at each step. Stops when a statement's start offset reaches or exceeds `ctx.cursor_offset`. After this function returns, `scope.get("$varName")` contains the types of `$varName` at the cursor position.
(
statements: impl Iterator<Item = &'b Statement<'b>>,
scope: &mut ScopeState,
ctx: &ForwardWalkCtx<'_>,
)
| 2176 | /// After this function returns, `scope.get("$varName")` contains the |
| 2177 | /// types of `$varName` at the cursor position. |
| 2178 | pub(crate) fn walk_body_forward<'b>( |
| 2179 | statements: impl Iterator<Item = &'b Statement<'b>>, |
| 2180 | scope: &mut ScopeState, |
| 2181 | ctx: &ForwardWalkCtx<'_>, |
| 2182 | ) { |
| 2183 | // When the diagnostic scope cache is active, record snapshots at |
| 2184 | // every statement boundary — even inside branches (if/else, try, |
| 2185 | // foreach, loops). Without this, member accesses inside branch |
| 2186 | // bodies would only see the scope from before the branch started, |
| 2187 | // missing assignments made inside the branch and causing false- |
| 2188 | // positive diagnostics. |
| 2189 | let record_snapshots = is_diagnostic_scope_active(); |
| 2190 | |
| 2191 | for stmt in statements { |
| 2192 | // Stop when we have passed the cursor. We use `>` rather than |
| 2193 | // `>=` so that a statement whose start offset exactly equals the |
| 2194 | // cursor is still processed. This matters when hovering on the |
| 2195 | // LHS variable of an assignment: the cursor sits at the first |
| 2196 | // token of the statement, and the user expects to see the *result* |
| 2197 | // type of the assignment, not the type from before it. |
| 2198 | if stmt.span().start.offset > ctx.cursor_offset { |
| 2199 | break; |
| 2200 | } |
| 2201 | |
| 2202 | // Check whether the cursor is inside a closure/arrow function |
| 2203 | // within this statement. If so, we need to resolve within |
| 2204 | // that closure's scope instead. |
| 2205 | let stmt_span = stmt.span(); |
| 2206 | if ctx.cursor_offset >= stmt_span.start.offset |
| 2207 | && ctx.cursor_offset <= stmt_span.end.offset |
| 2208 | && try_enter_closure(stmt, scope, ctx) |
| 2209 | { |
| 2210 | return; |
| 2211 | } |
| 2212 | |
| 2213 | // On the completion path, when the cursor is inside a ternary |
| 2214 | // instanceof branch or match(true) arm, apply narrowing to the |
| 2215 | // scope so the variable lookup sees the narrowed type. |
| 2216 | let cursor_inside_stmt = ctx.cursor_offset >= stmt_span.start.offset |
| 2217 | && ctx.cursor_offset <= stmt_span.end.offset; |
| 2218 | |
| 2219 | if record_snapshots { |
| 2220 | record_scope_snapshot(stmt_span.start.offset, scope); |
| 2221 | } |
| 2222 | |
| 2223 | process_statement(stmt, scope, ctx); |
| 2224 | |
| 2225 | if cursor_inside_stmt && !record_snapshots { |
| 2226 | let expr_opt = match stmt { |
| 2227 | Statement::Expression(es) => Some(es.expression), |
| 2228 | Statement::Return(ret) => ret.value, |
| 2229 | _ => None, |
| 2230 | }; |
| 2231 | if let Some(expr) = expr_opt { |
| 2232 | apply_cursor_ternary_narrowing(expr, scope, ctx); |
| 2233 | } |
| 2234 | |
| 2235 | // Also apply narrowing inside if/while/for conditions. |
no test coverage detected