Resolve self/parent/static in Closure::bind() scope - #4081
Conversation
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.
… 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.
b3c42c8 to
1fb897e
Compare
Closure::bind() scope binding improvements|
This pull request has been marked as ready for review. |
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.
… 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.
1fb897e to
8d9f662
Compare
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.
… 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.
8d9f662 to
0b15116
Compare
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.
… 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.
0b15116 to
58a5dde
Compare
… 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.
58a5dde to
514ad90
Compare
|
This pull request has been marked as ready for review. |
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.
… 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.
514ad90 to
2fc7cd8
Compare
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.
… 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.
2fc7cd8 to
5c4cac0
Compare
|
@SanderMuller please review :) |
|
Reviewed. I ran it rather than only reading it, and the approach holds up: the rescoping is confined to the closure body, everything the description advertises resolves, and the cases that fall outside it fail safe. Verified against
A closure nested inside a bound closure inherits the bind scope and resolves to The confinement claim is real, which was the part I most wanted to check. Inside a method of Gates on the head: full suite green (21288 tests, 96849 assertions), self-analysis clean. Nothing under Three cases sit outside the visitor. All of them decline to rescope rather than picking the wrong class, which is the right direction, but two are worth knowing about:
Three small code notes:
The single red job is On performance, since the visitor calls |
Resolves
self/parent/staticagainst theClosure::bind()scope (the 3rd argument) inside the bound closure body, for both type inference and the related rules.Rebased onto
2.2.xand reworked: the original inline patch inMutatingScope::getType()no longer exists after expression handling was split intoExprHandlerservices, so the logic now lives where each expression/rule resolves its class scope.What works now
Inside
Closure::bind(fn () => …, $newThis, Scope::class):self::CONST,parent::CONSTresolve against the bound scope.::class—self::class,parent::class.self::method(),parent::method()return types.self::$prop,parent::$proptypes.new self,new parent,new static.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 byClosureBindArgVisitor).Approach
ClosureBindArgVisitorannotatesself/parent/staticclass-name nodes inside the closure body only (1st argument) with the bind scope argument. The$newThis/$newScopearguments are intentionally not rescoped — this keepsClosure::bind(self::cond() ? … : …, …)correct (see #6319).ClosureBindScopeResolver(new) resolves that annotation to aClassReflection, shared byClassConstFetchHandlerand the rules.MutatingScope::resolveName()/resolveTypeByName()honour the annotation so static calls / properties /newresolve without touching each handler.Issues
self::/parent::constant,::class, static method, and private-member cases now resolve. (Remaining:static::late static binding and the bound$thisobject type — a separate concern, overlaps Narrow down static and $this together #2972.)Not included (follow-ups)
Closure::bindTo()(Support Closure::bindTo() phpstan#8569) — separate call, separate visitor path.static::LSB + bound$thistype (Closure::bind does not rebind correctly phpstan#5987 remainder) — overlaps the stalled Narrow down static and $this together #2972; better decided after review of this approach.