MCPcopy Create free account
hub / github.com/PHPantom-dev/phpantom_lsp / resolve_target_classes_expr

Function resolve_target_classes_expr

src/completion/resolver.rs:347–415  ·  view source on GitHub ↗

Core dispatch for [`resolve_target_classes`], operating on a pre-parsed [`SubjectExpr`].

(
    expr: &SubjectExpr,
    access_kind: AccessKind,
    ctx: &ResolutionCtx<'_>,
)

Source from the content-addressed store, hash-verified

345/// Core dispatch for [`resolve_target_classes`], operating on a
346/// pre-parsed [`SubjectExpr`].
347pub(crate) fn resolve_target_classes_expr(
348 expr: &SubjectExpr,
349 access_kind: AccessKind,
350 ctx: &ResolutionCtx<'_>,
351) -> Vec<ResolvedType> {
352 // ── Chain cache lookup ───────────────────────────────────────
353 // During diagnostic passes the chain cache is active and stores
354 // results by subject text. This eliminates O(depth²) re-resolution
355 // of shared chain prefixes (e.g. `$model->where(...)` resolved once
356 // and reused by `$model->where(...)->whereNotNull(...)` etc.).
357 //
358 // The cache is NOT used for variable-only subjects (no `->` or `::`
359 // in the expression) because those are context-sensitive: the same
360 // `$var` may resolve to different types at different cursor offsets
361 // due to reassignment or narrowing.
362 //
363 // PropertyChain expressions rooted in a variable (e.g. `$this->pet`,
364 // `$obj->prop`) are also excluded because instanceof narrowing can
365 // change the resolved type at different positions within the same
366 // method body. For example, `$this->pet` may resolve to `Dog`
367 // inside `if ($this->pet instanceof Dog)` but to `Cat` after
368 // `if (!$this->pet instanceof Cat) { return; }`.
369 //
370 // Call expressions and static accesses are safe to cache because
371 // their return types are deterministic (method signatures don't
372 // change based on narrowing context).
373 let is_cacheable_chain = match expr {
374 SubjectExpr::CallExpr { .. }
375 | SubjectExpr::MethodCall { .. }
376 | SubjectExpr::StaticMethodCall { .. }
377 | SubjectExpr::StaticAccess { .. } => true,
378 // PropertyChain is only cacheable when the base is NOT a
379 // bare variable — e.g. `$this->method()->prop` (CallExpr
380 // base) is safe, but `$this->pet` (This/Variable base) is
381 // subject to narrowing.
382 SubjectExpr::PropertyChain { base, .. } => !matches!(
383 base.as_ref(),
384 SubjectExpr::This
385 | SubjectExpr::SelfKw
386 | SubjectExpr::StaticKw
387 | SubjectExpr::Parent
388 | SubjectExpr::Variable(_)
389 ),
390 _ => false,
391 };
392 if is_cacheable_chain {
393 let cache_key = expr.to_subject_text();
394 let cached = CHAIN_CACHE.with(|cell| {
395 let borrow = cell.borrow();
396 borrow.as_ref().and_then(|map| map.get(&cache_key).cloned())
397 });
398 if let Some(result) = cached {
399 return result;
400 }
401
402 let result = resolve_target_classes_expr_inner(expr, access_kind, ctx);
403
404 CHAIN_CACHE.with(|cell| {

Calls 5

to_subject_textMethod · 0.80
cloneMethod · 0.80
getMethod · 0.45
insertMethod · 0.45

Tested by

no test coverage detected