diff --git a/composer.json b/composer.json index e0a2421..309ce0d 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ ], "require": { "php": ">=7.1", - "vimeo/psalm": "^4.8", + "vimeo/psalm": "^4.8 || ^5.12", "yiisoft/yii2": "^2.0" }, "require-dev": { diff --git a/src/Handlers/BaseObjectPropertyAccessor.php b/src/Handlers/BaseObjectPropertyAccessor.php index 1d7a078..a1aca4c 100644 --- a/src/Handlers/BaseObjectPropertyAccessor.php +++ b/src/Handlers/BaseObjectPropertyAccessor.php @@ -141,7 +141,8 @@ public static function getPropertyType(PropertyTypeProviderEvent $event): ?Union return Type::getMixed(); } - foreach ($type->getAtomicTypes() as $atomic_type) { + $mutableType = method_exists($type, 'getBuilder') ? $type->getBuilder() : $type; + foreach ($mutableType->getAtomicTypes() as $atomic_type) { if ($atomic_type instanceof TGenericObject) { if ($atomic_type->value === ActiveQuery::class || $codebase->classExtendsOrImplements($atomic_type->value, ActiveQueryInterface::class) @@ -151,19 +152,19 @@ public static function getPropertyType(PropertyTypeProviderEvent $event): ?Union $isArray = true; } - $type->removeType($atomic_type->getKey()); + $mutableType->removeType($atomic_type->getKey()); if ($isArray) { - $type->addType(new TArray([Type::getArrayKey(), $atomic_type->type_params[0]])); + $mutableType->addType(new TArray([Type::getArrayKey(), $atomic_type->type_params[0]])); } else { foreach ($atomic_type->type_params[0]->getAtomicTypes() as $type_to_add) { - $type->addType($type_to_add); + $mutableType->addType($type_to_add); } } } } } - return $type; + return method_exists($mutableType, 'freeze') ? $mutableType->freeze() : $mutableType; } if (self::doesSetterExist($codebase, $fq_classlike_name, $property_name)) { diff --git a/stubs/BaseActiveRecord.phpstub b/stubs/BaseActiveRecord.phpstub index 266d46c..0e960b8 100644 --- a/stubs/BaseActiveRecord.phpstub +++ b/stubs/BaseActiveRecord.phpstub @@ -10,7 +10,9 @@ declare(strict_types=1); namespace yii\db; -class BaseActiveRecord +use yii\base\Model; + +class BaseActiveRecord extends Model implements ActiveRecordInterface { /** * @template T diff --git a/stubs/BaseObject.phpstub b/stubs/BaseObject.phpstub index 25ab7c2..d8f475d 100644 --- a/stubs/BaseObject.phpstub +++ b/stubs/BaseObject.phpstub @@ -14,4 +14,5 @@ namespace yii\base; * BaseObject stub to force the class to be loaded. If we dont add this stub * then psalm will not be able to find the class storage for this class */ -class BaseObject {} +class BaseObject implements Configurable +{} diff --git a/stubs/Query.phpstub b/stubs/Query.phpstub index ed99b31..6d05c74 100644 --- a/stubs/Query.phpstub +++ b/stubs/Query.phpstub @@ -10,6 +10,8 @@ declare(strict_types=1); namespace yii\db; +use yii\base\Component; + /** * @template TModel * @template TMultiple @@ -17,4 +19,5 @@ namespace yii\db; * @method TModel|null one() * @method array all() */ -class Query {} +class Query extends Component implements QueryInterface, ExpressionInterface +{} diff --git a/tests/acceptance/Query.feature b/tests/acceptance/Query.feature index b0c7db4..3b2b9fb 100644 --- a/tests/acceptance/Query.feature +++ b/tests/acceptance/Query.feature @@ -28,12 +28,17 @@ Feature: Query::class; declare(strict_types=1); namespace Practically\PsalmPluginYii2\Tests\Sandbox; - + use yii\db\ActiveQuery; """ Scenario: You can create a instance of a model and its typed Given I have the following code """ + /** + * @template TModel + * @template TMultiple + * @extends ActiveQuery + */ class MyQuery extends ActiveQuery { public function active(): self