Recursively search an expression for a closure/arrow function containing the cursor.
(
expr: &'b Expression<'b>,
scope: &mut ScopeState,
ctx: &ForwardWalkCtx<'_>,
inferred_params: Option<&[PhpType]>,
)
| 9145 | /// Recursively search an expression for a closure/arrow function |
| 9146 | /// containing the cursor. |
| 9147 | fn try_enter_closure_expr<'b>( |
| 9148 | expr: &'b Expression<'b>, |
| 9149 | scope: &mut ScopeState, |
| 9150 | ctx: &ForwardWalkCtx<'_>, |
| 9151 | inferred_params: Option<&[PhpType]>, |
| 9152 | ) -> bool { |
| 9153 | match expr { |
| 9154 | Expression::Closure(closure) => { |
| 9155 | let body_span = closure.body.span(); |
| 9156 | if ctx.cursor_offset >= body_span.start.offset |
| 9157 | && ctx.cursor_offset <= body_span.end.offset |
| 9158 | { |
| 9159 | // Create a fresh scope for the closure (closures have |
| 9160 | // isolated scope in PHP). |
| 9161 | let mut closure_scope = ScopeState::new(); |
| 9162 | |
| 9163 | // PHP closures implicitly capture `$this` from the |
| 9164 | // enclosing class method. |
| 9165 | let this_types = scope.get("$this"); |
| 9166 | if !this_types.is_empty() { |
| 9167 | closure_scope.set("$this", this_types.to_vec()); |
| 9168 | } |
| 9169 | |
| 9170 | // Seed with `use(...)` variables from the outer scope. |
| 9171 | if let Some(ref use_clause) = closure.use_clause { |
| 9172 | for use_var in use_clause.variables.iter() { |
| 9173 | let var_name = use_var.variable.name.to_string(); |
| 9174 | let from_outer = scope.get(&var_name); |
| 9175 | if !from_outer.is_empty() { |
| 9176 | closure_scope.set(&var_name, from_outer.to_vec()); |
| 9177 | } |
| 9178 | } |
| 9179 | } |
| 9180 | |
| 9181 | // Seed with parameter types, using callable inference |
| 9182 | // when available (mirroring the diagnostic path's |
| 9183 | // seed_closure_params logic). |
| 9184 | let inferred = inferred_params.unwrap_or(&[]); |
| 9185 | let filtered_inferred = filter_resolvable_inferred_params(inferred, ctx); |
| 9186 | seed_closure_params( |
| 9187 | &mut closure_scope, |
| 9188 | &closure.parameter_list, |
| 9189 | closure.span().start.offset, |
| 9190 | &filtered_inferred, |
| 9191 | ctx, |
| 9192 | ); |
| 9193 | |
| 9194 | // Walk the closure body. |
| 9195 | walk_body_forward(closure.body.statements.iter(), &mut closure_scope, ctx); |
| 9196 | |
| 9197 | // Replace the outer scope with the closure scope. |
| 9198 | *scope = closure_scope; |
| 9199 | return true; |
| 9200 | } |
| 9201 | } |
| 9202 | Expression::ArrowFunction(arrow) => { |
| 9203 | let body_span = arrow.expression.span(); |
| 9204 | if ctx.cursor_offset >= body_span.start.offset |
no test coverage detected