Collect Phase 2 (slow) diagnostics: unknown class/member/function, argument count, implementation errors, deprecated usage. These require type resolution and are expensive.
(
&self,
uri_str: &str,
content: &str,
out: &mut Vec<Diagnostic>,
)
| 215 | /// argument count, implementation errors, deprecated usage. These |
| 216 | /// require type resolution and are expensive. |
| 217 | pub fn collect_slow_diagnostics( |
| 218 | &self, |
| 219 | uri_str: &str, |
| 220 | content: &str, |
| 221 | out: &mut Vec<Diagnostic>, |
| 222 | ) { |
| 223 | // Activate the chain resolution cache so that all slow |
| 224 | // diagnostic collectors share cached intermediate chain |
| 225 | // prefix results (e.g. `$model->where(...)` resolved once |
| 226 | // and reused by `$model->where(...)->whereNotNull(...)`). |
| 227 | // This eliminates O(depth²) re-resolution of shared chain |
| 228 | // prefixes across unknown_member, argument_count, type_error, |
| 229 | // and deprecated collectors. |
| 230 | let _chain_guard = crate::completion::resolver::with_chain_resolution_cache(); |
| 231 | |
| 232 | // Activate the callable target cache so that the same method |
| 233 | // on the same class is resolved at most once across all |
| 234 | // diagnostic collectors. For example, `Builder::where` is |
| 235 | // looked up once and reused for every `$q->where(...)`, |
| 236 | // `$query->where(...)`, and `Product::query()->where(...)` |
| 237 | // call site in the file. |
| 238 | let _callable_guard = crate::completion::call_resolution::with_callable_target_cache(); |
| 239 | let _body_infer_guard = self.activate_body_return_inferrer(); |
| 240 | |
| 241 | // ── Phase 2: forward-walked diagnostic scope cache ────── |
| 242 | // Walk every function/method body in the file once with the |
| 243 | // forward walker, recording scope snapshots at each statement |
| 244 | // boundary. All subsequent `resolve_variable_types` calls |
| 245 | // from diagnostic collectors hit the cache (O(log N) lookup) |
| 246 | // instead of doing a full backward scan per member-access |
| 247 | // span. This eliminates the O(N × depth × file_size) cost |
| 248 | // that caused multi-minute analysis times on large files. |
| 249 | let _scope_guard = crate::completion::variable::forward_walk::with_diagnostic_scope_cache(); |
| 250 | { |
| 251 | let file_ctx = self.file_context(uri_str); |
| 252 | let class_loader = self.class_loader(&file_ctx); |
| 253 | let function_loader_cl = self.function_loader(&file_ctx); |
| 254 | let constant_loader_cl = self.constant_loader(); |
| 255 | let loaders = crate::completion::resolver::Loaders { |
| 256 | function_loader: Some(&function_loader_cl), |
| 257 | constant_loader: Some(&constant_loader_cl), |
| 258 | }; |
| 259 | crate::completion::variable::forward_walk::build_diagnostic_scopes( |
| 260 | content, |
| 261 | &file_ctx.classes, |
| 262 | &class_loader, |
| 263 | loaders, |
| 264 | Some(&self.resolved_class_cache), |
| 265 | ); |
| 266 | } |
| 267 | |
| 268 | self.collect_unknown_class_diagnostics(uri_str, content, out); |
| 269 | self.collect_unknown_member_diagnostics(uri_str, content, out); |
| 270 | self.collect_unknown_function_diagnostics(uri_str, content, out); |
| 271 | // NOTE: unresolved_member_access diagnostics are now emitted |
| 272 | // inside collect_unknown_member_diagnostics (in the Untyped arm) |
| 273 | // to avoid a second full walk with duplicate type resolution. |
| 274 | self.collect_argument_count_diagnostics(uri_str, content, out); |
no test coverage detected