Skip to content

Commit 1b48917

Browse files
Fix ConstantArrayType::isCallable
1 parent 5e3a364 commit 1b48917

File tree

3 files changed

+76
-7
lines changed

3 files changed

+76
-7
lines changed

src/Type/Constant/ConstantArrayType.php

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -458,17 +458,41 @@ public function equals(Type $type): bool
458458

459459
public function isCallable(): TrinaryLogic
460460
{
461-
$typeAndMethods = $this->findTypeAndMethodNames();
462-
if ($typeAndMethods === []) {
461+
$callableArray = $this->getClassOrObjectAndMethods();
462+
if ($callableArray === []) {
463463
return TrinaryLogic::createNo();
464464
}
465465

466-
$results = array_map(
467-
static fn (ConstantArrayTypeAndMethod $typeAndMethod): TrinaryLogic => $typeAndMethod->getCertainty(),
468-
$typeAndMethods,
469-
);
466+
[$classOrObject, $methods] = $callableArray;
467+
if (count($methods->getConstantStrings()) === 0) {
468+
return TrinaryLogic::createMaybe();
469+
}
470+
471+
$type = $classOrObject->getObjectTypeOrClassStringObjectType();
472+
if (!$type->isObject()->yes()) {
473+
return TrinaryLogic::createMaybe();
474+
}
475+
476+
$hasMethodTrinary = [];
477+
$phpVersion = PhpVersionStaticAccessor::getInstance();
478+
foreach ($methods->getConstantStrings() as $method) {
479+
$has = $type->hasMethod($method->getValue());
480+
481+
if ($has->yes()) {
482+
if (!$phpVersion->supportsCallableInstanceMethods()) {
483+
$methodReflection = $type->getMethod($method->getValue(), new OutOfClassScope());
484+
if ($classOrObject->isString()->yes() && !$methodReflection->isStatic()) {
485+
$has = $has->and(TrinaryLogic::createNo());
486+
}
487+
} elseif ($this->isOptionalKey(0) || $this->isOptionalKey(1)) {
488+
$has = $has->and(TrinaryLogic::createMaybe());
489+
}
490+
}
491+
492+
$hasMethodTrinary[] = $has;
493+
}
470494

471-
return TrinaryLogic::createYes()->and(...$results);
495+
return TrinaryLogic::extremeIdentity(...$hasMethodTrinary);
472496
}
473497

474498
public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array

tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,4 +1102,11 @@ public function testAlwaysTruePregMatch(): void
11021102
$this->analyse([__DIR__ . '/data/always-true-preg-match.php'], []);
11031103
}
11041104

1105+
public function testBug12063(): void
1106+
{
1107+
$this->checkAlwaysTrueCheckTypeFunctionCall = true;
1108+
$this->treatPhpDocTypesAsCertain = true;
1109+
$this->analyse([__DIR__ . '/data/bug-12063.php'], []);
1110+
}
1111+
11051112
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php declare(strict_types=1); // lint >= 7.4
2+
3+
namespace Bug12063;
4+
5+
use BadFunctionCallException;
6+
7+
final class View
8+
{
9+
public function existingMethod(): void
10+
{
11+
}
12+
}
13+
14+
final class TwigExtension
15+
{
16+
private View $viewFunctions;
17+
18+
public function __construct(View $viewFunctions)
19+
{
20+
$this->viewFunctions = $viewFunctions;
21+
}
22+
23+
public function iterateFunctions(): void
24+
{
25+
$functionMappings = [
26+
'i_exist' => 'existingMethod',
27+
'i_dont_exist' => 'nonExistingMethod'
28+
];
29+
30+
$functions = [];
31+
foreach ($functionMappings as $nameFrom => $nameTo) {
32+
$callable = [$this->viewFunctions, $nameTo];
33+
if (!is_callable($callable)) {
34+
throw new BadFunctionCallException("Function $nameTo does not exist in view functions");
35+
}
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)