From e11537b259b4df9ac53ae11381cb5aab4aa67098 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 25 Jan 2024 14:31:18 +0100 Subject: [PATCH] Narrow types based on non-strict equality with constant types --- src/Analyser/TypeSpecifier.php | 34 ++++++- .../Analyser/NodeScopeResolverTest.php | 1 + .../Analyser/data/loose-equal-consts.php | 92 +++++++++++++++++++ 3 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/loose-equal-consts.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 4e7b61cc25..4de421b1d6 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -38,6 +38,7 @@ use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; use PHPStan\Type\ConditionalTypeForParameter; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -1469,15 +1470,24 @@ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\ $leftExpr = $leftExpr->getExpr(); } + $isNativeConstFetch = static function (Expr $expr): bool { + if (!$expr instanceof ConstFetch) { + return false; + } + + $loweredConstName = strtolower($expr->name->toString()); + return in_array($loweredConstName, ['true', 'false', 'null'], true); + }; + if ( $leftType instanceof ConstantScalarType - && !$rightExpr instanceof ConstFetch + && !$isNativeConstFetch($rightExpr) && !$rightExpr instanceof ClassConstFetch ) { return [$binaryOperation->right, $leftType]; } elseif ( $rightType instanceof ConstantScalarType - && !$leftExpr instanceof ConstFetch + && !$isNativeConstFetch($leftExpr) && !$leftExpr instanceof ClassConstFetch ) { return [$binaryOperation->left, $rightType]; @@ -1809,6 +1819,26 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif ); } + $looseyType = new UnionType([ + new NullType(), + new ConstantBooleanType(true), + new ConstantBooleanType(false), + new ConstantIntegerType(0), + new ConstantIntegerType(1), + new FloatType(), // equality on floats is not usefull + new ConstantStringType(''), + new ConstantStringType('0'), + new ConstantStringType('1'), + new ConstantArrayType([], []), + ]); + + if ($context->true() + && ($exprNode instanceof Expr\Variable || $exprNode instanceof ConstFetch) + && !$looseyType->isSuperTypeOf($constantType)->yes() + ) { + return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Identical($expr->left, $expr->right), $context, $rootExpr); + } + if ( $exprNode instanceof FuncCall && $exprNode->name instanceof Name diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 0b90bd110e..e7eb5ca126 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1422,6 +1422,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5961.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10189.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10317.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/loose-equal-consts.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10302-interface-extends.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10302-trait-extends.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10302-trait-implements.php'); diff --git a/tests/PHPStan/Analyser/data/loose-equal-consts.php b/tests/PHPStan/Analyser/data/loose-equal-consts.php new file mode 100644 index 0000000000..b25e570695 --- /dev/null +++ b/tests/PHPStan/Analyser/data/loose-equal-consts.php @@ -0,0 +1,92 @@ +