Skip to content

Commit 9340249

Browse files
committed
Fix resolving self and static in @phpstan-closure-this from trait stub file
1 parent 9ff5aaf commit 9340249

File tree

10 files changed

+211
-63
lines changed

10 files changed

+211
-63
lines changed

src/Analyser/NameScope.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,20 @@ public function withTemplateTypeMap(TemplateTypeMap $map): self
171171
);
172172
}
173173

174+
public function withClassName(string $className): self
175+
{
176+
return new self(
177+
$this->namespace,
178+
$this->uses,
179+
$className,
180+
$this->functionName,
181+
$this->templateTypeMap,
182+
$this->typeAliasesMap,
183+
$this->bypassTypeAliases,
184+
$this->constUses,
185+
);
186+
}
187+
174188
public function unsetTemplateType(string $name): self
175189
{
176190
$map = $this->templateTypeMap;

src/Analyser/NodeScopeResolver.php

Lines changed: 51 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2636,67 +2636,63 @@ static function (): void {
26362636
$impurePoints = array_merge($impurePoints, $result->getImpurePoints());
26372637
$scope = $result->getScope();
26382638
} elseif ($expr->class instanceof Name) {
2639-
$className = $scope->resolveName($expr->class);
2640-
if ($this->reflectionProvider->hasClass($className)) {
2641-
$classReflection = $this->reflectionProvider->getClass($className);
2642-
$methodName = $expr->name->name;
2643-
if ($classReflection->hasMethod($methodName)) {
2644-
$methodReflection = $classReflection->getMethod($methodName, $scope);
2645-
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
2646-
$scope,
2647-
$expr->getArgs(),
2648-
$methodReflection->getVariants(),
2649-
$methodReflection->getNamedArgumentsVariants(),
2650-
);
2639+
$classType = $scope->resolveTypeByName($expr->class);
2640+
$methodName = $expr->name->name;
2641+
if ($classType->hasMethod($methodName)->yes()) {
2642+
$methodReflection = $classType->getMethod($methodName, $scope);
2643+
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
2644+
$scope,
2645+
$expr->getArgs(),
2646+
$methodReflection->getVariants(),
2647+
$methodReflection->getNamedArgumentsVariants(),
2648+
);
26512649

2652-
$methodThrowPoint = $this->getStaticMethodThrowPoint($methodReflection, $parametersAcceptor, $expr, $scope);
2653-
if ($methodThrowPoint !== null) {
2654-
$throwPoints[] = $methodThrowPoint;
2655-
}
2656-
if (
2657-
$classReflection->getName() === 'Closure'
2658-
&& strtolower($methodName) === 'bind'
2659-
) {
2660-
$thisType = null;
2661-
$nativeThisType = null;
2662-
if (isset($expr->getArgs()[1])) {
2663-
$argType = $scope->getType($expr->getArgs()[1]->value);
2664-
if ($argType->isNull()->yes()) {
2665-
$thisType = null;
2666-
} else {
2667-
$thisType = $argType;
2668-
}
2650+
$methodThrowPoint = $this->getStaticMethodThrowPoint($methodReflection, $parametersAcceptor, $expr, $scope);
2651+
if ($methodThrowPoint !== null) {
2652+
$throwPoints[] = $methodThrowPoint;
2653+
}
26692654

2670-
$nativeArgType = $scope->getNativeType($expr->getArgs()[1]->value);
2671-
if ($nativeArgType->isNull()->yes()) {
2672-
$nativeThisType = null;
2673-
} else {
2674-
$nativeThisType = $nativeArgType;
2675-
}
2655+
$declaringClass = $methodReflection->getDeclaringClass();
2656+
if (
2657+
$declaringClass->getName() === 'Closure'
2658+
&& strtolower($methodName) === 'bind'
2659+
) {
2660+
$thisType = null;
2661+
$nativeThisType = null;
2662+
if (isset($expr->getArgs()[1])) {
2663+
$argType = $scope->getType($expr->getArgs()[1]->value);
2664+
if ($argType->isNull()->yes()) {
2665+
$thisType = null;
2666+
} else {
2667+
$thisType = $argType;
2668+
}
2669+
2670+
$nativeArgType = $scope->getNativeType($expr->getArgs()[1]->value);
2671+
if ($nativeArgType->isNull()->yes()) {
2672+
$nativeThisType = null;
2673+
} else {
2674+
$nativeThisType = $nativeArgType;
26762675
}
2677-
$scopeClasses = ['static'];
2678-
if (isset($expr->getArgs()[2])) {
2679-
$argValue = $expr->getArgs()[2]->value;
2680-
$argValueType = $scope->getType($argValue);
2681-
2682-
$scopeClasses = [];
2683-
$directClassNames = $argValueType->getObjectClassNames();
2684-
if (count($directClassNames) > 0) {
2685-
$scopeClasses = $directClassNames;
2686-
$thisTypes = [];
2687-
foreach ($directClassNames as $directClassName) {
2688-
$thisTypes[] = new ObjectType($directClassName);
2689-
}
2690-
$thisType = TypeCombinator::union(...$thisTypes);
2691-
} else {
2692-
$thisType = $argValueType->getClassStringObjectType();
2693-
$scopeClasses = $thisType->getObjectClassNames();
2676+
}
2677+
$scopeClasses = ['static'];
2678+
if (isset($expr->getArgs()[2])) {
2679+
$argValue = $expr->getArgs()[2]->value;
2680+
$argValueType = $scope->getType($argValue);
2681+
2682+
$directClassNames = $argValueType->getObjectClassNames();
2683+
if (count($directClassNames) > 0) {
2684+
$scopeClasses = $directClassNames;
2685+
$thisTypes = [];
2686+
foreach ($directClassNames as $directClassName) {
2687+
$thisTypes[] = new ObjectType($directClassName);
26942688
}
2689+
$thisType = TypeCombinator::union(...$thisTypes);
2690+
} else {
2691+
$thisType = $argValueType->getClassStringObjectType();
2692+
$scopeClasses = $thisType->getObjectClassNames();
26952693
}
2696-
$closureBindScope = $scope->enterClosureBind($thisType, $nativeThisType, $scopeClasses);
26972694
}
2698-
} else {
2699-
$throwPoints[] = ThrowPoint::createImplicit($scope, $expr);
2695+
$closureBindScope = $scope->enterClosureBind($thisType, $nativeThisType, $scopeClasses);
27002696
}
27012697
} else {
27022698
$throwPoints[] = ThrowPoint::createImplicit($scope, $expr);

src/PhpDoc/PhpDocInheritanceResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ private function docBlockToResolvedDocBlock(PhpDocBlock $phpDocBlock, ?string $t
119119
$classReflection = $phpDocBlock->getClassReflection();
120120
if ($functionName !== null && $classReflection->getNativeReflection()->hasMethod($functionName)) {
121121
$methodReflection = $classReflection->getNativeReflection()->getMethod($functionName);
122-
$stub = $this->stubPhpDocProvider->findMethodPhpDoc($classReflection->getName(), $functionName, array_map(static fn (ReflectionParameter $parameter): string => $parameter->getName(), $methodReflection->getParameters()));
122+
$stub = $this->stubPhpDocProvider->findMethodPhpDoc($classReflection->getName(), $classReflection->getName(), $functionName, array_map(static fn (ReflectionParameter $parameter): string => $parameter->getName(), $methodReflection->getParameters()));
123123
if ($stub !== null) {
124124
return $stub;
125125
}

src/PhpDoc/ResolvedPhpDocBlock.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public static function create(
161161
ReflectionProvider $reflectionProvider,
162162
): self
163163
{
164-
// new property also needs to be added to createEmpty() and merge()
164+
// new property also needs to be added to withNameScope(), createEmpty() and merge()
165165
$self = new self();
166166
$self->phpDocNode = $phpDocNode;
167167
$self->phpDocNodes = [$phpDocNode];
@@ -176,6 +176,22 @@ public static function create(
176176
return $self;
177177
}
178178

179+
public function withNameScope(NameScope $nameScope): self
180+
{
181+
$self = new self();
182+
$self->phpDocNode = $this->phpDocNode;
183+
$self->phpDocNodes = $this->phpDocNodes;
184+
$self->phpDocString = $this->phpDocString;
185+
$self->filename = $this->filename;
186+
$self->nameScope = $nameScope;
187+
$self->templateTypeMap = $this->templateTypeMap;
188+
$self->templateTags = $this->templateTags;
189+
$self->phpDocNodeResolver = $this->phpDocNodeResolver;
190+
$self->reflectionProvider = $this->reflectionProvider;
191+
192+
return $self;
193+
}
194+
179195
public static function createEmpty(): self
180196
{
181197
// new property also needs to be added to merge()

src/PhpDoc/StubPhpDocProvider.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,12 @@ public function findClassConstantPhpDoc(string $className, string $constantName)
146146
/**
147147
* @param array<int, string> $positionalParameterNames
148148
*/
149-
public function findMethodPhpDoc(string $className, string $methodName, array $positionalParameterNames): ?ResolvedPhpDocBlock
149+
public function findMethodPhpDoc(
150+
string $className,
151+
string $implementingClassName,
152+
string $methodName,
153+
array $positionalParameterNames,
154+
): ?ResolvedPhpDocBlock
150155
{
151156
if (!$this->isKnownClass($className)) {
152157
return null;
@@ -170,6 +175,12 @@ public function findMethodPhpDoc(string $className, string $methodName, array $p
170175
throw new ShouldNotHappenException();
171176
}
172177

178+
if ($className !== $implementingClassName && $resolvedPhpDoc->getNullableNameScope() !== null) {
179+
$resolvedPhpDoc = $resolvedPhpDoc->withNameScope(
180+
$resolvedPhpDoc->getNullableNameScope()->withClassName($implementingClassName),
181+
);
182+
}
183+
173184
$methodParameterNames = $this->knownMethodsParameterNames[$className][$methodName];
174185
$parameterNameMapping = [];
175186
foreach ($positionalParameterNames as $i => $parameterName) {

src/Reflection/Php/PhpClassReflectionExtension.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ private function createMethod(
502502
$stubImmediatelyInvokedCallableParameters = [];
503503
$stubClosureThisParameters = [];
504504
if (count($methodSignatures) === 1) {
505-
$stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors($declaringClass, $methodReflection->getName(), array_map(static fn (ParameterSignature $parameterSignature): string => $parameterSignature->getName(), $methodSignature->getParameters()));
505+
$stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors($declaringClass, $declaringClass, $methodReflection->getName(), array_map(static fn (ParameterSignature $parameterSignature): string => $parameterSignature->getName(), $methodSignature->getParameters()));
506506
if ($stubPhpDocPair !== null) {
507507
[$stubPhpDoc, $stubDeclaringClass] = $stubPhpDocPair;
508508
$templateTypeMap = $stubDeclaringClass->getActiveTemplateTypeMap();
@@ -637,7 +637,7 @@ private function createMethod(
637637
public function createUserlandMethodReflection(ClassReflection $fileDeclaringClass, ClassReflection $actualDeclaringClass, BuiltinMethodReflection $methodReflection, ?string $declaringTraitName): PhpMethodReflection
638638
{
639639
$resolvedPhpDoc = null;
640-
$stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors($fileDeclaringClass, $methodReflection->getName(), array_map(static fn (ReflectionParameter $parameter): string => $parameter->getName(), $methodReflection->getParameters()));
640+
$stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors($fileDeclaringClass, $fileDeclaringClass, $methodReflection->getName(), array_map(static fn (ReflectionParameter $parameter): string => $parameter->getName(), $methodReflection->getParameters()));
641641
$phpDocBlockClassReflection = $fileDeclaringClass;
642642

643643
if ($methodReflection->getReflection() !== null) {
@@ -647,6 +647,7 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla
647647
if (! $methodReflection->getDeclaringClass()->isTrait() || $methodDeclaringClass->getName() !== $methodReflection->getDeclaringClass()->getName()) {
648648
$stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors(
649649
$this->reflectionProviderProvider->getReflectionProvider()->getClass($methodDeclaringClass->getName()),
650+
$this->reflectionProviderProvider->getReflectionProvider()->getClass($methodReflection->getDeclaringClass()->getName()),
650651
$methodReflection->getName(),
651652
array_map(
652653
static fn (ReflectionParameter $parameter): string => $parameter->getName(),
@@ -1126,10 +1127,15 @@ private function getPhpDocReturnType(ClassReflection $phpDocBlockClassReflection
11261127
* @param array<int, string> $positionalParameterNames
11271128
* @return array{ResolvedPhpDocBlock, ClassReflection}|null
11281129
*/
1129-
private function findMethodPhpDocIncludingAncestors(ClassReflection $declaringClass, string $methodName, array $positionalParameterNames): ?array
1130+
private function findMethodPhpDocIncludingAncestors(
1131+
ClassReflection $declaringClass,
1132+
ClassReflection $implementingClass,
1133+
string $methodName,
1134+
array $positionalParameterNames,
1135+
): ?array
11301136
{
11311137
$declaringClassName = $declaringClass->getName();
1132-
$resolved = $this->stubPhpDocProvider->findMethodPhpDoc($declaringClassName, $methodName, $positionalParameterNames);
1138+
$resolved = $this->stubPhpDocProvider->findMethodPhpDoc($declaringClassName, $implementingClass->getName(), $methodName, $positionalParameterNames);
11331139
if ($resolved !== null) {
11341140
return [$resolved, $declaringClass];
11351141
}
@@ -1146,7 +1152,7 @@ private function findMethodPhpDocIncludingAncestors(ClassReflection $declaringCl
11461152
continue;
11471153
}
11481154

1149-
$resolved = $this->stubPhpDocProvider->findMethodPhpDoc($ancestor->getName(), $methodName, $positionalParameterNames);
1155+
$resolved = $this->stubPhpDocProvider->findMethodPhpDoc($ancestor->getName(), $ancestor->getName(), $methodName, $positionalParameterNames);
11501156
if ($resolved === null) {
11511157
continue;
11521158
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PHPStan\Testing\TypeInferenceTestCase;
6+
7+
class Bug11009Test extends TypeInferenceTestCase
8+
{
9+
10+
public function dataFileAsserts(): iterable
11+
{
12+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-11009.php');
13+
}
14+
15+
/**
16+
* @dataProvider dataFileAsserts
17+
* @param mixed ...$args
18+
*/
19+
public function testFileAsserts(
20+
string $assertType,
21+
string $file,
22+
...$args,
23+
): void
24+
{
25+
$this->assertFileAsserts($assertType, $file, ...$args);
26+
}
27+
28+
public static function getAdditionalConfigFiles(): array
29+
{
30+
return [
31+
__DIR__ . '/../../../conf/bleedingEdge.neon',
32+
__DIR__ . '/bug-11009.neon',
33+
];
34+
}
35+
36+
}

tests/PHPStan/Analyser/bug-11009.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
parameters:
2+
stubFiles:
3+
- data/bug-11009.stub
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Bug11009;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
trait A
8+
{
9+
public static function callbackStatic(callable $cb): void
10+
{
11+
}
12+
13+
public static function callbackSelf(callable $cb): void
14+
{
15+
}
16+
17+
public function returnStatic()
18+
{
19+
return $this;
20+
}
21+
22+
public function returnSelf()
23+
{
24+
return new self;
25+
}
26+
}
27+
28+
class B
29+
{
30+
use A;
31+
}
32+
33+
function (): void {
34+
B::callbackStatic(function (): void {
35+
assertType(B::class, $this);
36+
});
37+
38+
B::callbackSelf(function (): void {
39+
assertType(B::class, $this);
40+
});
41+
42+
$b = new B();
43+
assertType(B::class, $b->returnStatic());
44+
assertType(B::class, $b->returnSelf());
45+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Bug11009;
4+
5+
trait A {
6+
/**
7+
* @param-closure-this static $cb
8+
*/
9+
public static function callbackStatic(callable $cb): void {}
10+
11+
/**
12+
* @param-closure-this self $cb
13+
*/
14+
public static function callbackSelf(callable $cb): void {}
15+
16+
/** @return static */
17+
public function returnStatic() {}
18+
19+
/** @return self */
20+
public function returnSelf() {}
21+
}

0 commit comments

Comments
 (0)