Skip to content

Stop a scope at the class body - #902

Draft
Firehed wants to merge 2 commits into
mainfrom
scope-stops-at-class-body
Draft

Firehed wants to merge 2 commits into
mainfrom
scope-stops-at-class-body

Conversation

@Firehed

@Firehed Firehed commented Nov 3, 2026

Copy link
Copy Markdown
Owner

Problem

Code in a class body outside its methods sees variables from the code around the class:

class A {
    public function m(): object {
        $local = 1;
        return new class {
            public int $p = 1; // the scope here lists $local
        };
    }
}

$top = 1;
class B {
    public int $q = 1; // the scope here lists $top
}

PHP evaluates property defaults, constant values, and enum case values as constant expressions, which cannot read a variable, and a class body sees nothing of the code that declares the class.

Cause

Scope::at walks up from a position and keeps the first class-like and the first function-like it meets, without checking that the function-like is inside the class-like.
So a position in new class { … } inside a method pairs the anonymous class with the outer method, and takes that method's variables.
A class body with no function-like around it is read as the file's global scope, and takes the file's top-level variables.

Change

  • Scope::at stops looking for a function-like once it has passed a class-like.
  • A scope with a class-like and no function-like is a class body, and holds no statements that bind variables.

Behavior

At a position in a class body outside any method, no variable from around the class is visible, so variable completion offers none of them.
$this was already unbound in these positions, since its type came from the class's name, and that stays true.

This prepares for binding $this in an anonymous class's methods, which without it would also bind $this in that class's property defaults.
It is groundwork for #881.

Tests

VariableBindingsTest checks a property default in an anonymous class inside a method, and in a class after top-level code.

Known limits

Found during review, in code this pull request does not change:

🤖 Generated with Claude Code

Firehed and others added 2 commits November 3, 2026 15:17
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.71%. Comparing base (44f6a1f) to head (a348eb4).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff            @@
##               main     #902   +/-   ##
=========================================
  Coverage     99.71%   99.71%           
- Complexity     1970     1973    +3     
=========================================
  Files           147      147           
  Lines          4488     4490    +2     
=========================================
+ Hits           4475     4477    +2     
  Misses           13       13           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant