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/DataObjectReturnTypeExtension.php b/src/Type/DataObjectReturnTypeExtension.php index e496fd5..adc3bc8 100644 --- a/src/Type/DataObjectReturnTypeExtension.php +++ b/src/Type/DataObjectReturnTypeExtension.php @@ -3,24 +3,20 @@ namespace Syntro\SilverstripePHPStan\Type; use Exception; -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 { @@ -74,11 +70,14 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method case 'dbObject': $className = ''; + if ($type instanceof UnionType) { + $type = array_filter($type->getTypes(), fn ($subType) => $subType instanceof ObjectType)[0] ?? null; + } 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) { 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); + } +}