Skip to content

Variable resolution can recurse while building global scope #327

Description

@aaaaaandrew

Summary

Variable resolution can re-enter the exact same query while constructing the top-level scope used by PHP global declarations. The relevant path is still present on current main in src/type_engine/variable/resolution.rs.

The cycle is:

  1. resolve_variable_types starts a forward walk.
  2. resolve_variable_in_statements sees global in the file and starts a top-level scope walk.
  3. Resolving an RHS call argument requests the same variable resolution.
  4. That request starts another top-level scope walk, repeating the cycle.

Minimal fixture

<?php
function capture($input) {
    global $shared;
    $shared = transform($input);
}
$arg = source_value();
$old_global = factory($arg);
$value = consume($old_global);
$value->method();

A semantic consumer resolving the receiver of $value->method() reaches the cycle. The important shape is a file containing global plus top-level call arguments that require variable resolution.

Expected behavior

Resolution should terminate without disabling valid global inference. A re-entrant query cannot contribute information while its outer invocation is still incomplete, so only that exact cycle should be cut.

Possible implementation

Use thread-local RAII guards to track active resolution work:

resolve(key):
    if key is already active:
        return no additional types

    mark key active
    defer unmark key
    perform resolution

The resolution key should include source identity, variable name, cursor offset, and current class. Top-level-scope construction needs a separate source-level guard because it can recursively initiate another resolution before the original scope is complete.

The guard must be ownership-aware: rejecting a nested acquisition must not remove the outer acquisition when the rejected guard is dropped.

Acceptance criteria

  • The fixture terminates without stack growth or unbounded work.
  • Existing global member resolution remains intact.
  • Unrelated variables and cursor positions are not suppressed.
  • Guard state is released after normal return and unwinding.
  • Repeated resolution on the same thread continues to work.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions