From cb6ef0e1fc7f9be5dd9a83b3684ceb197262063e Mon Sep 17 00:00:00 2001 From: Dominik Beerbohm Date: Wed, 14 Feb 2024 10:46:32 +0100 Subject: [PATCH 1/3] Add null to DataObject::get_one and similar methods https://github.com/syntro-opensource/silverstripe-phpstan/issues/10 --- phpstan.neon | 6 ++ src/Type/DataListReturnTypeExtension.php | 3 +- ...DataObjectGetStaticReturnTypeExtension.php | 7 +- .../VersionedGetStaticReturnTypeExtension.php | 96 +++++++++++++++++++ 4 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 src/Type/VersionedGetStaticReturnTypeExtension.php diff --git a/phpstan.neon b/phpstan.neon index b06fbef..14dab7e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -32,6 +32,12 @@ services: class: Syntro\SilverstripePHPStan\Type\DataObjectGetStaticReturnTypeExtension tags: - phpstan.broker.dynamicStaticMethodReturnTypeExtension + # This adds additional type info to `Versioned::get*()` so that it knows what class + # while be returned when iterating. + - + class: Syntro\SilverstripePHPStan\Type\VersionedGetStaticReturnTypeExtension + tags: + - phpstan.broker.dynamicStaticMethodReturnTypeExtension # This allows `singleton("File")` calls to understand the exact classes being returned # by using your configuration. (ie. uses Injector information if it's set) - diff --git a/src/Type/DataListReturnTypeExtension.php b/src/Type/DataListReturnTypeExtension.php index 7712f47..da9b860 100644 --- a/src/Type/DataListReturnTypeExtension.php +++ b/src/Type/DataListReturnTypeExtension.php @@ -3,6 +3,7 @@ namespace Syntro\SilverstripePHPStan\Type; use Exception; +use PHPStan\Type\TypeCombinator; use Syntro\SilverstripePHPStan\ClassHelper; use PhpParser\Node\Expr\MethodCall; use PHPStan\Reflection\MethodReflection; @@ -122,7 +123,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method case 'byID': case 'first': case 'last': - return $type->getIterableValueType(); + return TypeCombinator::addNull($type->getIterableValueType()); default: throw new Exception('Unhandled method call: '.$name); diff --git a/src/Type/DataObjectGetStaticReturnTypeExtension.php b/src/Type/DataObjectGetStaticReturnTypeExtension.php index 5c5fe90..af15913 100644 --- a/src/Type/DataObjectGetStaticReturnTypeExtension.php +++ b/src/Type/DataObjectGetStaticReturnTypeExtension.php @@ -2,6 +2,7 @@ namespace Syntro\SilverstripePHPStan\Type; +use PHPStan\Type\TypeCombinator; use Syntro\SilverstripePHPStan\ClassHelper; use Syntro\SilverstripePHPStan\Utility; use PhpParser\Node\Expr\StaticCall; @@ -57,17 +58,17 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, // Handle DataObject::get_one('Page') $arg = $methodCall->args[0]; $type = Utility::getTypeFromVariable($arg, $methodReflection); - return $type; + return TypeCombinator::addNull($type); } // Handle Page::get() / self::get() $callerClass = $methodCall->class->toString(); if ($callerClass === 'static') { - return Utility::getMethodReturnType($methodReflection); + return TypeCombinator::addNull(Utility::getMethodReturnType($methodReflection)); } if ($callerClass === 'self') { $callerClass = $scope->getClassReflection()->getName(); } - return new ObjectType($callerClass); + return TypeCombinator::addNull(new ObjectType($callerClass)); } // NOTE(mleutenegger): 2019-11-10 // taken from https://github.com/phpstan/phpstan#dynamic-return-type-extensions diff --git a/src/Type/VersionedGetStaticReturnTypeExtension.php b/src/Type/VersionedGetStaticReturnTypeExtension.php new file mode 100644 index 0000000..8309b2a --- /dev/null +++ b/src/Type/VersionedGetStaticReturnTypeExtension.php @@ -0,0 +1,96 @@ +getName(); + + return in_array($name, [ + 'get_including_deleted', + 'get_by_stage', + 'get_all_versions', + + 'get_version', + 'get_one_by_stage', + 'get_latest_version', + ]); + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type + { + $name = $methodReflection->getName(); + switch ($name) { + case 'get_including_deleted': + case 'get_by_stage': + case 'get_all_versions': + if (count($methodCall->args) > 0) { + // Handle DataObject::get('Page') + $arg = $methodCall->args[0]; + $type = Utility::getTypeFromInjectorVariable($arg, new ObjectType('SilverStripe\ORM\DataObject')); + return new DataListType(ClassHelper::DataList, $type); + } + // Handle Page::get() / self::get() + $callerClass = $methodCall->class->toString(); + if ($callerClass === 'static') { + return Utility::getMethodReturnType($methodReflection); + } + if ($callerClass === 'self') { + $callerClass = $scope->getClassReflection()->getName(); + } + return new DataListType(ClassHelper::DataList, new ObjectType($callerClass)); + + case 'get_version': + case 'get_one_by_stage': + case 'get_latest_version': + if (count($methodCall->args) > 0) { + // Handle DataObject::get_one('Page') + $arg = $methodCall->args[0]; + $type = Utility::getTypeFromVariable($arg, $methodReflection); + return TypeCombinator::addNull($type); + } + // Handle Page::get() / self::get() + $callerClass = $methodCall->class->toString(); + if ($callerClass === 'static') { + return TypeCombinator::addNull(Utility::getMethodReturnType($methodReflection)); + } + if ($callerClass === 'self') { + $callerClass = $scope->getClassReflection()->getName(); + } + return TypeCombinator::addNull(new ObjectType($callerClass)); + } + // NOTE(mleutenegger): 2019-11-10 + // taken from https://github.com/phpstan/phpstan#dynamic-return-type-extensions + if (count($methodCall->args) === 0) { + return ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->args, + $methodReflection->getVariants() + )->getReturnType(); + } + $arg = $methodCall->args[0]->value; + + return $scope->getType($arg); + } +} From 18c752e9c87917d6890804d941e5e8a14a424149 Mon Sep 17 00:00:00 2001 From: Dominik Beerbohm Date: Fri, 16 Feb 2024 14:09:12 +0100 Subject: [PATCH 2/3] Unwrap UnionType --- src/Type/DataObjectReturnTypeExtension.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Type/DataObjectReturnTypeExtension.php b/src/Type/DataObjectReturnTypeExtension.php index e496fd5..64d2a3f 100644 --- a/src/Type/DataObjectReturnTypeExtension.php +++ b/src/Type/DataObjectReturnTypeExtension.php @@ -3,6 +3,8 @@ namespace Syntro\SilverstripePHPStan\Type; use Exception; +use PHPStan\Type\NullType; +use PHPStan\Type\UnionType; use Syntro\SilverstripePHPStan\ClassHelper; use Syntro\SilverstripePHPStan\ConfigHelper; use Syntro\SilverstripePHPStan\Utility; @@ -74,11 +76,17 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method case 'dbObject': $className = ''; + if ($type instanceof UnionType) { + $types = array_filter($type->getTypes(), fn ($subType) => !($subType instanceof NullType)); + if (count($types) == 1) { + $type = $types[0]; + } + } if ($type instanceof StaticType) { if (count($type->getReferencedClasses()) === 1) { $className = $type->getReferencedClasses()[0]; } - } else if ($type instanceof ObjectType) { + } elseif ($type instanceof ObjectType) { $className = $type->getClassName(); } if (!$className) { From a9daa3f000e7840ebebd3a42271b3eef6a5edbdb Mon Sep 17 00:00:00 2001 From: Dominik Beerbohm Date: Fri, 16 Feb 2024 14:35:01 +0100 Subject: [PATCH 3/3] Fix issue with union types --- src/Type/DataObjectReturnTypeExtension.php | 27 ++++++++-------------- 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/Type/DataObjectReturnTypeExtension.php b/src/Type/DataObjectReturnTypeExtension.php index 64d2a3f..adc3bc8 100644 --- a/src/Type/DataObjectReturnTypeExtension.php +++ b/src/Type/DataObjectReturnTypeExtension.php @@ -3,26 +3,20 @@ namespace Syntro\SilverstripePHPStan\Type; use Exception; -use PHPStan\Type\NullType; -use PHPStan\Type\UnionType; -use Syntro\SilverstripePHPStan\ClassHelper; -use Syntro\SilverstripePHPStan\ConfigHelper; -use Syntro\SilverstripePHPStan\Utility; -use PHPStan\Type\DynamicMethodReturnTypeExtension; -use PHPStan\Broker\Broker; +use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\MethodCall; -use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Scalar\String_; -use PhpParser\Node\Expr\ClassConstFetch; -use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\MethodReflection; use PHPStan\Analyser\Scope; -use PHPStan\Type\Type; -use PHPStan\Type\ArrayType; -use PHPStan\Type\IntegerType; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\ObjectType; use PHPStan\Type\StaticType; +use PHPStan\Type\Type; +use PHPStan\Type\UnionType; +use Syntro\SilverstripePHPStan\ClassHelper; +use Syntro\SilverstripePHPStan\ConfigHelper; +use Syntro\SilverstripePHPStan\Utility; class DataObjectReturnTypeExtension implements DynamicMethodReturnTypeExtension { @@ -77,10 +71,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method case 'dbObject': $className = ''; if ($type instanceof UnionType) { - $types = array_filter($type->getTypes(), fn ($subType) => !($subType instanceof NullType)); - if (count($types) == 1) { - $type = $types[0]; - } + $type = array_filter($type->getTypes(), fn ($subType) => $subType instanceof ObjectType)[0] ?? null; } if ($type instanceof StaticType) { if (count($type->getReferencedClasses()) === 1) {