Skip to content
Permalink
Browse files

Add initial support for @mixin Foo

  • Loading branch information
muglug committed Jan 3, 2020
1 parent 9177b41 commit d5d4a1826d54f63e6f74ca67b3d8c70ccfbc918b
@@ -876,6 +876,17 @@ public static function extractClassLikeDocblockInfo(
}
}

if (isset($parsed_docblock['specials']['mixin'])) {
$mixin = trim(reset($parsed_docblock['specials']['mixin']));
$mixin = explode(' ', $mixin)[0];

if ($mixin) {
$info->mixin = $mixin;
} else {
throw new DocblockParseException('@mixin annotation used without specifying class');
}
}

if (isset($parsed_docblock['specials']['psalm-seal-properties'])) {
$info->sealed_properties = true;
}
@@ -177,6 +177,10 @@ private function populateClassLikeStorage(ClassLikeStorage $storage, array $depe

$this->populateDataFromTraits($storage, $storage_provider, $dependent_classlikes);

if ($storage->mixin_fqcln) {
$this->populateDataFromMixin($storage, $storage_provider, $dependent_classlikes, $storage->mixin_fqcln);
}

$dependent_classlikes[$fq_classlike_name_lc] = true;

if ($storage->parent_classes) {
@@ -420,6 +424,30 @@ private function populateDataFromTraits(
}
}

/**
* @return void
*/
private function populateDataFromMixin(
ClassLikeStorage $storage,
ClassLikeStorageProvider $storage_provider,
array $dependent_classlikes,
string $mixin_fqcln
) {
try {
$mixin_fqcln = $this->classlikes->getUnAliasedName(
$mixin_fqcln
);
$mixin_storage = $storage_provider->get($mixin_fqcln);
} catch (\InvalidArgumentException $e) {
return;
}

$this->populateClassLikeStorage($mixin_storage, $dependent_classlikes);

$this->inheritMethodsFromParent($storage, $mixin_storage);
$this->inheritPropertiesFromParent($storage, $mixin_storage, false);
}

private static function extendType(
Type\Union $type,
ClassLikeStorage $storage
@@ -1013,8 +1041,10 @@ private function convertPhpStormGenericToPsalmGeneric(Type\Union $candidate, $is
*
* @return void
*/
protected function inheritMethodsFromParent(ClassLikeStorage $storage, ClassLikeStorage $parent_storage)
{
protected function inheritMethodsFromParent(
ClassLikeStorage $storage,
ClassLikeStorage $parent_storage
) {
$fq_class_name = $storage->name;

// register where they appear (can never be in a trait)
@@ -1119,8 +1149,11 @@ protected function inheritMethodsFromParent(ClassLikeStorage $storage, ClassLike
*
* @return void
*/
private function inheritPropertiesFromParent(ClassLikeStorage $storage, ClassLikeStorage $parent_storage)
{
private function inheritPropertiesFromParent(
ClassLikeStorage $storage,
ClassLikeStorage $parent_storage,
bool $include_protected = true
) {
// register where they appear (can never be in a trait)
foreach ($parent_storage->appearing_property_ids as $property_name => $appearing_property_id) {
if (isset($storage->appearing_property_ids[$property_name])) {
@@ -1129,7 +1162,10 @@ private function inheritPropertiesFromParent(ClassLikeStorage $storage, ClassLik

if (!$parent_storage->is_trait
&& isset($parent_storage->properties[$property_name])
&& $parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE
&& ($parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE
|| (!$include_protected
&& $parent_storage->properties[$property_name]->visibility
=== ClassLikeAnalyzer::VISIBILITY_PROTECTED))
) {
continue;
}
@@ -1148,7 +1184,10 @@ private function inheritPropertiesFromParent(ClassLikeStorage $storage, ClassLik

if (!$parent_storage->is_trait
&& isset($parent_storage->properties[$property_name])
&& $parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE
&& ($parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE
|| (!$include_protected
&& $parent_storage->properties[$property_name]->visibility
=== ClassLikeAnalyzer::VISIBILITY_PROTECTED))
) {
continue;
}
@@ -1160,7 +1199,10 @@ private function inheritPropertiesFromParent(ClassLikeStorage $storage, ClassLik
foreach ($parent_storage->inheritable_property_ids as $property_name => $inheritable_property_id) {
if (!$parent_storage->is_trait
&& isset($parent_storage->properties[$property_name])
&& $parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE
&& ($parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE
|| (!$include_protected
&& $parent_storage->properties[$property_name]->visibility
=== ClassLikeAnalyzer::VISIBILITY_PROTECTED))
) {
continue;
}
@@ -27,6 +27,11 @@ class ClassLikeDocblockComment
*/
public $psalm_internal = null;

/**
* @var null|string
*/
public $mixin = null;

/**
* @var array<int, array{string, ?string, ?string, bool, int}>
*/
@@ -1239,6 +1239,23 @@ function (array $l, array $r) : int {
$storage->internal = $docblock_info->internal;
$storage->psalm_internal = $docblock_info->psalm_internal;

if ($docblock_info->mixin) {
if (isset($this->class_template_types[$docblock_info->mixin])) {
if (IssueBuffer::accepts(
new InvalidDocblock(
'Templates are not currently supported for @mixin',
$name_location ?: $class_location
)
)) {
}
} else {
$storage->mixin_fqcln = Type::getFQCLNFromString(
$docblock_info->mixin,
$this->aliases
);
}
}

$storage->sealed_properties = $docblock_info->sealed_properties;
$storage->sealed_methods = $docblock_info->sealed_methods;

@@ -96,6 +96,11 @@ class ClassLikeStorage
*/
public $psalm_internal = null;

/**
* @var null|string
*/
public $mixin_fqcln = null;

/**
* @var array<string, bool>
*/
@@ -0,0 +1,77 @@
<?php
namespace Psalm\Tests;

use const DIRECTORY_SEPARATOR;
use Psalm\Config;
use Psalm\Context;

class MixinAnnotationTest extends TestCase
{
use Traits\ValidCodeAnalysisTestTrait;

/**
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
*/
public function providerValidCodeParse()
{
return [
'validSimpleAnnotations' => [
'<?php
class ParentClass {
public function __call(string $name, array $args) {}
}
class Provider {
public function getString() : string {
return "hello";
}
public function setInteger(int $i) : void {}
}
/** @mixin Provider */
class Child extends ParentClass {}
$child = new Child();
$a = $child->getString();
$child->setInteger(4);',
'assertions' => [
'$a' => 'string',
],
],
'anotherSimpleExample' => [
'<?php
/**
* @mixin B
*/
class A {
/** @var B */
private $b;
public function __construct() {
$this->b = new B();
}
/**
* @param array<mixed> $arguments
* @return mixed
*/
public function __call(string $method, array $arguments)
{
return $this->b->$method(...$arguments);
}
}
class B {
public function b(): void {
echo "b";
}
}
$a = new A();
$a->b();'
],
];
}
}

0 comments on commit d5d4a18

Please sign in to comment.
You can’t perform that action at this time.