mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 05:41:20 +01:00
Split out trait template tests
This commit is contained in:
parent
5a62dc5c40
commit
2992cc73c2
@ -916,133 +916,7 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
public function valid(): bool { return false; }
|
||||
}',
|
||||
],
|
||||
'traitUseNotExtended' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait CollectionTrait
|
||||
{
|
||||
/**
|
||||
* @return array<T>
|
||||
*/
|
||||
abstract function elements() : array;
|
||||
|
||||
/**
|
||||
* @return T|null
|
||||
*/
|
||||
public function first()
|
||||
{
|
||||
return $this->elements()[0] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
class Service
|
||||
{
|
||||
/**
|
||||
* @use CollectionTrait<int>
|
||||
*/
|
||||
use CollectionTrait;
|
||||
|
||||
/**
|
||||
* @return array<int>
|
||||
*/
|
||||
public function elements(): array
|
||||
{
|
||||
return [1, 2, 3, 4];
|
||||
}
|
||||
}',
|
||||
],
|
||||
'extendedTraitUse' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait CollectionTrait
|
||||
{
|
||||
/**
|
||||
* @return array<T>
|
||||
*/
|
||||
abstract function elements() : array;
|
||||
|
||||
/**
|
||||
* @return T|null
|
||||
*/
|
||||
public function first()
|
||||
{
|
||||
return $this->elements()[0] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TValue
|
||||
*/
|
||||
trait BridgeTrait
|
||||
{
|
||||
/**
|
||||
* @use CollectionTrait<TValue>
|
||||
*/
|
||||
use CollectionTrait;
|
||||
}
|
||||
|
||||
class Service
|
||||
{
|
||||
/**
|
||||
* @use BridgeTrait<int>
|
||||
*/
|
||||
use BridgeTrait;
|
||||
|
||||
/**
|
||||
* @return array<int>
|
||||
*/
|
||||
public function elements(): array
|
||||
{
|
||||
return [1, 2, 3, 4];
|
||||
}
|
||||
}',
|
||||
],
|
||||
'extendedTraitUseAlreadyBound' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait CollectionTrait
|
||||
{
|
||||
/**
|
||||
* @return array<T>
|
||||
*/
|
||||
abstract function elements() : array;
|
||||
|
||||
/**
|
||||
* @return T|null
|
||||
*/
|
||||
public function first()
|
||||
{
|
||||
return $this->elements()[0] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
trait BridgeTrait
|
||||
{
|
||||
/**
|
||||
* @use CollectionTrait<int>
|
||||
*/
|
||||
use CollectionTrait;
|
||||
}
|
||||
|
||||
class Service
|
||||
{
|
||||
use BridgeTrait;
|
||||
|
||||
/**
|
||||
* @return array<int>
|
||||
*/
|
||||
public function elements(): array
|
||||
{
|
||||
return [1, 2, 3, 4];
|
||||
}
|
||||
}',
|
||||
],
|
||||
'extendClassThatParameterizesTemplatedParent' => [
|
||||
'<?php
|
||||
/**
|
||||
@ -1598,31 +1472,6 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
}
|
||||
}',
|
||||
],
|
||||
'badTemplateUseUnionType' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait T {
|
||||
/** @var T */
|
||||
public $t;
|
||||
|
||||
/** @param T $t */
|
||||
public function __construct($t) {
|
||||
$this->t = $t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TT
|
||||
*/
|
||||
class B {
|
||||
/**
|
||||
* @template-use T<int|string>
|
||||
*/
|
||||
use T;
|
||||
}',
|
||||
],
|
||||
'extendWithEnoughArgs' => [
|
||||
'<?php
|
||||
/**
|
||||
@ -1864,100 +1713,6 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
|
||||
ord((new Child())->example("str"));',
|
||||
],
|
||||
'allowTraitExtendAndImplementWithExplicitParamType' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait ValueObjectTrait
|
||||
{
|
||||
/**
|
||||
* @psalm-var ?T
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @psalm-param T $value
|
||||
*
|
||||
* @param $value
|
||||
*/
|
||||
private function setValue($value): void {
|
||||
$this->validate($value);
|
||||
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param T $value
|
||||
*
|
||||
* @param $value
|
||||
*/
|
||||
abstract protected function validate($value): void;
|
||||
}
|
||||
|
||||
final class StringValidator {
|
||||
/**
|
||||
* @template-use ValueObjectTrait<string>
|
||||
*/
|
||||
use ValueObjectTrait;
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*/
|
||||
protected function validate($value): void
|
||||
{
|
||||
if (strlen($value) > 30) {
|
||||
throw new \Exception("bad");
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
'allowTraitExtendAndImplementWithoutExplicitParamType' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait ValueObjectTrait
|
||||
{
|
||||
/**
|
||||
* @psalm-var ?T
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @psalm-param T $value
|
||||
*
|
||||
* @param $value
|
||||
*/
|
||||
private function setValue($value): void {
|
||||
$this->validate($value);
|
||||
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param T $value
|
||||
*
|
||||
* @param $value
|
||||
*/
|
||||
abstract protected function validate($value): void;
|
||||
}
|
||||
|
||||
final class StringValidator {
|
||||
/**
|
||||
* @template-use ValueObjectTrait<string>
|
||||
*/
|
||||
use ValueObjectTrait;
|
||||
|
||||
protected function validate($value): void
|
||||
{
|
||||
if (strlen($value) > 30) {
|
||||
throw new \Exception("bad");
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
|
||||
'keyOfClassTemplateExtended' => [
|
||||
'<?php
|
||||
/**
|
||||
@ -2312,31 +2067,6 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
|
||||
foreach ($c->getIterator() as $k => $v) { atan($v); strlen($k); }',
|
||||
],
|
||||
'traitInImplicitExtendedClass' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
interface Foo {
|
||||
/**
|
||||
* @return T
|
||||
*/
|
||||
public function getItem();
|
||||
}
|
||||
|
||||
trait FooTrait {
|
||||
public function getItem() {
|
||||
return "hello";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template-implements Foo<string>
|
||||
*/
|
||||
class Bar implements Foo {
|
||||
use FooTrait;
|
||||
}',
|
||||
],
|
||||
'extendedPropertyType' => [
|
||||
'<?php
|
||||
interface I {}
|
||||
@ -2669,104 +2399,6 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
}
|
||||
}'
|
||||
],
|
||||
'useTraitReturnTypeForInheritedInterface' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template TValue
|
||||
* @template TNormalizedValue
|
||||
*/
|
||||
interface Normalizer
|
||||
{
|
||||
/**
|
||||
* @param TValue $v
|
||||
* @return TNormalizedValue
|
||||
*/
|
||||
function normalize($v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TTraitValue
|
||||
* @template TTraitNormalizedValue
|
||||
*/
|
||||
trait NormalizerTrait
|
||||
{
|
||||
/**
|
||||
* @param TTraitValue $v
|
||||
* @return TTraitNormalizedValue
|
||||
*/
|
||||
function normalize($v)
|
||||
{
|
||||
return $this->doNormalize($v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TTraitValue $v
|
||||
* @return TTraitNormalizedValue
|
||||
*/
|
||||
abstract protected function doNormalize($v);
|
||||
}
|
||||
|
||||
/** @implements Normalizer<string, string> */
|
||||
class StringNormalizer implements Normalizer
|
||||
{
|
||||
/** @use NormalizerTrait<string, string> */
|
||||
use NormalizerTrait;
|
||||
|
||||
protected function doNormalize($v): string
|
||||
{
|
||||
return trim($v);
|
||||
}
|
||||
}'
|
||||
],
|
||||
'useTraitReturnTypeForInheritedClass' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template TValue
|
||||
* @template TNormalizedValue
|
||||
*/
|
||||
abstract class Normalizer
|
||||
{
|
||||
/**
|
||||
* @param TValue $v
|
||||
* @return TNormalizedValue
|
||||
*/
|
||||
abstract function normalize($v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TTraitValue
|
||||
* @template TTraitNormalizedValue
|
||||
*/
|
||||
trait NormalizerTrait
|
||||
{
|
||||
/**
|
||||
* @param TTraitValue $v
|
||||
* @return TTraitNormalizedValue
|
||||
*/
|
||||
function normalize($v)
|
||||
{
|
||||
return $this->doNormalize($v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TTraitValue $v
|
||||
* @return TTraitNormalizedValue
|
||||
*/
|
||||
abstract protected function doNormalize($v);
|
||||
}
|
||||
|
||||
/** @extends Normalizer<string, string> */
|
||||
class StringNormalizer extends Normalizer
|
||||
{
|
||||
/** @use NormalizerTrait<string, string> */
|
||||
use NormalizerTrait;
|
||||
|
||||
protected function doNormalize($v): string
|
||||
{
|
||||
return trim($v);
|
||||
}
|
||||
}'
|
||||
],
|
||||
'implementsParameterisedIterator' => [
|
||||
'<?php
|
||||
/**
|
||||
@ -3922,46 +3554,6 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
[],
|
||||
'7.4'
|
||||
],
|
||||
'inheritTraitPropertyTKeyedArray' => [
|
||||
'<?php
|
||||
/** @template TValue */
|
||||
trait A {
|
||||
/** @psalm-var array{TValue} */
|
||||
private $foo;
|
||||
|
||||
/** @psalm-param array{TValue} $foo */
|
||||
public function __construct(array $foo)
|
||||
{
|
||||
$this->foo = $foo;
|
||||
}
|
||||
}
|
||||
|
||||
/** @template TValue */
|
||||
class B {
|
||||
/** @use A<TValue> */
|
||||
use A;
|
||||
}'
|
||||
],
|
||||
'inheritTraitPropertyArray' => [
|
||||
'<?php
|
||||
/** @template TValue */
|
||||
trait A {
|
||||
/** @psalm-var array<TValue> */
|
||||
private $foo;
|
||||
|
||||
/** @psalm-param array<TValue> $foo */
|
||||
public function __construct(array $foo)
|
||||
{
|
||||
$this->foo = $foo;
|
||||
}
|
||||
}
|
||||
|
||||
/** @template TValue */
|
||||
class B {
|
||||
/** @use A<TValue> */
|
||||
use A;
|
||||
}'
|
||||
],
|
||||
'staticShouldBeBoundInCall' => [
|
||||
'<?php
|
||||
/**
|
||||
@ -4156,28 +3748,6 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
}
|
||||
}'
|
||||
],
|
||||
'applyTemplatedValueInTraitProperty' => [
|
||||
'<?php
|
||||
/** @template T */
|
||||
trait ValueTrait {
|
||||
/** @psalm-param T $value */
|
||||
public function setValue($value): void {
|
||||
$this->value = $value;
|
||||
}
|
||||
}
|
||||
|
||||
class C {
|
||||
/** @use ValueTrait<string> */
|
||||
use ValueTrait;
|
||||
|
||||
/** @var string */
|
||||
private $value;
|
||||
|
||||
public function __construct(string $value) {
|
||||
$this->value = $value;
|
||||
}
|
||||
}'
|
||||
],
|
||||
'classStringTemplatedExtends' => [
|
||||
'<?php
|
||||
/** @template T */
|
||||
@ -4231,35 +3801,6 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
public function i(): Closure;
|
||||
}'
|
||||
],
|
||||
'traitSelfAsParam' => [
|
||||
'<?php
|
||||
trait InstancePool {
|
||||
/**
|
||||
* @template T as self
|
||||
* @param callable():?T $callback
|
||||
* @return ?T
|
||||
*/
|
||||
public static function getInstance(callable $callback)
|
||||
{
|
||||
return $callback();
|
||||
}
|
||||
}
|
||||
|
||||
class Foo
|
||||
{
|
||||
use InstancePool;
|
||||
}
|
||||
|
||||
class Bar
|
||||
{
|
||||
public function a(): void
|
||||
{
|
||||
Foo::getInstance(function () {
|
||||
return new Foo();
|
||||
});
|
||||
}
|
||||
}'
|
||||
],
|
||||
'extendsWithArraySameObject' => [
|
||||
'<?php
|
||||
/**
|
||||
@ -4990,110 +4531,6 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
class B implements I {}',
|
||||
'error_message' => 'InvalidDocblock',
|
||||
],
|
||||
'badTemplateUse' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait T {
|
||||
/** @var T */
|
||||
public $t;
|
||||
|
||||
/** @param T $t */
|
||||
public function __construct($t) {
|
||||
$this->t = $t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TT
|
||||
*/
|
||||
class B {
|
||||
/**
|
||||
* @template-use T<Z>
|
||||
*/
|
||||
use T;
|
||||
}',
|
||||
'error_message' => 'UndefinedDocblockClass',
|
||||
],
|
||||
'badTemplateUseBadFormat' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait T {
|
||||
/** @var T */
|
||||
public $t;
|
||||
|
||||
/** @param T $t */
|
||||
public function __construct($t) {
|
||||
$this->t = $t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TT
|
||||
*/
|
||||
class B {
|
||||
/**
|
||||
* @template-use T< >
|
||||
*/
|
||||
use T;
|
||||
}',
|
||||
'error_message' => 'InvalidDocblock',
|
||||
],
|
||||
'badTemplateUseInt' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait T {
|
||||
/** @var T */
|
||||
public $t;
|
||||
|
||||
/** @param T $t */
|
||||
public function __construct($t) {
|
||||
$this->t = $t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TT
|
||||
*/
|
||||
class B {
|
||||
/**
|
||||
* @template-use int
|
||||
*/
|
||||
use T;
|
||||
}',
|
||||
'error_message' => 'InvalidDocblock',
|
||||
],
|
||||
'badTemplateExtendsShouldBeUse' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait T {
|
||||
/** @var T */
|
||||
public $t;
|
||||
|
||||
/** @param T $t */
|
||||
public function __construct($t) {
|
||||
$this->t = $t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TT
|
||||
*/
|
||||
class B {
|
||||
/**
|
||||
* @template-extends T<int>
|
||||
*/
|
||||
use T;
|
||||
}',
|
||||
'error_message' => 'InvalidDocblock',
|
||||
],
|
||||
'templateExtendsWithoutAllParams' => [
|
||||
'<?php
|
||||
/**
|
||||
@ -5415,116 +4852,6 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'InvalidReturnStatement'
|
||||
],
|
||||
'possiblyNullReferenceOnTraitDefinedMethod' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
*/
|
||||
trait T1 {
|
||||
/**
|
||||
* @var array<TKey, TValue>
|
||||
*/
|
||||
protected $mocks = [];
|
||||
|
||||
/**
|
||||
* @param TKey $offset
|
||||
* @return TValue|null
|
||||
* @psalm-suppress LessSpecificImplementedReturnType
|
||||
* @psalm-suppress ImplementedParamTypeMismatch
|
||||
*/
|
||||
public function offsetGet($offset) {
|
||||
return $this->mocks[$offset] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
*/
|
||||
interface Arr {
|
||||
/**
|
||||
* @param TKey $offset
|
||||
* @return TValue|null
|
||||
*/
|
||||
public function offsetGet($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
* @implements Arr<TKey, TValue>
|
||||
*/
|
||||
class C implements Arr {
|
||||
/** @use T1<TKey, TValue> */
|
||||
use T1;
|
||||
|
||||
/**
|
||||
* @param TKey $offset
|
||||
* @psalm-suppress MixedMethodCall
|
||||
*/
|
||||
public function foo($offset) : void {
|
||||
$this->offsetGet($offset)->bar();
|
||||
}
|
||||
}',
|
||||
'error_message' => 'PossiblyNullReference'
|
||||
],
|
||||
'possiblyNullReferenceOnTraitDefinedMethodExtended' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
*/
|
||||
trait T1 {
|
||||
/**
|
||||
* @var array<TKey, TValue>
|
||||
*/
|
||||
protected $mocks = [];
|
||||
|
||||
/**
|
||||
* @param TKey $offset
|
||||
* @return TValue|null
|
||||
* @psalm-suppress LessSpecificImplementedReturnType
|
||||
* @psalm-suppress ImplementedParamTypeMismatch
|
||||
*/
|
||||
public function offsetGet($offset) {
|
||||
return $this->mocks[$offset] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
*/
|
||||
interface Arr {
|
||||
/**
|
||||
* @param TKey $offset
|
||||
* @return TValue|null
|
||||
*/
|
||||
public function offsetGet($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
* @implements Arr<TKey, TValue>
|
||||
*/
|
||||
class C implements Arr {
|
||||
/** @use T1<TKey, TValue> */
|
||||
use T1;
|
||||
}
|
||||
|
||||
class D extends C {
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @psalm-suppress MixedArgument
|
||||
*/
|
||||
public function foo($offset) : void {
|
||||
$this->offsetGet($offset)->bar();
|
||||
}
|
||||
}',
|
||||
'error_message' => 'MixedMethodCall'
|
||||
],
|
||||
'preventExtendingWithTemplatedClassWithExplicitTypeGiven' => [
|
||||
'<?php
|
||||
/**
|
||||
|
703
tests/Template/TraitTemplateTest.php
Normal file
703
tests/Template/TraitTemplateTest.php
Normal file
@ -0,0 +1,703 @@
|
||||
<?php
|
||||
namespace Psalm\Tests\Template;
|
||||
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
use Psalm\Tests\TestCase;
|
||||
use Psalm\Tests\Traits;
|
||||
|
||||
class TraitTemplateTest extends TestCase
|
||||
{
|
||||
use Traits\InvalidCodeAnalysisTestTrait;
|
||||
use Traits\ValidCodeAnalysisTestTrait;
|
||||
|
||||
/**
|
||||
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
|
||||
*/
|
||||
public function providerValidCodeParse(): iterable
|
||||
{
|
||||
return [
|
||||
'traitUseNotExtended' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait CollectionTrait
|
||||
{
|
||||
/**
|
||||
* @return array<T>
|
||||
*/
|
||||
abstract function elements() : array;
|
||||
|
||||
/**
|
||||
* @return T|null
|
||||
*/
|
||||
public function first()
|
||||
{
|
||||
return $this->elements()[0] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
class Service
|
||||
{
|
||||
/**
|
||||
* @use CollectionTrait<int>
|
||||
*/
|
||||
use CollectionTrait;
|
||||
|
||||
/**
|
||||
* @return array<int>
|
||||
*/
|
||||
public function elements(): array
|
||||
{
|
||||
return [1, 2, 3, 4];
|
||||
}
|
||||
}',
|
||||
],
|
||||
'extendedTraitUse' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait CollectionTrait
|
||||
{
|
||||
/**
|
||||
* @return array<T>
|
||||
*/
|
||||
abstract function elements() : array;
|
||||
|
||||
/**
|
||||
* @return T|null
|
||||
*/
|
||||
public function first()
|
||||
{
|
||||
return $this->elements()[0] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TValue
|
||||
*/
|
||||
trait BridgeTrait
|
||||
{
|
||||
/**
|
||||
* @use CollectionTrait<TValue>
|
||||
*/
|
||||
use CollectionTrait;
|
||||
}
|
||||
|
||||
class Service
|
||||
{
|
||||
/**
|
||||
* @use BridgeTrait<int>
|
||||
*/
|
||||
use BridgeTrait;
|
||||
|
||||
/**
|
||||
* @return array<int>
|
||||
*/
|
||||
public function elements(): array
|
||||
{
|
||||
return [1, 2, 3, 4];
|
||||
}
|
||||
}',
|
||||
],
|
||||
'extendedTraitUseAlreadyBound' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait CollectionTrait
|
||||
{
|
||||
/**
|
||||
* @return array<T>
|
||||
*/
|
||||
abstract function elements() : array;
|
||||
|
||||
/**
|
||||
* @return T|null
|
||||
*/
|
||||
public function first()
|
||||
{
|
||||
return $this->elements()[0] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
trait BridgeTrait
|
||||
{
|
||||
/**
|
||||
* @use CollectionTrait<int>
|
||||
*/
|
||||
use CollectionTrait;
|
||||
}
|
||||
|
||||
class Service
|
||||
{
|
||||
use BridgeTrait;
|
||||
|
||||
/**
|
||||
* @return array<int>
|
||||
*/
|
||||
public function elements(): array
|
||||
{
|
||||
return [1, 2, 3, 4];
|
||||
}
|
||||
}',
|
||||
],
|
||||
'badTemplateUseUnionType' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait T {
|
||||
/** @var T */
|
||||
public $t;
|
||||
|
||||
/** @param T $t */
|
||||
public function __construct($t) {
|
||||
$this->t = $t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TT
|
||||
*/
|
||||
class B {
|
||||
/**
|
||||
* @template-use T<int|string>
|
||||
*/
|
||||
use T;
|
||||
}',
|
||||
],
|
||||
'allowTraitExtendAndImplementWithExplicitParamType' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait ValueObjectTrait
|
||||
{
|
||||
/**
|
||||
* @psalm-var ?T
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @psalm-param T $value
|
||||
*
|
||||
* @param $value
|
||||
*/
|
||||
private function setValue($value): void {
|
||||
$this->validate($value);
|
||||
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param T $value
|
||||
*
|
||||
* @param $value
|
||||
*/
|
||||
abstract protected function validate($value): void;
|
||||
}
|
||||
|
||||
final class StringValidator {
|
||||
/**
|
||||
* @template-use ValueObjectTrait<string>
|
||||
*/
|
||||
use ValueObjectTrait;
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*/
|
||||
protected function validate($value): void
|
||||
{
|
||||
if (strlen($value) > 30) {
|
||||
throw new \Exception("bad");
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
'allowTraitExtendAndImplementWithoutExplicitParamType' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait ValueObjectTrait
|
||||
{
|
||||
/**
|
||||
* @psalm-var ?T
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @psalm-param T $value
|
||||
*
|
||||
* @param $value
|
||||
*/
|
||||
private function setValue($value): void {
|
||||
$this->validate($value);
|
||||
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param T $value
|
||||
*
|
||||
* @param $value
|
||||
*/
|
||||
abstract protected function validate($value): void;
|
||||
}
|
||||
|
||||
final class StringValidator {
|
||||
/**
|
||||
* @template-use ValueObjectTrait<string>
|
||||
*/
|
||||
use ValueObjectTrait;
|
||||
|
||||
protected function validate($value): void
|
||||
{
|
||||
if (strlen($value) > 30) {
|
||||
throw new \Exception("bad");
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
'traitInImplicitExtendedClass' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
interface Foo {
|
||||
/**
|
||||
* @return T
|
||||
*/
|
||||
public function getItem();
|
||||
}
|
||||
|
||||
trait FooTrait {
|
||||
public function getItem() {
|
||||
return "hello";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template-implements Foo<string>
|
||||
*/
|
||||
class Bar implements Foo {
|
||||
use FooTrait;
|
||||
}',
|
||||
],
|
||||
'useTraitReturnTypeForInheritedInterface' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template TValue
|
||||
* @template TNormalizedValue
|
||||
*/
|
||||
interface Normalizer
|
||||
{
|
||||
/**
|
||||
* @param TValue $v
|
||||
* @return TNormalizedValue
|
||||
*/
|
||||
function normalize($v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TTraitValue
|
||||
* @template TTraitNormalizedValue
|
||||
*/
|
||||
trait NormalizerTrait
|
||||
{
|
||||
/**
|
||||
* @param TTraitValue $v
|
||||
* @return TTraitNormalizedValue
|
||||
*/
|
||||
function normalize($v)
|
||||
{
|
||||
return $this->doNormalize($v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TTraitValue $v
|
||||
* @return TTraitNormalizedValue
|
||||
*/
|
||||
abstract protected function doNormalize($v);
|
||||
}
|
||||
|
||||
/** @implements Normalizer<string, string> */
|
||||
class StringNormalizer implements Normalizer
|
||||
{
|
||||
/** @use NormalizerTrait<string, string> */
|
||||
use NormalizerTrait;
|
||||
|
||||
protected function doNormalize($v): string
|
||||
{
|
||||
return trim($v);
|
||||
}
|
||||
}'
|
||||
],
|
||||
'useTraitReturnTypeForInheritedClass' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template TValue
|
||||
* @template TNormalizedValue
|
||||
*/
|
||||
abstract class Normalizer
|
||||
{
|
||||
/**
|
||||
* @param TValue $v
|
||||
* @return TNormalizedValue
|
||||
*/
|
||||
abstract function normalize($v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TTraitValue
|
||||
* @template TTraitNormalizedValue
|
||||
*/
|
||||
trait NormalizerTrait
|
||||
{
|
||||
/**
|
||||
* @param TTraitValue $v
|
||||
* @return TTraitNormalizedValue
|
||||
*/
|
||||
function normalize($v)
|
||||
{
|
||||
return $this->doNormalize($v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TTraitValue $v
|
||||
* @return TTraitNormalizedValue
|
||||
*/
|
||||
abstract protected function doNormalize($v);
|
||||
}
|
||||
|
||||
/** @extends Normalizer<string, string> */
|
||||
class StringNormalizer extends Normalizer
|
||||
{
|
||||
/** @use NormalizerTrait<string, string> */
|
||||
use NormalizerTrait;
|
||||
|
||||
protected function doNormalize($v): string
|
||||
{
|
||||
return trim($v);
|
||||
}
|
||||
}'
|
||||
],
|
||||
'inheritTraitPropertyTKeyedArray' => [
|
||||
'<?php
|
||||
/** @template TValue */
|
||||
trait A {
|
||||
/** @psalm-var array{TValue} */
|
||||
private $foo;
|
||||
|
||||
/** @psalm-param array{TValue} $foo */
|
||||
public function __construct(array $foo)
|
||||
{
|
||||
$this->foo = $foo;
|
||||
}
|
||||
}
|
||||
|
||||
/** @template TValue */
|
||||
class B {
|
||||
/** @use A<TValue> */
|
||||
use A;
|
||||
}'
|
||||
],
|
||||
'inheritTraitPropertyArray' => [
|
||||
'<?php
|
||||
/** @template TValue */
|
||||
trait A {
|
||||
/** @psalm-var array<TValue> */
|
||||
private $foo;
|
||||
|
||||
/** @psalm-param array<TValue> $foo */
|
||||
public function __construct(array $foo)
|
||||
{
|
||||
$this->foo = $foo;
|
||||
}
|
||||
}
|
||||
|
||||
/** @template TValue */
|
||||
class B {
|
||||
/** @use A<TValue> */
|
||||
use A;
|
||||
}'
|
||||
],
|
||||
'applyTemplatedValueInTraitProperty' => [
|
||||
'<?php
|
||||
/** @template T */
|
||||
trait ValueTrait {
|
||||
/** @psalm-param T $value */
|
||||
public function setValue($value): void {
|
||||
$this->value = $value;
|
||||
}
|
||||
}
|
||||
|
||||
class C {
|
||||
/** @use ValueTrait<string> */
|
||||
use ValueTrait;
|
||||
|
||||
/** @var string */
|
||||
private $value;
|
||||
|
||||
public function __construct(string $value) {
|
||||
$this->value = $value;
|
||||
}
|
||||
}'
|
||||
],
|
||||
'traitSelfAsParam' => [
|
||||
'<?php
|
||||
trait InstancePool {
|
||||
/**
|
||||
* @template T as self
|
||||
* @param callable():?T $callback
|
||||
* @return ?T
|
||||
*/
|
||||
public static function getInstance(callable $callback)
|
||||
{
|
||||
return $callback();
|
||||
}
|
||||
}
|
||||
|
||||
class Foo
|
||||
{
|
||||
use InstancePool;
|
||||
}
|
||||
|
||||
class Bar
|
||||
{
|
||||
public function a(): void
|
||||
{
|
||||
Foo::getInstance(function () {
|
||||
return new Foo();
|
||||
});
|
||||
}
|
||||
}'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
|
||||
*/
|
||||
public function providerInvalidCodeParse(): iterable
|
||||
{
|
||||
return [
|
||||
'badTemplateUse' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait T {
|
||||
/** @var T */
|
||||
public $t;
|
||||
|
||||
/** @param T $t */
|
||||
public function __construct($t) {
|
||||
$this->t = $t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TT
|
||||
*/
|
||||
class B {
|
||||
/**
|
||||
* @template-use T<Z>
|
||||
*/
|
||||
use T;
|
||||
}',
|
||||
'error_message' => 'UndefinedDocblockClass',
|
||||
],
|
||||
'badTemplateUseBadFormat' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait T {
|
||||
/** @var T */
|
||||
public $t;
|
||||
|
||||
/** @param T $t */
|
||||
public function __construct($t) {
|
||||
$this->t = $t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TT
|
||||
*/
|
||||
class B {
|
||||
/**
|
||||
* @template-use T< >
|
||||
*/
|
||||
use T;
|
||||
}',
|
||||
'error_message' => 'InvalidDocblock',
|
||||
],
|
||||
'badTemplateUseInt' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait T {
|
||||
/** @var T */
|
||||
public $t;
|
||||
|
||||
/** @param T $t */
|
||||
public function __construct($t) {
|
||||
$this->t = $t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TT
|
||||
*/
|
||||
class B {
|
||||
/**
|
||||
* @template-use int
|
||||
*/
|
||||
use T;
|
||||
}',
|
||||
'error_message' => 'InvalidDocblock',
|
||||
],
|
||||
'badTemplateExtendsShouldBeUse' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
trait T {
|
||||
/** @var T */
|
||||
public $t;
|
||||
|
||||
/** @param T $t */
|
||||
public function __construct($t) {
|
||||
$this->t = $t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TT
|
||||
*/
|
||||
class B {
|
||||
/**
|
||||
* @template-extends T<int>
|
||||
*/
|
||||
use T;
|
||||
}',
|
||||
'error_message' => 'InvalidDocblock',
|
||||
],
|
||||
'possiblyNullReferenceOnTraitDefinedMethod' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
*/
|
||||
trait T1 {
|
||||
/**
|
||||
* @var array<TKey, TValue>
|
||||
*/
|
||||
protected $mocks = [];
|
||||
|
||||
/**
|
||||
* @param TKey $offset
|
||||
* @return TValue|null
|
||||
* @psalm-suppress LessSpecificImplementedReturnType
|
||||
* @psalm-suppress ImplementedParamTypeMismatch
|
||||
*/
|
||||
public function offsetGet($offset) {
|
||||
return $this->mocks[$offset] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
*/
|
||||
interface Arr {
|
||||
/**
|
||||
* @param TKey $offset
|
||||
* @return TValue|null
|
||||
*/
|
||||
public function offsetGet($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
* @implements Arr<TKey, TValue>
|
||||
*/
|
||||
class C implements Arr {
|
||||
/** @use T1<TKey, TValue> */
|
||||
use T1;
|
||||
|
||||
/**
|
||||
* @param TKey $offset
|
||||
* @psalm-suppress MixedMethodCall
|
||||
*/
|
||||
public function foo($offset) : void {
|
||||
$this->offsetGet($offset)->bar();
|
||||
}
|
||||
}',
|
||||
'error_message' => 'PossiblyNullReference'
|
||||
],
|
||||
'possiblyNullReferenceOnTraitDefinedMethodExtended' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
*/
|
||||
trait T1 {
|
||||
/**
|
||||
* @var array<TKey, TValue>
|
||||
*/
|
||||
protected $mocks = [];
|
||||
|
||||
/**
|
||||
* @param TKey $offset
|
||||
* @return TValue|null
|
||||
* @psalm-suppress LessSpecificImplementedReturnType
|
||||
* @psalm-suppress ImplementedParamTypeMismatch
|
||||
*/
|
||||
public function offsetGet($offset) {
|
||||
return $this->mocks[$offset] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
*/
|
||||
interface Arr {
|
||||
/**
|
||||
* @param TKey $offset
|
||||
* @return TValue|null
|
||||
*/
|
||||
public function offsetGet($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
* @implements Arr<TKey, TValue>
|
||||
*/
|
||||
class C implements Arr {
|
||||
/** @use T1<TKey, TValue> */
|
||||
use T1;
|
||||
}
|
||||
|
||||
class D extends C {
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @psalm-suppress MixedArgument
|
||||
*/
|
||||
public function foo($offset) : void {
|
||||
$this->offsetGet($offset)->bar();
|
||||
}
|
||||
}',
|
||||
'error_message' => 'MixedMethodCall'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user