Skip to content

Commit

Permalink
Handle allowed static invocations when in the class.
Browse files Browse the repository at this point in the history
  • Loading branch information
mglaman committed Nov 11, 2021
1 parent 18d1f10 commit a9cfcd5
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 31 deletions.
2 changes: 1 addition & 1 deletion src/Rules/DeadCode/UnusedPrivateMethodRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public function processNode(Node $node, Scope $scope): array
if (!$arrayType instanceof ConstantArrayType) {
continue;
}
$typeAndMethod = $arrayType->findTypeAndMethodName();
$typeAndMethod = $arrayType->findTypeAndMethodName($scope);
if ($typeAndMethod === null) {
continue;
}
Expand Down
14 changes: 9 additions & 5 deletions src/Type/Constant/ConstantArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ public function equals(Type $type): bool

public function isCallable(): TrinaryLogic
{
$typeAndMethod = $this->findTypeAndMethodName();
$typeAndMethod = $this->findTypeAndMethodName(new OutOfClassScope());
if ($typeAndMethod === null) {
return TrinaryLogic::createNo();
}
Expand All @@ -329,7 +329,7 @@ public function isCallable(): TrinaryLogic
*/
public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
{
$typeAndMethodName = $this->findTypeAndMethodName();
$typeAndMethodName = $this->findTypeAndMethodName($scope);
if ($typeAndMethodName === null) {
throw new \PHPStan\ShouldNotHappenException();
}
Expand All @@ -348,7 +348,7 @@ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope)
return $method->getVariants();
}

public function findTypeAndMethodName(): ?ConstantArrayTypeAndMethod
public function findTypeAndMethodName(?ClassMemberAccessAnswerer $scope): ?ConstantArrayTypeAndMethod
{
if (count($this->keyTypes) !== 2) {
return null;
Expand Down Expand Up @@ -390,12 +390,16 @@ public function findTypeAndMethodName(): ?ConstantArrayTypeAndMethod
if ($this->isOptionalKey(0) || $this->isOptionalKey(1)) {
$has = $has->and(TrinaryLogic::createMaybe());
}
if (!$scope) {
$scope = new OutOfClassScope();
}
if (
$isClassString
&& $has->yes()
&& !$type->getMethod($method->getValue(), new OutOfClassScope())->isStatic()
&& !$scope->isInClass()
&& !$type->getMethod($method->getValue(), $scope)->isStatic()
) {
return null;
$has = $has->and(TrinaryLogic::createMaybe());
}

return ConstantArrayTypeAndMethod::createConcrete($type, $method->getValue(), $has);
Expand Down
12 changes: 11 additions & 1 deletion src/Type/Constant/ConstantStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,17 @@ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope)
if (!$scope->canCallMethod($method)) {
return [new InaccessibleMethod($method)];
}
if (!$method->isStatic()) {

if (!$scope->isInClass() && !$method->isStatic()) {
return [new InaccessibleMethod($method)];
}

if (
!$method->isStatic()
&& $scope->isInClass()
&& $scope->getClassReflection()->getName() !== $classReflection->getName()
&& !$scope->getClassReflection()->isSubclassOf($classReflection->getName())
) {
return [new InaccessibleMethod($method)];
}

Expand Down
8 changes: 4 additions & 4 deletions tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8236,20 +8236,20 @@ public function dataCallables(): array
'$closure()',
],
[
'*ERROR*',
'Callables\Bar',
'$arrayWithStaticMethod()',
],
[
'Callables\\Bar',
'$arrayWithStaticMethodActual()',
'$arrayWithStaticMethodActuallyStatic()',
],
[
'mixed',
'float',
'$stringWithStaticMethod()',
],
[
'float',
'$stringWithStaticMethodActual()',
'$stringWithStaticMethodActuallyStatic()',
],
[
'float',
Expand Down
8 changes: 4 additions & 4 deletions tests/PHPStan/Analyser/data/callables.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ public function doFoo(): float
};
$foo = $this;
$arrayWithStaticMethod = ['Callables\\Foo', 'doBar'];
$arrayWithStaticMethodActual = ['Callables\\Foo', 'doBarActual'];
$arrayWithStaticMethodActuallyStatic = ['Callables\\Foo', 'doBarActuallyStatic'];
$stringWithStaticMethod = 'Callables\\Foo::doFoo';
$stringWithStaticMethodActual = 'Callables\\Foo::doFooActual';
$stringWithStaticMethodActuallyStatic = 'Callables\\Foo::doFooActuallyStatic';
$arrayWithInstanceMethod = [$this, 'doFoo'];
die;
}

public static function doFooActual(): float {
public static function doFooActuallyStatic(): float {

}

Expand All @@ -28,7 +28,7 @@ public function doBar(): Bar

}

public static function doBarActual(): Bar
public static function doBarActuallyStatic(): Bar
{

}
Expand Down
21 changes: 10 additions & 11 deletions tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -432,28 +432,27 @@ public function testBug1971(): void
{
$this->checkThisOnly = false;
$this->analyse([__DIR__ . '/data/bug-1971.php'], [
[
'Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{\'Bug1971\\\HelloWorld\', \'sayHello\'} given.',
14,
],
[
'Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{class-string<static(Bug1971\\HelloWorld)>, \'sayHello\'} given.',
15,
],
[
'Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{class-string<static(Bug1971\\HelloWorld)>, \'sayHello2\'} given.',
16,
18,
],
]);
}

public function testBug5782(): void
{
if (PHP_VERSION_ID >= 80000) {
$this->markTestSkipped('Fatal error in PHP 8.0');
}
$this->checkThisOnly = false;
$this->analyse([__DIR__ . '/data/bug-5782.php'], [
[
'Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{class-string<static(Bug5782\\HelloWorld)>, \'sayHello2\'} given.',
16,
'Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{\'Bug5782\\\HelloWorld\', \'sayGoodbye\'} given.',
22,
],
[
'Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{\'Bug5782\\\HelloWorld\', \'sayGoodbye\'} given.',
23,
],
]);
}
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-1971.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public function getClosure(): void
{
$closure1 = \Closure::fromCallable([self::class, 'sayHello']);
$closure2 = \Closure::fromCallable([static::class, 'sayHello']);
$closure3 = \Closure::fromCallable('Bug1971\HelloWorld::sayHello');
$closure4 = \Closure::fromCallable([\Bug1971\HelloWorld::class, 'sayHello']);
$closure2 = \Closure::fromCallable([static::class, 'sayHello2']);
}
}
19 changes: 14 additions & 5 deletions tests/PHPStan/Rules/Methods/data/bug-5782.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,22 @@ class HelloWorld
{
public static function sayHello(): void
{
echo 'Hello';

}

public function getClosure(): void
public function sayGoodbye(): void
{
$closure1 = \Closure::fromCallable([self::class, 'sayHello']);
$closure2 = \Closure::fromCallable([static::class, 'sayHello']);
$closure2 = \Closure::fromCallable([static::class, 'sayHello2']);

}

}

function baz() {
$closure1 = \Closure::fromCallable([\Bug5782\HelloWorld::class, 'sayHello']);
$closure2 = \Closure::fromCallable('\Bug5782\HelloWorld::sayHello');
$closure3 = \Closure::fromCallable([\Bug5782\HelloWorld::class, 'sayGoodbye']);
// @todo this should also error on mixed type, but doesn't?
// ConstantStringType::getCallableParametersAcceptors returns a MixedType
// but that doesn't cause an error like ConstantArrayType.
$closure4 = \Closure::fromCallable('Bug5782\HelloWorld::sayGoodbye');
}

0 comments on commit a9cfcd5

Please sign in to comment.