Skip to content

Commit

Permalink
Fix in_array() for arrays with union value type
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Mar 2, 2022
1 parent b7b6655 commit 4321374
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 16 deletions.
5 changes: 0 additions & 5 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,6 @@ parameters:
count: 1
path: src/Testing/PHPStanTestCase.php

-
message: "#^Call to function in_array\\(\\) with arguments 'array', array\\<int, 'array'\\|'string'\\> and true will always evaluate to true\\.$#"
count: 1
path: src/Type/IntersectionType.php

-
message: """
#^Call to deprecated method getInstance\\(\\) of class PHPStan\\\\Broker\\\\Broker\\:
Expand Down
34 changes: 23 additions & 11 deletions src/Rules/Comparison/ImpossibleCheckTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,32 @@ public function findSpecifiedType(
return null;
}

if (!$haystackType instanceof ConstantArrayType || count($haystackType->getValueTypes()) > 0) {
$needleType = $scope->getType($node->getArgs()[0]->value);
$constantArrays = TypeUtils::getConstantArrays($haystackType);
$needleType = $scope->getType($node->getArgs()[0]->value);
$valueType = $haystackType->getIterableValueType();
$constantNeedleTypesCount = count(TypeUtils::getConstantScalars($needleType));
$constantHaystackTypesCount = count(TypeUtils::getConstantScalars($valueType));
$isNeedleSupertype = $needleType->isSuperTypeOf($valueType);
if (count($constantArrays) === 0) {
if ($haystackType->isIterableAtLeastOnce()->yes()) {
if ($constantNeedleTypesCount === 1 && $constantHaystackTypesCount === 1) {
if ($isNeedleSupertype->yes()) {
return true;
}
if ($isNeedleSupertype->no()) {
return false;
}
}
}
return null;
}

if (!$haystackType instanceof ConstantArrayType || count($haystackType->getValueTypes()) > 0) {
$haystackArrayTypes = TypeUtils::getArrays($haystackType);
if (count($haystackArrayTypes) === 1 && $haystackArrayTypes[0]->getIterableValueType() instanceof NeverType) {
return null;
}

$valueType = $haystackType->getIterableValueType();
$isNeedleSupertype = $needleType->isSuperTypeOf($valueType);

if ($isNeedleSupertype->maybe() || $isNeedleSupertype->yes()) {
foreach ($haystackArrayTypes as $haystackArrayType) {
foreach (TypeUtils::getConstantScalars($haystackArrayType->getIterableValueType()) as $constantScalarType) {
Expand All @@ -115,13 +130,10 @@ public function findSpecifiedType(
}

if ($isNeedleSupertype->yes()) {
$hasConstantNeedleTypes = count(TypeUtils::getConstantScalars($needleType)) > 0;
$hasConstantHaystackTypes = count(TypeUtils::getConstantScalars($valueType)) > 0;
$hasConstantNeedleTypes = $constantNeedleTypesCount > 0;
$hasConstantHaystackTypes = $constantHaystackTypesCount > 0;
if (
(
!$hasConstantNeedleTypes
&& !$hasConstantHaystackTypes
)
(!$hasConstantNeedleTypes && !$hasConstantHaystackTypes)
|| $hasConstantNeedleTypes !== $hasConstantHaystackTypes
) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,4 +462,24 @@ public function testBug5369(): void
$this->analyse([__DIR__ . '/data/bug-5369.php'], []);
}

public function testBugInArrayDateFormat(): void
{
$this->checkAlwaysTrueCheckTypeFunctionCall = true;
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/in-array-date-format.php'], [
[
'Call to function in_array() with arguments \'a\', non-empty-array<int, \'a\'> and true will always evaluate to true.',
39,
],
[
'Call to function in_array() with arguments \'b\', non-empty-array<int, \'a\'> and true will always evaluate to false.',
43,
],
[
'Call to function in_array() with arguments int, array{} and true will always evaluate to false.',
47,
],
]);
}

}
80 changes: 80 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/in-array-date-format.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

namespace InArrayDateFormat;

class Foo
{

public function doFoo(array $a, \DateTimeImmutable $dt)
{
$result = [];
foreach ($a as $k => $v) {
$result[$k] = $dt->format('d');
}

$d = new \DateTimeImmutable();
if (in_array($d->format('d'), $result, true)) {

}

if (in_array('01', $result, true)) {

}

$day = $d->format('d');
if (rand(0, 1)) {
$day = '32';
}

if (in_array($day, $result, true)) {

}
}

/**
* @param non-empty-array<int, 'a'> $a
*/
public function doBar(array $a, int $i)
{
if (in_array('a', $a, true)) {

}

if (in_array('b', $a, true)) {

}

if (in_array($i, [], true)) {

}
}

/**
* @param array<int, string> $a
*/
public function doBaz(array $a, int $i, string $s)
{
if (in_array($s, $a, true)) {

}

if (in_array($i, $a, true)) {

}
}

/**
* @param non-empty-array<int, 'a'|'b'> $a
*/
public function doLorem(array $a, int $i)
{
if (in_array('a', $a, true)) {

}

if (in_array('b', $a, true)) {

}
}

}

0 comments on commit 4321374

Please sign in to comment.