diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 9ae63c6cde8..6e19ced40cd 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -17,6 +17,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; @@ -137,6 +138,10 @@ public function isSuperTypeOf(Type $otherType): TrinaryLogic return $otherType->isSubTypeOf($this); } + if ($otherType instanceof IntegerRangeType && $otherType->getMin() !== null && $otherType->getMax() !== null) { + return $this->isSuperTypeOfIntegerRange($otherType); + } + $result = TrinaryLogic::createNo()->lazyOr($this->getTypes(), static fn (Type $innerType) => $innerType->isSuperTypeOf($otherType)); if ($result->yes()) { return $result; @@ -149,6 +154,41 @@ public function isSuperTypeOf(Type $otherType): TrinaryLogic return $result; } + private function isSuperTypeOfIntegerRange(IntegerRangeType $otherType): TrinaryLogic + { + $min = $otherType->getMin(); + $max = $otherType->getMax(); + if ($min === null || $max === null) { + throw new ShouldNotHappenException(); + } + + $matchingConstantIntegers = 0; + $results = []; + + foreach ($this->types as $innerType) { + if ($innerType instanceof ConstantIntegerType) { + if ($innerType->getValue() >= $min && $innerType->getValue() <= $max) { + $matchingConstantIntegers++; + continue; + } + return TrinaryLogic::createNo(); + } + + $result = $innerType->isSuperTypeOf($otherType); + if ($result->yes()) { + return $result; + } + + $results[] = $result; + } + + if ($matchingConstantIntegers === ($max - $min + 1)) { + return TrinaryLogic::createYes(); + } + + return TrinaryLogic::createNo()->or(...$results); + } + public function isSubTypeOf(Type $otherType): TrinaryLogic { return TrinaryLogic::lazyExtremeIdentity($this->getTypes(), static fn (Type $innerType) => $otherType->isSuperTypeOf($innerType)); diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 84dc5c01643..ce66dc066e1 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -445,4 +445,10 @@ public function testBug4680(): void $this->analyse([__DIR__ . '/data/bug-4680.php'], []); } + public function testBug3383(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-3383.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-3383.php b/tests/PHPStan/Rules/Properties/data/bug-3383.php new file mode 100644 index 00000000000..6ba907d2bb8 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-3383.php @@ -0,0 +1,13 @@ +classification = random_int(0, 3); + } +}