From 6b8479d1a475cd6a841e585555430715d3050957 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 6 Nov 2022 11:29:39 +0100 Subject: [PATCH 1/2] implement isInteger() on Type --- src/Type/Accessory/AccessoryArrayListType.php | 5 +++ .../Accessory/AccessoryLiteralStringType.php | 5 +++ .../Accessory/AccessoryNonEmptyStringType.php | 5 +++ .../Accessory/AccessoryNonFalsyStringType.php | 5 +++ .../Accessory/AccessoryNumericStringType.php | 5 +++ src/Type/Accessory/HasOffsetType.php | 5 +++ src/Type/Accessory/HasOffsetValueType.php | 5 +++ src/Type/Accessory/NonEmptyArrayType.php | 5 +++ src/Type/Accessory/OversizedArrayType.php | 5 +++ src/Type/ArrayType.php | 5 +++ src/Type/CallableType.php | 5 +++ src/Type/ClosureType.php | 5 +++ src/Type/FloatType.php | 5 +++ src/Type/IntegerType.php | 6 ++++ src/Type/IntersectionType.php | 5 +++ src/Type/IterableType.php | 5 +++ src/Type/JustNullableTypeTrait.php | 5 +++ src/Type/MixedType.php | 11 ++++++ src/Type/NeverType.php | 5 +++ src/Type/NullType.php | 5 +++ src/Type/ObjectType.php | 5 +++ src/Type/StaticType.php | 5 +++ src/Type/StrictMixedType.php | 5 +++ src/Type/StringType.php | 5 +++ src/Type/Traits/LateResolvableTypeTrait.php | 5 +++ src/Type/Traits/ObjectTypeTrait.php | 5 +++ src/Type/Type.php | 2 ++ src/Type/UnionType.php | 5 +++ src/Type/VoidType.php | 5 +++ tests/PHPStan/Type/MixedTypeTest.php | 36 +++++++++++++++++++ 30 files changed, 185 insertions(+) diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 642ee317c4..998495c4ac 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -252,6 +252,11 @@ public function isList(): TrinaryLogic return TrinaryLogic::createYes(); } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index d28f56d6bb..63cb76063f 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -166,6 +166,11 @@ public function toArrayKey(): Type return $this; } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index e40c100076..953df271f9 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -166,6 +166,11 @@ public function toArrayKey(): Type return $this; } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index c3ec76c90c..8ed4231bcd 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -166,6 +166,11 @@ public function toArrayKey(): Type return $this; } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 9f705c838b..33e5784638 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -169,6 +169,11 @@ public function toArrayKey(): Type return new IntegerType(); } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 83ed4b65de..8e47476ced 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -169,6 +169,11 @@ public function isList(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index be2afd0322..e7ae2aa75b 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -220,6 +220,11 @@ public function isList(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index e68db4798a..fb5bc25646 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -237,6 +237,11 @@ public function isList(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 86846b5b73..214bc234a9 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -236,6 +236,11 @@ public function isList(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 5a195c9534..02b9ef4395 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -260,6 +260,11 @@ public function isList(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index dc2b7213eb..4aeff1d6f9 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -322,6 +322,11 @@ public function isOversizedArray(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 3be01b6078..fed2f83a02 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -391,6 +391,11 @@ public function traverse(callable $cb): Type ); } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 56d8eeed0f..0987f7ce58 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -121,6 +121,11 @@ public function toArrayKey(): Type return new IntegerType(); } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index 0e28c6333e..54af0bef41 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -87,6 +88,11 @@ public function toArrayKey(): Type return $this; } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function tryRemove(Type $typeToRemove): ?Type { if ($typeToRemove instanceof IntegerRangeType || $typeToRemove instanceof ConstantIntegerType) { diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 00b170a28c..507d726713 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -608,6 +608,11 @@ public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThanOrEqual($otherType)); } + public function isInteger(): TrinaryLogic + { + return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isInteger()); + } + public function isGreaterThan(Type $otherType): TrinaryLogic { return $this->intersectResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThan($type)); diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 6e9248181f..2825ccfb64 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -260,6 +260,11 @@ public function getLastIterableValueType(): Type return $this->getItemType(); } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index 97d31f7eb1..1faa912537 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -52,6 +52,11 @@ public function traverse(callable $cb): Type return $this; } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index c7cffefcd5..b17a517d8b 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -602,6 +602,17 @@ public function isList(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isInteger(): TrinaryLogic + { + if ($this->subtractedType !== null) { + if ($this->subtractedType->isSuperTypeOf(new IntegerType())->yes()) { + return TrinaryLogic::createNo(); + } + } + + return TrinaryLogic::createMaybe(); + } + public function isString(): TrinaryLogic { if ($this->subtractedType !== null) { diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index b353b00587..7e66ee8738 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -333,6 +333,11 @@ public function traverse(callable $cb): Type return $this; } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/NullType.php b/src/Type/NullType.php index 1d896815b1..01215cc1e7 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -177,6 +177,11 @@ public function traverse(callable $cb): Type return $this; } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 214cb511cb..6b31e268ba 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -833,6 +833,11 @@ public function getLastIterableValueType(): Type return $this->getIterableValueType(); } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 8f09a63fc3..c65121f3f6 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -426,6 +426,11 @@ public function isList(): TrinaryLogic return $this->getStaticObjectType()->isList(); } + public function isInteger(): TrinaryLogic + { + return $this->getStaticObjectType()->isInteger(); + } + public function isString(): TrinaryLogic { return $this->getStaticObjectType()->isString(); diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index f4e966924a..4b3b65ee3d 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -152,6 +152,11 @@ public function getIterableValueType(): Type return $this; } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/StringType.php b/src/Type/StringType.php index 95fa44fffe..047f0bcee3 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -144,6 +144,11 @@ public function toArrayKey(): Type return $this; } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 09b4bf7fb9..bc300ec27c 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -310,6 +310,11 @@ public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic return $this->resolve()->isSmallerThanOrEqual($otherType); } + public function isInteger(): TrinaryLogic + { + return $this->resolve()->isInteger(); + } + public function isString(): TrinaryLogic { return $this->resolve()->isString(); diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index 15168c2c89..f56298f6c9 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -101,6 +101,11 @@ public function isCloneable(): TrinaryLogic return TrinaryLogic::createYes(); } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Type.php b/src/Type/Type.php index 2a24cb7fdb..911687537b 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -141,6 +141,8 @@ public function isSmallerThan(Type $otherType): TrinaryLogic; public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic; + public function isInteger(): TrinaryLogic; + public function isString(): TrinaryLogic; public function isNumericString(): TrinaryLogic; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 63a9b8dcc1..5585b06339 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -620,6 +620,11 @@ public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThanOrEqual($otherType)); } + public function isInteger(): TrinaryLogic + { + return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isInteger()); + } + public function getSmallerType(): Type { return $this->unionTypes(static fn (Type $type): Type => $type->getSmallerType()); diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index cd8ccfab01..ff52e902b2 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -104,6 +104,11 @@ public function toArrayKey(): Type return new ErrorType(); } + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isString(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index 63f0174608..2ed350cf30 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -645,6 +645,42 @@ public function dataSubstractedIsIterable(): array ]; } + /** + * @dataProvider dataSubstractedIsInteger + */ + public function testSubstractedIsInteger(MixedType $mixedType, Type $typeToSubtract, TrinaryLogic $expectedResult): void + { + $subtracted = $mixedType->subtract($typeToSubtract); + $actualResult = $subtracted->isInteger(); + + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isInteger()', $subtracted->describe(VerbosityLevel::precise())), + ); + } + + public function dataSubstractedIsInteger(): array + { + return [ + [ + new MixedType(), + new IntegerType(), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + IntegerRangeType::fromInterval(-5, 5), + TrinaryLogic::createMaybe(), + ], + [ + new MixedType(), + new StringType(), + TrinaryLogic::createMaybe(), + ], + ]; + } + /** * @dataProvider dataSubstractedIsIterable */ From 37a6d4b2d7017f61eb8ff96965d65a021f4bbbd2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 6 Nov 2022 11:34:54 +0100 Subject: [PATCH 2/2] get rid of `instanceof IntegerType` --- src/Reflection/InitializerExprTypeResolver.php | 4 ++-- src/Type/FloatType.php | 2 +- src/Type/Php/BcMathStringOrNullReturnTypeExtension.php | 2 +- src/Type/Php/FilterVarDynamicReturnTypeExtension.php | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index aa86855b4f..0ee1580b43 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -1507,7 +1507,7 @@ private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $ri $resultType = TypeCombinator::union($leftNumberType, $rightNumberType); if ($expr instanceof Expr\BinaryOp\Div) { - if ($types instanceof MixedType || $resultType instanceof IntegerType) { + if ($types instanceof MixedType || $resultType->isInteger()->yes()) { return new BenevolentUnionType([new IntegerType(), new FloatType()]); } @@ -1925,7 +1925,7 @@ public function getBitwiseNotType(Expr $expr, callable $getTypeCallback): Type return TypeCombinator::intersect(...$accessories); } - if ($type instanceof IntegerType || $type instanceof FloatType) { + if ($type->isInteger()->yes() || $type instanceof FloatType) { return new IntegerType(); //no const types here, result depends on PHP_INT_SIZE } return new ErrorType(); diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 0987f7ce58..f157e66192 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -48,7 +48,7 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { - if ($type instanceof self || $type instanceof IntegerType) { + if ($type instanceof self || $type->isInteger()->yes()) { return TrinaryLogic::createYes(); } diff --git a/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php b/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php index efc57ed1cb..67cf40eb62 100644 --- a/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php +++ b/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php @@ -61,7 +61,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $secondArgument = $scope->getType($functionCall->getArgs()[1]->value); - $secondArgumentIsNumeric = ($secondArgument instanceof ConstantScalarType && is_numeric($secondArgument->getValue())) || $secondArgument instanceof IntegerType; + $secondArgumentIsNumeric = ($secondArgument instanceof ConstantScalarType && is_numeric($secondArgument->getValue())) || $secondArgument->isInteger()->yes(); if ($secondArgument instanceof ConstantScalarType && ($this->isZero($secondArgument->getValue()) || !$secondArgumentIsNumeric)) { if ($this->phpVersion->throwsTypeErrorForInternalFunctions()) { diff --git a/src/Type/Php/FilterVarDynamicReturnTypeExtension.php b/src/Type/Php/FilterVarDynamicReturnTypeExtension.php index c85073f6f8..3687f7f3b9 100644 --- a/src/Type/Php/FilterVarDynamicReturnTypeExtension.php +++ b/src/Type/Php/FilterVarDynamicReturnTypeExtension.php @@ -206,7 +206,7 @@ public function getTypeFromFunctionCall( private function determineExactType(Type $in, int $filterValue, Type $defaultType, ?Arg $flagsArg, Scope $scope): ?Type { if (($filterValue === $this->getConstant('FILTER_VALIDATE_BOOLEAN') && $in instanceof BooleanType) - || ($filterValue === $this->getConstant('FILTER_VALIDATE_INT') && $in instanceof IntegerType) + || ($filterValue === $this->getConstant('FILTER_VALIDATE_INT') && $in->isInteger()->yes()) || ($filterValue === $this->getConstant('FILTER_VALIDATE_FLOAT') && $in instanceof FloatType)) { return $in; } @@ -229,7 +229,7 @@ private function determineExactType(Type $in, int $filterValue, Type $defaultTyp return preg_match('/\A[+-]?(?:0|[1-9][0-9]*)\z/', $value) === 1 ? $in->toInteger() : $defaultType; } - if ($filterValue === $this->getConstant('FILTER_VALIDATE_FLOAT') && $in instanceof IntegerType) { + if ($filterValue === $this->getConstant('FILTER_VALIDATE_FLOAT') && $in->isInteger()->yes()) { return $in->toFloat(); }