Skip to content

Commit

Permalink
Loosen up ArrayFilterStrictRule for unions with clearly truthy/falsey…
Browse files Browse the repository at this point in the history
… types
  • Loading branch information
ondrejmirtes committed Apr 19, 2024
1 parent 568210b commit 8afd4af
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 0 deletions.
36 changes: 36 additions & 0 deletions src/Rules/Functions/ArrayFilterStrictRule.php
Expand Up @@ -12,6 +12,7 @@
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use PHPStan\Type\VerbosityLevel;
use function count;
use function sprintf;
Expand Down Expand Up @@ -82,6 +83,41 @@ public function processNode(Node $node, Scope $scope): array
}

if (count($args) === 1) {
if ($this->treatPhpDocTypesAsCertain) {

This comment has been minimized.

Copy link
@mvorisek

mvorisek May 9, 2024

@ondrejmirtes would you be please so king to tag a new phpstan 1.10.x release to release this change? Thank you.

This comment has been minimized.

Copy link
@ondrejmirtes

ondrejmirtes May 9, 2024

Author Member

Err, what? We're in phpstan-strict-rules here, and this has already been released in 1.5.4.

This comment has been minimized.

Copy link
@mvorisek

mvorisek May 9, 2024

My bad, this is not compiled into phpstan/phpstan, but instead distributed as a separate package. Thanks for the hint!

$arrayType = $scope->getType($args[0]->value);
} else {
$arrayType = $scope->getNativeType($args[0]->value);
}

$itemType = $arrayType->getIterableValueType();
if ($itemType instanceof UnionType) {
$hasTruthy = false;
$hasFalsey = false;
foreach ($itemType->getTypes() as $innerType) {
$booleanType = $innerType->toBoolean();
if ($booleanType->isTrue()->yes()) {
$hasTruthy = true;
continue;
}
if ($booleanType->isFalse()->yes()) {
$hasFalsey = true;
continue;
}

$hasTruthy = false;
$hasFalsey = false;
break;
}

if ($hasTruthy && $hasFalsey) {
return [];
}
} elseif ($itemType->isBoolean()->yes()) {
return [];
} elseif ($itemType->isArray()->yes()) {
return [];
}

return [RuleErrorBuilder::message('Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.')->build()];
}

Expand Down
20 changes: 20 additions & 0 deletions tests/Rules/Functions/ArrayFilterStrictRuleTest.php
Expand Up @@ -55,4 +55,24 @@ public function testRule(): void
]);
}

public function testRuleAllowMissingCallbackInSomeCases(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->checkNullables = true;
$this->analyse([__DIR__ . '/data/array-filter-allow.php'], [
[
'Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.',
27,
],
[
'Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.',
37,
],
[
'Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.',
49,
],
]);
}

}
58 changes: 58 additions & 0 deletions tests/Rules/Functions/data/array-filter-allow.php
@@ -0,0 +1,58 @@
<?php

namespace ArrayFilterAllow;

use function array_filter;

class Foo
{

/**
* @param array<self|null> $a
*/
public function doFoo(
array $a
): array
{
return array_filter($a);
}

/**
* @param array<self> $a
*/
public function doFoo2(
array $a
): array
{
return array_filter($a);
}

/**
* @param array<int|null> $a
*/
public function doFoo3(
array $a
): array
{
return array_filter($a);
}

/** @param array<bool> $a */
public function doFoo4(array $a): array
{
return array_filter($a);
}

/** @param array<int> $a */
public function doFoo5(array $a): array
{
return array_filter($a);
}

/** @param array<array<int>> $a */
public function doFoo6(array $a): array
{
return array_filter($a);
}

}

0 comments on commit 8afd4af

Please sign in to comment.