From 997f3688579442a1e2bc22b12f2ab557f9bef9f7 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Tue, 27 Dec 2022 17:38:32 +0100 Subject: [PATCH] Add `Type::isScalar()` --- 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/BooleanType.php | 5 ++ src/Type/CallableType.php | 5 ++ src/Type/ClosureType.php | 5 ++ src/Type/FloatType.php | 5 ++ src/Type/IntegerType.php | 5 ++ 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 | 29 +++++++++ tests/PHPStan/Type/UnionTypeTest.php | 63 +++++++++++++++++++ 32 files changed, 245 insertions(+) diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 7eba12f762..78e453cae4 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -322,6 +322,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function toNumber(): Type { return new ErrorType(); diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index 00b647375d..8d6b19c63c 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -236,6 +236,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 8a75297a3a..8a4ed9f714 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -236,6 +236,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index 039abe161a..94b4f2a00b 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -236,6 +236,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 5e0bcbde44..1a1d2d302a 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -239,6 +239,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index cb26d38fad..e5bd3841fe 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -239,6 +239,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function getKeysArray(): Type { return new NonEmptyArrayType(); diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index 4994bed50e..9cbef14e77 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -290,6 +290,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function toNumber(): Type { return new ErrorType(); diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 1d6849e2a8..72ecda43f3 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -307,6 +307,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function toNumber(): Type { return new ErrorType(); diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 0d1542a7ad..feb0a5f0dc 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -306,6 +306,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function toNumber(): Type { return new ErrorType(); diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 3d2a5f307f..78bb81094b 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -327,6 +327,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isOffsetAccessible(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php index 4859bb202f..5fe296a250 100644 --- a/src/Type/BooleanType.php +++ b/src/Type/BooleanType.php @@ -113,6 +113,11 @@ public function isBoolean(): TrinaryLogic return TrinaryLogic::createYes(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function tryRemove(Type $typeToRemove): ?Type { if ($typeToRemove instanceof ConstantBooleanType) { diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 7ce1fbfe86..9d993a6fd8 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -392,6 +392,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isCommonCallable(): bool { return $this->isCommonCallable; diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 9afdacfc13..b7fdf5d901 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -461,6 +461,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + /** * @param mixed[] $properties */ diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index f588dca910..847cbe5ca5 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -191,6 +191,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index d0cdf9fee6..a775fb40fa 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -123,6 +123,11 @@ public function isInteger(): TrinaryLogic return TrinaryLogic::createYes(); } + public function isScalar(): 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 cfd5f3a03c..913e5a455e 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -534,6 +534,11 @@ public function isVoid(): TrinaryLogic return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isVoid()); } + public function isScalar(): TrinaryLogic + { + return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isScalar()); + } + public function isOffsetAccessible(): TrinaryLogic { return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isOffsetAccessible()); diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index bb1d26ae33..966731872d 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -330,6 +330,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap { if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index c13fadae2b..8a3c6ff5f9 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -117,4 +117,9 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + } diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index a23a25d257..e547d6b2da 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -776,6 +776,17 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isScalar(): TrinaryLogic + { + if ($this->subtractedType !== null) { + if ($this->subtractedType->isSuperTypeOf(new UnionType([new BooleanType(), new FloatType(), new IntegerType(), new StringType()]))->yes()) { + return TrinaryLogic::createNo(); + } + } + + return TrinaryLogic::createMaybe(); + } + public function tryRemove(Type $typeToRemove): ?Type { if ($this->isSuperTypeOf($typeToRemove)->yes()) { diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index cf8a719e39..14be4fa9c9 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -403,6 +403,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + /** * @param mixed[] $properties */ diff --git a/src/Type/NullType.php b/src/Type/NullType.php index b686855d24..57721fb980 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -247,6 +247,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function getSmallerType(): Type { return new NeverType(); diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 0f49698e22..a78c806587 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -909,6 +909,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + private function isExtraOffsetAccessibleClass(): TrinaryLogic { $classReflection = $this->getClassReflection(); diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 733be51794..b7aeddb2f3 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -496,6 +496,11 @@ public function isVoid(): TrinaryLogic return $this->getStaticObjectType()->isVoid(); } + public function isScalar(): TrinaryLogic + { + return $this->getStaticObjectType()->isScalar(); + } + /** * @return ParametersAcceptor[] */ diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 3e9001a92a..ed2271f833 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -222,6 +222,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isOffsetAccessible(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/StringType.php b/src/Type/StringType.php index 169ac7a5ff..d9d15b244a 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -209,6 +209,11 @@ public function isClassStringType(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function hasMethod(string $methodName): TrinaryLogic { if ($this->isClassStringType()->yes()) { diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index d6c49f021b..9a981fda8a 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -380,6 +380,11 @@ public function isVoid(): TrinaryLogic return $this->resolve()->isVoid(); } + public function isScalar(): TrinaryLogic + { + return $this->resolve()->isScalar(); + } + public function getSmallerType(): Type { return $this->resolve()->getSmallerType(); diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index c4074f9380..222461a06a 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -171,6 +171,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function toNumber(): Type { return new ErrorType(); diff --git a/src/Type/Type.php b/src/Type/Type.php index eaccffa6be..69886d58df 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -171,6 +171,8 @@ public function isClassStringType(): TrinaryLogic; public function isVoid(): TrinaryLogic; + public function isScalar(): TrinaryLogic; + public function getSmallerType(): Type; public function getSmallerOrEqualType(): Type; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 28491c6710..414de9b311 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -526,6 +526,11 @@ public function isVoid(): TrinaryLogic return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isVoid()); } + public function isScalar(): TrinaryLogic + { + return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isScalar()); + } + public function isOffsetAccessible(): TrinaryLogic { return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isOffsetAccessible()); diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index 0dcb9b03e7..14cfd6f88b 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -169,6 +169,11 @@ public function isVoid(): TrinaryLogic return TrinaryLogic::createYes(); } + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function traverse(callable $cb): Type { return $this; diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index 7143676d7d..6ee710a8c1 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -665,6 +665,35 @@ public function dataSubtractedIsVoid(): array ]; } + /** @dataProvider dataSubtractedIsScalar */ + public function testSubtractedIsScalar(MixedType $mixedType, Type $typeToSubtract, TrinaryLogic $expectedResult): void + { + $subtracted = $mixedType->subtract($typeToSubtract); + $actualResult = $subtracted->isScalar(); + + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isScalar()', $subtracted->describe(VerbosityLevel::precise())), + ); + } + + public function dataSubtractedIsScalar(): array + { + return [ + [ + new MixedType(), + new UnionType([new BooleanType(), new FloatType(), new IntegerType(), new StringType()]), + TrinaryLogic::createNo(), + ], + [ + new MixedType(), + new StringType(), + TrinaryLogic::createMaybe(), + ], + ]; + } + /** * @dataProvider dataSubstractedIsLiteralString */ diff --git a/tests/PHPStan/Type/UnionTypeTest.php b/tests/PHPStan/Type/UnionTypeTest.php index b26e9a00ad..b2c88c3a41 100644 --- a/tests/PHPStan/Type/UnionTypeTest.php +++ b/tests/PHPStan/Type/UnionTypeTest.php @@ -643,6 +643,69 @@ public function testIsSubTypeOfInversed(UnionType $type, Type $otherType, Trinar ); } + public function dataIsScalar(): array + { + return [ + [ + TypeCombinator::union( + new BooleanType(), + new IntegerType(), + new FloatType(), + new StringType(), + ), + TrinaryLogic::createYes(), + ], + [ + new UnionType([ + new BooleanType(), + new ObjectType(DateTimeImmutable::class), + ]), + TrinaryLogic::createMaybe(), + ], + [ + new UnionType([ + new IntegerType(), + new NullType(), + ]), + TrinaryLogic::createMaybe(), + ], + [ + new UnionType([ + new FloatType(), + new MixedType(), + ]), + TrinaryLogic::createMaybe(), + ], + [ + new UnionType([ + new ArrayType(new IntegerType(), new StringType()), + new StringType(), + ]), + TrinaryLogic::createMaybe(), + ], + [ + new UnionType([ + new ArrayType(new IntegerType(), new StringType()), + new NullType(), + new ObjectType(DateTimeImmutable::class), + new ResourceType(), + ]), + TrinaryLogic::createNo(), + ], + ]; + } + + /** @dataProvider dataIsScalar */ + public function testIsScalar(UnionType $type, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isScalar(); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isScalar()', $type->describe(VerbosityLevel::precise())), + ); + } + public function dataDescribe(): array { return [