Skip to content

Commit fdd28b7

Browse files
committed
wip
1 parent 8fbcf5b commit fdd28b7

File tree

4 files changed

+99
-0
lines changed

4 files changed

+99
-0
lines changed

src/PhpDoc/PhpDocNodeResolver.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
use function count;
4747
use function in_array;
4848
use function method_exists;
49+
use function preg_split;
4950
use function str_starts_with;
5051
use function substr;
5152

@@ -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 @@ 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

@@ -213,6 +216,7 @@ public static function createEmpty(): self
213216
$self->paramTags = [];
214217
$self->paramOutTags = [];
215218
$self->paramsImmediatelyInvokedCallable = [];
219+
$self->paramsPureUnlessCallableIsImpure = [];
216220
$self->paramClosureThisTags = [];
217221
$self->returnTag = null;
218222
$self->throwsTag = null;
@@ -277,6 +281,7 @@ public function merge(array $parents, array $parentPhpDocBlocks): self
277281
$result->paramTags = self::mergeParamTags($this->getParamTags(), $parents, $parentPhpDocBlocks);
278282
$result->paramOutTags = self::mergeParamOutTags($this->getParamOutTags(), $parents, $parentPhpDocBlocks);
279283
$result->paramsImmediatelyInvokedCallable = self::mergeParamsImmediatelyInvokedCallable($this->getParamsImmediatelyInvokedCallable(), $parents, $parentPhpDocBlocks);
284+
$result->paramsPureUnlessCallableIsImpure = self::mergeParamsPureUnlessCallableIsImpure($this->getParamsPureUnlessCallableIsImpure(), $parents, $parentPhpDocBlocks);
280285
$result->paramClosureThisTags = self::mergeParamClosureThisTags($this->getParamClosureThisTags(), $parents, $parentPhpDocBlocks);
281286
$result->returnTag = self::mergeReturnTags($this->getReturnTag(), $classReflection, $parents, $parentPhpDocBlocks);
282287
$result->throwsTag = self::mergeThrowsTags($this->getThrowsTag(), $parents);
@@ -582,6 +587,18 @@ public function getParamsImmediatelyInvokedCallable(): array
582587
return $this->paramsImmediatelyInvokedCallable;
583588
}
584589

590+
/**
591+
* @return array<string, bool>
592+
*/
593+
public function getParamsPureUnlessCallableIsImpure(): array
594+
{
595+
if ($this->paramsPureUnlessCallableIsImpure === false) {
596+
$this->paramsPureUnlessCallableIsImpure = $this->phpDocNodeResolver->resolveParamPureUnlessCallableIsImpure($this->phpDocNode);
597+
}
598+
599+
return $this->paramsPureUnlessCallableIsImpure;
600+
}
601+
585602
/**
586603
* @return array<string, ParamClosureThisTag>
587604
*/
@@ -1162,6 +1179,40 @@ private static function mergeOneParentParamImmediatelyInvokedCallable(array $par
11621179
return $paramsImmediatelyInvokedCallable;
11631180
}
11641181

1182+
/**
1183+
* @param array<string, bool> $paramsPureUnlessCallableIsImpure
1184+
* @param array<int, self> $parents
1185+
* @param array<int, PhpDocBlock> $parentPhpDocBlocks
1186+
* @return array<string, bool>
1187+
*/
1188+
private static function mergeParamsPureUnlessCallableIsImpure(array $paramsPureUnlessCallableIsImpure, array $parents, array $parentPhpDocBlocks): array
1189+
{
1190+
foreach ($parents as $i => $parent) {
1191+
$paramsPureUnlessCallableIsImpure = self::mergeOneParentParamPureUnlessCallableIsImpure($paramsPureUnlessCallableIsImpure, $parent, $parentPhpDocBlocks[$i]);
1192+
}
1193+
1194+
return $paramsPureUnlessCallableIsImpure;
1195+
}
1196+
1197+
/**
1198+
* @param array<string, bool> $paramsPureUnlessCallableIsImpure
1199+
* @return array<string, bool>
1200+
*/
1201+
private static function mergeOneParentParamPureUnlessCallableIsImpure(array $paramsPureUnlessCallableIsImpure, self $parent, PhpDocBlock $phpDocBlock): array
1202+
{
1203+
$parentPureUnlessCallableIsImpure = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamsPureUnlessCallableIsImpure());
1204+
1205+
foreach ($parentPureUnlessCallableIsImpure as $name => $parentIsPureUnlessCallableIsImpure) {
1206+
if (array_key_exists($name, $paramsPureUnlessCallableIsImpure)) {
1207+
continue;
1208+
}
1209+
1210+
$paramsPureUnlessCallableIsImpure[$name] = $parentIsPureUnlessCallableIsImpure;
1211+
}
1212+
1213+
return $paramsPureUnlessCallableIsImpure;
1214+
}
1215+
11651216
/**
11661217
* @param array<string, ParamClosureThisTag> $paramsClosureThisTags
11671218
* @param array<int, self> $parents

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', map('printf', []));

0 commit comments

Comments
 (0)