Run the forward walker on a single function/method body and record scope snapshots for diagnostics. `is_static` indicates whether this is a static method. When `false` and `current_class` has a non-empty name, `$this` is seeded in the scope so that expressions like `$this->prop` and `foreach ($this->items as $item)` can resolve without remaining unresolved.
(
parameters: impl Iterator<Item = &'b FunctionLikeParameter<'b>>,
body_statements: impl Iterator<Item = &'b Statement<'b>>,
fn_span_start: u32,
current_class: &ClassInfo,
method_n
| 1743 | /// scope so that expressions like `$this->prop` and `foreach ($this->items as $item)` |
| 1744 | /// can resolve without remaining unresolved. |
| 1745 | fn analyze_function_body<'b>( |
| 1746 | parameters: impl Iterator<Item = &'b FunctionLikeParameter<'b>>, |
| 1747 | body_statements: impl Iterator<Item = &'b Statement<'b>>, |
| 1748 | fn_span_start: u32, |
| 1749 | current_class: &ClassInfo, |
| 1750 | method_name: Option<&str>, |
| 1751 | is_static: bool, |
| 1752 | diag_ctx: &DiagnosticWalkCtx<'_>, |
| 1753 | ) { |
| 1754 | let ctx = ForwardWalkCtx { |
| 1755 | current_class, |
| 1756 | all_classes: diag_ctx.local_classes, |
| 1757 | content: diag_ctx.content, |
| 1758 | cursor_offset: u32::MAX, |
| 1759 | class_loader: diag_ctx.class_loader, |
| 1760 | loaders: diag_ctx.loaders, |
| 1761 | resolved_class_cache: diag_ctx.resolved_class_cache, |
| 1762 | enclosing_return_type: None, |
| 1763 | top_level_scope: None, |
| 1764 | }; |
| 1765 | |
| 1766 | let mut scope = ScopeState::new(); |
| 1767 | |
| 1768 | // Seed `$this` for non-static class methods so that expressions |
| 1769 | // referencing `$this` (e.g. `$this->prop`, `foreach ($this->items …)`) |
| 1770 | // resolve from the scope instead of falling through to the backward |
| 1771 | // scanner. |
| 1772 | if !is_static { |
| 1773 | seed_this(&mut scope, current_class); |
| 1774 | } |
| 1775 | |
| 1776 | // Seed scope with parameter types. |
| 1777 | // Detect whether this method has a #[Scope] attribute by scanning |
| 1778 | // the source text around the method span for `#[Scope]`. |
| 1779 | let has_scope_attr = method_name |
| 1780 | .map(|_| detect_scope_attribute_from_source(diag_ctx.content, fn_span_start as usize)) |
| 1781 | .unwrap_or(false); |
| 1782 | seed_params( |
| 1783 | &mut scope, |
| 1784 | parameters, |
| 1785 | fn_span_start, |
| 1786 | method_name, |
| 1787 | has_scope_attr, |
| 1788 | &ctx, |
| 1789 | ); |
| 1790 | |
| 1791 | // Seed superglobals so that accesses like `$_SERVER['key']` don't |
| 1792 | // remain unresolved. |
| 1793 | seed_superglobals(&mut scope); |
| 1794 | |
| 1795 | // Record the scope right at the function body start so that |
| 1796 | // member accesses on parameters before any assignment are covered. |
| 1797 | record_scope_snapshot(fn_span_start, &scope); |
| 1798 | |
| 1799 | // Walk the entire body, recording snapshots at each statement. |
| 1800 | walk_body_for_diagnostics(body_statements, &mut scope, &ctx); |
| 1801 | } |
| 1802 |
no test coverage detected