mirror of
https://github.com/danog/psalm.git
synced 2024-12-11 08:49:52 +01:00
365 lines
12 KiB
PHP
365 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Psalm\Tests;
|
|
|
|
use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait;
|
|
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;
|
|
|
|
class PropertyTypeInvarianceTest extends TestCase
|
|
{
|
|
use InvalidCodeAnalysisTestTrait;
|
|
use ValidCodeAnalysisTestTrait;
|
|
|
|
public function providerValidCodeParse(): iterable
|
|
{
|
|
return [
|
|
'validcode' => [
|
|
'code' => '<?php
|
|
class ParentClass
|
|
{
|
|
/** @var null|string */
|
|
protected $mightExist;
|
|
|
|
protected ?string $mightExistNative = null;
|
|
|
|
/** @var string */
|
|
protected $doesExist = "";
|
|
|
|
protected string $doesExistNative = "";
|
|
}
|
|
|
|
class ChildClass extends ParentClass
|
|
{
|
|
/** @var null|string */
|
|
protected $mightExist = "";
|
|
|
|
protected ?string $mightExistNative = null;
|
|
|
|
/** @var string */
|
|
protected $doesExist = "";
|
|
|
|
protected string $doesExistNative = "";
|
|
}',
|
|
],
|
|
'allowTemplatedInvariance' => [
|
|
'code' => '<?php
|
|
/**
|
|
* @template T as string|null
|
|
*/
|
|
abstract class A {
|
|
/** @var T */
|
|
public $foo;
|
|
}
|
|
|
|
/**
|
|
* @extends A<string>
|
|
*/
|
|
class AChild extends A {
|
|
/** @var string */
|
|
public $foo = "foo";
|
|
}',
|
|
],
|
|
'allowTemplatedInvarianceWithListTemplate' => [
|
|
'code' => '<?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' => [
|
|
'code' => '<?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;
|
|
}',
|
|
],
|
|
'allowTemplatedInvarianceWithClassStringTemplate' => [
|
|
'code' => '<?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;
|
|
}',
|
|
],
|
|
'templatedInvarianceGrandchild' => [
|
|
'code' => '<?php
|
|
abstract class Item {}
|
|
class Foo extends Item {}
|
|
class Bar extends Foo {}
|
|
|
|
/** @template T of Item */
|
|
abstract class ItemCollection
|
|
{
|
|
/** @var list<T> */
|
|
protected $items = [];
|
|
}
|
|
|
|
/**
|
|
* @template T of Foo
|
|
* @extends ItemCollection<T>
|
|
*/
|
|
class FooCollection extends ItemCollection
|
|
{
|
|
/** @var list<T> */
|
|
protected $items = [];
|
|
}
|
|
|
|
/** @extends FooCollection<Bar> */
|
|
class BarCollection extends FooCollection
|
|
{
|
|
/** @var list<Bar> */
|
|
protected $items = [];
|
|
}',
|
|
],
|
|
'allowTemplateCovariant' => [
|
|
'code' => '<?php
|
|
class Foo {}
|
|
class Bar extends Foo {}
|
|
class Baz extends Bar {}
|
|
|
|
/** @template-covariant T */
|
|
class Pair
|
|
{
|
|
/** @var T|null */
|
|
public $a;
|
|
|
|
/** @var T|null */
|
|
public $b;
|
|
}
|
|
|
|
/** @extends Pair<Foo> */
|
|
class FooPair extends Pair
|
|
{
|
|
/** @var Bar|null */
|
|
public $a;
|
|
|
|
/** @var Baz|null */
|
|
public $b;
|
|
}',
|
|
],
|
|
'allowTemplateCovariantManyTemplates' => [
|
|
'code' => '<?php
|
|
class A {}
|
|
class B extends A {}
|
|
class C extends B {}
|
|
|
|
/**
|
|
* @template Ta
|
|
* @template Tb
|
|
* @template-covariant Tc
|
|
* @template Td
|
|
*/
|
|
class Foo {
|
|
/** @var Ta|null */
|
|
public $a;
|
|
|
|
/** @var Tb|null */
|
|
public $b;
|
|
|
|
/** @var Tc|null */
|
|
public $c;
|
|
|
|
/** @var Td|null */
|
|
public $d;
|
|
}
|
|
|
|
/**
|
|
* @template Ta
|
|
* @template Tb
|
|
* @template-covariant Tc
|
|
* @template Td
|
|
* @extends Foo<Ta, Tb, Tc, Td>
|
|
*/
|
|
class Bar extends Foo {}
|
|
|
|
/**
|
|
* @template Ta
|
|
* @template Tb
|
|
* @template-covariant Tc
|
|
* @template Td
|
|
* @extends Bar<A, B, A, C>
|
|
*/
|
|
class Baz extends Bar {
|
|
/** @var A|null */
|
|
public $a;
|
|
|
|
/** @var B|null */
|
|
public $b;
|
|
|
|
/** @var C|null */
|
|
public $c;
|
|
|
|
/** @var C|null */
|
|
public $d;
|
|
}',
|
|
],
|
|
'allowReadonly' => [
|
|
'code' => '<?php
|
|
class ParentClass
|
|
{
|
|
/**
|
|
* @readonly
|
|
* @var null|string
|
|
*/
|
|
protected $mightExist;
|
|
}
|
|
|
|
class ChildClass extends ParentClass
|
|
{
|
|
/** @var string */
|
|
protected $mightExist = "";
|
|
}',
|
|
],
|
|
];
|
|
}
|
|
|
|
public function providerInvalidCodeParse(): iterable
|
|
{
|
|
return [
|
|
'variantDocblockProperties' => [
|
|
'code' => '<?php
|
|
class ParentClass
|
|
{
|
|
/** @var null|string */
|
|
protected $mightExist;
|
|
}
|
|
|
|
class ChildClass extends ParentClass
|
|
{
|
|
/** @var string */
|
|
protected $mightExist = "";
|
|
}',
|
|
'error_message' => 'NonInvariantDocblockPropertyType',
|
|
],
|
|
'variantProperties' => [
|
|
'code' => '<?php
|
|
class ParentClass
|
|
{
|
|
protected ?string $mightExist = null;
|
|
}
|
|
|
|
class ChildClass extends ParentClass
|
|
{
|
|
protected string $mightExist = "";
|
|
}',
|
|
'error_message' => 'NonInvariantPropertyType',
|
|
],
|
|
'variantTemplatedProperties' => [
|
|
'code' => '<?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' => 'NonInvariantDocblockPropertyType',
|
|
],
|
|
'variantTemplatedGrandchild' => [
|
|
'code' => '<?php
|
|
abstract class Item {}
|
|
class Foo extends Item {}
|
|
class Bar extends Foo {}
|
|
|
|
/** @template T of Item */
|
|
abstract class ItemCollection
|
|
{
|
|
/** @var list<T> */
|
|
protected $items = [];
|
|
}
|
|
|
|
/**
|
|
* @template T of Foo
|
|
* @extends ItemCollection<T>
|
|
*/
|
|
class FooCollection extends ItemCollection
|
|
{
|
|
/** @var list<T> */
|
|
protected $items = [];
|
|
}
|
|
|
|
/** @extends FooCollection<Bar> */
|
|
class BarCollection extends FooCollection
|
|
{
|
|
/** @var list<Item> */ // Should be list<Bar>
|
|
protected $items = [];
|
|
}',
|
|
'error_message' => 'NonInvariantDocblockPropertyType',
|
|
],
|
|
'variantPropertiesWithTemplateNotSpecified' => [
|
|
'code' => '<?php
|
|
class Foo {}
|
|
|
|
/** @template T */
|
|
class Pair
|
|
{
|
|
/** @var T|null */
|
|
protected $a;
|
|
|
|
/** @var T|null */
|
|
protected $b;
|
|
}
|
|
|
|
/**
|
|
* @psalm-suppress MissingTemplateParam
|
|
*/
|
|
class FooPair extends Pair
|
|
{
|
|
/** @var Foo|null */ // Template defaults to mixed, this is invariant
|
|
protected $a;
|
|
|
|
/** @var Foo|null */
|
|
protected $b;
|
|
}',
|
|
'error_message' => 'NonInvariantDocblockPropertyType',
|
|
],
|
|
];
|
|
}
|
|
}
|