Skip to content

Commit c91fead

Browse files
authored
Fix reflection error on unknown symbols (#273)
1 parent a44294e commit c91fead

File tree

5 files changed

+102
-3
lines changed

5 files changed

+102
-3
lines changed

src/Rule/ForbidCheckedExceptionInCallableRule.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ public function processFirstClassCallable(
175175
$errors = array_merge($errors, $this->processCall($scope, $callerType, $methodName, $line, $nodeHash));
176176
}
177177

178-
if ($callNode instanceof FuncCall && $callNode->name instanceof Name) {
178+
if ($callNode instanceof FuncCall && $callNode->name instanceof Name && $this->reflectionProvider->hasFunction($callNode->name, $scope)) {
179179
$functionReflection = $this->reflectionProvider->getFunction($callNode->name, $scope);
180180
$errors = array_merge($errors, $this->processThrowType($functionReflection->getThrowType(), $scope, $line, $nodeHash));
181181
}
@@ -427,7 +427,7 @@ private function whitelistAllowedCallables(CallLike $node, Scope $scope): void
427427

428428
} elseif ($node instanceof FuncCall && $node->name instanceof Name) {
429429
$callerType = null;
430-
$methodReflection = $this->reflectionProvider->getFunction($node->name, $scope);
430+
$methodReflection = $this->getFunctionReflection($node->name, $scope);
431431

432432
} elseif ($node instanceof FuncCall && $this->isFirstClassCallableOrClosureOrArrowFunction($node->name)) { // immediately called callable syntax
433433
$this->allowedCallables[spl_object_hash($node->name)] = true;
@@ -534,4 +534,11 @@ private function buildError(
534534
return $builder->build();
535535
}
536536

537+
private function getFunctionReflection(Name $functionName, Scope $scope): ?FunctionReflection
538+
{
539+
return $this->reflectionProvider->hasFunction($functionName, $scope)
540+
? $this->reflectionProvider->getFunction($functionName, $scope)
541+
: null;
542+
}
543+
537544
}

src/Rule/ForbidCustomFunctionsRule.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ private function validateCallOverExpr(array $methodNames, Expr $expr, Scope $sco
171171
*/
172172
private function validateMethod(array $methodNames, string $className): array
173173
{
174+
if (!$this->reflectionProvider->hasClass($className)) {
175+
return [];
176+
}
177+
174178
$errors = [];
175179

176180
foreach ($this->reflectionProvider->getClass($className)->getAncestors() as $ancestor) {

src/Rule/ForbidEnumInFunctionArgumentsRule.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PhpParser\Node\Name;
88
use PHPStan\Analyser\ArgumentsNormalizer;
99
use PHPStan\Analyser\Scope;
10+
use PHPStan\Reflection\FunctionReflection;
1011
use PHPStan\Reflection\ParametersAcceptorSelector;
1112
use PHPStan\Reflection\ReflectionProvider;
1213
use PHPStan\Rules\IdentifierRuleError;
@@ -86,7 +87,12 @@ public function processNode(Node $node, Scope $scope): array
8687

8788
$wrongArguments = [];
8889

89-
$functionReflection = $this->reflectionProvider->getFunction($node->name, $scope);
90+
$functionReflection = $this->getFunctionReflection($node->name, $scope);
91+
92+
if ($functionReflection === null) {
93+
return [];
94+
}
95+
9096
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $node->getArgs(), $functionReflection->getVariants());
9197
$funcCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node);
9298

@@ -150,4 +156,11 @@ private function containsEnum(Type $type): bool
150156
return $type->isEnum()->yes();
151157
}
152158

159+
private function getFunctionReflection(Name $functionName, Scope $scope): ?FunctionReflection
160+
{
161+
return $this->reflectionProvider->hasFunction($functionName, $scope)
162+
? $this->reflectionProvider->getFunction($functionName, $scope)
163+
: null;
164+
}
165+
153166
}

tests/Rule/data/unknown-symbol.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace UnknownSymbol;
4+
5+
class A {
6+
public function aMethod(): bool {
7+
return true;
8+
}
9+
}
10+
11+
function doFoo(B $b) {
12+
/** @var A $a */
13+
$a = createMagically();
14+
$a->aMethod();
15+
$b->bMethod();
16+
}
17+

tests/UnknownSymbolTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace ShipMonk\PHPStan;
4+
5+
use PHPStan\Analyser\Analyser;
6+
use PHPStan\Analyser\AnalyserResultFinalizer;
7+
use PHPStan\Analyser\Error;
8+
use PHPStan\Testing\PHPStanTestCase;
9+
use function array_map;
10+
use function array_merge;
11+
12+
class UnknownSymbolTest extends PHPStanTestCase
13+
{
14+
15+
/**
16+
* @return string[]
17+
*/
18+
public static function getAdditionalConfigFiles(): array
19+
{
20+
return array_merge(
21+
parent::getAdditionalConfigFiles(),
22+
[__DIR__ . '/../rules.neon'],
23+
);
24+
}
25+
26+
public function testNoInternalError(): void
27+
{
28+
$errors = $this->runAnalyser([__DIR__ . '/Rule/data/unknown-symbol.php']);
29+
30+
$internalErrors = [];
31+
32+
foreach ($errors as $error) {
33+
if ($error->getIdentifier() === 'phpstan.internal') {
34+
$internalErrors[] = $error->getMessage();
35+
}
36+
}
37+
38+
self::assertSame([], $internalErrors);
39+
}
40+
41+
/**
42+
* @param list<string> $filePaths
43+
* @return list<Error>
44+
*/
45+
private function runAnalyser(array $filePaths): array
46+
{
47+
$analyser = self::getContainer()->getByType(Analyser::class); // @phpstan-ignore phpstanApi.classConstant
48+
$finalizer = self::getContainer()->getByType(AnalyserResultFinalizer::class); // @phpstan-ignore phpstanApi.classConstant
49+
50+
$normalizedFilePaths = array_map(fn (string $path): string => $this->getFileHelper()->normalizePath($path), $filePaths);
51+
52+
$analyserResult = $analyser->analyse($normalizedFilePaths);
53+
$analyserResult = $finalizer->finalize($analyserResult, true, false)->getAnalyserResult();
54+
55+
return $analyserResult->getErrors();
56+
}
57+
58+
}

0 commit comments

Comments
 (0)