From 6a9501cc1dbc96dc97477f53fbc414b7ac84e5a3 Mon Sep 17 00:00:00 2001 From: Jean-Luc Herren Date: Sun, 18 Oct 2020 18:01:57 +0200 Subject: [PATCH 1/2] Sort IntegerRangeType numerically --- src/Type/UnionTypeHelper.php | 4 ++++ tests/PHPStan/Analyser/NodeScopeResolverTest.php | 4 ++-- tests/PHPStan/Analyser/TypeSpecifierTest.php | 2 +- tests/PHPStan/Analyser/data/bug-2954.php | 8 ++++---- tests/PHPStan/Analyser/data/integer-range-types.php | 6 +++--- .../StrictComparisonOfDifferentTypesRuleTest.php | 8 ++++---- tests/PHPStan/Type/TypeCombinatorTest.php | 10 +++++++++- 7 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/Type/UnionTypeHelper.php b/src/Type/UnionTypeHelper.php index 6f39d0a61a..70d14331a7 100644 --- a/src/Type/UnionTypeHelper.php +++ b/src/Type/UnionTypeHelper.php @@ -86,6 +86,10 @@ public static function sortTypes(array $types): array return 0; } + if ($a instanceof IntegerRangeType && $b instanceof IntegerRangeType) { + return $a->getMin() <=> $b->getMin(); + } + if ($a instanceof ConstantStringType && $b instanceof ConstantStringType) { return strcasecmp($a->getValue(), $b->getValue()); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index b9d31c92c8..7968968023 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -2343,7 +2343,7 @@ public function dataBinaryOperations(): array '@$stringOrNull ?: 12', ], [ - 'int<1, max>|int', + 'int|int<1, max>', '$integer ?: 12', ], [ @@ -5244,7 +5244,7 @@ public function dataArrayFunctions(): array 'array_filter($union)', ], [ - 'array|int|true>', + 'array|int<1, max>|true>', 'array_filter($withPossiblyFalsey)', ], [ diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index 5df0e023f6..1a3e013604 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -702,7 +702,7 @@ public function dataCondition(): array ) ), [ - '$n' => '~int<6, max>|int', + '$n' => '~int|int<6, max>', ], [ '$n' => '~int<3, 5>', diff --git a/tests/PHPStan/Analyser/data/bug-2954.php b/tests/PHPStan/Analyser/data/bug-2954.php index 2a8a1e1ba1..1727d11b76 100644 --- a/tests/PHPStan/Analyser/data/bug-2954.php +++ b/tests/PHPStan/Analyser/data/bug-2954.php @@ -6,7 +6,7 @@ function (int $x) { if ($x === 0) return; - assertType('int<1, max>|int', $x); + assertType('int|int<1, max>', $x); $x++; assertType('int', $x); @@ -14,7 +14,7 @@ function (int $x) { function (int $x) { if ($x === 0) return; - assertType('int<1, max>|int', $x); + assertType('int|int<1, max>', $x); ++$x; assertType('int', $x); @@ -22,7 +22,7 @@ function (int $x) { function (int $x) { if ($x === 0) return; - assertType('int<1, max>|int', $x); + assertType('int|int<1, max>', $x); $x--; assertType('int', $x); @@ -30,7 +30,7 @@ function (int $x) { function (int $x) { if ($x === 0) return; - assertType('int<1, max>|int', $x); + assertType('int|int<1, max>', $x); --$x; assertType('int', $x); diff --git a/tests/PHPStan/Analyser/data/integer-range-types.php b/tests/PHPStan/Analyser/data/integer-range-types.php index 2704b2d388..6c390c4c64 100644 --- a/tests/PHPStan/Analyser/data/integer-range-types.php +++ b/tests/PHPStan/Analyser/data/integer-range-types.php @@ -21,18 +21,18 @@ function (int $i) { assertType('int', $i); } - assertType('int<3, max>|int', $i); + assertType('int|int<3, max>', $i); if ($i < 3 && $i > 5) { assertType('*NEVER*', $i); } else { - assertType('int<3, max>|int', $i); + assertType('int|int<3, max>', $i); } if ($i > 3 && $i < 5) { assertType('4', $i); } else { - assertType('3|int<5, max>|int', $i); + assertType('3|int|int<5, max>', $i); } if ($i >= 3 && $i <= 5) { diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 87cea09280..aba5d5170c 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -159,11 +159,11 @@ public function testStrictComparison(): void 438, ], [ - 'Strict comparison using === between int<2, max>|int|string and 1.0 will always evaluate to false.', + 'Strict comparison using === between int|int<2, max>|string and 1.0 will always evaluate to false.', 464, ], [ - 'Strict comparison using === between int<2, max>|int|string and stdClass will always evaluate to false.', + 'Strict comparison using === between int|int<2, max>|string and stdClass will always evaluate to false.', 466, ], [ @@ -333,11 +333,11 @@ public function testStrictComparisonWithoutAlwaysTrue(): void 438, ], [ - 'Strict comparison using === between int<2, max>|int|string and 1.0 will always evaluate to false.', + 'Strict comparison using === between int|int<2, max>|string and 1.0 will always evaluate to false.', 464, ], [ - 'Strict comparison using === between int<2, max>|int|string and stdClass will always evaluate to false.', + 'Strict comparison using === between int|int<2, max>|string and stdClass will always evaluate to false.', 466, ], [ diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 35aa806db8..f0328fb297 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -1426,6 +1426,14 @@ public function dataUnion(): array UnionType::class, 'int<1, 3>|int<7, 9>', ], + [ + [ + IntegerRangeType::fromInterval(7, 9), + IntegerRangeType::fromInterval(1, 3), + ], + UnionType::class, + 'int<1, 3>|int<7, 9>', + ], [ [ IntegerRangeType::fromInterval(1, 3), @@ -3204,7 +3212,7 @@ public function dataRemove(): array new BenevolentUnionType([new IntegerType(), new StringType()]), new ConstantIntegerType(1), UnionType::class, - 'int<2, max>|int|string', + 'int|int<2, max>|string', ], [ new BenevolentUnionType([new IntegerType(), new StringType()]), From 95e27b491538cdddcbf4534277a046635efa3a81 Mon Sep 17 00:00:00 2001 From: Jean-Luc Herren Date: Sat, 24 Oct 2020 15:52:40 +0200 Subject: [PATCH 2/2] Improve type specifier for identical operator --- src/Analyser/TypeSpecifier.php | 185 +++++++----------- src/Type/TypeUtils.php | 35 ++++ .../Analyser/NodeScopeResolverTest.php | 12 +- tests/PHPStan/Analyser/TypeSpecifierTest.php | 92 +++++---- tests/PHPStan/Analyser/data/bug-2648.php | 2 +- tests/PHPStan/Analyser/data/bug-3009.php | 6 +- 6 files changed, 177 insertions(+), 155 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 562458ea0b..9665c0671e 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -32,7 +32,6 @@ use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; -use PHPStan\Type\NeverType; use PHPStan\Type\NonexistentParentClassType; use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; @@ -163,103 +162,25 @@ public function specifyTypesInCondition( if ($context->true()) { return $this->create($exprNode, new ObjectWithoutClassType(), $context); } - } elseif ($expr instanceof Node\Expr\BinaryOp\Identical) { - $expressions = $this->findTypeExpressionsFromBinaryOperation($scope, $expr); - if ($expressions !== null) { - /** @var Expr $exprNode */ - $exprNode = $expressions[0]; - if ($exprNode instanceof Expr\Assign) { - $exprNode = $exprNode->var; - } - /** @var \PHPStan\Type\ConstantScalarType $constantType */ - $constantType = $expressions[1]; - if ($constantType->getValue() === false) { - $types = $this->create($exprNode, $constantType, $context); - return $types->unionWith($this->specifyTypesInCondition( - $scope, - $exprNode, - $context->true() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createFalse()->negate() - )); - } - - if ($constantType->getValue() === true) { - $types = $this->create($exprNode, $constantType, $context); - return $types->unionWith($this->specifyTypesInCondition( - $scope, - $exprNode, - $context->true() ? TypeSpecifierContext::createTrue() : TypeSpecifierContext::createTrue()->negate() - )); - } - - if ($constantType->getValue() === null) { - return $this->create($exprNode, $constantType, $context); - } - - if ( - !$context->null() - && $exprNode instanceof FuncCall - && count($exprNode->args) === 1 - && $exprNode->name instanceof Name - && strtolower((string) $exprNode->name) === 'count' - && $constantType instanceof ConstantIntegerType - ) { - if ($context->truthy() || $constantType->getValue() === 0) { - $newContext = $context; - if ($constantType->getValue() === 0) { - $newContext = $newContext->negate(); - } - $argType = $scope->getType($exprNode->args[0]->value); - if ((new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($argType)->yes()) { - return $this->create($exprNode->args[0]->value, new NonEmptyArrayType(), $newContext); - } - } - } - } - - if ($context->true()) { - $type = TypeCombinator::intersect($scope->getType($expr->right), $scope->getType($expr->left)); - $leftTypes = $this->create($expr->left, $type, $context); - $rightTypes = $this->create($expr->right, $type, $context); - return $leftTypes->unionWith($rightTypes); - - } elseif ($context->false()) { - $identicalType = $scope->getType($expr); - if ($identicalType instanceof ConstantBooleanType) { - $never = new NeverType(); - $contextForTypes = $identicalType->getValue() ? $context->negate() : $context; - $leftTypes = $this->create($expr->left, $never, $contextForTypes); - $rightTypes = $this->create($expr->right, $never, $contextForTypes); - return $leftTypes->unionWith($rightTypes); - } + } elseif ($expr instanceof Node\Expr\BinaryOp\Identical && !$context->null()) { + $leftType = $scope->getType($expr->left); + $rightType = $scope->getType($expr->right); - if ( - ( - $expr->left instanceof Node\Scalar - || $expr->left instanceof Expr\Array_ - ) - && !$expr->right instanceof Node\Scalar - ) { - return $this->create( - $expr->right, - $scope->getType($expr->left), - $context - ); + if ($context->truthy()) { + return $this->specifyTypesInExpression($scope, $expr->left, $rightType, $context) + ->unionWith($this->specifyTypesInExpression($scope, $expr->right, $leftType, $context)); + } elseif ($context->falsey()) { + $specifiedTypes = new SpecifiedTypes(); + if (TypeUtils::isOneDefiniteType($leftType)->yes()) { + $specifiedTypes = $this->specifyTypesInExpression($scope, $expr->right, $leftType, $context); } - if ( - ( - $expr->right instanceof Node\Scalar - || $expr->right instanceof Expr\Array_ - ) - && !$expr->left instanceof Node\Scalar - ) { - return $this->create( - $expr->left, - $scope->getType($expr->right), - $context + if (TypeUtils::isOneDefiniteType($rightType)->yes()) { + $specifiedTypes = $specifiedTypes->unionWith( + $this->specifyTypesInExpression($scope, $expr->left, $rightType, $context) ); } + return $specifiedTypes; } - } elseif ($expr instanceof Node\Expr\BinaryOp\NotIdentical) { return $this->specifyTypesInCondition( $scope, @@ -462,7 +383,7 @@ public function specifyTypesInCondition( } if ($defaultHandleFunctions) { - return $this->handleDefaultTruthyOrFalseyContext($context, $expr); + return $this->handleDefaultTruthyOrFalseyContext($scope, $context, $expr); } } elseif ($expr instanceof MethodCall && $expr->name instanceof Node\Identifier) { $methodCalledOnType = $scope->getType($expr->var); @@ -485,7 +406,7 @@ public function specifyTypesInCondition( } if ($defaultHandleFunctions) { - return $this->handleDefaultTruthyOrFalseyContext($context, $expr); + return $this->handleDefaultTruthyOrFalseyContext($scope, $context, $expr); } } elseif ($expr instanceof StaticCall && $expr->name instanceof Node\Identifier) { if ($expr->class instanceof Name) { @@ -513,7 +434,7 @@ public function specifyTypesInCondition( } if ($defaultHandleFunctions) { - return $this->handleDefaultTruthyOrFalseyContext($context, $expr); + return $this->handleDefaultTruthyOrFalseyContext($scope, $context, $expr); } } elseif ($expr instanceof BooleanAnd || $expr instanceof LogicalAnd) { $leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context); @@ -529,11 +450,9 @@ public function specifyTypesInCondition( if (!$scope instanceof MutatingScope) { throw new \PHPStan\ShouldNotHappenException(); } - if ($context->null()) { - return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->expr, $context); - } - - return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->var, $context); + $scope = $scope->exitFirstLevelStatements(); + return $this->specifyTypesInCondition($scope, $expr->var, $context) + ->unionWith($this->specifyTypesInCondition($scope, $expr->expr, $context)); } elseif ( ( $expr instanceof Expr\Isset_ @@ -652,20 +571,24 @@ public function specifyTypesInCondition( } elseif ($expr instanceof Expr\ErrorSuppress) { return $this->specifyTypesInCondition($scope, $expr->expr, $context, $defaultHandleFunctions); } elseif (!$context->null()) { - return $this->handleDefaultTruthyOrFalseyContext($context, $expr); + return $this->handleDefaultTruthyOrFalseyContext($scope, $context, $expr); } return new SpecifiedTypes(); } - private function handleDefaultTruthyOrFalseyContext(TypeSpecifierContext $context, Expr $expr): SpecifiedTypes + private function handleDefaultTruthyOrFalseyContext(Scope $scope, TypeSpecifierContext $context, Expr $expr): SpecifiedTypes { - if (!$context->truthy()) { - $type = StaticTypeFactory::truthy(); - return $this->create($expr, $type, TypeSpecifierContext::createFalse()); - } elseif (!$context->falsey()) { - $type = StaticTypeFactory::falsey(); - return $this->create($expr, $type, TypeSpecifierContext::createFalse()); + if ($context->truthy()) { + $falsey = StaticTypeFactory::falsey(); + if (!$falsey->isSuperTypeOf($scope->getType($expr))->no()) { + return $this->create($expr, $falsey, TypeSpecifierContext::createFalsey()); + } + } elseif ($context->falsey()) { + $falsey = StaticTypeFactory::falsey(); + if (!$falsey->isSuperTypeOf($scope->getType($expr))->yes()) { + return $this->create($expr, $falsey, TypeSpecifierContext::createTruthy()); + } } return new SpecifiedTypes(); @@ -800,4 +723,48 @@ private function getTypeSpecifyingExtensionsForType(array $extensions, string $c return array_merge(...$extensionsForClass); } + private function specifyTypesInExpression(Scope $scope, Expr $expr, Type $type, TypeSpecifierContext $context): SpecifiedTypes + { + if ($expr instanceof Expr\Assign) { + return $this->specifyTypesInExpression($scope, $expr->var, $type, $context) + ->unionWith($this->specifyTypesInExpression($scope, $expr->expr, $type, $context)); + } + + $originalType = $scope->getType($expr); + + if ($context->truthy()) { + $newType = TypeCombinator::intersect($originalType, $type); + } elseif ($context->falsey()) { + $newType = TypeCombinator::remove($originalType, $type); + } else { + return new SpecifiedTypes(); + } + + if ($originalType->equals($newType)) { + return new SpecifiedTypes(); + } + + $specifiedTypes = $this->create($expr, $type, $context); + + if ( + !StaticTypeFactory::truthy()->isSuperTypeOf($originalType)->yes() + && StaticTypeFactory::truthy()->isSuperTypeOf($newType)->yes() + ) { + $specifiedTypes = $specifiedTypes->unionWith( + $this->specifyTypesInCondition($scope, $expr, TypeSpecifierContext::createTruthy()) + ); + } + + if ( + !StaticTypeFactory::falsey()->isSuperTypeOf($originalType)->yes() + && StaticTypeFactory::falsey()->isSuperTypeOf($newType)->yes() + ) { + $specifiedTypes = $specifiedTypes->unionWith( + $this->specifyTypesInCondition($scope, $expr, TypeSpecifierContext::createFalsey()), + ); + } + + return $specifiedTypes; + } + } diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index f9055a4e89..16ec7e5b50 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryType; use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Constant\ConstantArrayType; @@ -339,4 +340,38 @@ public static function containsCallable(Type $type): bool return false; } + public static function isOneDefiniteType(Type $type): TrinaryLogic + { + if ($type instanceof ConstantScalarType) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof ConstantArrayType) { + if (count($type->getOptionalKeys()) !== 0) { + return TrinaryLogic::createNo(); + } + foreach ($type->getValueTypes() as $valueType) { + if (!self::isOneDefiniteType($valueType)) { + return TrinaryLogic::createNo(); + } + } + return TrinaryLogic::createYes(); + } + + if ($type instanceof UnionType) { + return TrinaryLogic::createNo(); + } + + if ($type instanceof IntersectionType) { + foreach ($type->getTypes() as $innerType) { + $innerTypeIsOneDefiniteType = self::isOneDefiniteType($innerType); + if (!$innerTypeIsOneDefiniteType->maybe()) { + return $innerTypeIsOneDefiniteType; + } + } + } + + return TrinaryLogic::createMaybe(); + } + } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 7968968023..110ecfca3a 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -7591,17 +7591,17 @@ public function dataWhileLoopVariables(): array { return [ [ - 'int', + 'int', '$i', "'begin'", ], [ - 'int', + 'int', '$i', "'end'", ], [ - 'int', + 'int', '$i', "'afterLoop'", ], @@ -8921,7 +8921,7 @@ public function dataConstantTypeAfterDuplicateCondition(): array "'afterFirst'", ], [ - 'int', + 'int|int<1, max>', '$a', "'afterSecond'", ], @@ -8936,12 +8936,12 @@ public function dataConstantTypeAfterDuplicateCondition(): array "'afterSecond'", ], [ - 'int', + 'int|int<1, max>', '$a', "'afterThird'", ], [ - 'int', + 'int|int<1, max>', '$b', "'afterThird'", ], diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index 1a3e013604..91c164af1e 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -28,10 +28,8 @@ class TypeSpecifierTest extends \PHPStan\Testing\TestCase { - private const FALSEY_TYPE_DESCRIPTION = '0|0.0|\'\'|\'0\'|array()|false|null'; - private const TRUTHY_TYPE_DESCRIPTION = 'mixed~' . self::FALSEY_TYPE_DESCRIPTION; - private const SURE_NOT_FALSEY = '~' . self::FALSEY_TYPE_DESCRIPTION; - private const SURE_NOT_TRUTHY = '~' . self::TRUTHY_TYPE_DESCRIPTION; + private const SURE_FALSEY = '0|0.0|\'\'|\'0\'|array()|false|null'; + private const SURE_NOT_FALSEY = '~' . self::SURE_FALSEY; /** @var \PhpParser\PrettyPrinter\Standard() */ private $printer; @@ -204,7 +202,7 @@ public function dataCondition(): array [ new Variable('foo'), ['$foo' => self::SURE_NOT_FALSEY], - ['$foo' => self::SURE_NOT_TRUTHY], + ['$foo' => self::SURE_FALSEY], ], [ new Expr\BinaryOp\BooleanAnd( @@ -220,18 +218,18 @@ public function dataCondition(): array $this->createFunctionCall('random') ), [], - ['$foo' => self::SURE_NOT_TRUTHY], + ['$foo' => self::SURE_FALSEY], ], [ new Expr\BooleanNot(new Variable('bar')), - ['$bar' => self::SURE_NOT_TRUTHY], - ['$bar' => self::SURE_NOT_FALSEY], + ['$bar' => self::SURE_FALSEY], + [], ], [ new PropertyFetch(new Variable('this'), 'foo'), ['$this->foo' => self::SURE_NOT_FALSEY], - ['$this->foo' => self::SURE_NOT_TRUTHY], + ['$this->foo' => self::SURE_FALSEY], ], [ new Expr\BinaryOp\BooleanAnd( @@ -247,11 +245,11 @@ public function dataCondition(): array $this->createFunctionCall('random') ), [], - ['$this->foo' => self::SURE_NOT_TRUTHY], + ['$this->foo' => self::SURE_FALSEY], ], [ new Expr\BooleanNot(new PropertyFetch(new Variable('this'), 'foo')), - ['$this->foo' => self::SURE_NOT_TRUTHY], + ['$this->foo' => self::SURE_FALSEY], ['$this->foo' => self::SURE_NOT_FALSEY], ], @@ -343,7 +341,7 @@ public function dataCondition(): array new Variable('foo'), new Expr\ConstFetch(new Name('true')) ), - ['$foo' => 'true & ~' . self::FALSEY_TYPE_DESCRIPTION], + ['$foo' => 'true & ~' . self::SURE_FALSEY], ['$foo' => '~true'], ], [ @@ -351,7 +349,7 @@ public function dataCondition(): array new Variable('foo'), new Expr\ConstFetch(new Name('false')) ), - ['$foo' => 'false & ~' . self::TRUTHY_TYPE_DESCRIPTION], + ['$foo' => 'false'], ['$foo' => '~false'], ], [ @@ -391,7 +389,7 @@ public function dataCondition(): array new Variable('foo'), new Expr\ConstFetch(new Name('false')) ), - ['$foo' => self::SURE_NOT_TRUTHY], + ['$foo' => self::SURE_FALSEY], ['$foo' => self::SURE_NOT_FALSEY], ], [ @@ -399,7 +397,7 @@ public function dataCondition(): array new Variable('foo'), new Expr\ConstFetch(new Name('null')) ), - ['$foo' => self::SURE_NOT_TRUTHY], + ['$foo' => self::SURE_FALSEY], ['$foo' => self::SURE_NOT_FALSEY], ], [ @@ -407,7 +405,7 @@ public function dataCondition(): array new Variable('foo'), new Variable('bar') ), - ['$foo' => 'Bar', '$bar' => 'Bar'], + ['$foo' => 'Bar & ' . self::SURE_NOT_FALSEY], [], ], [ @@ -478,16 +476,28 @@ public function dataCondition(): array new Variable('foo'), new Variable('stringOrNull') ), - ['$foo' => self::SURE_NOT_FALSEY], - ['$foo' => self::SURE_NOT_TRUTHY], + [ + '$foo' => self::SURE_NOT_FALSEY, + '$stringOrNull' => self::SURE_NOT_FALSEY, + ], + [ + '$foo' => self::SURE_FALSEY, + '$stringOrNull' => self::SURE_FALSEY, + ], ], [ new Expr\Assign( new Variable('foo'), new Variable('stringOrFalse') ), - ['$foo' => self::SURE_NOT_FALSEY], - ['$foo' => self::SURE_NOT_TRUTHY], + [ + '$foo' => self::SURE_NOT_FALSEY, + '$stringOrFalse' => self::SURE_NOT_FALSEY, + ], + [ + '$foo' => self::SURE_FALSEY, + '$stringOrFalse' => self::SURE_FALSEY, + ], ], [ new Expr\Assign( @@ -495,7 +505,10 @@ public function dataCondition(): array new Variable('bar') ), ['$foo' => self::SURE_NOT_FALSEY], - ['$foo' => self::SURE_NOT_TRUTHY], + [ + '$foo' => self::SURE_FALSEY, + '$bar' => self::SURE_FALSEY, + ], ], [ new Expr\Isset_([ @@ -507,7 +520,7 @@ public function dataCondition(): array '$barOrNull' => '~null', ], [ - 'isset($stringOrNull, $barOrNull)' => self::SURE_NOT_TRUTHY, + 'isset($stringOrNull, $barOrNull)' => self::SURE_FALSEY, ], ], [ @@ -525,8 +538,7 @@ public function dataCondition(): array new LNumber(123) ), [ - '$foo' => '123', - 123 => '123', + '$foo' => '123 & ' . self::SURE_NOT_FALSEY, ], ['$foo' => '~123'], ], @@ -576,7 +588,7 @@ public function dataCondition(): array '$foo' => self::SURE_NOT_FALSEY, ], [ - '$foo' => self::SURE_NOT_TRUTHY, + '$foo' => self::SURE_FALSEY, ], ], [ @@ -585,7 +597,7 @@ public function dataCondition(): array '$array' => self::SURE_NOT_FALSEY, ], [ - '$array' => self::SURE_NOT_TRUTHY, + '$array' => self::SURE_FALSEY, ], ], [ @@ -623,7 +635,7 @@ public function dataCondition(): array '$foo->bar' => '~null', ], [ - 'isset($foo->bar)' => self::SURE_NOT_TRUTHY, + 'isset($foo->bar)' => self::SURE_FALSEY, ], ], [ @@ -636,7 +648,7 @@ public function dataCondition(): array 'Foo::$bar' => '~null', ], [ - 'isset(Foo::$bar)' => self::SURE_NOT_TRUTHY, + 'isset(Foo::$bar)' => self::SURE_FALSEY, ], ], [ @@ -648,7 +660,7 @@ public function dataCondition(): array '$barOrNull' => 'null', ], [ - '$barOrNull' => '~null', + '$barOrNull' => self::SURE_NOT_FALSEY, ], ], [ @@ -661,9 +673,11 @@ public function dataCondition(): array ), [ '$notNullBar' => 'null', + '$barOrNull' => 'null', ], [ '$notNullBar' => '~null', + '$barOrNull' => self::SURE_NOT_FALSEY, ], ], [ @@ -672,7 +686,7 @@ public function dataCondition(): array new Expr\ConstFetch(new Name('null')) ), [ - '$barOrNull' => '~null', + '$barOrNull' => self::SURE_NOT_FALSEY, ], [ '$barOrNull' => 'null', @@ -735,9 +749,11 @@ public function dataCondition(): array ), [ '$notNullBar' => '~null', + '$barOrNull' => self::SURE_NOT_FALSEY, ], [ '$notNullBar' => 'null', + '$barOrNull' => 'null', ], ], [ @@ -746,10 +762,10 @@ public function dataCondition(): array new Expr\ConstFetch(new Name('false')) ), [ - '$barOrFalse' => 'false & ' . self::SURE_NOT_TRUTHY, + '$barOrFalse' => 'false', ], [ - '$barOrFalse' => '~false', + '$barOrFalse' => self::SURE_NOT_FALSEY, ], ], [ @@ -761,10 +777,12 @@ public function dataCondition(): array new Expr\ConstFetch(new Name('false')) ), [ - '$notFalseBar' => 'false & ' . self::SURE_NOT_TRUTHY, + '$notFalseBar' => 'false', + '$barOrFalse' => 'false', ], [ '$notFalseBar' => '~false', + '$barOrFalse' => self::SURE_NOT_FALSEY, ], ], [ @@ -773,10 +791,10 @@ public function dataCondition(): array new Expr\ConstFetch(new Name('false')) ), [ - '$barOrFalse' => '~false', + '$barOrFalse' => self::SURE_NOT_FALSEY, ], [ - '$barOrFalse' => 'false & ' . self::SURE_NOT_TRUTHY, + '$barOrFalse' => 'false', ], ], [ @@ -789,9 +807,11 @@ public function dataCondition(): array ), [ '$notFalseBar' => '~false', + '$barOrFalse' => self::SURE_NOT_FALSEY, ], [ - '$notFalseBar' => 'false & ' . self::SURE_NOT_TRUTHY, + '$notFalseBar' => 'false', + '$barOrFalse' => 'false', ], ], [ diff --git a/tests/PHPStan/Analyser/data/bug-2648.php b/tests/PHPStan/Analyser/data/bug-2648.php index c1d8e1668d..fd83d9acdc 100644 --- a/tests/PHPStan/Analyser/data/bug-2648.php +++ b/tests/PHPStan/Analyser/data/bug-2648.php @@ -37,7 +37,7 @@ public function doBar(array $list): void assertType('int<0, max>', count($list)); if (count($list) === 1) { - assertType('int<1, max>', count($list)); + assertType('1', count($list)); break; } } diff --git a/tests/PHPStan/Analyser/data/bug-3009.php b/tests/PHPStan/Analyser/data/bug-3009.php index fcaa76419d..0f5dad69f1 100644 --- a/tests/PHPStan/Analyser/data/bug-3009.php +++ b/tests/PHPStan/Analyser/data/bug-3009.php @@ -15,14 +15,14 @@ public function createRedirectRequest(string $redirectUri): ?string return null; } - assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); + assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); if (true === array_key_exists('query', $redirectUrlParts)) { - assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, \'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); + assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, \'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); $redirectServer['QUERY_STRING'] = $redirectUrlParts['query']; } - assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); + assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); return 'foo'; }