Skip to content

Resolve self/parent/static in Closure::bind() scope#4081

Open
zonuexe wants to merge 3 commits into
phpstan:2.2.xfrom
zonuexe:fix/bind-class-scope
Open

Resolve self/parent/static in Closure::bind() scope#4081
zonuexe wants to merge 3 commits into
phpstan:2.2.xfrom
zonuexe:fix/bind-class-scope

Conversation

@zonuexe

@zonuexe zonuexe commented Jul 4, 2025

Copy link
Copy Markdown
Contributor

Resolves self / parent / static against the Closure::bind() scope (the 3rd argument) inside the bound closure body, for both type inference and the related rules.

Rebased onto 2.2.x and reworked: the original inline patch in MutatingScope::getType() no longer exists after expression handling was split into ExprHandler services, so the logic now lives where each expression/rule resolves its class scope.

What works now

Inside Closure::bind(fn () => …, $newThis, Scope::class):

  • Class constantsself::CONST, parent::CONST resolve against the bound scope.
  • ::classself::class, parent::class.
  • Static method callsself::method(), parent::method() return types.
  • Static property accessself::$prop, parent::$prop types.
  • Instantiationnew self, new parent, new static.
  • Rules no longer emit Using self outside of class scope. etc. and check accessibility against the bound class: ClassConstantRule, StaticMethodCallCheck, AccessStaticPropertiesCheck, InstantiationRule.

This works even when Closure::bind() is called outside a class context (the closure body type is inferred in the enclosing scope, where the closure-bind scope classes are otherwise unavailable — hence the class-name nodes are annotated by ClosureBindArgVisitor).

Approach

  • ClosureBindArgVisitor annotates self/parent/static class-name nodes inside the closure body only (1st argument) with the bind scope argument. The $newThis/$newScope arguments are intentionally not rescoped — this keeps Closure::bind(self::cond() ? … : …, …) correct (see #6319).
  • ClosureBindScopeResolver (new) resolves that annotation to a ClassReflection, shared by ClassConstFetchHandler and the rules.
  • MutatingScope::resolveName() / resolveTypeByName() honour the annotation so static calls / properties / new resolve without touching each handler.

Issues

Not included (follow-ups)

zonuexe added a commit to zonuexe/phpstan-src that referenced this pull request Jul 8, 2026
Annotate special class-name nodes (self/parent/static) that appear inside a
Closure::bind() call with the bind scope argument via ClosureBindArgVisitor, and
resolve the class constant type against that bound class in ClassConstFetchHandler.

Reconstruction of PR phpstan#4081 (closure-bind-scope) rebased onto 2.2.x: the original
MutatingScope::getType() ClassConstFetch branch no longer exists after expression
handling was split into ExprHandler services.
zonuexe added a commit to zonuexe/phpstan-src that referenced this pull request Jul 8, 2026
… properties and instantiation

Extend the Closure::bind() scope binding beyond class constants to the natural
remaining surface:

- MutatingScope::resolveName()/resolveTypeByName() now honour the bind scope
  annotated on self/parent/static class-name nodes, so static method-call return
  types, static property types and `new self/parent/static` resolve against the
  bound class even outside a class context (the closure body's type is inferred in
  the enclosing scope where the closure-bind scope classes are otherwise absent).
- StaticMethodCallCheck, AccessStaticPropertiesCheck and InstantiationRule no
  longer report "outside of class scope" for self/parent/static inside a bound
  closure, resolving accessibility against the bound class.
- ClosureBindArgVisitor now only rescopes the closure body (1st argument), not the
  $newThis/$newScope arguments, avoiding a self-referential scope annotation on a
  `self::class` scope argument.

Continues the reconstruction of PR phpstan#4081 on 2.2.x.
@zonuexe
zonuexe force-pushed the fix/bind-class-scope branch from b3c42c8 to 1fb897e Compare July 8, 2026 12:46
@zonuexe
zonuexe changed the base branch from 2.1.x to 2.2.x July 8, 2026 12:46
@zonuexe zonuexe changed the title Closure::bind() scope binding improvements Resolve self/parent/static in Closure::bind() scope Jul 8, 2026
@zonuexe
zonuexe marked this pull request as ready for review July 8, 2026 12:49
@phpstan-bot

Copy link
Copy Markdown
Collaborator

This pull request has been marked as ready for review.

@zonuexe
zonuexe marked this pull request as draft July 14, 2026 03:23
zonuexe added a commit to zonuexe/phpstan-src that referenced this pull request Jul 14, 2026
Annotate special class-name nodes (self/parent/static) that appear inside a
Closure::bind() call with the bind scope argument via ClosureBindArgVisitor, and
resolve the class constant type against that bound class in ClassConstFetchHandler.

Reconstruction of PR phpstan#4081 (closure-bind-scope) rebased onto 2.2.x: the original
MutatingScope::getType() ClassConstFetch branch no longer exists after expression
handling was split into ExprHandler services.
zonuexe added a commit to zonuexe/phpstan-src that referenced this pull request Jul 14, 2026
… properties and instantiation

Extend the Closure::bind() scope binding beyond class constants to the natural
remaining surface:

- MutatingScope::resolveName()/resolveTypeByName() now honour the bind scope
  annotated on self/parent/static class-name nodes, so static method-call return
  types, static property types and `new self/parent/static` resolve against the
  bound class even outside a class context (the closure body's type is inferred in
  the enclosing scope where the closure-bind scope classes are otherwise absent).
- StaticMethodCallCheck, AccessStaticPropertiesCheck and InstantiationRule no
  longer report "outside of class scope" for self/parent/static inside a bound
  closure, resolving accessibility against the bound class.
- ClosureBindArgVisitor now only rescopes the closure body (1st argument), not the
  $newThis/$newScope arguments, avoiding a self-referential scope annotation on a
  `self::class` scope argument.

Continues the reconstruction of PR phpstan#4081 on 2.2.x.
@zonuexe
zonuexe force-pushed the fix/bind-class-scope branch from 1fb897e to 8d9f662 Compare July 14, 2026 03:29
zonuexe added a commit to zonuexe/phpstan-src that referenced this pull request Jul 14, 2026
Annotate special class-name nodes (self/parent/static) that appear inside a
Closure::bind() call with the bind scope argument via ClosureBindArgVisitor, and
resolve the class constant type against that bound class in ClassConstFetchHandler.

Reconstruction of PR phpstan#4081 (closure-bind-scope) rebased onto 2.2.x: the original
MutatingScope::getType() ClassConstFetch branch no longer exists after expression
handling was split into ExprHandler services.
zonuexe added a commit to zonuexe/phpstan-src that referenced this pull request Jul 14, 2026
… properties and instantiation

Extend the Closure::bind() scope binding beyond class constants to the natural
remaining surface:

- MutatingScope::resolveName()/resolveTypeByName() now honour the bind scope
  annotated on self/parent/static class-name nodes, so static method-call return
  types, static property types and `new self/parent/static` resolve against the
  bound class even outside a class context (the closure body's type is inferred in
  the enclosing scope where the closure-bind scope classes are otherwise absent).
- StaticMethodCallCheck, AccessStaticPropertiesCheck and InstantiationRule no
  longer report "outside of class scope" for self/parent/static inside a bound
  closure, resolving accessibility against the bound class.
- ClosureBindArgVisitor now only rescopes the closure body (1st argument), not the
  $newThis/$newScope arguments, avoiding a self-referential scope annotation on a
  `self::class` scope argument.

Continues the reconstruction of PR phpstan#4081 on 2.2.x.
@zonuexe
zonuexe force-pushed the fix/bind-class-scope branch from 8d9f662 to 0b15116 Compare July 14, 2026 13:23
zonuexe added 2 commits July 20, 2026 14:14
Annotate special class-name nodes (self/parent/static) that appear inside a
Closure::bind() call with the bind scope argument via ClosureBindArgVisitor, and
resolve the class constant type against that bound class in ClassConstFetchHandler.

Reconstruction of PR phpstan#4081 (closure-bind-scope) rebased onto 2.2.x: the original
MutatingScope::getType() ClassConstFetch branch no longer exists after expression
handling was split into ExprHandler services.
Extract the bind-scope resolution into a shared ClosureBindScopeResolver service
and use it both in ClassConstFetchHandler (type) and ClassConstantRule (rules), so
self::/parent::/static:: class constants inside a bound closure no longer report
"Using self outside of class scope" and resolve accessibility against the bound class.
zonuexe added a commit to zonuexe/phpstan-src that referenced this pull request Jul 20, 2026
… properties and instantiation

Extend the Closure::bind() scope binding beyond class constants to the natural
remaining surface:

- MutatingScope::resolveName()/resolveTypeByName() now honour the bind scope
  annotated on self/parent/static class-name nodes, so static method-call return
  types, static property types and `new self/parent/static` resolve against the
  bound class even outside a class context (the closure body's type is inferred in
  the enclosing scope where the closure-bind scope classes are otherwise absent).
- StaticMethodCallCheck, AccessStaticPropertiesCheck and InstantiationRule no
  longer report "outside of class scope" for self/parent/static inside a bound
  closure, resolving accessibility against the bound class.
- ClosureBindArgVisitor now only rescopes the closure body (1st argument), not the
  $newThis/$newScope arguments, avoiding a self-referential scope annotation on a
  `self::class` scope argument.

Continues the reconstruction of PR phpstan#4081 on 2.2.x.
@zonuexe
zonuexe force-pushed the fix/bind-class-scope branch from 0b15116 to 58a5dde Compare July 20, 2026 05:15
… properties and instantiation

Extend the Closure::bind() scope binding beyond class constants to the natural
remaining surface:

- MutatingScope::resolveName()/resolveTypeByName() now honour the bind scope
  annotated on self/parent/static class-name nodes, so static method-call return
  types, static property types and `new self/parent/static` resolve against the
  bound class even outside a class context (the closure body's type is inferred in
  the enclosing scope where the closure-bind scope classes are otherwise absent).
- StaticMethodCallCheck, AccessStaticPropertiesCheck and InstantiationRule no
  longer report "outside of class scope" for self/parent/static inside a bound
  closure, resolving accessibility against the bound class.
- ClosureBindArgVisitor now only rescopes the closure body (1st argument), not the
  $newThis/$newScope arguments, avoiding a self-referential scope annotation on a
  `self::class` scope argument.

Continues the reconstruction of PR phpstan#4081 on 2.2.x.
@zonuexe
zonuexe force-pushed the fix/bind-class-scope branch from 58a5dde to 514ad90 Compare July 20, 2026 05:33
@zonuexe
zonuexe marked this pull request as ready for review July 20, 2026 05:41
@phpstan-bot

Copy link
Copy Markdown
Collaborator

This pull request has been marked as ready for review.

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.

2 participants