Skip to content

Commit 8faf0fb

Browse files
authored
Fix invalidate static expressions in closures
1 parent 850de78 commit 8faf0fb

File tree

5 files changed

+99
-2
lines changed

5 files changed

+99
-2
lines changed

src/Analyser/MutatingScope.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2898,7 +2898,7 @@ private function enterAnonymousFunctionWithoutReflection(
28982898
$nativeTypes[$paramExprString] = ExpressionTypeHolder::createYes($use->var, $variableNativeType);
28992899
}
29002900

2901-
foreach ($this->expressionTypes as $exprString => $typeHolder) {
2901+
foreach ($this->invalidateStaticExpressions($this->expressionTypes) as $exprString => $typeHolder) {
29022902
$expr = $typeHolder->getExpr();
29032903
if ($expr instanceof Variable) {
29042904
continue;
@@ -2961,6 +2961,27 @@ private function expressionTypeIsUnchangeable(ExpressionTypeHolder $typeHolder):
29612961
&& (new ConstantBooleanType(true))->isSuperTypeOf($type)->yes();
29622962
}
29632963

2964+
/**
2965+
* @param ExpressionTypeHolder[] $expressionTypes
2966+
* @return ExpressionTypeHolder[]
2967+
*/
2968+
private function invalidateStaticExpressions(array $expressionTypes): array
2969+
{
2970+
$filteredExpressionTypes = [];
2971+
$nodeFinder = new NodeFinder();
2972+
foreach ($expressionTypes as $exprString => $expressionType) {
2973+
$staticExpression = $nodeFinder->findFirst(
2974+
[$expressionType->getExpr()],
2975+
static fn ($node) => $node instanceof Expr\StaticCall || $node instanceof Expr\StaticPropertyFetch,
2976+
);
2977+
if ($staticExpression !== null) {
2978+
continue;
2979+
}
2980+
$filteredExpressionTypes[$exprString] = $expressionType;
2981+
}
2982+
return $filteredExpressionTypes;
2983+
}
2984+
29642985
/**
29652986
* @api
29662987
* @param ParameterReflection[]|null $callableParameters
@@ -3037,7 +3058,7 @@ private function enterArrowFunctionWithoutReflection(Expr\ArrowFunction $arrowFu
30373058
$this->isDeclareStrictTypes(),
30383059
$arrowFunctionScope->getFunction(),
30393060
$arrowFunctionScope->getNamespace(),
3040-
$arrowFunctionScope->expressionTypes,
3061+
$this->invalidateStaticExpressions($arrowFunctionScope->expressionTypes),
30413062
$arrowFunctionScope->conditionalExpressions,
30423063
$arrowFunctionScope->inClosureBindScopeClass,
30433064
null,

tests/PHPStan/Rules/Arrays/DeadForeachRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,9 @@ public function testBug7913(): void
3535
$this->analyse([__DIR__ . '/data/bug-7913.php'], []);
3636
}
3737

38+
public function testBug8292(): void
39+
{
40+
$this->analyse([__DIR__ . '/data/bug-8292.php'], []);
41+
}
42+
3843
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1); // lint >= 7.4
2+
3+
namespace Bug8292;
4+
5+
class World{
6+
public function addOnUnloadCallback(\Closure $c) : void{}
7+
}
8+
9+
interface Compressor{}
10+
11+
class ChunkCache
12+
{
13+
/** @var self[][] */
14+
private static array $instances = [];
15+
16+
/**
17+
* Fetches the ChunkCache instance for the given world. This lazily creates cache systems as needed.
18+
*/
19+
public static function getInstance(World $world, Compressor $compressor) : void{
20+
$worldId = spl_object_id($world);
21+
$compressorId = spl_object_id($compressor);
22+
if(!isset(self::$instances[$worldId])){
23+
self::$instances[$worldId] = [];
24+
$world->addOnUnloadCallback(static function() use ($worldId) : void{
25+
foreach(self::$instances[$worldId] as $cache){
26+
$cache->caches = [];
27+
}
28+
unset(self::$instances[$worldId]);
29+
});
30+
}
31+
}
32+
}

tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,4 +257,9 @@ public function testBug6701(): void
257257
]);
258258
}
259259

260+
public function testStaticCallInFunctions(): void
261+
{
262+
$this->analyse([__DIR__ . '/data/static-call-in-functions.php'], []);
263+
}
264+
260265
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace StaticCallsInFunctions;
4+
5+
class HelloWorld
6+
{
7+
/** @var \Closure[] */
8+
private static $resolvers;
9+
10+
/** @return \Closure[] */
11+
public static function getResolvers(): array
12+
{
13+
if (self::$resolvers === null) {
14+
self::$resolvers = [
15+
'a' => static function ($one, $two) {
16+
return $one ?? $two;
17+
},
18+
'b' => static function ($one, $two, $three) {
19+
return $one ?? $two ?? $three;
20+
},
21+
];
22+
23+
foreach (['c', 'd', 'e'] as $name) {
24+
self::$resolvers[$name] = static function ($one, $two) {
25+
return self::$resolvers['a']($one, $two);
26+
};
27+
28+
self::$resolvers[$name] = static fn ($one, $two) => self::$resolvers['a']($one, $two);
29+
}
30+
}
31+
32+
return self::$resolvers;
33+
}
34+
}

0 commit comments

Comments
 (0)