Skip to content

Commit

Permalink
Array is non-empty even when in_array is not strict
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm authored May 30, 2024
1 parent b543e8f commit b650df6
Show file tree
Hide file tree
Showing 8 changed files with 396 additions and 3 deletions.
17 changes: 16 additions & 1 deletion src/Rules/Comparison/ImpossibleCheckTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public function findSpecifiedType(
return null;
} elseif ($functionName === 'array_search') {
return null;
} elseif ($functionName === 'in_array' && $argsCount >= 3) {
} elseif ($functionName === 'in_array' && $argsCount >= 2) {
$haystackArg = $node->getArgs()[1]->value;
$haystackType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($haystackArg) : $scope->getNativeType($haystackArg));
if ($haystackType instanceof MixedType) {
Expand All @@ -101,6 +101,21 @@ public function findSpecifiedType(

$needleArg = $node->getArgs()[0]->value;
$needleType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($needleArg) : $scope->getNativeType($needleArg));

$isStrictComparison = false;
if ($argsCount >= 3) {
$strictNodeType = $scope->getType($node->getArgs()[2]->value);
$isStrictComparison = $strictNodeType->isTrue()->yes();
}

$isStrictComparison = $isStrictComparison
|| $needleType->isEnum()->yes()
|| $haystackType->getIterableValueType()->isEnum()->yes();

if (!$isStrictComparison) {
return null;
}

$valueType = $haystackType->getIterableValueType();
$constantNeedleTypesCount = count($needleType->getFiniteTypes());
$constantHaystackTypesCount = count($valueType->getFiniteTypes());
Expand Down
15 changes: 14 additions & 1 deletion src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,24 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
|| $arrayValueType->isEnum()->yes();

if (!$isStrictComparison) {
if (
$context->true()
&& $arrayType->isArray()->yes()
&& $arrayType->getIterableValueType()->isSuperTypeOf($needleType)->yes()
) {
return $this->typeSpecifier->create(
$node->getArgs()[1]->value,
TypeCombinator::intersect($arrayType, new NonEmptyArrayType()),
$context,
false,
$scope,
);
}

return new SpecifiedTypes();
}

$specifiedTypes = new SpecifiedTypes();

if (
$context->true()
|| (
Expand Down
4 changes: 4 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1442,6 +1442,10 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9397.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10080.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/http-response-header.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9662.php');
if (PHP_VERSION_ID >= 80100) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9662-enums.php');
}
yield from $this->gatherAssertTypes(__DIR__ . '/data/impure-error-log.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/falsy-isset.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/falsey-coalesce.php');
Expand Down
105 changes: 105 additions & 0 deletions tests/PHPStan/Analyser/data/bug-9662-enums.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php // lint >= 8.1

namespace Bug9662Enums;

use function PHPStan\Testing\assertType;

enum Suit
{
case Hearts;
case Diamonds;
case Clubs;
case Spades;
}

/**
* @param array<Suit> $suite
*/
function doEnum(array $suite, array $arr) {
if (in_array('NotAnEnumCase', $suite) === false) {
assertType('array<Bug9662Enums\Suit>', $suite);
} else {
assertType("non-empty-array<Bug9662Enums\Suit>", $suite);
}
assertType('array<Bug9662Enums\Suit>', $suite);

if (in_array(Suit::Hearts, $suite) === false) {
assertType('array<Bug9662Enums\Suit~Bug9662Enums\Suit::Hearts>', $suite);
} else {
assertType("non-empty-array<Bug9662Enums\Suit>", $suite);
}
assertType('array<Bug9662Enums\Suit>', $suite);


if (in_array(Suit::Hearts, $arr) === false) {
assertType('array<mixed~Bug9662Enums\Suit::Hearts>', $arr);
} else {
assertType("non-empty-array", $arr);
}
assertType('array', $arr);


if (in_array('NotAnEnumCase', $arr) === false) {
assertType('array', $arr);
} else {
assertType("non-empty-array", $arr);
}
assertType('array', $arr);
}

enum StringBackedSuit: string
{
case Hearts = 'H';
case Diamonds = 'D';
case Clubs = 'C';
case Spades = 'S';
}

/**
* @param array<StringBackedSuit> $suite
*/
function doBackedEnum(array $suite, array $arr, string $s, int $i, $mixed) {
if (in_array('NotAnEnumCase', $suite) === false) {
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);
} else {
assertType("non-empty-array<Bug9662Enums\StringBackedSuit>", $suite);
}
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);

if (in_array(StringBackedSuit::Hearts, $suite) === false) {
assertType('array<Bug9662Enums\StringBackedSuit~Bug9662Enums\StringBackedSuit::Hearts>', $suite);
} else {
assertType("non-empty-array<Bug9662Enums\StringBackedSuit>", $suite);
}
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);


if (in_array($s, $suite) === false) {
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);
} else {
assertType("non-empty-array<Bug9662Enums\StringBackedSuit>", $suite);
}
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);

if (in_array($i, $suite) === false) {
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);
} else {
assertType("non-empty-array<Bug9662Enums\StringBackedSuit>", $suite);
}
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);

if (in_array($mixed, $suite) === false) {
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);
} else {
assertType("non-empty-array<Bug9662Enums\StringBackedSuit>", $suite);
}
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);


if (in_array(StringBackedSuit::Hearts, $arr) === false) {
assertType('array<mixed~Bug9662Enums\StringBackedSuit::Hearts>', $arr);
} else {
assertType("non-empty-array", $arr);
}
assertType('array', $arr);
}
189 changes: 189 additions & 0 deletions tests/PHPStan/Analyser/data/bug-9662.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<?php

namespace Bug9662;

use function PHPStan\Testing\assertType;

/**
* @param array<mixed> $a
* @param array<string> $strings
* @return void
*/
function doFoo(string $s, $a, $strings, $mixed) {
if (in_array('foo', $a, true)) {
assertType('non-empty-array', $a);
} else {
assertType("array<mixed~'foo'>", $a);
}
assertType('array', $a);

if (in_array('foo', $a, false)) {
assertType('non-empty-array', $a);
} else {
assertType("array", $a);
}
assertType('array', $a);

if (in_array('foo', $a)) {
assertType('non-empty-array', $a);
} else {
assertType("array", $a);
}
assertType('array', $a);

if (in_array('0', $a)) {
assertType('non-empty-array', $a);
} else {
assertType("array", $a);
}
assertType('array', $a);

if (in_array('1', $a)) {
assertType('non-empty-array', $a);
} else {
assertType("array", $a);
}
assertType('array', $a);

if (in_array(true, $a)) {
assertType('non-empty-array', $a);
} else {
assertType("array", $a);
}
assertType('array', $a);

if (in_array(false, $a)) {
assertType('non-empty-array', $a);
} else {
assertType("array", $a);
}
assertType('array', $a);

if (in_array($s, $a, true)) {
assertType('non-empty-array', $a);
} else {
assertType("array", $a);
}
assertType('array', $a);

if (in_array($s, $a, false)) {
assertType('non-empty-array', $a);
} else {
assertType("array", $a);
}
assertType('array', $a);

if (in_array($s, $a)) {
assertType('non-empty-array', $a);
} else {
assertType("array", $a);
}
assertType('array', $a);

if (in_array($mixed, $strings, true)) {
assertType('non-empty-array<string>', $strings);
} else {
assertType("array<string>", $strings);
}
assertType('array<string>', $strings);

if (in_array($mixed, $strings, false)) {
assertType('array<string>', $strings);
} else {
assertType("array<string>", $strings);
}
assertType('array<string>', $strings);

if (in_array($mixed, $strings)) {
assertType('array<string>', $strings);
} else {
assertType("array<string>", $strings);
}
assertType('array<string>', $strings);

if (in_array($s, $strings, true)) {
assertType('non-empty-array<string>', $strings);
} else {
assertType("array<string>", $strings);
}
assertType('array<string>', $strings);

if (in_array($s, $strings, false)) {
assertType('non-empty-array<string>', $strings);
} else {
assertType("array<string>", $strings);
}
assertType('array<string>', $strings);

if (in_array($s, $strings)) {
assertType('non-empty-array<string>', $strings);
} else {
assertType("array<string>", $strings);
}
assertType('array<string>', $strings);

if (in_array($s, $strings, true) === true) {
assertType('non-empty-array<string>', $strings);
} else {
assertType("array<string>", $strings);
}
assertType('array<string>', $strings);

if (in_array($s, $strings, false) === true) {
assertType('non-empty-array<string>', $strings);
} else {
assertType("array<string>", $strings);
}
assertType('array<string>', $strings);

if (in_array($s, $strings) === true) {
assertType('non-empty-array<string>', $strings);
} else {
assertType("array<string>", $strings);
}
assertType('array<string>', $strings);

if (in_array($s, $strings, true) === false) {
assertType('array<string>', $strings);
} else {
assertType("non-empty-array<string>", $strings);
}
assertType('array<string>', $strings);

if (in_array($s, $strings, false) === false) {
assertType('array<string>', $strings);
} else {
assertType("non-empty-array<string>", $strings);
}
assertType('array<string>', $strings);

if (in_array($s, $strings) === false) {
assertType('array<string>', $strings);
} else {
assertType("non-empty-array<string>", $strings);
}
assertType('array<string>', $strings);
}

/**
* Add new delivery prices.
*
* @param array $price_list Prices list in multiple arrays (changed to array since 1.5.0)
* @param bool $delete
*/
function addDeliveryPrice($price_list, $delete = false): void
{
if (!$price_list) {
return;
}

$keys = array_keys($price_list[0]);
if (!in_array('id_shop', $keys)) {
$keys[] = 'id_shop';
}
if (!in_array('id_shop_group', $keys)) {
$keys[] = 'id_shop_group';
}

var_dump($keys);
}
Loading

0 comments on commit b650df6

Please sign in to comment.