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:
parent
5b9efd579d
commit
9d840ee87b
@ -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(
|
||||
|
33
src/Psalm/Internal/TypeVisitor/ContainsTemplateVisitor.php
Normal file
33
src/Psalm/Internal/TypeVisitor/ContainsTemplateVisitor.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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>
|
||||
*/
|
||||
|
@ -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',
|
||||
// ],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user