From 6c993298cc8d1a87286438d39e775db5fb98181d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pudil?= Date: Sun, 26 Jun 2022 19:38:21 +0200 Subject: [PATCH 01/12] Add extensible ClassReflection::getAllowedSubTypes() to restrict inheritance hierarchies --- conf/config.neon | 10 +++ phpstan-baseline.neon | 4 +- src/Analyser/NodeScopeResolver.php | 1 + src/Broker/BrokerFactory.php | 1 + ...assReflectionExtensionRegistryProvider.php | 9 +++ ...assReflectionExtensionRegistryProvider.php | 1 + ...llowedSubTypesClassReflectionExtension.php | 18 +++++ .../BetterReflectionProvider.php | 2 + src/Reflection/ClassReflection.php | 25 +++++++ .../ClassReflectionExtensionRegistry.php | 12 +++- ...llowedSubTypesClassReflectionExtension.php | 28 ++++++++ ...llowedSubTypesClassReflectionExtension.php | 28 ++++++++ src/Type/ObjectType.php | 72 ++++++++----------- .../Analyser/NodeScopeResolverTest.php | 6 ++ .../data/allowed-subtypes-datetime.php | 17 +++++ .../Analyser/data/allowed-subtypes-enum.php | 27 +++++++ 16 files changed, 216 insertions(+), 45 deletions(-) create mode 100644 src/Reflection/AllowedSubTypesClassReflectionExtension.php create mode 100644 src/Reflection/Php/DateTimeInterfaceAllowedSubTypesClassReflectionExtension.php create mode 100644 src/Reflection/Php/EnumAllowedSubTypesClassReflectionExtension.php create mode 100644 tests/PHPStan/Analyser/data/allowed-subtypes-datetime.php create mode 100644 tests/PHPStan/Analyser/data/allowed-subtypes-enum.php diff --git a/conf/config.neon b/conf/config.neon index 45361cefbd..e60bdbcdb0 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -840,6 +840,16 @@ services: tags: - phpstan.broker.methodsClassReflectionExtension + - + class: PHPStan\Reflection\Php\DateTimeInterfaceAllowedSubTypesClassReflectionExtension + tags: + - phpstan.broker.allowedSubTypesClassReflectionExtension + + - + class: PHPStan\Reflection\Php\EnumAllowedSubTypesClassReflectionExtension + tags: + - phpstan.broker.allowedSubTypesClassReflectionExtension + - class: PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension tags: diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 2edc80baaa..6bb37c490e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -34,7 +34,7 @@ parameters: path: src/Analyser/MutatingScope.php - - message: "#^Parameter \\#10 \\$reflection of class PHPStan\\\\Reflection\\\\ClassReflection constructor expects PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionClass\\|PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum, object given\\.$#" + message: "#^Parameter \\#11 \\$reflection of class PHPStan\\\\Reflection\\\\ClassReflection constructor expects PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionClass\\|PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum, object given\\.$#" count: 1 path: src/Analyser/NodeScopeResolver.php @@ -192,7 +192,7 @@ parameters: path: src/Reflection/BetterReflection/BetterReflectionProvider.php - - message: "#^Parameter \\#10 \\$reflection of class PHPStan\\\\Reflection\\\\ClassReflection constructor expects PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionClass\\|PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum, object given\\.$#" + message: "#^Parameter \\#11 \\$reflection of class PHPStan\\\\Reflection\\\\ClassReflection constructor expects PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionClass\\|PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum, object given\\.$#" count: 1 path: src/Reflection/BetterReflection/BetterReflectionProvider.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 7b74b94fd2..e30f63d22e 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1524,6 +1524,7 @@ private function createAstClassReflection(Node\Stmt\ClassLike $stmt, string $cla $this->phpVersion, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), + $this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions(), $betterReflectionClass->getName(), $betterReflectionClass instanceof ReflectionEnum && PHP_VERSION_ID >= 80000 ? new $enumAdapter($betterReflectionClass) : new ReflectionClass($betterReflectionClass), null, diff --git a/src/Broker/BrokerFactory.php b/src/Broker/BrokerFactory.php index 182b0ca3ca..adff2e4705 100644 --- a/src/Broker/BrokerFactory.php +++ b/src/Broker/BrokerFactory.php @@ -10,6 +10,7 @@ class BrokerFactory public const PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG = 'phpstan.broker.propertiesClassReflectionExtension'; public const METHODS_CLASS_REFLECTION_EXTENSION_TAG = 'phpstan.broker.methodsClassReflectionExtension'; + public const ALLOWED_SUB_TYPES_CLASS_REFLECTION_EXTENSION_TAG = 'phpstan.broker.allowedSubTypesClassReflectionExtension'; public const DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG = 'phpstan.broker.dynamicMethodReturnTypeExtension'; public const DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG = 'phpstan.broker.dynamicStaticMethodReturnTypeExtension'; public const DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG = 'phpstan.broker.dynamicFunctionReturnTypeExtension'; diff --git a/src/DependencyInjection/Reflection/DirectClassReflectionExtensionRegistryProvider.php b/src/DependencyInjection/Reflection/DirectClassReflectionExtensionRegistryProvider.php index fa557b070a..3eb8fbd374 100644 --- a/src/DependencyInjection/Reflection/DirectClassReflectionExtensionRegistryProvider.php +++ b/src/DependencyInjection/Reflection/DirectClassReflectionExtensionRegistryProvider.php @@ -3,6 +3,7 @@ namespace PHPStan\DependencyInjection\Reflection; use PHPStan\Broker\Broker; +use PHPStan\Reflection\AllowedSubTypesClassReflectionExtension; use PHPStan\Reflection\ClassReflectionExtensionRegistry; use PHPStan\Reflection\MethodsClassReflectionExtension; use PHPStan\Reflection\PropertiesClassReflectionExtension; @@ -18,10 +19,12 @@ class DirectClassReflectionExtensionRegistryProvider implements ClassReflectionE /** * @param PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions * @param MethodsClassReflectionExtension[] $methodsClassReflectionExtensions + * @param AllowedSubTypesClassReflectionExtension[] $allowedSubTypesClassReflectionExtensions */ public function __construct( private array $propertiesClassReflectionExtensions, private array $methodsClassReflectionExtensions, + private array $allowedSubTypesClassReflectionExtensions, ) { } @@ -41,12 +44,18 @@ public function addMethodsClassReflectionExtension(MethodsClassReflectionExtensi $this->methodsClassReflectionExtensions[] = $extension; } + public function addAllowedSubTypesClassReflectionExtension(AllowedSubTypesClassReflectionExtension $extension): void + { + $this->allowedSubTypesClassReflectionExtensions[] = $extension; + } + public function getRegistry(): ClassReflectionExtensionRegistry { return new ClassReflectionExtensionRegistry( $this->broker, $this->propertiesClassReflectionExtensions, $this->methodsClassReflectionExtensions, + $this->allowedSubTypesClassReflectionExtensions, ); } diff --git a/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php b/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php index 259600a280..bcc28e7696 100644 --- a/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php +++ b/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php @@ -31,6 +31,7 @@ public function getRegistry(): ClassReflectionExtensionRegistry $this->container->getByType(Broker::class), array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsPropertiesClassReflectionExtension]), array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsMethodsClassReflectionExtension]), + $this->container->getServicesByTag(BrokerFactory::ALLOWED_SUB_TYPES_CLASS_REFLECTION_EXTENSION_TAG), ); } diff --git a/src/Reflection/AllowedSubTypesClassReflectionExtension.php b/src/Reflection/AllowedSubTypesClassReflectionExtension.php new file mode 100644 index 0000000000..22e609cbdd --- /dev/null +++ b/src/Reflection/AllowedSubTypesClassReflectionExtension.php @@ -0,0 +1,18 @@ + + */ + public function getAllowedSubTypes(ClassReflection $classReflection): array; + +} diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 51dbf425ec..6deeb402da 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -136,6 +136,7 @@ public function getClass(string $className): ClassReflection $this->phpVersion, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), + $this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions(), $reflectionClass->getName(), $reflectionClass instanceof ReflectionEnum && PHP_VERSION_ID >= 80000 ? new $enumAdapter($reflectionClass) : new ReflectionClass($reflectionClass), null, @@ -211,6 +212,7 @@ public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $ $this->phpVersion, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), + $this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions(), sprintf('class@anonymous/%s:%s', $filename, $classNode->getLine()), new ReflectionClass($reflectionClass), $scopeFile, diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index b37da57a1a..4c79ba9210 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -120,6 +120,7 @@ class ClassReflection /** * @param PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions * @param MethodsClassReflectionExtension[] $methodsClassReflectionExtensions + * @param AllowedSubTypesClassReflectionExtension[] $allowedSubTypesClassReflectionExtensions */ public function __construct( private ReflectionProvider $reflectionProvider, @@ -130,6 +131,7 @@ public function __construct( private PhpVersion $phpVersion, private array $propertiesClassReflectionExtensions, private array $methodsClassReflectionExtensions, + private array $allowedSubTypesClassReflectionExtensions, private string $displayName, private ReflectionClass|ReflectionEnum $reflection, private ?string $anonymousFilename, @@ -1292,6 +1294,7 @@ public function withTypes(array $types): self $this->phpVersion, $this->propertiesClassReflectionExtensions, $this->methodsClassReflectionExtensions, + $this->allowedSubTypesClassReflectionExtensions, $this->displayName, $this->reflection, $this->anonymousFilename, @@ -1491,4 +1494,26 @@ public function getResolvedMixinTypes(): array return $types; } + /** + * @return array|null + */ + public function getAllowedSubTypes(): ?array + { + $subTypes = []; + + foreach ($this->allowedSubTypesClassReflectionExtensions as $allowedSubTypesClassReflectionExtension) { + if (!$allowedSubTypesClassReflectionExtension->supports($this)) { + continue; + } + + $subTypes[] = $allowedSubTypesClassReflectionExtension->getAllowedSubTypes($this); + } + + if (count($subTypes) === 0) { + return null; + } + + return array_merge(...$subTypes); + } + } diff --git a/src/Reflection/ClassReflectionExtensionRegistry.php b/src/Reflection/ClassReflectionExtensionRegistry.php index e18880e0be..e5ed2e5425 100644 --- a/src/Reflection/ClassReflectionExtensionRegistry.php +++ b/src/Reflection/ClassReflectionExtensionRegistry.php @@ -11,14 +11,16 @@ class ClassReflectionExtensionRegistry /** * @param PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions * @param MethodsClassReflectionExtension[] $methodsClassReflectionExtensions + * @param AllowedSubTypesClassReflectionExtension[] $allowedSubTypesClassReflectionExtensions */ public function __construct( Broker $broker, private array $propertiesClassReflectionExtensions, private array $methodsClassReflectionExtensions, + private array $allowedSubTypesClassReflectionExtensions, ) { - foreach (array_merge($propertiesClassReflectionExtensions, $methodsClassReflectionExtensions) as $extension) { + foreach (array_merge($propertiesClassReflectionExtensions, $methodsClassReflectionExtensions, $allowedSubTypesClassReflectionExtensions) as $extension) { if (!($extension instanceof BrokerAwareExtension)) { continue; } @@ -43,4 +45,12 @@ public function getMethodsClassReflectionExtensions(): array return $this->methodsClassReflectionExtensions; } + /** + * @return AllowedSubTypesClassReflectionExtension[] + */ + public function getAllowedSubTypesClassReflectionExtensions(): array + { + return $this->allowedSubTypesClassReflectionExtensions; + } + } diff --git a/src/Reflection/Php/DateTimeInterfaceAllowedSubTypesClassReflectionExtension.php b/src/Reflection/Php/DateTimeInterfaceAllowedSubTypesClassReflectionExtension.php new file mode 100644 index 0000000000..97678298d6 --- /dev/null +++ b/src/Reflection/Php/DateTimeInterfaceAllowedSubTypesClassReflectionExtension.php @@ -0,0 +1,28 @@ +getName() === DateTimeInterface::class; + } + + public function getAllowedSubTypes(ClassReflection $classReflection): array + { + return [ + new ObjectType(DateTime::class), + new ObjectType(DateTimeImmutable::class), + ]; + } + +} diff --git a/src/Reflection/Php/EnumAllowedSubTypesClassReflectionExtension.php b/src/Reflection/Php/EnumAllowedSubTypesClassReflectionExtension.php new file mode 100644 index 0000000000..3ba005da82 --- /dev/null +++ b/src/Reflection/Php/EnumAllowedSubTypesClassReflectionExtension.php @@ -0,0 +1,28 @@ +isEnum(); + } + + public function getAllowedSubTypes(ClassReflection $classReflection): array + { + $cases = []; + foreach (array_keys($classReflection->getEnumCases()) as $name) { + $cases[] = new EnumCaseObjectType($classReflection->getName(), $name); + } + + return $cases; + } + +} diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 7d527b98f1..5e8820610a 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -4,9 +4,6 @@ use ArrayAccess; use Closure; -use DateTime; -use DateTimeImmutable; -use DateTimeInterface; use Iterator; use IteratorAggregate; use PHPStan\Analyser\OutOfClassScope; @@ -40,7 +37,6 @@ use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; use Traversable; use function array_key_exists; -use function array_keys; use function array_map; use function array_merge; use function array_values; @@ -1072,55 +1068,57 @@ public function changeSubtractedType(?Type $subtractedType): Type { if ($subtractedType !== null) { $classReflection = $this->getClassReflection(); - if ($classReflection !== null && $classReflection->isEnum()) { - $cases = []; - foreach (array_keys($classReflection->getEnumCases()) as $name) { - $cases[$name] = new EnumCaseObjectType($classReflection->getName(), $name); + $allowedSubTypesList = $classReflection !== null ? $classReflection->getAllowedSubTypes() : null; + if ($allowedSubTypesList !== null) { + $allowedSubTypes = []; + foreach ($allowedSubTypesList as $allowedSubType) { + $allowedSubTypes[$allowedSubType->describe(VerbosityLevel::precise())] = $allowedSubType; } - $originalCases = $cases; + $originalAllowedSubTypes = $allowedSubTypes; + $subtractedSubTypes = []; - $subtractedTypes = TypeUtils::flattenTypes($subtractedType); + $subtractedTypesList = TypeUtils::flattenTypes($subtractedType); if ($this->subtractedType !== null) { - $subtractedTypes = array_merge($subtractedTypes, TypeUtils::flattenTypes($this->subtractedType)); + $subtractedTypesList = array_merge($subtractedTypesList, TypeUtils::flattenTypes($this->subtractedType)); } - $subtractedCases = []; - foreach ($subtractedTypes as $subType) { - if (!$subType instanceof EnumCaseObjectType) { - return new self($this->className, $subtractedType); - } - if ($subType->getClassName() !== $this->getClassName()) { - return new self($this->className, $subtractedType); - } + $subtractedTypes = []; + foreach ($subtractedTypesList as $type) { + $subtractedTypes[$type->describe(VerbosityLevel::precise())] = $type; + } - if (!array_key_exists($subType->getEnumCaseName(), $cases)) { - return new self($this->className, $subtractedType); + foreach ($subtractedTypes as $subType) { + foreach ($allowedSubTypes as $description => $allowedSubType) { + if ($subType->equals($allowedSubType)) { + $subtractedSubTypes[$description] = $subType; + unset($allowedSubTypes[$description]); + continue 2; + } } - $subtractedCases[$subType->getEnumCaseName()] = $subType; - unset($originalCases[$subType->getEnumCaseName()]); + return new self($this->className, $subtractedType); } - if (count($originalCases) === 1) { - return array_values($originalCases)[0]; + if (count($allowedSubTypes) === 1) { + return array_values($allowedSubTypes)[0]; } - $subtractedCases = array_values($subtractedCases); - $subtractedCasesCount = count($subtractedCases); - if ($subtractedCasesCount === count($cases)) { + $subtractedSubTypes = array_values($subtractedSubTypes); + $subtractedSubTypesCount = count($subtractedSubTypes); + if ($subtractedSubTypesCount === count($originalAllowedSubTypes)) { return new NeverType(); } - if ($subtractedCasesCount === 0) { + if ($subtractedSubTypesCount === 0) { return new self($this->className); } - if (count($subtractedCases) === 1) { - return new self($this->className, $subtractedCases[0]); + if ($subtractedSubTypesCount === 1) { + return new self($this->className, $subtractedSubTypes[0]); } - return new self($this->className, new UnionType($subtractedCases)); + return new self($this->className, new UnionType($subtractedSubTypes)); } } @@ -1274,16 +1272,6 @@ private function getInterfaces(): array public function tryRemove(Type $typeToRemove): ?Type { - if ($this->getClassName() === DateTimeInterface::class) { - if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === DateTimeImmutable::class) { - return new ObjectType(DateTime::class); - } - - if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === DateTime::class) { - return new ObjectType(DateTimeImmutable::class); - } - } - if ($this->isSuperTypeOf($typeToRemove)->yes()) { return $this->subtract($typeToRemove); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index a18f62d5f4..c5bf05717e 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1072,6 +1072,12 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/self-out.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/native-expressions.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-5333.php'); + + if (PHP_VERSION_ID >= 80100) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/allowed-subtypes-enum.php'); + } + + yield from $this->gatherAssertTypes(__DIR__ . '/data/allowed-subtypes-datetime.php'); } /** diff --git a/tests/PHPStan/Analyser/data/allowed-subtypes-datetime.php b/tests/PHPStan/Analyser/data/allowed-subtypes-datetime.php new file mode 100644 index 0000000000..46c43086e0 --- /dev/null +++ b/tests/PHPStan/Analyser/data/allowed-subtypes-datetime.php @@ -0,0 +1,17 @@ + Date: Fri, 16 Sep 2022 15:30:55 +0200 Subject: [PATCH 02/12] Add restricted inheritance hierarchies to StaticType --- src/Type/StaticType.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 140835b663..9287940e2f 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -14,7 +14,6 @@ use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\TrinaryLogic; -use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -487,21 +486,17 @@ public function changeSubtractedType(?Type $subtractedType): Type { if ($subtractedType !== null) { $classReflection = $this->getClassReflection(); - if ($classReflection->isEnum()) { + if ($classReflection->getAllowedSubTypes() !== null) { $objectType = $this->getStaticObjectType()->changeSubtractedType($subtractedType); if ($objectType instanceof NeverType) { return $objectType; } - if ($objectType instanceof EnumCaseObjectType) { - return TypeCombinator::intersect($this, $objectType); - } - - if ($objectType instanceof ObjectType) { + if ($objectType instanceof ObjectType && $objectType->getSubtractedType() !== null) { return new self($classReflection, $objectType->getSubtractedType()); } - return $this; + return TypeCombinator::intersect($this, $objectType); } } From 2095ab14e0b7fa9241c31f1d6727a75e3a757056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pudil?= Date: Fri, 16 Sep 2022 15:32:08 +0200 Subject: [PATCH 03/12] Only resolve first AllowedSubTypesClassReflectionExtension that supports given class --- src/Reflection/ClassReflection.php | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 4c79ba9210..17273ef564 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1499,21 +1499,13 @@ public function getResolvedMixinTypes(): array */ public function getAllowedSubTypes(): ?array { - $subTypes = []; - foreach ($this->allowedSubTypesClassReflectionExtensions as $allowedSubTypesClassReflectionExtension) { - if (!$allowedSubTypesClassReflectionExtension->supports($this)) { - continue; + if ($allowedSubTypesClassReflectionExtension->supports($this)) { + return $allowedSubTypesClassReflectionExtension->getAllowedSubTypes($this); } - - $subTypes[] = $allowedSubTypesClassReflectionExtension->getAllowedSubTypes($this); } - if (count($subTypes) === 0) { - return null; - } - - return array_merge(...$subTypes); + return null; } } From 72ae1df7981b459e0b1b03dd4fb14b3525d9d9b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pudil?= Date: Thu, 22 Sep 2022 18:12:40 +0200 Subject: [PATCH 04/12] Implement AllowedSubTypesRule that checks for disallowed extends/implements --- src/Rules/Classes/AllowedSubTypesRule.php | 67 +++++++++++++++++++ .../Rules/Classes/AllowedSubTypesRuleTest.php | 38 +++++++++++ .../Rules/Classes/data/allowed-sub-types.neon | 3 + .../Rules/Classes/data/allowed-sub-types.php | 27 ++++++++ 4 files changed, 135 insertions(+) create mode 100644 src/Rules/Classes/AllowedSubTypesRule.php create mode 100644 tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/data/allowed-sub-types.neon create mode 100644 tests/PHPStan/Rules/Classes/data/allowed-sub-types.php diff --git a/src/Rules/Classes/AllowedSubTypesRule.php b/src/Rules/Classes/AllowedSubTypesRule.php new file mode 100644 index 0000000000..1af04faeee --- /dev/null +++ b/src/Rules/Classes/AllowedSubTypesRule.php @@ -0,0 +1,67 @@ + + */ +class AllowedSubTypesRule implements Rule +{ + + public function getNodeType(): string + { + return InClassNode::class; + } + + /** + * @param InClassNode $node + */ + public function processNode(Node $node, Scope $scope): array + { + $classReflection = $node->getClassReflection(); + $className = $classReflection->getName(); + + $parents = array_values($classReflection->getImmediateInterfaces()); + $parentClass = $classReflection->getParentClass(); + if ($parentClass !== null) { + $parents[] = $parentClass; + } + + $messages = []; + + foreach ($parents as $parentReflection) { + $allowedSubTypes = $parentReflection->getAllowedSubTypes(); + if ($allowedSubTypes === null) { + continue; + } + + foreach ($allowedSubTypes as $allowedSubType) { + if (!$allowedSubType instanceof ObjectType) { + continue; + } + + if ($className === $allowedSubType->getClassName()) { + continue 2; + } + } + + $messages[] = RuleErrorBuilder::message(sprintf( + 'Type %s is not allowed to be a subtype of %s.', + $className, + $parentReflection->getName(), + ))->build(); + } + + return $messages; + } + +} diff --git a/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php b/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php new file mode 100644 index 0000000000..5d61ee9496 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php @@ -0,0 +1,38 @@ + + */ +class AllowedSubTypesRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new AllowedSubTypesRule(); + } + + public function testRule(): void + { + require __DIR__ . '/data/allowed-sub-types.php'; + $this->analyse([__DIR__ . '/data/allowed-sub-types.php'], [ + [ + 'Type AllowedSubTypes\\Baz is not allowed to be a subtype of AllowedSubTypes\\Foo.', + 11, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/../../../../conf/bleedingEdge.neon', + __DIR__ . '/data/allowed-sub-types.neon', + ]; + } + +} diff --git a/tests/PHPStan/Rules/Classes/data/allowed-sub-types.neon b/tests/PHPStan/Rules/Classes/data/allowed-sub-types.neon new file mode 100644 index 0000000000..1366adc94a --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/allowed-sub-types.neon @@ -0,0 +1,3 @@ +services: + - factory: AllowedSubTypes\Extension + tags: [phpstan.broker.allowedSubTypesClassReflectionExtension] diff --git a/tests/PHPStan/Rules/Classes/data/allowed-sub-types.php b/tests/PHPStan/Rules/Classes/data/allowed-sub-types.php new file mode 100644 index 0000000000..986e68ba7a --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/allowed-sub-types.php @@ -0,0 +1,27 @@ +getName() === 'AllowedSubTypes\\Foo'; + } + + public function getAllowedSubTypes(ClassReflection $classReflection): array + { + return [ + new ObjectType('AllowedSubTypes\\Bar'), + ]; + } +} From 812514ead58463cef866812d58e5a26f457c8274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pudil?= Date: Thu, 22 Sep 2022 18:28:02 +0200 Subject: [PATCH 05/12] Add test for custom AllowedSubTypesClassReflectionExtension --- ...edSubTypesClassReflectionExtensionTest.php | 37 +++++++++++++++ .../Reflection/data/allowed-sub-types.neon | 3 ++ .../Reflection/data/allowed-sub-types.php | 46 +++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 tests/PHPStan/Reflection/AllowedSubTypesClassReflectionExtensionTest.php create mode 100644 tests/PHPStan/Reflection/data/allowed-sub-types.neon create mode 100644 tests/PHPStan/Reflection/data/allowed-sub-types.php diff --git a/tests/PHPStan/Reflection/AllowedSubTypesClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/AllowedSubTypesClassReflectionExtensionTest.php new file mode 100644 index 0000000000..be454772f6 --- /dev/null +++ b/tests/PHPStan/Reflection/AllowedSubTypesClassReflectionExtensionTest.php @@ -0,0 +1,37 @@ +gatherAssertTypes(__DIR__ . '/data/allowed-sub-types.php'); + } + + /** + * @dataProvider dataFileAsserts + * @param mixed ...$args + */ + public function testFileAsserts( + string $assertType, + string $file, + ...$args, + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/../../../conf/bleedingEdge.neon', + __DIR__ . '/data/allowed-sub-types.neon', + ]; + } + +} diff --git a/tests/PHPStan/Reflection/data/allowed-sub-types.neon b/tests/PHPStan/Reflection/data/allowed-sub-types.neon new file mode 100644 index 0000000000..78256dd392 --- /dev/null +++ b/tests/PHPStan/Reflection/data/allowed-sub-types.neon @@ -0,0 +1,3 @@ +services: + - factory: AllowedSubTypesClassReflectionExtensionTest\Extension + tags: [phpstan.broker.allowedSubTypesClassReflectionExtension] diff --git a/tests/PHPStan/Reflection/data/allowed-sub-types.php b/tests/PHPStan/Reflection/data/allowed-sub-types.php new file mode 100644 index 0000000000..cdb8c34609 --- /dev/null +++ b/tests/PHPStan/Reflection/data/allowed-sub-types.php @@ -0,0 +1,46 @@ +getName() === 'AllowedSubTypesClassReflectionExtensionTest\\Foo'; + } + + public function getAllowedSubTypes(ClassReflection $classReflection): array + { + return [ + new ObjectType('AllowedSubTypesClassReflectionExtensionTest\\Bar'), + new ObjectType('AllowedSubTypesClassReflectionExtensionTest\\Baz'), + new ObjectType('AllowedSubTypesClassReflectionExtensionTest\\Qux'), + ]; + } +} + +function acceptsFoo(Foo $foo): void { + assertType('AllowedSubTypesClassReflectionExtensionTest\\Foo', $foo); + + if ($foo instanceof Bar) { + return; + } + + assertType('AllowedSubTypesClassReflectionExtensionTest\\Foo~AllowedSubTypesClassReflectionExtensionTest\\Bar', $foo); + + if ($foo instanceof Qux) { + return; + } + + assertType('AllowedSubTypesClassReflectionExtensionTest\\Baz', $foo); +} From ff8d1a8557025a695819dd70ac240a84065053a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pudil?= Date: Fri, 23 Sep 2022 11:34:17 +0200 Subject: [PATCH 06/12] register PHPStan\Rules\Classes\AllowedSubTypesRule --- conf/config.level0.neon | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/config.level0.neon b/conf/config.level0.neon index b53cd3127b..5cab73d39c 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -34,6 +34,7 @@ rules: - PHPStan\Rules\Arrays\EmptyArrayItemRule - PHPStan\Rules\Arrays\OffsetAccessWithoutDimForReadingRule - PHPStan\Rules\Cast\UnsetCastRule + - PHPStan\Rules\Classes\AllowedSubTypesRule - PHPStan\Rules\Classes\ClassAttributesRule - PHPStan\Rules\Classes\ClassConstantAttributesRule - PHPStan\Rules\Classes\ClassConstantRule From 74a9da2d1be7ec219357c5faf73327dd565f14a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pudil?= Date: Fri, 23 Sep 2022 11:46:15 +0200 Subject: [PATCH 07/12] test that DateTimeInterface cannot be implemented in userland --- .../Rules/Classes/AllowedSubTypesRuleTest.php | 7 +++ .../data/allowed-sub-types-datetime.php | 53 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 tests/PHPStan/Rules/Classes/data/allowed-sub-types-datetime.php diff --git a/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php b/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php index 5d61ee9496..e067ec7d05 100644 --- a/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php @@ -25,6 +25,13 @@ public function testRule(): void 11, ], ]); + + $this->analyse([__DIR__ . '/data/allowed-sub-types-datetime.php'], [ + [ + 'Type AllowedSubTypesDateTime\\MyDateTime is not allowed to be a subtype of DateTimeInterface.', + 12, + ], + ]); } public static function getAdditionalConfigFiles(): array diff --git a/tests/PHPStan/Rules/Classes/data/allowed-sub-types-datetime.php b/tests/PHPStan/Rules/Classes/data/allowed-sub-types-datetime.php new file mode 100644 index 0000000000..ceb9a21664 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/allowed-sub-types-datetime.php @@ -0,0 +1,53 @@ + Date: Fri, 23 Sep 2022 11:47:05 +0200 Subject: [PATCH 08/12] test that Throwable cannot be implemented in userland --- conf/config.neon | 5 +++ ...llowedSubTypesClassReflectionExtension.php | 28 ++++++++++++ .../Rules/Classes/AllowedSubTypesRuleTest.php | 7 +++ .../data/allowed-sub-types-throwable.php | 45 +++++++++++++++++++ 4 files changed, 85 insertions(+) create mode 100644 src/Reflection/Php/ThrowableAllowedSubTypesClassReflectionExtension.php create mode 100644 tests/PHPStan/Rules/Classes/data/allowed-sub-types-throwable.php diff --git a/conf/config.neon b/conf/config.neon index e60bdbcdb0..f41ddde729 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -850,6 +850,11 @@ services: tags: - phpstan.broker.allowedSubTypesClassReflectionExtension + - + class: PHPStan\Reflection\Php\ThrowableAllowedSubTypesClassReflectionExtension + tags: + - phpstan.broker.allowedSubTypesClassReflectionExtension + - class: PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension tags: diff --git a/src/Reflection/Php/ThrowableAllowedSubTypesClassReflectionExtension.php b/src/Reflection/Php/ThrowableAllowedSubTypesClassReflectionExtension.php new file mode 100644 index 0000000000..d49d68b651 --- /dev/null +++ b/src/Reflection/Php/ThrowableAllowedSubTypesClassReflectionExtension.php @@ -0,0 +1,28 @@ +getName() === Throwable::class; + } + + public function getAllowedSubTypes(ClassReflection $classReflection): array + { + return [ + new ObjectType(Error::class), + new ObjectType(Exception::class), // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException + ]; + } + +} diff --git a/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php b/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php index e067ec7d05..67fbc37d6a 100644 --- a/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php @@ -32,6 +32,13 @@ public function testRule(): void 12, ], ]); + + $this->analyse([__DIR__ . '/data/allowed-sub-types-throwable.php'], [ + [ + 'Type AllowedSubTypesThrowable\\MyError is not allowed to be a subtype of Throwable.', + 10, + ], + ]); } public static function getAdditionalConfigFiles(): array diff --git a/tests/PHPStan/Rules/Classes/data/allowed-sub-types-throwable.php b/tests/PHPStan/Rules/Classes/data/allowed-sub-types-throwable.php new file mode 100644 index 0000000000..fe8608d6cb --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/allowed-sub-types-throwable.php @@ -0,0 +1,45 @@ + Date: Sat, 24 Sep 2022 14:42:04 +0200 Subject: [PATCH 09/12] remove AllowedSubTypesExtension for DateTimeInterface and Throwable --- conf/config.neon | 10 ---- ...llowedSubTypesClassReflectionExtension.php | 28 ---------- ...llowedSubTypesClassReflectionExtension.php | 28 ---------- src/Type/ObjectType.php | 13 +++++ .../Rules/Classes/AllowedSubTypesRuleTest.php | 14 ----- .../data/allowed-sub-types-datetime.php | 53 ------------------- .../data/allowed-sub-types-throwable.php | 45 ---------------- 7 files changed, 13 insertions(+), 178 deletions(-) delete mode 100644 src/Reflection/Php/DateTimeInterfaceAllowedSubTypesClassReflectionExtension.php delete mode 100644 src/Reflection/Php/ThrowableAllowedSubTypesClassReflectionExtension.php delete mode 100644 tests/PHPStan/Rules/Classes/data/allowed-sub-types-datetime.php delete mode 100644 tests/PHPStan/Rules/Classes/data/allowed-sub-types-throwable.php diff --git a/conf/config.neon b/conf/config.neon index f41ddde729..d596bcec1f 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -840,21 +840,11 @@ services: tags: - phpstan.broker.methodsClassReflectionExtension - - - class: PHPStan\Reflection\Php\DateTimeInterfaceAllowedSubTypesClassReflectionExtension - tags: - - phpstan.broker.allowedSubTypesClassReflectionExtension - - class: PHPStan\Reflection\Php\EnumAllowedSubTypesClassReflectionExtension tags: - phpstan.broker.allowedSubTypesClassReflectionExtension - - - class: PHPStan\Reflection\Php\ThrowableAllowedSubTypesClassReflectionExtension - tags: - - phpstan.broker.allowedSubTypesClassReflectionExtension - - class: PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension tags: diff --git a/src/Reflection/Php/DateTimeInterfaceAllowedSubTypesClassReflectionExtension.php b/src/Reflection/Php/DateTimeInterfaceAllowedSubTypesClassReflectionExtension.php deleted file mode 100644 index 97678298d6..0000000000 --- a/src/Reflection/Php/DateTimeInterfaceAllowedSubTypesClassReflectionExtension.php +++ /dev/null @@ -1,28 +0,0 @@ -getName() === DateTimeInterface::class; - } - - public function getAllowedSubTypes(ClassReflection $classReflection): array - { - return [ - new ObjectType(DateTime::class), - new ObjectType(DateTimeImmutable::class), - ]; - } - -} diff --git a/src/Reflection/Php/ThrowableAllowedSubTypesClassReflectionExtension.php b/src/Reflection/Php/ThrowableAllowedSubTypesClassReflectionExtension.php deleted file mode 100644 index d49d68b651..0000000000 --- a/src/Reflection/Php/ThrowableAllowedSubTypesClassReflectionExtension.php +++ /dev/null @@ -1,28 +0,0 @@ -getName() === Throwable::class; - } - - public function getAllowedSubTypes(ClassReflection $classReflection): array - { - return [ - new ObjectType(Error::class), - new ObjectType(Exception::class), // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException - ]; - } - -} diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 5e8820610a..410614f68d 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -4,6 +4,9 @@ use ArrayAccess; use Closure; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; use Iterator; use IteratorAggregate; use PHPStan\Analyser\OutOfClassScope; @@ -1272,6 +1275,16 @@ private function getInterfaces(): array public function tryRemove(Type $typeToRemove): ?Type { + if ($this->getClassName() === DateTimeInterface::class) { + if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === DateTimeImmutable::class) { + return new ObjectType(DateTime::class); + } + + if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === DateTime::class) { + return new ObjectType(DateTimeImmutable::class); + } + } + if ($this->isSuperTypeOf($typeToRemove)->yes()) { return $this->subtract($typeToRemove); } diff --git a/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php b/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php index 67fbc37d6a..5d61ee9496 100644 --- a/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php @@ -25,20 +25,6 @@ public function testRule(): void 11, ], ]); - - $this->analyse([__DIR__ . '/data/allowed-sub-types-datetime.php'], [ - [ - 'Type AllowedSubTypesDateTime\\MyDateTime is not allowed to be a subtype of DateTimeInterface.', - 12, - ], - ]); - - $this->analyse([__DIR__ . '/data/allowed-sub-types-throwable.php'], [ - [ - 'Type AllowedSubTypesThrowable\\MyError is not allowed to be a subtype of Throwable.', - 10, - ], - ]); } public static function getAdditionalConfigFiles(): array diff --git a/tests/PHPStan/Rules/Classes/data/allowed-sub-types-datetime.php b/tests/PHPStan/Rules/Classes/data/allowed-sub-types-datetime.php deleted file mode 100644 index ceb9a21664..0000000000 --- a/tests/PHPStan/Rules/Classes/data/allowed-sub-types-datetime.php +++ /dev/null @@ -1,53 +0,0 @@ - Date: Sat, 24 Sep 2022 14:43:11 +0200 Subject: [PATCH 10/12] add type narrowing for Throwable --- src/Type/ObjectType.php | 13 +++++++++++++ .../PHPStan/Analyser/NodeScopeResolverTest.php | 1 + .../data/allowed-subtypes-throwable.php | 17 +++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/allowed-subtypes-throwable.php diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 410614f68d..a2f4156d8a 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -7,6 +7,8 @@ use DateTime; use DateTimeImmutable; use DateTimeInterface; +use Error; +use Exception; use Iterator; use IteratorAggregate; use PHPStan\Analyser\OutOfClassScope; @@ -38,6 +40,7 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; +use Throwable; use Traversable; use function array_key_exists; use function array_map; @@ -1285,6 +1288,16 @@ public function tryRemove(Type $typeToRemove): ?Type } } + if ($this->getClassName() === Throwable::class) { + if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === Error::class) { + return new ObjectType(Exception::class); // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException + } + + if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === Exception::class) { // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException + return new ObjectType(Error::class); + } + } + if ($this->isSuperTypeOf($typeToRemove)->yes()) { return $this->subtract($typeToRemove); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index c5bf05717e..6a41525474 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1078,6 +1078,7 @@ public function dataFileAsserts(): iterable } yield from $this->gatherAssertTypes(__DIR__ . '/data/allowed-subtypes-datetime.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/allowed-subtypes-throwable.php'); } /** diff --git a/tests/PHPStan/Analyser/data/allowed-subtypes-throwable.php b/tests/PHPStan/Analyser/data/allowed-subtypes-throwable.php new file mode 100644 index 0000000000..4336e79d64 --- /dev/null +++ b/tests/PHPStan/Analyser/data/allowed-subtypes-throwable.php @@ -0,0 +1,17 @@ + Date: Fri, 14 Oct 2022 08:06:25 +0200 Subject: [PATCH 11/12] include Throwable type narrowing in tests --- .../Rules/Classes/ImpossibleInstanceOfRuleTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php index df65a574ef..01bc7c7fbc 100644 --- a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php @@ -159,6 +159,11 @@ public function testInstanceof(): void 388, $tipText, ], + [ + 'Instanceof between T of Exception and Error will always evaluate to false.', + 404, + $tipText, + ], [ 'Instanceof between class-string and DateTimeInterface will always evaluate to false.', 418, @@ -256,6 +261,11 @@ public function testInstanceofWithoutAlwaysTrue(): void 'Instanceof between mixed and ImpossibleInstanceOf\InvalidTypeTest|int results in an error.', 362, ], + [ + 'Instanceof between T of Exception and Error will always evaluate to false.', + 404, + $tipText, + ], [ 'Instanceof between class-string and DateTimeInterface will always evaluate to false.', 418, From 54e3dfee6228b19ab95872f517697b56b5933522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pudil?= Date: Fri, 14 Oct 2022 09:11:53 +0200 Subject: [PATCH 12/12] do not require data files in tests --- .../Reflection/AllowedSubTypesClassReflectionExtensionTest.php | 1 - tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/PHPStan/Reflection/AllowedSubTypesClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/AllowedSubTypesClassReflectionExtensionTest.php index be454772f6..58890ef52e 100644 --- a/tests/PHPStan/Reflection/AllowedSubTypesClassReflectionExtensionTest.php +++ b/tests/PHPStan/Reflection/AllowedSubTypesClassReflectionExtensionTest.php @@ -9,7 +9,6 @@ class AllowedSubTypesClassReflectionExtensionTest extends TypeInferenceTestCase public function dataFileAsserts(): iterable { - require_once __DIR__ . '/data/allowed-sub-types.php'; yield from $this->gatherAssertTypes(__DIR__ . '/data/allowed-sub-types.php'); } diff --git a/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php b/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php index 5d61ee9496..403a35e6ff 100644 --- a/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/AllowedSubTypesRuleTest.php @@ -18,7 +18,6 @@ protected function getRule(): Rule public function testRule(): void { - require __DIR__ . '/data/allowed-sub-types.php'; $this->analyse([__DIR__ . '/data/allowed-sub-types.php'], [ [ 'Type AllowedSubTypes\\Baz is not allowed to be a subtype of AllowedSubTypes\\Foo.',