Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 29 additions & 37 deletions src/PhpStan/FunctionStrictModeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\IdentifierRuleError;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\Type;
Expand Down Expand Up @@ -48,54 +47,47 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

if (3 === \count($node->args)) {
if (!$node->args[2] instanceof Node\Arg) {
return [];
}
if (!$node->args[0] instanceof Node\Arg || !$node->args[1] instanceof Node\Arg) {
return [];
}

$isThreeArgs = 3 === \count($node->args);
$needleType = $scope->getType($node->args[0]->value);
$isNeedleScalarType = $this->isSafeToCompareNonStrict($needleType);

if ($isThreeArgs && !$node->args[2] instanceof Node\Arg) {
return [];
}

if (!$isThreeArgs && $isNeedleScalarType) {
return [
RuleErrorBuilder::message('The function in_array must be used in strict mode.')
->identifier('functionCall.strictMode')
->build(),
];
}

if ($isThreeArgs && $node->args[2] instanceof Node\Arg) {
$modeType = $scope->getType($node->args[2]->value);

if ($modeType->isFalse()->yes()) {
if ($isNeedleScalarType && $modeType->isFalse()->yes()) {
return [
RuleErrorBuilder::message('The function in_array must be used in strict mode.')
->identifier('functionCall.strictMode')
->build(),
];
}

return [];
}

return $this->analyzeArgs($node, $scope);
}

/**
* Analyze function arguments types
*
* @param Node\Expr\FuncCall $node
* @param Scope $scope
*
* @return list<IdentifierRuleError>
*/
private function analyzeArgs(Node\Expr\FuncCall $node, Scope $scope): array
{
if (!$node->args[0] instanceof Node\Arg || !$node->args[1] instanceof Node\Arg) {
return [];
}

$needleType = $scope->getType($node->args[0]->value);
$isNeedleScalarType = $this->isSafeToCompareNonStrict($needleType);

if (!$isNeedleScalarType) {
return [];
if (!$isNeedleScalarType && $modeType->isTrue()->yes()) {
return [
RuleErrorBuilder::message('You don\'t need to use strict mode when comparison objects.')
->identifier('functionCall.strictMode')
->build(),
];
}
}

return [
RuleErrorBuilder::message('The function in_array must be used in strict mode.')
->identifier('functionCall.strictMode')
->build(),
];

return [];
}

private function isSafeToCompareNonStrict(Type $argType): bool
Expand Down
13 changes: 8 additions & 5 deletions tests/PhpStan/FunctionStrictModeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@ public function shouldSuccessProcess(): void
$this->analyse(
[__DIR__.'/Resources/function-strict-mode.php'],
[
['The function in_array must be used in strict mode.', 16],
['The function in_array must be used in strict mode.', 17],
['The function in_array must be used in strict mode.', 18],
['The function in_array must be used in strict mode.', 21],
['The function in_array must be used in strict mode.', 23],
['The function in_array must be used in strict mode.', 26],
['The function in_array must be used in strict mode.', 27],
['The function in_array must be used in strict mode.', 28],
['The function in_array must be used in strict mode.', 31],
['The function in_array must be used in strict mode.', 33],
['The function in_array must be used in strict mode.', 35],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No correct, because if we check object, object also checks hash too, as result, we can not use strict mode for enums, because enums is objects. We must use strict checks for scalar, as example 1 !== "1" or true !== 1.

// correct
\in_array(1, [1, 2, 3], true); // Because check scalars, and 1 !== "1".

$a = "1";
\in_array($a, [1, 2, 3], true);

$enum = SomeEnum::Bla;
\in_array($enum, [SomeEnum::Bla, SomeEnum::Foo, SomeEnum::Bar]); // correct because enum is not scalar and php correct check by "hashes".

['You don\'t need to use strict mode when comparison objects.', 36],
['You don\'t need to use strict mode when comparison objects.', 56],
],
);
}
Expand Down
24 changes: 24 additions & 0 deletions tests/PhpStan/Resources/function-strict-mode.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
<?php

enum SomeEnum: string {
case Foo = 'FOO';
case Bar = 'BAR';

public function foo() : bool
{
return true;
}
}

// success
$res = \in_array('a', ['a'], true);
$res = \in_array(1, [1], true);
Expand All @@ -22,6 +32,9 @@

$res = \in_array(rand(0, 1) ? 1 : '1', [1, 2, 3]);

$res = \in_array('a', [SomeEnum::Foo, SomeEnum::Bar]);
$res = \in_array([new stdClass()], [new stdClass(), new stdClass()], true);

// ignore
$strict = rand(0, 1) === 1;
$res = \in_array('x', ['x'], $strict);
Expand All @@ -32,3 +45,14 @@
// fail-safe
$fn = 'in_array';
$res = $fn('a', ['a']);

// error
class Some23 {

public SomeEnum $foo;

function baz() : void
{
$res = \in_array($this->foo, [SomeEnum::Foo, SomeEnum::Bar], true);
}
}