Skip to content

Commit 1605bb2

Browse files
committed
Code around class_exists() does not complain about nonexistent classes
1 parent 8b097e5 commit 1605bb2

32 files changed

+413
-23
lines changed

phpstan-baseline.neon

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@ parameters:
1515
count: 1
1616
path: src/Analyser/MutatingScope.php
1717

18-
-
19-
message: "#^Access to constant EXTENSIONS on an unknown class PHPStan\\\\ExtensionInstaller\\\\GeneratedConfig\\.$#"
20-
count: 1
21-
path: src/Command/CommandHelper.php
22-
2318
-
2419
message: "#^Anonymous function has an unused use \\$container\\.$#"
2520
count: 2

src/Analyser/DirectScopeFactory.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public function __construct(
7878
* @param bool $inFirstLevelStatement
7979
* @param array<string, true> $currentlyAssignedExpressions
8080
* @param array<string, Type> $nativeExpressionTypes
81+
* @param array<\PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection> $inFunctionCallsStack
8182
*
8283
* @return MutatingScope
8384
*/
@@ -92,7 +93,8 @@ public function create(
9293
?ParametersAcceptor $anonymousFunctionReflection = null,
9394
bool $inFirstLevelStatement = true,
9495
array $currentlyAssignedExpressions = [],
95-
array $nativeExpressionTypes = []
96+
array $nativeExpressionTypes = [],
97+
array $inFunctionCallsStack = []
9698
): MutatingScope
9799
{
98100
$scopeClass = $this->scopeClass;
@@ -119,6 +121,7 @@ public function create(
119121
$inFirstLevelStatement,
120122
$currentlyAssignedExpressions,
121123
$nativeExpressionTypes,
124+
$inFunctionCallsStack,
122125
$this->dynamicConstantNames,
123126
$this->treatPhpDocTypesAsCertain
124127
);

src/Analyser/LazyScopeFactory.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public function __construct(
4949
* @param bool $inFirstLevelStatement
5050
* @param array<string, true> $currentlyAssignedExpressions
5151
* @param array<string, Type> $nativeExpressionTypes
52+
* @param array<\PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection> $inFunctionCallsStack
5253
*
5354
* @return MutatingScope
5455
*/
@@ -63,7 +64,8 @@ public function create(
6364
?ParametersAcceptor $anonymousFunctionReflection = null,
6465
bool $inFirstLevelStatement = true,
6566
array $currentlyAssignedExpressions = [],
66-
array $nativeExpressionTypes = []
67+
array $nativeExpressionTypes = [],
68+
array $inFunctionCallsStack = []
6769
): MutatingScope
6870
{
6971
$scopeClass = $this->scopeClass;
@@ -90,6 +92,7 @@ public function create(
9092
$inFirstLevelStatement,
9193
$currentlyAssignedExpressions,
9294
$nativeExpressionTypes,
95+
$inFunctionCallsStack,
9396
$this->dynamicConstantNames,
9497
$this->treatPhpDocTypesAsCertain
9598
);

src/Analyser/MutatingScope.php

Lines changed: 94 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Nette\Utils\Strings;
66
use PhpParser\Node;
7+
use PhpParser\Node\Arg;
78
use PhpParser\Node\Expr;
89
use PhpParser\Node\Expr\Array_;
910
use PhpParser\Node\Expr\BinaryOp;
@@ -19,6 +20,7 @@
1920
use PhpParser\Node\Expr\PropertyFetch;
2021
use PhpParser\Node\Expr\Variable;
2122
use PhpParser\Node\Name;
23+
use PhpParser\Node\Name\FullyQualified;
2224
use PhpParser\Node\Scalar\DNumber;
2325
use PhpParser\Node\Scalar\EncapsedStringPart;
2426
use PhpParser\Node\Scalar\LNumber;
@@ -27,6 +29,7 @@
2729
use PHPStan\Reflection\ClassReflection;
2830
use PHPStan\Reflection\ConstantReflection;
2931
use PHPStan\Reflection\Dummy\DummyConstructorReflection;
32+
use PHPStan\Reflection\FunctionReflection;
3033
use PHPStan\Reflection\MethodReflection;
3134
use PHPStan\Reflection\Native\NativeParameterReflection;
3235
use PHPStan\Reflection\ParametersAcceptor;
@@ -149,6 +152,9 @@ class MutatingScope implements Scope
149152
/** @var array<string, true> */
150153
private $currentlyAssignedExpressions = [];
151154

155+
/** @var array<MethodReflection|FunctionReflection> */
156+
private $inFunctionCallsStack = [];
157+
152158
/** @var array<string, Type> */
153159
private $nativeExpressionTypes;
154160

@@ -177,6 +183,7 @@ class MutatingScope implements Scope
177183
* @param bool $inFirstLevelStatement
178184
* @param array<string, true> $currentlyAssignedExpressions
179185
* @param array<string, Type> $nativeExpressionTypes
186+
* @param array<MethodReflection|FunctionReflection> $inFunctionCallsStack
180187
* @param string[] $dynamicConstantNames
181188
* @paarm bool $treatPhpDocTypesAsCertain
182189
*/
@@ -199,6 +206,7 @@ public function __construct(
199206
bool $inFirstLevelStatement = true,
200207
array $currentlyAssignedExpressions = [],
201208
array $nativeExpressionTypes = [],
209+
array $inFunctionCallsStack = [],
202210
array $dynamicConstantNames = [],
203211
bool $treatPhpDocTypesAsCertain = true
204212
)
@@ -225,6 +233,7 @@ public function __construct(
225233
$this->inFirstLevelStatement = $inFirstLevelStatement;
226234
$this->currentlyAssignedExpressions = $currentlyAssignedExpressions;
227235
$this->nativeExpressionTypes = $nativeExpressionTypes;
236+
$this->inFunctionCallsStack = $inFunctionCallsStack;
228237
$this->dynamicConstantNames = $dynamicConstantNames;
229238
$this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain;
230239
}
@@ -1793,6 +1802,7 @@ public function doNotTreatPhpDocTypesAsCertain(): Scope
17931802
$this->inFirstLevelStatement,
17941803
$this->currentlyAssignedExpressions,
17951804
$this->nativeExpressionTypes,
1805+
$this->inFunctionCallsStack,
17961806
$this->dynamicConstantNames,
17971807
false
17981808
);
@@ -2027,6 +2037,74 @@ public function isSpecified(Expr $node): bool
20272037
&& $this->moreSpecificTypes[$exprString]->getCertainty()->yes();
20282038
}
20292039

2040+
/**
2041+
* @param MethodReflection|FunctionReflection $reflection
2042+
* @return self
2043+
*/
2044+
public function pushInFunctionCall($reflection): self
2045+
{
2046+
$stack = $this->inFunctionCallsStack;
2047+
$stack[] = $reflection;
2048+
2049+
return $this->scopeFactory->create(
2050+
$this->context,
2051+
$this->isDeclareStrictTypes(),
2052+
$this->getFunction(),
2053+
$this->getNamespace(),
2054+
$this->getVariableTypes(),
2055+
$this->moreSpecificTypes,
2056+
$this->inClosureBindScopeClass,
2057+
$this->anonymousFunctionReflection,
2058+
$this->isInFirstLevelStatement(),
2059+
$this->currentlyAssignedExpressions,
2060+
$this->nativeExpressionTypes,
2061+
$stack
2062+
);
2063+
}
2064+
2065+
public function popInFunctionCall(): self
2066+
{
2067+
$stack = $this->inFunctionCallsStack;
2068+
array_pop($stack);
2069+
2070+
return $this->scopeFactory->create(
2071+
$this->context,
2072+
$this->isDeclareStrictTypes(),
2073+
$this->getFunction(),
2074+
$this->getNamespace(),
2075+
$this->getVariableTypes(),
2076+
$this->moreSpecificTypes,
2077+
$this->inClosureBindScopeClass,
2078+
$this->anonymousFunctionReflection,
2079+
$this->isInFirstLevelStatement(),
2080+
$this->currentlyAssignedExpressions,
2081+
$this->nativeExpressionTypes,
2082+
$stack
2083+
);
2084+
}
2085+
2086+
public function isInClassExists(string $className): bool
2087+
{
2088+
foreach ($this->inFunctionCallsStack as $inFunctionCall) {
2089+
if (!$inFunctionCall instanceof FunctionReflection) {
2090+
continue;
2091+
}
2092+
2093+
if (in_array($inFunctionCall->getName(), [
2094+
'class_exists',
2095+
'interface_exists',
2096+
'trait_exists',
2097+
], true)) {
2098+
return true;
2099+
}
2100+
}
2101+
$expr = new FuncCall(new FullyQualified('class_exists'), [
2102+
new Arg(new String_($className)),
2103+
]);
2104+
2105+
return (new ConstantBooleanType(true))->isSuperTypeOf($this->getType($expr))->yes();
2106+
}
2107+
20302108
public function enterClass(ClassReflection $classReflection): self
20312109
{
20322110
return $this->scopeFactory->create(
@@ -2628,7 +2706,8 @@ public function assignVariable(string $variableName, Type $type, ?TrinaryLogic $
26282706
$this->anonymousFunctionReflection,
26292707
$this->inFirstLevelStatement,
26302708
$this->currentlyAssignedExpressions,
2631-
$nativeTypes
2709+
$nativeTypes,
2710+
$this->inFunctionCallsStack
26322711
);
26332712
}
26342713

@@ -2710,7 +2789,8 @@ public function specifyExpressionType(Expr $expr, Type $type): self
27102789
$this->anonymousFunctionReflection,
27112790
$this->inFirstLevelStatement,
27122791
$this->currentlyAssignedExpressions,
2713-
$nativeTypes
2792+
$nativeTypes,
2793+
$this->inFunctionCallsStack
27142794
);
27152795
} elseif ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) {
27162796
$constantArrays = TypeUtils::getConstantArrays($this->getType($expr->var));
@@ -2881,7 +2961,8 @@ public function exitFirstLevelStatements(): self
28812961
$this->anonymousFunctionReflection,
28822962
false,
28832963
$this->currentlyAssignedExpressions,
2884-
$this->nativeExpressionTypes
2964+
$this->nativeExpressionTypes,
2965+
$this->inFunctionCallsStack
28852966
);
28862967
}
28872968

@@ -2946,7 +3027,8 @@ public function mergeWith(?self $otherScope): self
29463027
array_map($typeToVariableHolder, $otherScope->nativeExpressionTypes)
29473028
), static function (VariableTypeHolder $holder): bool {
29483029
return $holder->getCertainty()->yes();
2949-
}))
3030+
})),
3031+
[]
29503032
);
29513033
}
29523034

@@ -3009,7 +3091,8 @@ public function processFinallyScope(self $finallyScope, self $originalFinallySco
30093091
array_map($typeToVariableHolder, $this->nativeExpressionTypes),
30103092
array_map($typeToVariableHolder, $finallyScope->nativeExpressionTypes),
30113093
array_map($typeToVariableHolder, $originalFinallyScope->nativeExpressionTypes)
3012-
))
3094+
)),
3095+
[]
30133096
);
30143097
}
30153098

@@ -3097,7 +3180,8 @@ public function processClosureScope(
30973180
$this->anonymousFunctionReflection,
30983181
$this->inFirstLevelStatement,
30993182
[],
3100-
$this->nativeExpressionTypes
3183+
$this->nativeExpressionTypes,
3184+
$this->inFunctionCallsStack
31013185
);
31023186
}
31033187

@@ -3142,7 +3226,8 @@ public function processAlwaysIterableForeachScopeWithoutPollute(self $finalScope
31423226
$this->anonymousFunctionReflection,
31433227
$this->inFirstLevelStatement,
31443228
[],
3145-
$nativeTypes
3229+
$nativeTypes,
3230+
[]
31463231
);
31473232
}
31483233

@@ -3180,7 +3265,8 @@ public function generalizeWith(self $otherScope): self
31803265
$this->anonymousFunctionReflection,
31813266
$this->inFirstLevelStatement,
31823267
[],
3183-
$nativeTypes
3268+
$nativeTypes,
3269+
[]
31843270
);
31853271
}
31863272

src/Analyser/NodeScopeResolver.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2120,6 +2120,10 @@ private function processArgs(
21202120
$parameters = $parametersAcceptor->getParameters();
21212121
}
21222122

2123+
if ($calleeReflection !== null) {
2124+
$scope = $scope->pushInFunctionCall($calleeReflection);
2125+
}
2126+
21232127
$hasYield = false;
21242128
foreach ($args as $i => $arg) {
21252129
$nodeCallback($arg, $scope);
@@ -2185,6 +2189,10 @@ private function processArgs(
21852189
$scope = $scope->restoreOriginalScopeAfterClosureBind($originalScope);
21862190
}
21872191

2192+
if ($calleeReflection !== null) {
2193+
$scope = $scope->popInFunctionCall();
2194+
}
2195+
21882196
return new ExpressionResult($scope, $hasYield);
21892197
}
21902198

src/Analyser/Scope.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ public function getTypeFromValue($value): Type;
6868

6969
public function isSpecified(Expr $node): bool;
7070

71+
public function isInClassExists(string $className): bool;
72+
7173
public function isInClosureBind(): bool;
7274

7375
public function isParameterValueNullable(Param $parameter): bool;

src/Analyser/ScopeFactory.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace PHPStan\Analyser;
44

5+
use PHPStan\Reflection\FunctionReflection;
6+
use PHPStan\Reflection\MethodReflection;
57
use PHPStan\Reflection\ParametersAcceptor;
68
use PHPStan\Type\Type;
79

@@ -20,6 +22,7 @@ interface ScopeFactory
2022
* @param bool $inFirstLevelStatement
2123
* @param array<string, true> $currentlyAssignedExpressions
2224
* @param array<string, Type> $nativeExpressionTypes
25+
* @param array<MethodReflection|FunctionReflection> $inFunctionCallsStack
2326
*
2427
* @return MutatingScope
2528
*/
@@ -34,7 +37,8 @@ public function create(
3437
?ParametersAcceptor $anonymousFunctionReflection = null,
3538
bool $inFirstLevelStatement = true,
3639
array $currentlyAssignedExpressions = [],
37-
array $nativeExpressionTypes = []
40+
array $nativeExpressionTypes = [],
41+
array $inFunctionCallsStack = []
3842
): MutatingScope;
3943

4044
}

src/Rules/Classes/ClassConstantRule.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ public function processNode(Node $node, Scope $scope): array
8787
$className = $currentClassReflection->getParentClass()->getName();
8888
} else {
8989
if (!$this->reflectionProvider->hasClass($className)) {
90+
if ($scope->isInClassExists($className)) {
91+
return [];
92+
}
93+
9094
if (strtolower($constantName) === 'class') {
9195
return [
9296
RuleErrorBuilder::message(sprintf('Class %s not found.', $className))->build(),

src/Rules/Classes/ExistingClassInInstanceOfRule.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ public function processNode(Node $node, Scope $scope): array
6666
}
6767

6868
if (!$this->reflectionProvider->hasClass($name)) {
69+
if ($scope->isInClassExists($name)) {
70+
return [];
71+
}
72+
6973
return [
7074
RuleErrorBuilder::message(sprintf('Class %s not found.', $name))->line($class->getLine())->build(),
7175
];

src/Rules/Classes/InstantiationRule.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ private function checkClassName(string $class, Node $node, Scope $scope): array
118118
$classReflection = $scope->getClassReflection()->getParentClass();
119119
} else {
120120
if (!$this->reflectionProvider->hasClass($class)) {
121+
if ($scope->isInClassExists($class)) {
122+
return [];
123+
}
124+
121125
return [
122126
RuleErrorBuilder::message(sprintf('Instantiated class %s not found.', $class))->build(),
123127
];

0 commit comments

Comments
 (0)