Process a `for` loop. Uses the same assignment-depth-bounded iteration as `process_foreach`: a cheap AST walk determines the dependency chain depth, then the body is re-walked up to that many times with fixed-point early exit.
(for_stmt: &'b For<'b>, scope: &mut ScopeState, ctx: &ForwardWalkCtx<'_>)
| 7046 | /// a cheap AST walk determines the dependency chain depth, then the body |
| 7047 | /// is re-walked up to that many times with fixed-point early exit. |
| 7048 | fn process_for<'b>(for_stmt: &'b For<'b>, scope: &mut ScopeState, ctx: &ForwardWalkCtx<'_>) { |
| 7049 | let loop_depth = enter_loop(); |
| 7050 | |
| 7051 | // Hard limit: skip the body entirely at excessive nesting depth. |
| 7052 | if loop_depth > MAX_LOOP_DEPTH { |
| 7053 | leave_loop(loop_depth); |
| 7054 | return; |
| 7055 | } |
| 7056 | |
| 7057 | // Process initializer expressions (e.g. `$i = 0`). |
| 7058 | for init_expr in for_stmt.initializations.iter() { |
| 7059 | process_assignment_expr(init_expr, scope, ctx); |
| 7060 | } |
| 7061 | |
| 7062 | // Process condition assignments (e.g. `for (; $x = nextItem(); )`) |
| 7063 | // and pass-by-ref in conditions (e.g. `for (; preg_match(..., $m); )`). |
| 7064 | for cond_expr in for_stmt.conditions.iter() { |
| 7065 | process_condition_assignment(cond_expr, scope, ctx); |
| 7066 | seed_pass_by_ref_in_condition(cond_expr, scope, ctx); |
| 7067 | } |
| 7068 | |
| 7069 | let pre_loop_scope = scope.clone(); |
| 7070 | |
| 7071 | // When the cursor is inside the loop body (completion path), discovery |
| 7072 | // passes must walk the ENTIRE body; the final pass uses the real |
| 7073 | // cursor_offset so it stops at the cursor as usual. |
| 7074 | let body_span = match &for_stmt.body { |
| 7075 | ForBody::Statement(inner) => inner.span(), |
| 7076 | ForBody::ColonDelimited(body) => body.span(), |
| 7077 | }; |
| 7078 | let cursor_in_body = |
| 7079 | ctx.cursor_offset >= body_span.start.offset && ctx.cursor_offset <= body_span.end.offset; |
| 7080 | let discovery_ctx = if cursor_in_body && !is_diagnostic_scope_active() { |
| 7081 | ctx.with_cursor_offset(u32::MAX) |
| 7082 | } else { |
| 7083 | ctx.with_cursor_offset(ctx.cursor_offset) |
| 7084 | }; |
| 7085 | |
| 7086 | // ── Assignment-depth-bounded loop iteration ───────────────── |
| 7087 | let body_stmts: Vec<&Statement<'b>> = match &for_stmt.body { |
| 7088 | ForBody::Statement(inner) => vec![*inner], |
| 7089 | ForBody::ColonDelimited(body) => body.statements.iter().collect(), |
| 7090 | }; |
| 7091 | let assignment_depth = |
| 7092 | clamp_iterations_for_depth(assignment_map_depth(&body_stmts), loop_depth); |
| 7093 | |
| 7094 | // ── Initial walk (always performed) ───────────────────────── |
| 7095 | let initial_ctx = if assignment_depth > 1 { |
| 7096 | &discovery_ctx |
| 7097 | } else { |
| 7098 | ctx |
| 7099 | }; |
| 7100 | match &for_stmt.body { |
| 7101 | ForBody::Statement(inner) => { |
| 7102 | walk_body_forward(std::iter::once(*inner), scope, initial_ctx); |
| 7103 | } |
| 7104 | ForBody::ColonDelimited(body) => { |
| 7105 | walk_body_forward(body.statements.iter(), scope, initial_ctx); |
no test coverage detected