Skip to content

Commit f262eaa

Browse files
committed
wip
1 parent 9dbff97 commit f262eaa

File tree

5 files changed

+101
-2
lines changed

5 files changed

+101
-2
lines changed

src/PhpDoc/PhpDocNodeResolver.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
use function count;
4949
use function in_array;
5050
use function method_exists;
51+
use function preg_split;
5152
use function str_starts_with;
5253
use function substr;
5354

@@ -419,6 +420,24 @@ public function resolveParamImmediatelyInvokedCallable(PhpDocNode $phpDocNode):
419420
return $parameters;
420421
}
421422

423+
/**
424+
* @return array<string, bool>
425+
*/
426+
public function resolveParamPureUnlessCallableIsImpure(PhpDocNode $phpDocNode): array
427+
{
428+
$parameters = [];
429+
// TODO: implement phpstan/phpdoc-parser
430+
foreach ($phpDocNode->getTagsByName('@pure-unless-callable-impure') as $tag) {
431+
$value = preg_split('/\s/u', (string)$tag->value)[0] ?? null;
432+
if ($value !== null && str_starts_with($value, '$')) {
433+
$parameters[substr($value, 1)] = true;
434+
}
435+
}
436+
437+
return $parameters;
438+
}
439+
440+
422441
/**
423442
* @return array<string, ParamClosureThisTag>
424443
*/

src/PhpDoc/ResolvedPhpDocBlock.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ final class ResolvedPhpDocBlock
9696
/** @var array<string, bool>|false */
9797
private array|false $paramsImmediatelyInvokedCallable = false;
9898

99+
/** @var array<string, bool>|false */
100+
private array|false $paramsPureUnlessCallableIsImpure = false;
101+
99102
/** @var array<string, ParamClosureThisTag>|false */
100103
private array|false $paramClosureThisTags = false;
101104

@@ -216,6 +219,7 @@ public static function createEmpty(): self
216219
$self->paramTags = [];
217220
$self->paramOutTags = [];
218221
$self->paramsImmediatelyInvokedCallable = [];
222+
$self->paramsPureUnlessCallableIsImpure = [];
219223
$self->paramClosureThisTags = [];
220224
$self->returnTag = null;
221225
$self->throwsTag = null;
@@ -281,6 +285,7 @@ public function merge(array $parents, array $parentPhpDocBlocks): self
281285
$result->paramTags = self::mergeParamTags($this->getParamTags(), $parents, $parentPhpDocBlocks);
282286
$result->paramOutTags = self::mergeParamOutTags($this->getParamOutTags(), $parents, $parentPhpDocBlocks);
283287
$result->paramsImmediatelyInvokedCallable = self::mergeParamsImmediatelyInvokedCallable($this->getParamsImmediatelyInvokedCallable(), $parents, $parentPhpDocBlocks);
288+
$result->paramsPureUnlessCallableIsImpure = self::mergeParamsPureUnlessCallableIsImpure($this->getParamsPureUnlessCallableIsImpure(), $parents, $parentPhpDocBlocks);
284289
$result->paramClosureThisTags = self::mergeParamClosureThisTags($this->getParamClosureThisTags(), $parents, $parentPhpDocBlocks);
285290
$result->returnTag = self::mergeReturnTags($this->getReturnTag(), $classReflection, $parents, $parentPhpDocBlocks);
286291
$result->throwsTag = self::mergeThrowsTags($this->getThrowsTag(), $parents);
@@ -587,6 +592,18 @@ public function getParamsImmediatelyInvokedCallable(): array
587592
return $this->paramsImmediatelyInvokedCallable;
588593
}
589594

595+
/**
596+
* @return array<string, bool>
597+
*/
598+
public function getParamsPureUnlessCallableIsImpure(): array
599+
{
600+
if ($this->paramsPureUnlessCallableIsImpure === false) {
601+
$this->paramsPureUnlessCallableIsImpure = $this->phpDocNodeResolver->resolveParamPureUnlessCallableIsImpure($this->phpDocNode);
602+
}
603+
604+
return $this->paramsPureUnlessCallableIsImpure;
605+
}
606+
590607
/**
591608
* @return array<string, ParamClosureThisTag>
592609
*/
@@ -1154,6 +1171,40 @@ private static function mergeOneParentParamImmediatelyInvokedCallable(array $par
11541171
return $paramsImmediatelyInvokedCallable;
11551172
}
11561173

1174+
/**
1175+
* @param array<string, bool> $paramsPureUnlessCallableIsImpure
1176+
* @param array<int, self> $parents
1177+
* @param array<int, PhpDocBlock> $parentPhpDocBlocks
1178+
* @return array<string, bool>
1179+
*/
1180+
private static function mergeParamsPureUnlessCallableIsImpure(array $paramsPureUnlessCallableIsImpure, array $parents, array $parentPhpDocBlocks): array
1181+
{
1182+
foreach ($parents as $i => $parent) {
1183+
$paramsPureUnlessCallableIsImpure = self::mergeOneParentParamPureUnlessCallableIsImpure($paramsPureUnlessCallableIsImpure, $parent, $parentPhpDocBlocks[$i]);
1184+
}
1185+
1186+
return $paramsPureUnlessCallableIsImpure;
1187+
}
1188+
1189+
/**
1190+
* @param array<string, bool> $paramsPureUnlessCallableIsImpure
1191+
* @return array<string, bool>
1192+
*/
1193+
private static function mergeOneParentParamPureUnlessCallableIsImpure(array $paramsPureUnlessCallableIsImpure, self $parent, PhpDocBlock $phpDocBlock): array
1194+
{
1195+
$parentPureUnlessCallableIsImpure = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamsPureUnlessCallableIsImpure());
1196+
1197+
foreach ($parentPureUnlessCallableIsImpure as $name => $parentIsPureUnlessCallableIsImpure) {
1198+
if (array_key_exists($name, $paramsPureUnlessCallableIsImpure)) {
1199+
continue;
1200+
}
1201+
1202+
$paramsPureUnlessCallableIsImpure[$name] = $parentIsPureUnlessCallableIsImpure;
1203+
}
1204+
1205+
return $paramsPureUnlessCallableIsImpure;
1206+
}
1207+
11571208
/**
11581209
* @param array<string, ParamClosureThisTag> $paramsClosureThisTags
11591210
* @param array<int, self> $parents

tests/PHPStan/Analyser/TestClosureTypeRuleTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ public function testRule(): void
2121
$this->analyse([__DIR__ . '/nsrt/closure-passed-to-type.php'], [
2222
[
2323
'Closure type: Closure(mixed): (1|2|3)',
24-
25,
24+
26,
2525
],
2626
[
2727
'Closure type: Closure(mixed): (1|2|3)',
28-
35,
28+
36,
2929
],
3030
]);
3131
}

tests/PHPStan/Analyser/nsrt/closure-passed-to-type.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class Foo
1313
* @param array<T> $items
1414
* @param callable(T): U $cb
1515
* @return array<U>
16+
* @pure-unless-callable-impure $cb
1617
*/
1718
public function doFoo(array $items, callable $cb)
1819
{
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace ParamPureUnlessCallableIsImpure;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @template TValue
9+
* @template TResult
10+
* @param Closure(TValue): TResult $f
11+
* @param iterable<TValue> $a
12+
* @return array<TResult>
13+
* @pure-unless-callable-is-impure $f
14+
*/
15+
function map(Closure $f, iterable $a): array
16+
{
17+
$result = [];
18+
foreach ($a as $i => $v) {
19+
$retult[$i] = $f($v);
20+
}
21+
22+
return $result;
23+
}
24+
25+
map('printf', []);
26+
map('sprintf', []);
27+
28+
assertType('array<mixed>', map('printf', []));

0 commit comments

Comments
 (0)