Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 22 additions & 24 deletions src/Rules/DeadCode/UnusedPrivateMethodRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,31 +120,29 @@ public function processNode(Node $node, Scope $scope): array
if (!$arrayType instanceof ConstantArrayType) {
continue;
}
$typeAndMethod = $arrayType->findTypeAndMethodName();
if ($typeAndMethod === null) {
continue;
}
if ($typeAndMethod->isUnknown()) {
return [];
}
if (!$typeAndMethod->getCertainty()->yes()) {
return [];
}
$calledOnType = $typeAndMethod->getType();
if ($classType->isSuperTypeOf($calledOnType)->no()) {
continue;
}
if ($calledOnType instanceof MixedType) {
continue;
}
$inMethod = $arrayScope->getFunction();
if (!$inMethod instanceof MethodReflection) {
continue;
}
if ($inMethod->getName() === $typeAndMethod->getMethod()) {
continue;
foreach ($arrayType->findTypeAndMethodNames() as $typeAndMethod) {
if ($typeAndMethod->isUnknown()) {
return [];
}
if (!$typeAndMethod->getCertainty()->yes()) {
return [];
}
$calledOnType = $typeAndMethod->getType();
if ($classType->isSuperTypeOf($calledOnType)->no()) {
continue;
}
if ($calledOnType instanceof MixedType) {
continue;
}
$inMethod = $arrayScope->getFunction();
if (!$inMethod instanceof MethodReflection) {
continue;
}
if ($inMethod->getName() === $typeAndMethod->getMethod()) {
continue;
}
unset($methods[$typeAndMethod->getMethod()]);
}
unset($methods[$typeAndMethod->getMethod()]);
}
}

Expand Down
99 changes: 86 additions & 13 deletions src/Type/Constant/ConstantArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\UnionType;
use PHPStan\Type\VerbosityLevel;
use function array_keys;
use function array_map;
use function array_merge;
use function array_pop;
use function array_push;
use function array_reverse;
use function array_slice;
use function array_unique;
Expand Down Expand Up @@ -340,38 +342,51 @@ public function equals(Type $type): bool

public function isCallable(): TrinaryLogic
{
$typeAndMethod = $this->findTypeAndMethodName();
if ($typeAndMethod === null) {
$typeAndMethods = $this->findTypeAndMethodNames();
if ($typeAndMethods === []) {
return TrinaryLogic::createNo();
}

return $typeAndMethod->getCertainty();
$results = array_map(
static fn (ConstantArrayTypeAndMethod $typeAndMethod): TrinaryLogic => $typeAndMethod->getCertainty(),
$typeAndMethods,
);

return TrinaryLogic::createYes()->and(...$results);
}

/**
* @return ParametersAcceptor[]
*/
public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
{
$typeAndMethodName = $this->findTypeAndMethodName();
if ($typeAndMethodName === null) {
$typeAndMethodNames = $this->findTypeAndMethodNames();
if ($typeAndMethodNames === []) {
throw new ShouldNotHappenException();
}

if ($typeAndMethodName->isUnknown() || !$typeAndMethodName->getCertainty()->yes()) {
return [new TrivialParametersAcceptor()];
}
$acceptors = [];
foreach ($typeAndMethodNames as $typeAndMethodName) {
if ($typeAndMethodName->isUnknown() || !$typeAndMethodName->getCertainty()->yes()) {
$acceptors[] = new TrivialParametersAcceptor();
continue;
}

$method = $typeAndMethodName->getType()
->getMethod($typeAndMethodName->getMethod(), $scope);
$method = $typeAndMethodName->getType()
->getMethod($typeAndMethodName->getMethod(), $scope);

if (!$scope->canCallMethod($method)) {
return [new InaccessibleMethod($method)];
if (!$scope->canCallMethod($method)) {
$acceptors[] = new InaccessibleMethod($method);
continue;
}

array_push($acceptors, ...$method->getVariants());
}

return $method->getVariants();
return $acceptors;
}

/** @deprecated Use findTypeAndMethodNames() instead */
public function findTypeAndMethodName(): ?ConstantArrayTypeAndMethod
Comment on lines +389 to 390
Copy link
Contributor Author

@herndlm herndlm Jul 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what the best approach here is. I wanted to play it safe and don't touch it at all. But findTypeAndMethodNames is basically a super set of this one. What I want to say - this most likely could be replaced by smth like

/** @deprecated Use findTypeAndMethodNames() instead  */
public function findTypeAndMethodName(): ?ConstantArrayTypeAndMethod
{
    $typeAndMethodNames = $this->findTypeAndMethodNames();
    return count($typeAndMethodNames) === 1 ? $typeAndMethodNames[0] : null;
}

Fyi inside of phpstan it is unused already.

{
if (count($this->keyTypes) !== 2) {
Expand Down Expand Up @@ -418,6 +433,64 @@ public function findTypeAndMethodName(): ?ConstantArrayTypeAndMethod
return null;
}

/** @return ConstantArrayTypeAndMethod[] */
public function findTypeAndMethodNames(): array
{
if (count($this->keyTypes) !== 2) {
return [];
}

if ($this->keyTypes[0]->isSuperTypeOf(new ConstantIntegerType(0))->no()) {
return [];
}

if ($this->keyTypes[1]->isSuperTypeOf(new ConstantIntegerType(1))->no()) {
return [];
}

[$classOrObjects, $methods] = $this->valueTypes;
$classOrObjects = TypeUtils::flattenTypes($classOrObjects);
$methods = TypeUtils::flattenTypes($methods);

$typeAndMethods = [];
foreach ($classOrObjects as $classOrObject) {
if ($classOrObject instanceof ConstantStringType) {
$reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
if (!$reflectionProvider->hasClass($classOrObject->getValue())) {
continue;
}
$type = new ObjectType($reflectionProvider->getClass($classOrObject->getValue())->getName());
} elseif ($classOrObject instanceof GenericClassStringType) {
$type = $classOrObject->getGenericType();
} elseif ((new ObjectWithoutClassType())->isSuperTypeOf($classOrObject)->yes()) {
$type = $classOrObject;
} else {
$typeAndMethods[] = ConstantArrayTypeAndMethod::createUnknown();
continue;
}

foreach ($methods as $method) {
if (!$method instanceof ConstantStringType) {
$typeAndMethods[] = ConstantArrayTypeAndMethod::createUnknown();
continue;
}

$has = $type->hasMethod($method->getValue());
if ($has->no()) {
continue;
}

if ($this->isOptionalKey(0) || $this->isOptionalKey(1)) {
$has = $has->and(TrinaryLogic::createMaybe());
}

$typeAndMethods[] = ConstantArrayTypeAndMethod::createConcrete($type, $method->getValue(), $has);
}
}

return $typeAndMethods;
}

public function hasOffsetValueType(Type $offsetType): TrinaryLogic
{
$offsetType = ArrayType::castToArrayKeyType($offsetType);
Expand Down
8 changes: 8 additions & 0 deletions tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ public function testRule(): void
'Trying to invoke array{\'CallCallables\\\\CallableInForeach\', \'bar\'|\'foo\'} but it might not be a callable.',
179,
],
[
'Trying to invoke array{\'CallCallables\\\\ConstantArrayUnionCallables\'|\'DateTimeImmutable\', \'doFoo\'} but it might not be a callable.',
205,
],
[
'Trying to invoke array{\'CallCallables\\\ConstantArrayUnionCallables\', \'doBaz\'|\'doFoo\'} but it might not be a callable.',
212,
],
]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1123,4 +1123,9 @@ public function testBug6781(): void
$this->analyse([__DIR__ . '/data/bug-6781.php'], []);
}

public function testBug2343(): void
{
$this->analyse([__DIR__ . '/data/bug-2343.php'], []);
}

}
26 changes: 26 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-2343.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php declare(strict_types = 1);

namespace Bug2343;

class A {
public static function say(string $name, int $age) {
echo "Name: {$name}, Age: {$age}\n";
}

public static function bye(string $name, int $age) {
echo "Bye, Name: {$name}, Age: {$age}\n";
}

public static function all() {
$name = 'Jack';
$age = 20;
$functions = [
'say',
'bye'
];

foreach ($functions as $function) {
call_user_func([__CLASS__, $function], $name, $age);
}
}
}
48 changes: 48 additions & 0 deletions tests/PHPStan/Rules/Functions/data/callables.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,51 @@ public function doFoo(bool $foo = true): void
}

}

class ConstantArrayUnionCallables
{

public function doFoo(): void
{
}

public function doBar(): void
{
}

public function invalidClass(): void
{
$class = rand(0, 1) ? __CLASS__ : \DateTimeImmutable::class;
$callable = [$class, 'doFoo'];
$callable();
}

public function invalidMethod(): void
{
$method = rand(0, 1) ? 'doFoo' : 'doBaz';
$callable = [__CLASS__, $method];
$callable();
}

public function classAndMethodValid(): void
{
$class = rand(0, 1) ? __CLASS__ : ConstantArrayUnionCallablesTest::class;
$method = rand(0, 1) ? 'doFoo' : 'doBar';
$callable = [$class, $method];
$callable();
}

}

class ConstantArrayUnionCallablesTest
{

public function doFoo(): void
{
}

public function doBar(): void
{
}

}