Skip to content

Commit a71f791

Browse files
committed
wip
1 parent a34c2f4 commit a71f791

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

@@ -416,6 +417,24 @@ public function resolveParamImmediatelyInvokedCallable(PhpDocNode $phpDocNode):
416417
return $parameters;
417418
}
418419

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

src/PhpDoc/ResolvedPhpDocBlock.php

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

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

@@ -212,6 +215,7 @@ public static function createEmpty(): self
212215
$self->paramTags = [];
213216
$self->paramOutTags = [];
214217
$self->paramsImmediatelyInvokedCallable = [];
218+
$self->paramsPureUnlessCallableIsImpure = [];
215219
$self->paramClosureThisTags = [];
216220
$self->returnTag = null;
217221
$self->throwsTag = null;
@@ -276,6 +280,7 @@ public function merge(array $parents, array $parentPhpDocBlocks): self
276280
$result->paramTags = self::mergeParamTags($this->getParamTags(), $parents, $parentPhpDocBlocks);
277281
$result->paramOutTags = self::mergeParamOutTags($this->getParamOutTags(), $parents, $parentPhpDocBlocks);
278282
$result->paramsImmediatelyInvokedCallable = self::mergeParamsImmediatelyInvokedCallable($this->getParamsImmediatelyInvokedCallable(), $parents, $parentPhpDocBlocks);
283+
$result->paramsPureUnlessCallableIsImpure = self::mergeParamsPureUnlessCallableIsImpure($this->getParamsPureUnlessCallableIsImpure(), $parents, $parentPhpDocBlocks);
279284
$result->paramClosureThisTags = self::mergeParamClosureThisTags($this->getParamClosureThisTags(), $parents, $parentPhpDocBlocks);
280285
$result->returnTag = self::mergeReturnTags($this->getReturnTag(), $classReflection, $parents, $parentPhpDocBlocks);
281286
$result->throwsTag = self::mergeThrowsTags($this->getThrowsTag(), $parents);
@@ -581,6 +586,18 @@ public function getParamsImmediatelyInvokedCallable(): array
581586
return $this->paramsImmediatelyInvokedCallable;
582587
}
583588

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

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