diff --git a/conf/config.level4.neon b/conf/config.level4.neon index a01448efb8..3e646f2f75 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -8,6 +8,7 @@ rules: - PHPStan\Rules\Comparison\BooleanOrConstantConditionRule - PHPStan\Rules\Comparison\ElseIfConstantConditionRule - PHPStan\Rules\Comparison\IfConstantConditionRule + - PHPStan\Rules\Comparison\InvalidNullCoalesce - PHPStan\Rules\Comparison\TernaryOperatorConstantConditionRule - PHPStan\Rules\Comparison\NumberComparisonOperatorsConstantConditionRule - PHPStan\Rules\Comparison\UnreachableIfBranchesRule diff --git a/src/Rules/Comparison/InvalidNullCoalesce.php b/src/Rules/Comparison/InvalidNullCoalesce.php new file mode 100644 index 0000000000..79a89bb17d --- /dev/null +++ b/src/Rules/Comparison/InvalidNullCoalesce.php @@ -0,0 +1,49 @@ + + */ +class InvalidNullCoalesce implements Rule +{ + + /** + * @phpstan-return class-string<\PhpParser\Node\Expr\BinaryOp\Coalesce> + * @return string + */ + public function getNodeType(): string + { + return Node\Expr\BinaryOp\Coalesce::class; + } + + /** + * @phpstan-param \PhpParser\Node\Expr\BinaryOp\Coalesce $node + * @param \PhpParser\Node $node + * @param \PHPStan\Analyser\Scope $scope + * @return RuleError[] errors + */ + public function processNode(Node $node, Scope $scope): array + { + $type = $scope->getType($node->left); + $onlyNull = $type instanceof NullType; + if($onlyNull || $type->accepts(new NullType(), $scope->isDeclareStrictTypes())->no()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Null coalesce on type %s is always %s.', + $type->describe(VerbosityLevel::value()), + $onlyNull ? 'true' : 'false' + ))->line($node->left->getLine())->build(), + ]; + } + return []; + } +} diff --git a/tests/PHPStan/Rules/Comparison/InvalidNullCoalesceTest.php b/tests/PHPStan/Rules/Comparison/InvalidNullCoalesceTest.php new file mode 100644 index 0000000000..1c05d5faba --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/InvalidNullCoalesceTest.php @@ -0,0 +1,52 @@ + + */ +class InvalidNullCoalesceTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new InvalidNullCoalesce(); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/invalid-null-coalesce.php'], [ + [ + 'Null coalesce on type array is always false.', + 10, + ], + [ + 'Null coalesce on type int is always false.', + 11, + ], + [ + 'Null coalesce on type bool is always false.', + 12, + ], + [ + 'Null coalesce on type string is always false.', + 16, + ], + [ + 'Null coalesce on type int|string is always false.', + 27, + ], + [ + 'Null coalesce on type int|false is always false.', + 36, + ], + [ + 'Null coalesce on type null is always true.', + 38, + ], + ]); + } +} diff --git a/tests/PHPStan/Rules/Comparison/data/invalid-null-coalesce.php b/tests/PHPStan/Rules/Comparison/data/invalid-null-coalesce.php new file mode 100644 index 0000000000..116335d871 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/invalid-null-coalesce.php @@ -0,0 +1,40 @@ +