Skip to content

Commit 211183c

Browse files
committed
Merge remote-tracking branch 'origin/1.12.x' into 2.0.x
2 parents 8019243 + d9b383f commit 211183c

20 files changed

+1030
-391
lines changed

conf/config.neon

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,11 @@ services:
230230
tags:
231231
- phpstan.parser.richParserNodeVisitor
232232

233+
-
234+
class: PHPStan\Parser\ArrayFindArgVisitor
235+
tags:
236+
- phpstan.parser.richParserNodeVisitor
237+
233238
-
234239
class: PHPStan\Parser\ArrayMapArgVisitor
235240
tags:
@@ -1104,6 +1109,9 @@ services:
11041109
tags:
11051110
- phpstan.broker.dynamicFunctionReturnTypeExtension
11061111

1112+
-
1113+
class: PHPStan\Type\Php\ArrayFilterFunctionReturnTypeHelper
1114+
11071115
-
11081116
class: PHPStan\Type\Php\ArrayFilterFunctionReturnTypeExtension
11091117
tags:
@@ -1114,6 +1122,16 @@ services:
11141122
tags:
11151123
- phpstan.broker.dynamicFunctionReturnTypeExtension
11161124

1125+
-
1126+
class: PHPStan\Type\Php\ArrayFindFunctionReturnTypeExtension
1127+
tags:
1128+
- phpstan.broker.dynamicFunctionReturnTypeExtension
1129+
1130+
-
1131+
class: PHPStan\Type\Php\ArrayFindKeyFunctionReturnTypeExtension
1132+
tags:
1133+
- phpstan.broker.dynamicFunctionReturnTypeExtension
1134+
11171135
-
11181136
class: PHPStan\Type\Php\ArrayKeyDynamicReturnTypeExtension
11191137
tags:

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1453,7 +1453,7 @@ parameters:
14531453
message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#'
14541454
identifier: phpstanApi.instanceofType
14551455
count: 1
1456-
path: src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php
1456+
path: src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php
14571457

14581458
-
14591459
message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#'

resources/functionMap.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5016,9 +5016,9 @@
50165016
'Imagick::waveImage' => ['bool', 'amplitude'=>'float', 'length'=>'float'],
50175017
'Imagick::whiteThresholdImage' => ['bool', 'threshold'=>'mixed'],
50185018
'Imagick::writeImage' => ['bool', 'filename='=>'string'],
5019-
'Imagick::writeImageFile' => ['bool', 'filehandle'=>'resource'],
5019+
'Imagick::writeImageFile' => ['bool', 'filehandle'=>'resource', 'format='=>'?string'],
50205020
'Imagick::writeImages' => ['bool', 'filename'=>'string', 'adjoin'=>'bool'],
5021-
'Imagick::writeImagesFile' => ['bool', 'filehandle'=>'resource'],
5021+
'Imagick::writeImagesFile' => ['bool', 'filehandle'=>'resource', 'format='=>'?string'],
50225022
'ImagickDraw::__construct' => ['void'],
50235023
'ImagickDraw::affine' => ['bool', 'affine'=>'array'],
50245024
'ImagickDraw::annotation' => ['bool', 'x'=>'float', 'y'=>'float', 'text'=>'string'],

src/Analyser/MutatingScope.php

Lines changed: 9 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5527,18 +5527,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type
55275527
private function filterTypeWithMethod(Type $typeWithMethod, string $methodName): ?Type
55285528
{
55295529
if ($typeWithMethod instanceof UnionType) {
5530-
$newTypes = [];
5531-
foreach ($typeWithMethod->getTypes() as $innerType) {
5532-
if (!$innerType->hasMethod($methodName)->yes()) {
5533-
continue;
5534-
}
5535-
5536-
$newTypes[] = $innerType;
5537-
}
5538-
if (count($newTypes) === 0) {
5539-
return null;
5540-
}
5541-
$typeWithMethod = TypeCombinator::union(...$newTypes);
5530+
$typeWithMethod = $typeWithMethod->filterTypes(static fn (Type $innerType) => $innerType->hasMethod($methodName)->yes());
55425531
}
55435532

55445533
if (!$typeWithMethod->hasMethod($methodName)->yes()) {
@@ -5641,18 +5630,7 @@ private function methodCallReturnType(Type $typeWithMethod, string $methodName,
56415630
public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection
56425631
{
56435632
if ($typeWithProperty instanceof UnionType) {
5644-
$newTypes = [];
5645-
foreach ($typeWithProperty->getTypes() as $innerType) {
5646-
if (!$innerType->hasProperty($propertyName)->yes()) {
5647-
continue;
5648-
}
5649-
5650-
$newTypes[] = $innerType;
5651-
}
5652-
if (count($newTypes) === 0) {
5653-
return null;
5654-
}
5655-
$typeWithProperty = TypeCombinator::union(...$newTypes);
5633+
$typeWithProperty = $typeWithProperty->filterTypes(static fn (Type $innerType) => $innerType->hasProperty($propertyName)->yes());
56565634
}
56575635
if (!$typeWithProperty->hasProperty($propertyName)->yes()) {
56585636
return null;
@@ -5681,18 +5659,7 @@ private function propertyFetchType(Type $fetchedOnType, string $propertyName, Ex
56815659
public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ClassConstantReflection
56825660
{
56835661
if ($typeWithConstant instanceof UnionType) {
5684-
$newTypes = [];
5685-
foreach ($typeWithConstant->getTypes() as $innerType) {
5686-
if (!$innerType->hasConstant($constantName)->yes()) {
5687-
continue;
5688-
}
5689-
5690-
$newTypes[] = $innerType;
5691-
}
5692-
if (count($newTypes) === 0) {
5693-
return null;
5694-
}
5695-
$typeWithConstant = TypeCombinator::union(...$newTypes);
5662+
$typeWithConstant = $typeWithConstant->filterTypes(static fn (Type $innerType) => $innerType->hasConstant($constantName)->yes());
56965663
}
56975664
if (!$typeWithConstant->hasConstant($constantName)->yes()) {
56985665
return null;
@@ -5736,18 +5703,10 @@ private function getNativeConstantTypes(): array
57365703
public function getIterableKeyType(Type $iteratee): Type
57375704
{
57385705
if ($iteratee instanceof UnionType) {
5739-
$newTypes = [];
5740-
foreach ($iteratee->getTypes() as $innerType) {
5741-
if (!$innerType->isIterable()->yes()) {
5742-
continue;
5743-
}
5744-
5745-
$newTypes[] = $innerType;
5746-
}
5747-
if (count($newTypes) === 0) {
5748-
return $iteratee->getIterableKeyType();
5706+
$filtered = $iteratee->filterTypes(static fn (Type $innerType) => $innerType->isIterable()->yes());
5707+
if (!$filtered instanceof NeverType) {
5708+
$iteratee = $filtered;
57495709
}
5750-
$iteratee = TypeCombinator::union(...$newTypes);
57515710
}
57525711

57535712
return $iteratee->getIterableKeyType();
@@ -5756,18 +5715,10 @@ public function getIterableKeyType(Type $iteratee): Type
57565715
public function getIterableValueType(Type $iteratee): Type
57575716
{
57585717
if ($iteratee instanceof UnionType) {
5759-
$newTypes = [];
5760-
foreach ($iteratee->getTypes() as $innerType) {
5761-
if (!$innerType->isIterable()->yes()) {
5762-
continue;
5763-
}
5764-
5765-
$newTypes[] = $innerType;
5766-
}
5767-
if (count($newTypes) === 0) {
5768-
return $iteratee->getIterableValueType();
5718+
$filtered = $iteratee->filterTypes(static fn (Type $innerType) => $innerType->isIterable()->yes());
5719+
if (!$filtered instanceof NeverType) {
5720+
$iteratee = $filtered;
57695721
}
5770-
$iteratee = TypeCombinator::union(...$newTypes);
57715722
}
57725723

57735724
return $iteratee->getIterableValueType();

src/Analyser/NodeScopeResolver.php

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,13 +1307,18 @@ private function processStmtNode(
13071307

13081308
$bodyScope = $initScope;
13091309
$isIterableAtLeastOnce = TrinaryLogic::createYes();
1310+
$lastCondExpr = $stmt->cond[count($stmt->cond) - 1] ?? null;
13101311
foreach ($stmt->cond as $condExpr) {
13111312
$condResult = $this->processExprNode($stmt, $condExpr, $bodyScope, static function (): void {
13121313
}, ExpressionContext::createDeep());
13131314
$initScope = $condResult->getScope();
13141315
$condResultScope = $condResult->getScope();
1315-
$condTruthiness = ($this->treatPhpDocTypesAsCertain ? $condResultScope->getType($condExpr) : $condResultScope->getNativeType($condExpr))->toBoolean();
1316-
$isIterableAtLeastOnce = $isIterableAtLeastOnce->and($condTruthiness->isTrue());
1316+
1317+
if ($condExpr === $lastCondExpr) {
1318+
$condTruthiness = ($this->treatPhpDocTypesAsCertain ? $condResultScope->getType($condExpr) : $condResultScope->getNativeType($condExpr))->toBoolean();
1319+
$isIterableAtLeastOnce = $isIterableAtLeastOnce->and($condTruthiness->isTrue());
1320+
}
1321+
13171322
$hasYield = $hasYield || $condResult->hasYield();
13181323
$throwPoints = array_merge($throwPoints, $condResult->getThrowPoints());
13191324
$impurePoints = array_merge($impurePoints, $condResult->getImpurePoints());
@@ -1325,8 +1330,8 @@ private function processStmtNode(
13251330
do {
13261331
$prevScope = $bodyScope;
13271332
$bodyScope = $bodyScope->mergeWith($initScope);
1328-
foreach ($stmt->cond as $condExpr) {
1329-
$bodyScope = $this->processExprNode($stmt, $condExpr, $bodyScope, static function (): void {
1333+
if ($lastCondExpr !== null) {
1334+
$bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, static function (): void {
13301335
}, ExpressionContext::createDeep())->getTruthyScope();
13311336
}
13321337
$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void {
@@ -1356,8 +1361,8 @@ private function processStmtNode(
13561361
}
13571362

13581363
$bodyScope = $bodyScope->mergeWith($initScope);
1359-
foreach ($stmt->cond as $condExpr) {
1360-
$bodyScope = $this->processExprNode($stmt, $condExpr, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope();
1364+
if ($lastCondExpr !== null) {
1365+
$bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope();
13611366
}
13621367

13631368
$finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints();
@@ -1371,8 +1376,8 @@ private function processStmtNode(
13711376
$loopScope = $this->processExprNode($stmt, $loopExpr, $loopScope, $nodeCallback, ExpressionContext::createTopLevel())->getScope();
13721377
}
13731378
$finalScope = $finalScope->generalizeWith($loopScope);
1374-
foreach ($stmt->cond as $condExpr) {
1375-
$finalScope = $finalScope->filterByFalseyValue($condExpr);
1379+
if ($lastCondExpr !== null) {
1380+
$finalScope = $finalScope->filterByFalseyValue($lastCondExpr);
13761381
}
13771382

13781383
foreach ($finalScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) {

src/Parser/ArrayFindArgVisitor.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Parser;
4+
5+
use PhpParser\Node;
6+
use PhpParser\NodeVisitorAbstract;
7+
use function in_array;
8+
9+
final class ArrayFindArgVisitor extends NodeVisitorAbstract
10+
{
11+
12+
public const ATTRIBUTE_NAME = 'isArrayFindArg';
13+
14+
public function enterNode(Node $node): ?Node
15+
{
16+
if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) {
17+
$functionName = $node->name->toLowerString();
18+
if (in_array($functionName, ['array_all', 'array_any', 'array_find', 'array_find_key'], true)) {
19+
$args = $node->getRawArgs();
20+
if (isset($args[0])) {
21+
$args[0]->setAttribute(self::ATTRIBUTE_NAME, true);
22+
}
23+
}
24+
}
25+
return null;
26+
}
27+
28+
}

src/Reflection/ParametersAcceptorSelector.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PHPStan\Analyser\Scope;
1010
use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr;
1111
use PHPStan\Parser\ArrayFilterArgVisitor;
12+
use PHPStan\Parser\ArrayFindArgVisitor;
1213
use PHPStan\Parser\ArrayMapArgVisitor;
1314
use PHPStan\Parser\ArrayWalkArgVisitor;
1415
use PHPStan\Parser\ClosureBindArgVisitor;
@@ -233,6 +234,37 @@ public static function selectFromArgs(
233234
];
234235
}
235236

237+
if (isset($args[0]) && (bool) $args[0]->getAttribute(ArrayFindArgVisitor::ATTRIBUTE_NAME)) {
238+
$acceptor = $parametersAcceptors[0];
239+
$parameters = $acceptor->getParameters();
240+
$argType = $scope->getType($args[0]->value);
241+
$parameters[1] = new NativeParameterReflection(
242+
$parameters[1]->getName(),
243+
$parameters[1]->isOptional(),
244+
new CallableType(
245+
[
246+
new DummyParameter('value', $scope->getIterableValueType($argType), false, PassedByReference::createNo(), false, null),
247+
new DummyParameter('key', $scope->getIterableKeyType($argType), false, PassedByReference::createNo(), false, null),
248+
],
249+
new BooleanType(),
250+
false,
251+
),
252+
$parameters[1]->passedByReference(),
253+
$parameters[1]->isVariadic(),
254+
$parameters[1]->getDefaultValue(),
255+
);
256+
$parametersAcceptors = [
257+
new FunctionVariant(
258+
$acceptor->getTemplateTypeMap(),
259+
$acceptor->getResolvedTemplateTypeMap(),
260+
array_values($parameters),
261+
$acceptor->isVariadic(),
262+
$acceptor->getReturnType(),
263+
$acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
264+
),
265+
];
266+
}
267+
236268
if (isset($args[0])) {
237269
$closureBindToVar = $args[0]->getAttribute(ClosureBindToVarVisitor::ATTRIBUTE_NAME);
238270
if (

0 commit comments

Comments
 (0)