Skip to content

Commit 45ee104

Browse files
authored
Fix false positives #118, #119, #130
1 parent a83a4d2 commit 45ee104

File tree

7 files changed

+110
-41
lines changed

7 files changed

+110
-41
lines changed

src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

+20-41
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PhpParser\Node\Arg;
99
use PhpParser\Node\Expr;
1010
use PhpParser\Node\Expr\Array_;
11+
use PhpParser\Node\Expr\ArrayDimFetch;
1112
use PhpParser\Node\Expr\ArrayItem;
1213
use PhpParser\Node\Expr\BinaryOp;
1314
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
@@ -25,12 +26,9 @@
2526
use PhpParser\Node\Expr\FuncCall;
2627
use PhpParser\Node\Expr\Instanceof_;
2728
use PhpParser\Node\Expr\StaticCall;
28-
use PhpParser\Node\Expr\Variable;
2929
use PhpParser\Node\Name;
30-
use PhpParser\Node\Param;
3130
use PhpParser\Node\Scalar\LNumber;
3231
use PhpParser\Node\Scalar\String_;
33-
use PhpParser\Node\Stmt\Return_;
3432
use PHPStan\Analyser\Scope;
3533
use PHPStan\Analyser\SpecifiedTypes;
3634
use PHPStan\Analyser\TypeSpecifier;
@@ -44,6 +42,7 @@
4442
use PHPStan\Type\Constant\ConstantStringType;
4543
use PHPStan\Type\IterableType;
4644
use PHPStan\Type\MixedType;
45+
use PHPStan\Type\NeverType;
4746
use PHPStan\Type\ObjectType;
4847
use PHPStan\Type\StaticMethodTypeSpecifyingExtension;
4948
use PHPStan\Type\StringType;
@@ -59,7 +58,6 @@
5958
use function array_reduce;
6059
use function array_shift;
6160
use function count;
62-
use function key;
6361
use function lcfirst;
6462
use function substr;
6563

@@ -774,56 +772,35 @@ private function handleAll(
774772
Scope $scope
775773
): SpecifiedTypes
776774
{
777-
$closureItemVariable = new Variable('item');
778-
$closureArgs = $node->getArgs();
779-
$closureArgs[0] = new Arg($closureItemVariable);
780-
781-
$expression = new BooleanAnd(
782-
new FuncCall(new Name('is_iterable'), [$node->getArgs()[0]]),
783-
new Identical(
784-
$node->getArgs()[0]->value,
785-
new FuncCall(
786-
new Name('array_filter'),
787-
[
788-
new Arg($node->getArgs()[0]->value),
789-
new Arg(
790-
new Expr\Closure(
791-
[
792-
'static' => true,
793-
'params' => [new Param($closureItemVariable)],
794-
'stmts' => [
795-
new Return_(self::createExpression($scope, $methodName, $closureArgs)),
796-
],
797-
]
798-
)
799-
),
800-
]
801-
)
802-
)
803-
);
775+
$args = $node->getArgs();
776+
$args[0] = new Arg(new ArrayDimFetch($args[0]->value, new LNumber(0)));
777+
$expression = self::createExpression($scope, $methodName, $args);
778+
if ($expression === null) {
779+
return new SpecifiedTypes();
780+
}
804781

805782
$specifiedTypes = $this->typeSpecifier->specifyTypesInCondition(
806783
$scope,
807784
$expression,
808785
TypeSpecifierContext::createTruthy()
809786
);
810787

811-
if (count($specifiedTypes->getSureTypes()) > 0) {
812-
$sureTypes = $specifiedTypes->getSureTypes();
813-
$exprString = key($sureTypes);
814-
[$exprNode, $type] = $sureTypes[$exprString];
788+
$sureNotTypes = $specifiedTypes->getSureNotTypes();
789+
foreach ($specifiedTypes->getSureTypes() as $exprStr => [$exprNode, $type]) {
790+
if ($exprNode !== $args[0]->value) {
791+
continue;
792+
}
793+
794+
$type = TypeCombinator::remove($type, $sureNotTypes[$exprStr][1] ?? new NeverType());
815795

816796
return $this->arrayOrIterable(
817797
$scope,
818-
$exprNode,
798+
$node->getArgs()[0]->value,
819799
static function () use ($type): Type {
820-
return $type->getIterableValueType();
800+
return $type;
821801
}
822802
);
823803
}
824-
if (count($specifiedTypes->getSureNotTypes()) > 0) {
825-
throw new ShouldNotHappenException();
826-
}
827804

828805
return $specifiedTypes;
829806
}
@@ -861,7 +838,9 @@ private function arrayOrIterable(
861838
return $this->typeSpecifier->create(
862839
$expr,
863840
$specifiedType,
864-
TypeSpecifierContext::createTruthy()
841+
TypeSpecifierContext::createTruthy(),
842+
false,
843+
$scope
865844
);
866845
}
867846

tests/Type/WebMozartAssert/ImpossibleCheckTypeMethodCallRuleTest.php

+19
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ public function testExtension(): void
6464
'Call to static method Webmozart\Assert\Assert::nullOrStringNotEmpty() with non-empty-string|null will always evaluate to true.',
6565
53,
6666
],
67+
[
68+
'Call to static method Webmozart\Assert\Assert::allCount() with array<non-empty-array> and 2 will always evaluate to true.',
69+
76,
70+
],
6771
]);
6872
}
6973

@@ -175,6 +179,21 @@ public function testBug85(): void
175179
$this->analyse([__DIR__ . '/data/bug-85.php'], []);
176180
}
177181

182+
public function testBug118(): void
183+
{
184+
$this->analyse([__DIR__ . '/data/bug-118.php'], []);
185+
}
186+
187+
public function testBug119(): void
188+
{
189+
$this->analyse([__DIR__ . '/data/bug-119.php'], []);
190+
}
191+
192+
public function testBug130(): void
193+
{
194+
$this->analyse([__DIR__ . '/data/bug-130.php'], []);
195+
}
196+
178197
public static function getAdditionalConfigFiles(): array
179198
{
180199
return [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WebmozartAssertBug118;
6+
7+
use DateTime;
8+
use Webmozart\Assert\Assert;
9+
10+
function test(float $a, DateTime $b): void
11+
{
12+
Assert::range($a, 0, 1);
13+
Assert::range($b, 123456789, 9876543321);
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WebmozartAssertBug119;
6+
7+
use DateTime;
8+
use Webmozart\Assert\Assert;
9+
10+
function test(float $a, float $b, float $c, float $d, DateTime $e): void
11+
{
12+
Assert::greaterThan($a, 0);
13+
Assert::greaterThanEq($b, 0);
14+
Assert::lessThan($c, 0);
15+
Assert::lessThanEq($d, 0);
16+
Assert::greaterThanEq($e, 639828000);
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace WebmozartAssertBug130;
4+
5+
use Webmozart\Assert\Assert;
6+
7+
class Bug130
8+
{
9+
/** @var array<int, array{id: string, num_order: string}> */
10+
protected $orders = [];
11+
12+
public function setOrders(array $orders): self
13+
{
14+
Assert::allCount($orders, 2);
15+
Assert::allKeyExists($orders, 'id');
16+
Assert::allKeyExists($orders, 'num_order');
17+
18+
$this->orders = $orders;
19+
20+
return $this;
21+
}
22+
}

tests/Type/WebMozartAssert/data/collection.php

+9
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,15 @@ public function allKeyExists(array $a, array $b, array $c, $d): void
120120
\PHPStan\Testing\assertType('array<array&hasOffset(\'id\')>', $c);
121121
}
122122

123+
/**
124+
* @param array<array> $a
125+
*/
126+
public function allCount(array $a): void
127+
{
128+
Assert::allCount($a, 2);
129+
\PHPStan\Testing\assertType('array<non-empty-array>', $a);
130+
}
131+
123132
}
124133

125134
class CollectionFoo

tests/Type/WebMozartAssert/data/impossible-check.php

+9
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ public function notSame(Bar $a, Bar $b): void
6767
Assert::notSame(Baz::create(), Baz::create());
6868
}
6969

70+
/**
71+
* @param array<array> $a
72+
*/
73+
public function allCount(array $a): void
74+
{
75+
Assert::allCount($a, 2);
76+
Assert::allCount($a, 2);
77+
}
78+
7079
}
7180

7281
interface Bar {};

0 commit comments

Comments
 (0)