1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Disable property invariance checks for properties with templates (#5380)

* Disable property invariance checks for templates

Property invariance checks were already disabled for template
properties, this also disabled the checks for arrays, lists,
class-string-maps, and iterables that contain templates.

Partially fixes #5371

* CS fixes

* Fix and simplify hasTemplate.

Add hasTemplate to TypeNode and simplify implementation with getChildNodes.

* Revert hasTemplate change and add containsTemplate.

* Check class-string too.
This commit is contained in:
AndrolGenhald 2021-03-16 12:43:30 -05:00 committed by GitHub
parent 5b9efd579d
commit 9d840ee87b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 128 additions and 3 deletions

View File

@ -709,7 +709,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
&& $guide_property_storage->type
&& $property_storage->location
&& !$property_storage->type->equals($guide_property_storage->type)
&& !$guide_property_storage->type->hasTemplate()
&& !$guide_property_storage->type->containsTemplate()
&& $guide_class_storage->user_defined
) {
if (IssueBuffer::accepts(

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Psalm\Internal\TypeVisitor;
use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Type\Atomic\TTemplateParamClass;
use Psalm\Type\TypeNode;
use Psalm\Type\NodeVisitor;
class ContainsTemplateVisitor extends NodeVisitor
{
/**
* @var bool
*/
private $contains_template = false;
protected function enterNode(TypeNode $type): ?int
{
if ($type instanceof TTemplateParam || $type instanceof TTemplateParamClass) {
$this->contains_template = true;
return NodeVisitor::STOP_TRAVERSAL;
}
return null;
}
public function matches(): bool
{
return $this->contains_template;
}
}

View File

@ -1319,6 +1319,18 @@ class Union implements TypeNode
return $classlike_visitor->matches();
}
/**
* Returns true if union contains template at any nested level.
*/
public function containsTemplate(): bool
{
$template_visitor = new \Psalm\Internal\TypeVisitor\ContainsTemplateVisitor();
$template_visitor->traverseArray($this->types);
return $template_visitor->matches();
}
/**
* @return list<TTemplateParam>
*/

View File

@ -38,7 +38,7 @@ class PropertyTypeInvarianceTest extends TestCase
protected $doesExist = "";
protected string $doesExistNative = "";
}'
}',
],
'allowTemplatedInvariance' => [
'<?php
@ -56,7 +56,67 @@ class PropertyTypeInvarianceTest extends TestCase
class AChild extends A {
/** @var string */
public $foo = "foo";
}'
}',
],
'allowTemplatedInvarianceWithListTemplate' => [
'<?php
abstract class Item {}
class Foo extends Item {}
/** @template TItem of Item */
abstract class ItemCollection
{
/** @var list<TItem> */
protected $items = [];
}
/** @extends ItemCollection<Foo> */
class FooCollection extends ItemCollection
{
/** @var list<Foo> */
protected $items = [];
}',
],
'allowTemplatedInvarianceWithClassTemplate' => [
'<?php
abstract class Item {}
class Foo extends Item {}
/** @template T */
class Collection {}
/** @template TItem of Item */
abstract class ItemCollection
{
/** @var Collection<TItem>|null */
protected $items;
}
/** @extends ItemCollection<Foo> */
class FooCollection extends ItemCollection
{
/** @var Collection<Foo>|null */
protected $items;
}',
],
'allowTemplatedInvarianceWithClassString' => [
'<?php
abstract class Item {}
class Foo extends Item {}
/** @template T of Item */
abstract class ItemType
{
/** @var class-string<T>|null */
protected $type;
}
/** @extends ItemType<Foo> */
class FooTypes extends ItemType
{
/** @var class-string<Foo>|null */
protected $type;
}',
],
];
}
@ -95,6 +155,26 @@ class PropertyTypeInvarianceTest extends TestCase
}',
'error_message' => 'NonInvariantPropertyType',
],
// TODO support property invariance checks with templates
// 'disallowInvalidTemplatedInvariance' => [
// '<?php
// /**
// * @template T as string|null
// */
// abstract class A {
// /** @var T */
// public $foo;
// }
// /**
// * @extends A<string>
// */
// class AChild extends A {
// /** @var int */
// public $foo = 0;
// }',
// 'error_message' => 'NonInvariantPropertyType',
// ],
];
}
}