diff --git a/composer.json b/composer.json index 71e3279..a59c36b 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "keywords": ["static analysis"], "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0" + "phpstan/phpstan": "^2.2.15" }, "require-dev": { "beberlei/assert": "^3.3.0", diff --git a/src/Type/BeberleiAssert/AssertHelper.php b/src/Type/BeberleiAssert/AssertHelper.php index 55e6fa3..3f98afe 100644 --- a/src/Type/BeberleiAssert/AssertHelper.php +++ b/src/Type/BeberleiAssert/AssertHelper.php @@ -79,7 +79,7 @@ public static function specifyTypes( bool $nullOr ): SpecifiedTypes { - [$expression, $rootExpr] = self::createExpression($scope, $assertName, $args); + [$expression, $isEquality] = self::createExpression($scope, $assertName, $args); if ($expression === null) { return new SpecifiedTypes([], []); } @@ -98,9 +98,12 @@ public static function specifyTypes( $scope, $expression, TypeSpecifierContext::createTruthy(), - )->setRootExpr($rootExpr ?? $expression); + ); + if ($isEquality) { + $specifiedTypes = $specifiedTypes->setEquality(); + } - return self::specifyRootExprIfSet($rootExpr, $scope, $specifiedTypes, $typeSpecifier); + return $specifiedTypes; } public static function handleAll( @@ -242,7 +245,7 @@ private static function allArrayOrIterable( /** * @param Arg[] $args - * @return array{?Expr, ?Expr} + * @return array{?Expr, bool} */ private static function createExpression( Scope $scope, @@ -255,21 +258,21 @@ private static function createExpression( $resolverResult = $resolver($scope, ...$args); if (is_array($resolverResult)) { - [$expr, $rootExpr] = $resolverResult; + [$expr, $isEquality] = $resolverResult; } else { $expr = $resolverResult; - $rootExpr = null; + $isEquality = false; } if ($expr === null) { - return [null, null]; + return [null, false]; } - return [$expr, $rootExpr]; + return [$expr, $isEquality]; } /** - * @return array + * @return array */ private static function getExpressionResolvers(): array { @@ -438,47 +441,22 @@ private static function getExpressionResolvers(): array 'isJsonString', ]; foreach ($assertionsResultingAtLeastInNonEmptyString as $name) { - self::$resolvers[$name] = static fn (Scope $scope, Arg $value): array => self::createIsNonEmptyStringAndSomethingExprPair($name, [$value]); + self::$resolvers[$name] = static fn (Scope $scope, Arg $value): array => [ + new BooleanAnd( + new FuncCall( + new Name('is_string'), + [$value], + ), + new NotIdentical( + $value->value, + new String_(''), + ), + ), + true, + ]; } return self::$resolvers; } - /** - * @param Arg[] $args - * @return array{Expr, Expr} - */ - private static function createIsNonEmptyStringAndSomethingExprPair(string $name, array $args): array - { - $expr = new BooleanAnd( - new FuncCall( - new Name('is_string'), - [$args[0]], - ), - new NotIdentical( - $args[0]->value, - new String_(''), - ), - ); - - $rootExpr = new BooleanAnd( - $expr, - new FuncCall(new Name('FAUX_FUNCTION_ ' . $name), $args), - ); - - return [$expr, $rootExpr]; - } - - private static function specifyRootExprIfSet(?Expr $rootExpr, Scope $scope, SpecifiedTypes $specifiedTypes, TypeSpecifier $typeSpecifier): SpecifiedTypes - { - if ($rootExpr === null) { - return $specifiedTypes; - } - - // Makes consecutive calls with a rootExpr adding unknown info via FAUX_FUNCTION evaluate to true - return $specifiedTypes->unionWith( - $typeSpecifier->create($rootExpr, new ConstantBooleanType(true), TypeSpecifierContext::createTruthy(), $scope), - ); - } - } diff --git a/tests/Type/BeberleiAssert/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/Type/BeberleiAssert/ImpossibleCheckTypeMethodCallRuleTest.php index 735a93e..2c4aa14 100644 --- a/tests/Type/BeberleiAssert/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/Type/BeberleiAssert/ImpossibleCheckTypeMethodCallRuleTest.php @@ -33,7 +33,8 @@ public function testExtension(): void [ 'Call to method Assert\AssertionChain::isJsonString() will always evaluate to true.', 22, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + '• Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%. +• If Assert\AssertionChain::isJsonString() is impure, add @phpstan-impure PHPDoc tag above its declaration. Learn more: https://phpstan.org/blog/remembering-and-forgetting-returned-values', ], [ 'Call to method Assert\AssertionChain::isJsonString() will always evaluate to true.', diff --git a/tests/Type/BeberleiAssert/ImpossibleCheckTypeStaticMethodCallRuleTest.php b/tests/Type/BeberleiAssert/ImpossibleCheckTypeStaticMethodCallRuleTest.php index 7790b8a..0355c6c 100644 --- a/tests/Type/BeberleiAssert/ImpossibleCheckTypeStaticMethodCallRuleTest.php +++ b/tests/Type/BeberleiAssert/ImpossibleCheckTypeStaticMethodCallRuleTest.php @@ -23,7 +23,6 @@ public function testExtension(): void [ 'Call to static method Assert\Assertion::string() with string will always evaluate to true.', 12, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to static method Assert\Assertion::allString() with array will always evaluate to true.',