1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Add initial support for @mixin Foo

This commit is contained in:
Matthew Brown 2020-01-02 23:50:19 -05:00
parent 9177b410cb
commit d5d4a1826d
6 changed files with 164 additions and 7 deletions

View File

@ -876,6 +876,17 @@ class CommentAnalyzer
}
}
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;
}

View File

@ -177,6 +177,10 @@ class Populator
$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 @@ class Populator
}
}
/**
* @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 @@ class Populator
*
* @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 @@ class Populator
*
* @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 @@ class Populator
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 @@ class Populator
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 @@ class Populator
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;
}

View File

@ -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}>
*/

View File

@ -1239,6 +1239,23 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
$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;

View File

@ -96,6 +96,11 @@ class ClassLikeStorage
*/
public $psalm_internal = null;
/**
* @var null|string
*/
public $mixin_fqcln = null;
/**
* @var array<string, bool>
*/

View File

@ -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();'
],
];
}
}