2017-02-10 02:35:17 +01:00
< ? php
2019-01-26 04:33:42 +01:00
namespace Psalm\Tests\Template ;
2019-07-05 22:24:00 +02:00
use const DIRECTORY_SEPARATOR ;
2019-01-26 04:33:42 +01:00
use Psalm\Tests\TestCase ;
use Psalm\Tests\Traits ;
2017-02-10 02:35:17 +01:00
2019-06-25 19:00:06 +02:00
class ClassTemplateTest extends TestCase
2017-02-10 02:35:17 +01:00
{
2018-11-06 03:57:36 +01:00
use Traits\InvalidCodeAnalysisTestTrait ;
use Traits\ValidCodeAnalysisTestTrait ;
2017-02-10 02:35:17 +01:00
/**
2019-03-01 21:55:20 +01:00
* @ return iterable < string , array { string , assertions ? : array < string , string > , error_levels ? : string []} >
2017-02-10 02:35:17 +01:00
*/
2020-09-12 17:24:05 +02:00
public function providerValidCodeParse () : iterable
2017-02-10 02:35:17 +01:00
{
2017-04-25 05:45:02 +02:00
return [
2020-10-13 17:35:49 +02:00
'cachingIterator' => [
' < ? php
$input = range ( " a " , " z " );
$arrayIterator = new ArrayIterator ( $input );
$decoratorIterator = new CachingIterator ( $arrayIterator );
$next = $decoratorIterator -> hasNext ();
$key = $decoratorIterator -> key ();
$value = $decoratorIterator -> current ();
' ,
'assertions' => [
2021-01-07 16:07:07 +01:00
'$key' => 'int|null' ,
'$value' => 'null|string' ,
2020-10-13 17:35:49 +02:00
'$next' => 'bool' ,
],
],
'infiniteIterator' => [
' < ? php
$input = range ( " a " , " z " );
$arrayIterator = new ArrayIterator ( $input );
$decoratorIterator = new InfiniteIterator ( $arrayIterator );
$key = $decoratorIterator -> key ();
$value = $decoratorIterator -> current ();
' ,
'assertions' => [
2021-01-07 16:07:07 +01:00
'$key' => 'int|null' ,
'$value' => 'null|string' ,
2020-10-13 17:35:49 +02:00
],
],
'limitIterator' => [
' < ? php
$input = range ( " a " , " z " );
$arrayIterator = new ArrayIterator ( $input );
$decoratorIterator = new LimitIterator ( $arrayIterator , 1 , 1 );
$key = $decoratorIterator -> key ();
$value = $decoratorIterator -> current ();
' ,
'assertions' => [
2021-01-07 16:07:07 +01:00
'$key' => 'int|null' ,
'$value' => 'null|string' ,
2020-10-13 17:35:49 +02:00
],
],
'callbackFilterIterator' => [
' < ? php
$input = range ( " a " , " z " );
$arrayIterator = new ArrayIterator ( $input );
$decoratorIterator = new CallbackFilterIterator (
$arrayIterator ,
static function ( string $value ) : bool { return " a " === $value ;}
);
$key = $decoratorIterator -> key ();
$value = $decoratorIterator -> current ();
' ,
'assertions' => [
2021-01-07 16:07:07 +01:00
'$key' => 'int|null' ,
'$value' => 'null|string' ,
2020-10-13 17:35:49 +02:00
],
],
'noRewindIterator' => [
' < ? php
$input = range ( " a " , " z " );
$arrayIterator = new ArrayIterator ( $input );
$decoratorIterator = new NoRewindIterator ( $arrayIterator );
$key = $decoratorIterator -> key ();
$value = $decoratorIterator -> current ();
' ,
'assertions' => [
2021-01-07 16:07:07 +01:00
'$key' => 'int|null' ,
'$value' => 'null|string' ,
2020-10-13 17:35:49 +02:00
],
],
2017-04-25 05:45:02 +02:00
'classTemplate' => [
' < ? php
class A {}
class B {}
class C {}
class D {}
2017-06-29 16:22:49 +02:00
2017-04-25 05:45:02 +02:00
/**
* @ template T as object
*/
class Foo {
2018-12-18 05:29:27 +01:00
/** @var T::class */
2017-04-25 05:45:02 +02:00
public $T ;
2017-06-29 16:22:49 +02:00
2017-04-25 05:45:02 +02:00
/**
2020-08-30 17:44:14 +02:00
* @ param class - string < T > $T
2017-04-25 05:45:02 +02:00
*/
public function __construct ( string $T ) {
$this -> T = $T ;
}
2017-06-29 16:22:49 +02:00
2017-04-25 05:45:02 +02:00
/**
* @ return T
2020-08-06 16:18:55 +02:00
* @ psalm - suppress MixedMethodCall
2017-04-25 05:45:02 +02:00
*/
public function bar () {
$t = $this -> T ;
return new $t ();
}
}
2017-06-29 16:22:49 +02:00
2017-04-25 05:45:02 +02:00
$at = " A " ;
2017-06-29 16:22:49 +02:00
2020-08-30 17:44:14 +02:00
/**
* @ var Foo < A >
* @ psalm - suppress ArgumentTypeCoercion
*/
2017-04-25 05:45:02 +02:00
$afoo = new Foo ( $at );
$afoo_bar = $afoo -> bar ();
2017-06-29 16:22:49 +02:00
2017-04-25 05:45:02 +02:00
$bfoo = new Foo ( B :: class );
$bfoo_bar = $bfoo -> bar ();
2017-06-29 16:22:49 +02:00
2017-12-03 18:44:08 +01:00
// this shouldn’ t cause a problem as it’ s a docbblock type
if ( ! ( $bfoo_bar instanceof B )) {}
2018-12-13 06:09:01 +01:00
$c = C :: class ;
$cfoo = new Foo ( $c );
$cfoo_bar = $cfoo -> bar (); ' ,
2018-03-06 19:59:59 +01:00
'assertions' => [
'$afoo' => 'Foo<A>' ,
'$afoo_bar' => 'A' ,
'$bfoo' => 'Foo<B>' ,
'$bfoo_bar' => 'B' ,
'$cfoo' => 'Foo<C>' ,
'$cfoo_bar' => 'C' ,
],
'error_levels' => [
'MixedReturnStatement' ,
'LessSpecificReturnStatement' ,
2019-02-27 20:02:02 +01:00
'DocblockTypeContradiction' ,
2018-03-06 19:59:59 +01:00
],
],
'classTemplateSelfs' => [
' < ? php
/**
* @ template T as object
*/
class Foo {
2019-06-25 19:20:30 +02:00
/** @var class-string<T> */
2018-03-06 19:59:59 +01:00
public $T ;
/**
2020-08-30 17:44:14 +02:00
* @ param class - string < T > $T
2018-03-06 19:59:59 +01:00
*/
public function __construct ( string $T ) {
$this -> T = $T ;
}
/**
* @ return T
2020-08-06 16:18:55 +02:00
* @ psalm - suppress MixedMethodCall
2018-03-06 19:59:59 +01:00
*/
public function bar () {
$t = $this -> T ;
return new $t ();
}
}
2018-03-05 23:36:08 +01:00
class E {
/**
* @ return Foo < self >
*/
public static function getFoo () {
return new Foo ( __CLASS__ );
}
2018-03-06 19:59:59 +01:00
/**
* @ return Foo < self >
*/
public static function getFoo2 () {
return new Foo ( self :: class );
}
2017-04-25 05:45:02 +02:00
2018-03-06 19:59:59 +01:00
/**
* @ return Foo < static >
*/
public static function getFoo3 () {
return new Foo ( static :: class );
}
}
2017-04-25 05:45:02 +02:00
2018-03-06 19:59:59 +01:00
class G extends E {}
2017-04-25 05:45:02 +02:00
2018-03-06 19:59:59 +01:00
$efoo = E :: getFoo ();
$efoo2 = E :: getFoo2 ();
$efoo3 = E :: getFoo3 ();
2017-12-03 18:44:08 +01:00
2018-03-06 19:59:59 +01:00
$gfoo = G :: getFoo ();
$gfoo2 = G :: getFoo2 ();
$gfoo3 = G :: getFoo3 (); ' ,
'assertions' => [
2018-03-05 23:36:08 +01:00
'$efoo' => 'Foo<E>' ,
2018-03-06 19:59:59 +01:00
'$efoo2' => 'Foo<E>' ,
'$efoo3' => 'Foo<E>' ,
'$gfoo' => 'Foo<E>' ,
'$gfoo2' => 'Foo<E>' ,
'$gfoo3' => 'Foo<G>' ,
2017-12-03 18:44:08 +01:00
],
2018-02-07 00:44:53 +01:00
'error_levels' => [
'LessSpecificReturnStatement' ,
2018-02-07 21:20:47 +01:00
'RedundantConditionGivenDocblockType' ,
2018-02-07 00:44:53 +01:00
],
2017-12-03 18:44:08 +01:00
],
2017-07-25 22:11:02 +02:00
'classTemplateExternalClasses' => [
' < ? php
/**
* @ template T as object
*/
class Foo {
2018-12-18 05:29:27 +01:00
/** @var T::class */
2017-07-25 22:11:02 +02:00
public $T ;
/**
2020-08-30 17:44:14 +02:00
* @ param class - string < T > $T
2017-07-25 22:11:02 +02:00
*/
public function __construct ( string $T ) {
$this -> T = $T ;
}
/**
* @ return T
2020-08-06 16:18:55 +02:00
* @ psalm - suppress MixedMethodCall
2017-07-25 22:11:02 +02:00
*/
public function bar () {
$t = $this -> T ;
return new $t ();
}
}
2018-12-18 05:29:27 +01:00
$efoo = new Foo ( \Exception :: class );
2017-07-25 22:11:02 +02:00
$efoo_bar = $efoo -> bar ();
2018-12-18 05:29:27 +01:00
$ffoo = new Foo ( \LogicException :: class );
2017-07-25 22:11:02 +02:00
$ffoo_bar = $ffoo -> bar (); ' ,
'assertions' => [
'$efoo' => 'Foo<Exception>' ,
'$efoo_bar' => 'Exception' ,
'$ffoo' => 'Foo<LogicException>' ,
'$ffoo_bar' => 'LogicException' ,
],
2018-12-13 06:09:01 +01:00
'error_levels' => [ 'LessSpecificReturnStatement' ],
2017-07-25 22:11:02 +02:00
],
2019-11-04 03:27:40 +01:00
'classTemplateContainerSimpleCall' => [
2017-04-25 05:45:02 +02:00
' < ? php
class A {}
2017-06-29 16:22:49 +02:00
2019-11-04 03:27:40 +01:00
/**
* @ template T
*/
class Foo {
/** @var T */
public $obj ;
/**
* @ param T $obj
*/
public function __construct ( $obj ) {
$this -> obj = $obj ;
}
/**
* @ return T
*/
public function bar () {
return $this -> obj ;
}
}
$afoo = new Foo ( new A ());
$afoo_bar = $afoo -> bar (); ' ,
'assertions' => [
'$afoo' => 'Foo<A>' ,
'$afoo_bar' => 'A' ,
],
],
'classTemplateContainerThisCall' => [
' < ? php
2017-04-25 05:45:02 +02:00
/**
* @ template T
*/
class Foo {
/** @var T */
public $obj ;
2017-06-29 16:22:49 +02:00
2017-04-25 05:45:02 +02:00
/**
* @ param T $obj
*/
public function __construct ( $obj ) {
$this -> obj = $obj ;
}
2017-06-29 16:22:49 +02:00
2017-04-25 05:45:02 +02:00
/**
* @ return T
*/
public function bar () {
return $this -> obj ;
}
2017-06-29 16:22:49 +02:00
2018-04-20 16:52:23 +02:00
/**
* @ return T
*/
public function bat () {
return $this -> bar ();
}
2018-01-11 21:50:45 +01:00
public function __toString () : string {
2017-04-25 05:45:02 +02:00
return " hello " . $this -> obj ;
}
2019-11-04 03:27:40 +01:00
} ' ,
[],
2018-04-20 16:52:23 +02:00
'error_levels' => [ 'MixedOperand' ],
2017-04-25 05:45:02 +02:00
],
2019-02-09 17:02:24 +01:00
'validPsalmTemplatedClassType' => [
' < ? php
class A {}
/**
* @ psalm - template T
*/
class Foo {
/**
* @ param T $x
*/
public function bar ( $x ) : void { }
}
$afoo = new Foo ();
$afoo -> bar ( new A ()); ' ,
],
2018-05-09 05:17:11 +02:00
'intersectionTemplatedTypes' => [
' < ? php
namespace NS ;
use Countable ;
/** @template T */
class Collection
{
/** @psalm-var iterable<T> */
private $data ;
/** @psalm-param iterable<T> $data */
public function __construct ( iterable $data ) {
$this -> data = $data ;
}
}
class Item {}
/** @psalm-param Collection<Item> $c */
function takesCollectionOfItems ( Collection $c ) : void {}
/** @psalm-var iterable<Item> $data2 */
$data2 = [];
takesCollectionOfItems ( new Collection ( $data2 ));
/** @psalm-var iterable<Item>&Countable $data */
$data = [];
takesCollectionOfItems ( new Collection ( $data )); ' ,
],
2018-05-18 23:47:40 +02:00
'repeatedCall' => [
' < ? php
namespace NS ;
use Closure ;
/**
2019-01-05 06:15:53 +01:00
* @ template TKey as array - key
2018-05-18 23:47:40 +02:00
* @ template TValue
2020-08-06 01:39:27 +02:00
* @ psalm - consistent - constructor
2018-05-18 23:47:40 +02:00
*/
class ArrayCollection {
/** @var array<TKey,TValue> */
private $data ;
/** @param array<TKey,TValue> $data */
public function __construct ( array $data ) {
$this -> data = $data ;
}
/**
* @ template T
* @ param Closure ( TValue ) : T $func
* @ return ArrayCollection < TKey , T >
*/
public function map ( Closure $func ) {
2020-02-08 17:17:24 +01:00
return new static ( array_map ( $func , $this -> data ));
2018-05-18 23:47:40 +02:00
}
}
class Item {}
/**
2019-01-26 22:58:49 +01:00
* @ param ArrayCollection < array - key , Item > $i
2018-05-18 23:47:40 +02:00
*/
function takesCollectionOfItems ( ArrayCollection $i ) : void {}
$c = new ArrayCollection ([ new Item ]);
takesCollectionOfItems ( $c );
takesCollectionOfItems ( $c -> map ( function ( Item $i ) : Item { return $i ;}));
2019-03-23 19:27:54 +01:00
takesCollectionOfItems ( $c -> map ( function ( Item $i ) : Item { return $i ;})); ' ,
2018-05-18 23:47:40 +02:00
],
2018-06-13 14:38:07 +02:00
'noRepeatedTypeException' => [
' < ? php
2019-06-25 19:20:30 +02:00
/** @template T as object */
2018-06-13 14:38:07 +02:00
class Foo
{
/**
2019-06-25 19:20:30 +02:00
* @ psalm - var class - string < T >
2018-06-13 14:38:07 +02:00
*/
private $type ;
/** @var array<T> */
private $items ;
/**
2019-06-25 19:20:30 +02:00
* @ param class - string < T > $type
2018-06-13 14:38:07 +02:00
*/
public function __construct ( string $type )
{
if ( ! in_array ( $type , [ A :: class , B :: class ], true )) {
throw new \InvalidArgumentException ;
}
2019-06-25 19:20:30 +02:00
2018-06-13 14:38:07 +02:00
$this -> type = $type ;
$this -> items = [];
}
/** @param T $item */
public function add ( $item ) : void
{
$this -> items [] = $item ;
}
}
class FooFacade
{
/**
2019-11-29 17:20:37 +01:00
* @ template T as object
2018-06-13 14:38:07 +02:00
* @ param T $item
*/
2019-11-29 17:20:37 +01:00
public function add ( object $item ) : void
2018-06-13 14:38:07 +02:00
{
$foo = $this -> ensureFoo ([ $item ]);
$foo -> add ( $item );
}
/**
2019-11-29 17:20:37 +01:00
* @ template T as object
2018-06-13 14:38:07 +02:00
* @ param array < mixed , T > $items
* @ return Foo < T >
*/
2019-01-26 22:58:49 +01:00
private function ensureFoo ( array $items ) : Foo
2018-06-13 14:38:07 +02:00
{
2019-06-25 19:20:30 +02:00
/** @var class-string<T> */
2018-06-13 14:38:07 +02:00
$type = $items [ 0 ] instanceof A ? A :: class : B :: class ;
return new Foo ( $type );
}
}
class A {}
2019-03-23 19:27:54 +01:00
class B {} ' ,
2018-06-13 14:38:07 +02:00
],
2018-06-19 19:19:34 +02:00
'collectionOfClosure' => [
' < ? php
/**
* @ template TKey
* @ template TValue
*/
class Collection {
/**
* @ param Closure ( TValue ) : bool $p
* @ return Collection < TKey , TValue >
*/
2019-01-19 18:19:51 +01:00
public function filter ( Closure $p ) {
return $this ;
}
2018-06-19 19:19:34 +02:00
}
class I {}
/** @var Collection<mixed,Collection<mixed,I>> $c */
$c = new Collection ;
$c -> filter (
2020-02-08 17:17:24 +01:00
/** @param Collection<mixed,I> $elt */
function ( Collection $elt ) : bool { return ( bool ) rand ( 0 , 1 ); }
2018-06-19 19:19:34 +02:00
);
$c -> filter (
2020-02-08 17:17:24 +01:00
/** @param Collection<mixed,I> $elt */
function ( Collection $elt ) : bool { return true ; }
2018-06-19 19:19:34 +02:00
); ' ,
],
2018-11-21 23:56:04 +01:00
'templatedInterfaceIteration' => [
' < ? php
namespace NS ;
/**
* @ template TKey
* @ template TValue
*/
interface ICollection extends \IteratorAggregate {
/** @return \Traversable<TKey,TValue> */
public function getIterator ();
}
class Collection implements ICollection {
/** @var array */
private $data ;
public function __construct ( array $data ) {
$this -> data = $data ;
}
public function getIterator () : \Traversable {
return new \ArrayIterator ( $this -> data );
}
}
/** @var ICollection<string, int> */
$c = new Collection ([ " a " => 1 ]);
2019-03-23 19:27:54 +01:00
foreach ( $c as $k => $v ) { atan ( $v ); strlen ( $k ); } ' ,
2018-11-21 23:56:04 +01:00
],
2018-12-08 21:17:43 +01:00
'allowTemplatedIntersectionToExtend' => [
' < ? php
interface Foo {}
interface AlmostFoo {
/**
* @ return Foo
*/
public function makeFoo ();
}
/**
* @ template T
*/
final class AlmostFooMap implements AlmostFoo {
/** @var T&Foo */
private $bar ;
/**
2019-02-23 17:02:04 +01:00
* @ param T & Foo $bar
2018-12-08 21:17:43 +01:00
*/
public function __construct ( Foo $bar )
{
$this -> bar = $bar ;
}
/**
* @ return T & Foo
*/
public function makeFoo ()
{
return $this -> bar ;
}
2019-03-23 19:27:54 +01:00
} ' ,
2018-12-13 06:09:01 +01:00
],
'restrictTemplateInputWithTClassGoodInput' => [
' < ? php
namespace Bar ;
2020-07-22 01:40:35 +02:00
/** @template T as object */
2018-12-13 06:09:01 +01:00
class Foo
{
/**
* @ psalm - var T :: class
*/
private $type ;
/** @var array<T> */
private $items ;
/**
* @ param T :: class $type
*/
public function __construct ( string $type )
{
if ( ! in_array ( $type , [ A :: class , B :: class ], true )) {
throw new \InvalidArgumentException ;
}
$this -> type = $type ;
$this -> items = [];
}
/** @param T $item */
public function add ( $item ) : void
{
$this -> items [] = $item ;
}
}
class A {}
class B {}
$foo = new Foo ( A :: class );
$foo -> add ( new A ); ' ,
],
2018-12-18 05:29:27 +01:00
'classTemplateFunctionImplementsInterface' => [
' < ? php
namespace A\B ;
interface Foo {}
interface IFooGetter {
/**
* @ return Foo
*/
public function getFoo ();
}
/**
* @ template T as Foo
*/
class FooGetter implements IFooGetter {
/** @var T */
private $t ;
/**
* @ param T $t
*/
public function __construct ( Foo $t )
{
$this -> t = $t ;
}
/**
* @ return T
*/
public function getFoo ()
{
return $this -> t ;
}
}
function passFoo ( Foo $f ) : Foo {
return ( new FooGetter ( $f )) -> getFoo ();
} ' ,
],
2019-06-25 19:00:06 +02:00
'getPropertyOnClass' => [
2018-12-18 05:29:27 +01:00
' < ? php
2019-06-25 19:00:06 +02:00
class Foo {
/** @var int */
public $id = 0 ;
2018-12-18 05:29:27 +01:00
}
/**
2019-06-25 19:00:06 +02:00
* @ template T as Foo
2018-12-18 05:29:27 +01:00
*/
2019-06-25 19:00:06 +02:00
class Collection {
/**
* @ var class - string < T >
*/
private $type ;
/**
* @ param class - string < T > $type
*/
public function __construct ( string $type ) {
$this -> type = $type ;
}
/**
* @ return class - string < T >
*/
public function getType ()
{
return $this -> type ;
}
/**
* @ param T $object
*/
public function bar ( Foo $object ) : void
{
if ( $this -> getType () !== get_class ( $object )) {
return ;
}
2018-12-18 05:29:27 +01:00
2019-06-25 19:00:06 +02:00
echo $object -> id ;
}
}
2018-12-18 05:29:27 +01:00
2019-06-25 19:00:06 +02:00
class FooChild extends Foo {}
2018-12-18 05:29:27 +01:00
2019-06-25 19:00:06 +02:00
/** @param Collection<Foo> $c */
function handleCollectionOfFoo ( Collection $c ) : void {
if ( $c -> getType () === FooChild :: class ) {}
2018-12-18 05:29:27 +01:00
} ' ,
],
2020-08-10 18:45:21 +02:00
'getMagicPropertyOnClass' => [
' < ? php
class A {}
/**
* @ template T as A
* @ property ? T $x
*/
class B {
/** @var ?T */
public $y ;
public function __get () {}
}
$b = new B ();
$b_x = $b -> x ;
$b_y = $b -> y ;
' ,
'assertions' => [
'$b_x' => 'A|null' ,
'$b_y' => 'A|null' ,
],
],
'getMagicPropertyOnThis' => [
' < ? php
abstract class A {}
class X extends A {}
/**
* @ template T as A
* @ property ? T $x
*/
class B {
/** @var ?T */
public $y ;
public function __get () {}
public function test () : void {
if ( $this -> x instanceof X ) {}
if ( $this -> y instanceof X ) {}
}
}
' ,
],
2019-06-25 19:00:06 +02:00
'getEquateClass' => [
2019-01-02 12:46:10 +01:00
' < ? php
2019-06-25 19:00:06 +02:00
class Foo {
/** @var int */
public $id = 0 ;
2019-01-02 12:46:10 +01:00
}
/**
2019-06-25 19:00:06 +02:00
* @ template T as Foo
2019-01-02 12:46:10 +01:00
*/
2019-06-25 19:00:06 +02:00
class Container {
/**
* @ var T
*/
private $obj ;
2019-01-08 22:55:53 +01:00
/**
* @ param T $obj
*/
public function __construct ( Foo $obj ) {
$this -> obj = $obj ;
}
/**
* @ param T $object
*/
public function bar ( Foo $object ) : void
{
if ( $this -> obj === $object ) {}
}
} ' ,
],
2019-01-08 23:34:58 +01:00
'allowComparisonGetTypeResult' => [
' < ? php
class Foo {}
/**
* @ template T as Foo
*/
class Collection {
/**
* @ var class - string < T >
*/
private $type ;
/**
* @ param class - string < T > $type
*/
public function __construct ( string $type ) {
$this -> type = $type ;
}
/**
* @ return class - string < T >| null
*/
public function getType ()
{
return $this -> type ;
}
}
function foo ( Collection $c ) : void {
$val = $c -> getType ();
if ( ! $val ) {}
if ( $val ) {}
} ' ,
],
2019-01-10 22:59:44 +01:00
'mixedTemplatedParamOutWithNoExtendedTemplate' => [
2019-01-10 16:27:40 +01:00
' < ? php
/**
* @ template TValue
*/
class ValueContainer
{
/**
* @ var TValue
*/
private $v ;
/**
* @ param TValue $v
*/
public function __construct ( $v )
{
$this -> v = $v ;
}
/**
* @ return TValue
*/
public function getValue ()
{
return $this -> v ;
}
}
/**
* @ template TKey
2019-01-10 18:13:49 +01:00
* @ template TValue
2019-01-10 16:27:40 +01:00
*/
class KeyValueContainer extends ValueContainer
{
/**
* @ var TKey
*/
private $k ;
/**
* @ param TKey $k
2019-01-10 18:13:49 +01:00
* @ param TValue $v
2019-01-10 16:27:40 +01:00
*/
public function __construct ( $k , $v )
{
$this -> k = $k ;
parent :: __construct ( $v );
}
/**
* @ return TKey
*/
public function getKey ()
{
return $this -> k ;
}
}
$a = new KeyValueContainer ( " hello " , 15 );
$b = $a -> getValue (); ' ,
[
'$a' => 'KeyValueContainer<string, int>' ,
2019-03-23 19:27:54 +01:00
'$b' => 'mixed' ,
2019-01-10 16:27:40 +01:00
],
'error_levels' => [ 'MixedAssignment' ],
],
2019-01-10 22:59:44 +01:00
'mixedTemplatedParamOutDifferentParamName' => [
' < ? php
/**
* @ template TValue
*/
class ValueContainer
{
/**
* @ var TValue
*/
private $v ;
/**
* @ param TValue $v
*/
public function __construct ( $v )
{
$this -> v = $v ;
}
/**
* @ return TValue
*/
public function getValue ()
{
return $this -> v ;
}
}
/**
* @ template TKey
* @ template Tv
*/
class KeyValueContainer extends ValueContainer
{
/**
* @ var TKey
*/
private $k ;
/**
* @ param TKey $k
* @ param Tv $v
*/
public function __construct ( $k , $v )
{
$this -> k = $k ;
parent :: __construct ( $v );
}
/**
* @ return TKey
*/
public function getKey ()
{
return $this -> k ;
}
}
$a = new KeyValueContainer ( " hello " , 15 );
$b = $a -> getValue (); ' ,
[
'$a' => 'KeyValueContainer<string, int>' ,
2019-03-23 19:27:54 +01:00
'$b' => 'mixed' ,
2019-01-10 22:59:44 +01:00
],
'error_levels' => [ 'MixedAssignment' ],
],
2019-01-26 04:33:42 +01:00
'doesntExtendTemplateAndDoesNotOverride' => [
2019-01-10 22:59:44 +01:00
' < ? php
/**
2019-01-26 04:33:42 +01:00
* @ template T as array - key
2019-01-10 22:59:44 +01:00
*/
2019-01-26 04:33:42 +01:00
abstract class User
2019-01-10 22:59:44 +01:00
{
/**
2019-01-26 04:33:42 +01:00
* @ var T
2019-01-10 22:59:44 +01:00
*/
2019-01-26 04:33:42 +01:00
private $id ;
2019-01-10 22:59:44 +01:00
/**
2019-01-26 04:33:42 +01:00
* @ param T $id
2019-01-10 22:59:44 +01:00
*/
2019-01-26 04:33:42 +01:00
public function __construct ( $id )
2019-01-10 22:59:44 +01:00
{
2019-01-26 04:33:42 +01:00
$this -> id = $id ;
2019-01-10 22:59:44 +01:00
}
/**
2019-01-26 04:33:42 +01:00
* @ return T
2019-01-10 22:59:44 +01:00
*/
2019-01-26 04:33:42 +01:00
public function getID ()
2019-01-10 22:59:44 +01:00
{
2019-01-26 04:33:42 +01:00
return $this -> id ;
2019-01-10 22:59:44 +01:00
}
}
2019-01-26 04:33:42 +01:00
class AppUser extends User {}
$au = new AppUser ( - 1 );
$id = $au -> getId (); ' ,
2019-01-10 22:59:44 +01:00
[
2019-01-26 04:33:42 +01:00
'$au' => 'AppUser' ,
'$id' => 'array-key' ,
2019-03-23 19:27:54 +01:00
],
2019-01-10 22:59:44 +01:00
],
2020-08-30 17:44:14 +02:00
'templateTKeyedArrayValues' => [
2019-01-27 20:20:41 +01:00
' < ? php
/**
* @ template TKey
* @ template TValue
*/
class Collection {
/**
* @ return array { 0 : Collection < TKey , TValue > , 1 : Collection < TKey , TValue > }
* @ psalm - suppress InvalidReturnType
*/
public function partition () {}
}
/** @var Collection<int,string> $c */
$c = new Collection ;
[ $partA , $partB ] = $c -> partition (); ' ,
[
'$partA' => 'Collection<int, string>' ,
'$partB' => 'Collection<int, string>' ,
2019-03-23 19:27:54 +01:00
],
2019-01-27 20:20:41 +01:00
],
2019-01-28 23:09:23 +01:00
'doublyLinkedListConstructor' => [
' < ? php
$list = new SplDoublyLinkedList ();
$list -> add ( 5 , " hello " );
$list -> add ( " hello " , 5 );
/** @var SplDoublyLinkedList<int, string> */
$templated_list = new SplDoublyLinkedList ();
$templated_list -> add ( 5 , " hello " );
$a = $templated_list -> bottom (); ' ,
[
'$a' => 'string' ,
2019-03-23 19:27:54 +01:00
],
2019-01-28 23:09:23 +01:00
],
2019-03-08 04:32:38 +01:00
'templateDefaultSimpleString' => [
2019-03-08 00:25:48 +01:00
' < ? php
/**
* @ template T as string
*/
class C {
/** @var T */
public $t ;
/**
* @ param T $t
*/
function __construct ( string $t = " hello " ) {
$this -> t = $t ;
}
}
$c = new C (); ' ,
'assertions' => [
2021-01-26 05:41:42 +01:00
'$c===' => 'C<"hello">' ,
2019-03-08 00:25:48 +01:00
],
],
2019-03-08 04:32:38 +01:00
'SKIPPED-templateDefaultConstant' => [
' < ? php
const FOO = " bar " ;
/**
* @ template T as string
*/
class E {
/** @var T */
public $t ;
/**
* @ param T $t
*/
function __construct ( string $t = FOO ) {
$this -> t = $t ;
}
}
$e = new E (); ' ,
'assertions' => [
'$e===' => 'E<string(bar)>' ,
],
],
2019-11-30 18:54:08 +01:00
'SKIPPED-templateDefaultClassMemberConstant' => [
2019-03-08 04:32:38 +01:00
' < ? php
class D {
const FOO = " bar " ;
}
/**
* @ template T as string
*/
class E {
/** @var T */
public $t ;
/**
* @ param T $t
*/
function __construct ( string $t = D :: FOO ) {
$this -> t = $t ;
}
}
$e = new E (); ' ,
'assertions' => [
'$e===' => 'E<string(bar)>' ,
],
],
2019-11-30 18:54:08 +01:00
'templateDefaultClassConstant' => [
' < ? php
class D {}
/**
* @ template T as object
*/
class E {
/** @var class-string<T> */
public $t ;
/**
* @ param class - string < T > $t
*/
function __construct ( string $t = D :: class ) {
$this -> t = $t ;
}
}
$e = new E (); ' ,
'assertions' => [
'$e===' => 'E<D>' ,
],
],
2019-03-16 02:37:50 +01:00
'allowNullablePropertyAssignment' => [
' < ? php
/**
* @ template T1
*/
interface I {
/**
* @ return T1
*/
public function get ();
}
/**
* @ template T2
*/
class C {
/**
* @ var T2 | null
*/
private $bar ;
/**
* @ param I < T2 > $foo
*/
public function __construct ( I $foo ) {
$this -> bar = $foo -> get ();
}
2019-03-23 19:27:54 +01:00
} ' ,
2019-03-16 02:37:50 +01:00
],
2019-06-25 19:00:06 +02:00
'reflectionClass' => [
2019-03-17 15:19:15 +01:00
' < ? php
/**
* @ template T as object
*
* @ property - read class - string < T > $name
*/
class CustomReflectionClass {
/**
2020-03-24 23:32:57 +01:00
* @ var class - string < T >
*/
public $name ;
/**
* @ param T | class - string < T > $argument
*/
public function __construct ( $argument ) {
if ( is_object ( $argument )) {
$this -> name = get_class ( $argument );
} else {
$this -> name = $argument ;
}
}
}
/**
* @ template T as object
* @ param class - string < T > $className
* @ return CustomReflectionClass < T >
*/
function getTypeOf ( string $className ) {
return new CustomReflectionClass ( $className );
} ' ,
],
'psalmReflectionClass' => [
' < ? php
/**
* @ template T as object
*
* @ psalm - property - read class - string < T > $name
*/
class CustomReflectionClass {
/**
2019-03-17 15:19:15 +01:00
* @ var class - string < T >
*/
public $name ;
/**
* @ param T | class - string < T > $argument
*/
public function __construct ( $argument ) {
if ( is_object ( $argument )) {
$this -> name = get_class ( $argument );
} else {
$this -> name = $argument ;
}
}
}
/**
* @ template T as object
* @ param class - string < T > $className
* @ return CustomReflectionClass < T >
*/
function getTypeOf ( string $className ) {
return new CustomReflectionClass ( $className );
2019-03-23 19:27:54 +01:00
} ' ,
2019-03-17 15:19:15 +01:00
],
2019-03-17 17:20:57 +01:00
'ignoreTooManyGenericObjectArgs' => [
' < ? php
/**
* @ template T
*/
class C {
/** @var T */
public $t ;
/** @param T $t */
public function __construct ( $t ) {
$this -> t = $t ;
}
}
/** @param C<int> $c */
function takesC ( C $c ) : void {}
/**
* @ psalm - suppress TooManyTemplateParams
* @ var C < int , int >
*/
$c = new C ( 5 );
2019-03-23 19:27:54 +01:00
takesC ( $c ); ' ,
2019-03-17 17:20:57 +01:00
],
'classTemplateUnionType' => [
' < ? php
/**
* @ template T0 as int | string
*/
class C {
/**
* @ param T0 $t
*/
public function foo ( $t ) : void {}
}
/** @param C<int> $c */
function foo ( C $c ) : void {}
/** @param C<string> $c */
function bar ( C $c ) : void {} ' ,
],
2019-03-17 23:52:42 +01:00
'unionAsTypeReturnType' => [
' < ? php
/**
* @ template TKey of ? array - key
* @ template T
*/
interface Collection
{
/**
* @ param Closure ( T = ) : bool $p
* @ return Collection < TKey , T >
*/
public function filter ( Closure $p );
} ' ,
],
2019-03-22 23:05:45 +01:00
'converterObject' => [
' < ? php
/**
* @ template I as array - key
* @ template V
*/
class Converter
{
/**
* @ var array < I , V > $records
*/
public $records ;
/**
2020-02-08 17:17:24 +01:00
* @ param array < I , V > $records
*/
2019-03-22 23:05:45 +01:00
public function __construct ( array $records ) {
$this -> records = $records ;
}
/**
* @ template Q2 as object
*
* @ param Q2 $obj2
*
* @ return array < I , V | Q2 >
*/
private function appender ( object $obj2 ) : array
{
$arr = [];
foreach ( $this -> records as $key => $obj ) {
if ( rand ( 0 , 1 )) {
2020-02-08 17:17:24 +01:00
$obj = $obj2 ;
2019-03-22 23:05:45 +01:00
}
$arr [ $key ] = $obj ;
}
return $arr ;
}
/**
* @ template Q1 as object
*
* @ param Q1 $obj
*
* @ return array < I , V | Q1 >
*/
public function appendProperty ( object $obj ) : array
{
return $this -> appender ( $obj );
}
} ' ,
],
'converterClassString' => [
' < ? php
/**
* @ template I as array - key
* @ template V
*/
class Converter
{
/**
* @ var array < I , V > $records
*/
public $records ;
/**
* @ param array < I , V > $records
*/
public function __construct ( array $records ) {
$this -> records = $records ;
}
/**
* @ template Q as object
*
* @ param class - string < Q > $obj
*
2019-06-25 19:20:30 +02:00
* @ return array < I , V | Q >
2019-03-22 23:05:45 +01:00
*/
public function appendProperty ( string $obj ) : array
{
return $this -> appender ( $obj );
}
/**
* @ template Q as object
*
* @ param class - string < Q > $obj2
*
* @ return array < I , V | Q >
2020-08-06 16:18:55 +02:00
*
* @ psalm - suppress MixedMethodCall
2019-03-22 23:05:45 +01:00
*/
private function appender ( string $obj2 ) : array
{
$arr = [];
foreach ( $this -> records as $key => $obj ) {
if ( rand ( 0 , 1 )) {
2020-02-08 17:17:24 +01:00
$obj = new $obj2 ;
2019-03-22 23:05:45 +01:00
}
$arr [ $key ] = $obj ;
}
return $arr ;
}
} ' ,
],
2019-03-28 15:19:02 +01:00
'allowTemplateReconciliation' => [
' < ? php
/**
* @ template T
*/
abstract class C {
/** @param T $t */
public function foo ( $t ) : void {
if ( ! $t ) {}
if ( $t ) {}
}
2019-07-05 22:24:00 +02:00
} ' ,
2019-03-28 15:19:02 +01:00
],
2019-04-26 13:54:12 +02:00
'allowTemplateParamsToCoerceToMinimumTypes' => [
' < ? php
/**
* @ psalm - template TKey of array - key
* @ psalm - template T
*/
class ArrayCollection
{
/**
* @ var array < TKey , T >
*/
private $elements ;
/**
* @ param array < TKey , T > $elements
*/
public function __construct ( array $elements = [])
{
$this -> elements = $elements ;
}
}
/** @psalm-suppress MixedArgument */
$c = new ArrayCollection ( $_GET [ " a " ]); ' ,
[
'$c' => 'ArrayCollection<array-key, mixed>' ,
],
],
2019-05-06 22:38:08 +02:00
'doNotCombineTypes' => [
' < ? php
class A {}
class B {}
/**
* @ template T
*/
class C {
/**
* @ var T
*/
private $t ;
/**
* @ param T $t
*/
public function __construct ( $t ) {
$this -> t = $t ;
}
/**
* @ return T
*/
public function get () {
return $this -> t ;
}
}
/**
* @ param C < A > $a
* @ param C < B > $b
* @ return C < A >| C < B >
*/
function randomCollection ( C $a , C $b ) : C {
if ( rand ( 0 , 1 )) {
return $a ;
}
return $b ;
}
$random_collection = randomCollection ( new C ( new A ), new C ( new B ));
$a_or_b = $random_collection -> get (); ' ,
[
2020-12-08 15:35:11 +01:00
'$random_collection' => 'C<A>|C<B>' ,
'$a_or_b' => 'A|B' ,
],
],
'doNotCombineTypesWhenMemoized' => [
' < ? php
class A {}
class B {}
/**
* @ template T
*/
class C {
/**
* @ var T
*/
private $t ;
/**
* @ param T $t
*/
public function __construct ( $t ) {
$this -> t = $t ;
}
/**
* @ return T
* @ psalm - mutation - free
*/
public function get () {
return $this -> t ;
}
}
/** @var C<A>|C<B> $random_collection **/
$a_or_b = $random_collection -> get (); ' ,
[
2019-05-06 22:38:08 +02:00
'$random_collection' => 'C<A>|C<B>' ,
2019-10-17 07:14:33 +02:00
'$a_or_b' => 'A|B' ,
2019-05-06 22:38:08 +02:00
],
],
2019-05-07 00:44:10 +02:00
'inferClosureParamTypeFromContext' => [
' < ? php
/**
* @ template E
*/
interface Collection {
/**
* @ template R
* @ param callable ( E ) : R $action
* @ return Collection < R >
*/
function map ( callable $action ) : self ;
}
/**
* @ template T
*/
interface Optional {
/**
* @ return T
*/
function get ();
}
/**
* @ param Collection < Optional < string >> $collection
* @ return Collection < string >
*/
function expandOptions ( Collection $collection ) : Collection {
return $collection -> map (
2019-05-07 02:47:55 +02:00
function ( $optional ) {
2019-05-07 00:44:10 +02:00
return $optional -> get ();
}
);
} ' ,
],
2019-05-21 15:02:38 +02:00
'templateEmptyParamCoercion' => [
' < ? php
namespace NS ;
use Countable ;
/** @template T */
class Collection
{
/** @psalm-var iterable<T> */
private $data ;
/** @psalm-param iterable<T> $data */
public function __construct ( iterable $data = []) {
$this -> data = $data ;
}
}
class Item {}
/** @psalm-param Collection<Item> $c */
function takesCollectionOfItems ( Collection $c ) : void {}
2019-05-21 18:11:17 +02:00
takesCollectionOfItems ( new Collection ());
takesCollectionOfItems ( new Collection ([])); ' ,
2019-05-21 15:02:38 +02:00
],
2019-05-23 19:10:23 +02:00
'templatedGet' => [
' < ? php
/**
* @ template P as string
* @ template V as mixed
*/
class PropertyBag {
/** @var array<P,V> */
2019-05-23 19:55:55 +02:00
protected $data = [];
2019-05-23 19:10:23 +02:00
/** @param array<P,V> $data */
public function __construct ( array $data ) {
$this -> data = $data ;
}
/** @param P $name */
public function __isset ( string $name ) : bool {
return isset ( $this -> data [ $name ]);
}
/**
* @ param P $name
* @ return V
*/
public function __get ( string $name ) {
return $this -> data [ $name ];
}
}
$p = new PropertyBag ([ " a " => " data for a " , " b " => " data for b " ]);
$a = $p -> a ; ' ,
[
2019-07-05 22:24:00 +02:00
'$a' => 'string' ,
],
2019-05-23 19:10:23 +02:00
],
2019-05-24 00:06:22 +02:00
'templateAsArray' => [
' < ? php
/**
* @ template DATA as array < string , scalar | array | object | null >
*/
abstract class Foo {
/**
* @ var DATA
*/
protected $data ;
/**
* @ param DATA $data
*/
public function __construct ( array $data ) {
$this -> data = $data ;
}
/**
* @ return scalar | array | object | null
*/
public function __get ( string $property ) {
2020-11-26 02:04:57 +01:00
return isset ( $this -> data [ $property ]) ? $this -> data [ $property ] : null ;
2019-05-24 00:06:22 +02:00
}
/**
* @ param scalar | array | object | null $value
*/
public function __set ( string $property , $value ) {
$this -> data [ $property ] = $value ;
}
} ' ,
],
2019-05-28 00:36:34 +02:00
'keyOfClassTemplateAcceptingIndexedAccess' => [
2019-05-24 08:12:58 +02:00
' < ? php
/**
* @ template TData as array
*/
abstract class DataBag {
/**
* @ var TData
*/
protected $data ;
/**
* @ param TData $data
*/
public function __construct ( array $data ) {
$this -> data = $data ;
}
/**
2019-05-28 00:36:34 +02:00
* @ template K as key - of < TData >
*
* @ param K $property
* @ param TData [ K ] $value
2019-05-24 08:12:58 +02:00
*/
2019-05-28 00:36:34 +02:00
public function __set ( string $property , $value ) {
$this -> data [ $property ] = $value ;
}
2019-07-05 22:24:00 +02:00
} ' ,
2019-05-28 00:36:34 +02:00
],
'keyOfClassTemplateReturningIndexedAccess' => [
' < ? php
/**
* @ template TData as array
*/
abstract class DataBag {
/**
* @ var TData
*/
protected $data ;
/**
* @ param TData $data
*/
public function __construct ( array $data ) {
$this -> data = $data ;
2019-05-24 08:12:58 +02:00
}
/**
* @ template K as key - of < TData >
*
* @ param K $property
2019-05-28 00:36:34 +02:00
*
* @ return TData [ K ]
2019-05-24 08:12:58 +02:00
*/
2019-05-28 00:36:34 +02:00
public function __get ( string $property ) {
return $this -> data [ $property ];
2019-05-24 08:12:58 +02:00
}
2019-07-05 22:24:00 +02:00
} ' ,
2019-05-24 08:12:58 +02:00
],
2019-05-24 18:48:37 +02:00
'SKIPPED-templatedInterfaceIntersectionFirst' => [
' < ? php
/** @psalm-template T */
interface IParent {
/** @psalm-return T */
function foo ();
}
interface IChild extends IParent {}
class C {}
/** @psalm-return IParent<C>&IChild */
function makeConcrete () : IChild {
return new class () implements IChild {
public function foo () {
return new C ();
}
};
}
$a = makeConcrete () -> foo (); ' ,
[
'$a' => 'C' ,
2019-07-05 22:24:00 +02:00
],
2019-05-24 18:48:37 +02:00
],
'templatedInterfaceIntersectionSecond' => [
' < ? php
/** @psalm-template T */
interface IParent {
/** @psalm-return T */
function foo ();
}
interface IChild extends IParent {}
class C {}
/** @psalm-return IChild&IParent<C> */
function makeConcrete () : IChild {
return new class () implements IChild {
public function foo () {
return new C ();
}
};
}
$a = makeConcrete () -> foo (); ' ,
[
'$a' => 'C' ,
2019-07-05 22:24:00 +02:00
],
2019-05-24 18:48:37 +02:00
],
2019-05-24 23:34:40 +02:00
'returnTemplateIntersectionGenericObjectAndTemplate' => [
' < ? php
/** @psalm-template Tp */
interface I {
/** @psalm-return Tp */
function getMe ();
}
class C {}
/**
* @ psalm - template T as object
*
* @ psalm - param class - string < T > $className
*
* @ psalm - return T & I < T >
*/
function makeConcrete ( string $className ) : object
{
2020-02-28 00:42:15 +01:00
/** @var T&I<T> */
2019-05-24 23:34:40 +02:00
return new class () extends C implements I {
public function getMe () {
return $this ;
}
};
}
$a = makeConcrete ( C :: class ); ' ,
[
2019-07-05 22:24:00 +02:00
'$a' => 'C&I<C>' ,
],
2019-05-24 23:34:40 +02:00
],
2019-06-08 03:27:50 +02:00
'keyOfArrayGet' => [
' < ? php
/**
* @ template DATA as array < string , int | bool >
*/
abstract class Foo {
/**
* @ var DATA
*/
protected $data ;
/**
* @ param DATA $data
*/
public function __construct ( array $data ) {
$this -> data = $data ;
}
/**
* @ template K as key - of < DATA >
*
* @ param K $property
*
* @ return DATA [ K ]
*/
public function __get ( string $property ) {
return $this -> data [ $property ];
}
} ' ,
],
'keyOfArrayRandomKey' => [
' < ? php
/**
* @ template DATA as array < string , int | bool >
*/
abstract class Foo {
/**
* @ var DATA
*/
protected $data ;
/**
* @ param DATA $data
*/
public function __construct ( array $data ) {
$this -> data = $data ;
}
/**
* @ return key - of < DATA >
*/
abstract public function getRandomKey () : string ;
2019-07-05 22:24:00 +02:00
} ' ,
2019-06-08 03:27:50 +02:00
],
2019-07-05 22:24:00 +02:00
'allowBoolTemplateCoercion' => [
2019-06-13 14:55:36 +02:00
' < ? php
/** @template T */
class TestPromise {
/** @psalm-param T $value */
public function __construct ( $value ) {}
}
/** @return TestPromise<bool> */
function test () : TestPromise {
return new TestPromise ( true );
} ' ,
],
2019-07-05 23:44:22 +02:00
'classTemplatedPropertyEmptyAssignment' => [
' < ? php
/** @template T */
class Foo {
/** @param \Closure():T $closure */
public function __construct ( $closure ) {}
}
class Bar {
/** @var Foo<array> */
private $FooArray ;
public function __construct () {
$this -> FooArray = new Foo ( function () : array { return []; });
}
} ' ,
],
2019-07-09 20:48:19 +02:00
'classTemplatedPropertyAssignmentWithMoreSpecificArray' => [
' < ? php
/** @template T */
class Foo {
/** @param \Closure():T $closure */
public function __construct ( $closure ) {}
}
class Bar {
/** @var Foo<array> */
private $FooArray ;
public function __construct () {
$this -> FooArray = new Foo ( function () : array { return []; });
}
} ' ,
],
2019-07-06 06:18:53 +02:00
'insideClosureVarTemplate' => [
' < ? php
/**
* @ template T of object
*/
class Foo {
/**
2020-10-24 01:53:04 +02:00
* @ psalm - return callable ( T ) : ? T
2019-07-06 06:18:53 +02:00
*/
public function bar () {
return
2020-10-24 01:53:04 +02:00
/**
* @ param T $data
* @ return ? T
*/
function ( $data ) {
$data = rand ( 0 , 1 ) ? $data : null ;
2019-07-06 06:18:53 +02:00
return $data ;
};
}
} ' ,
2019-07-09 20:48:19 +02:00
],
2019-09-07 19:44:47 +02:00
'reflectTemplatedClass' => [
' < ? php
/** @template T1 of object */
class Foo {
/**
* @ param class - string < T1 > $a
* @ psalm - return ReflectionClass < T1 >
*/
public function reflection ( string $a ) {
return new ReflectionClass ( $a );
}
} ' ,
],
2019-09-27 18:58:32 +02:00
'anonymousClassMustNotBreakParentTemplate' => [
' < ? php
/** @template T */
class Foo {
/** @psalm-var ?T */
private $value ;
/** @psalm-param T $val */
public function set ( $val ) : void {
$this -> value = $val ;
new class extends Foo {};
}
/** @psalm-return ?T */
public function get () {
return $this -> value ;
}
} '
],
2019-09-29 22:01:33 +02:00
'templatedInvoke' => [
' < ? php
/**
* @ template T
2020-08-06 01:39:27 +02:00
* @ psalm - consistent - constructor
2019-09-29 22:01:33 +02:00
*/
class Foo {
/** @var T */
private $value ;
/** @param T $val */
public function __construct ( $val ) {
$this -> value = $val ;
}
/** @return T */
public function get () {
return $this -> value ;
}
/**
* @ param T $val
* @ return Foo < T >
*/
public function __invoke ( $val ) {
return new static ( $val );
}
/**
* @ param T $val
* @ return Foo < T >
*/
public function create ( $val ) {
return new static ( $val );
}
}
function bar ( string $s ) : string {
$foo = new Foo ( $s );
$bar = $foo ( $s );
return $bar -> get ();
} '
],
2019-10-11 18:02:41 +02:00
'templatedLiteralStringReplacement' => [
2019-10-11 05:38:57 +02:00
' < ? php
/**
* @ template T
*/
final class Value {
/**
* @ psalm - var T
*/
private $value ;
/**
* @ psalm - param T $value
*/
public function __construct ( $value ) {
$this -> value = $value ;
}
/**
* @ psalm - return T
*/
public function value () {
return $this -> value ;
}
}
/**
* @ template T
* @ psalm - param T $value
* @ psalm - return Value < T >
*/
function value ( $value ) : Value {
return new Value ( $value );
}
/**
* @ psalm - param Value < string > $value
*/
function client ( $value ) : void {}
client ( value ( " awdawd " )); '
],
2019-10-18 17:01:16 +02:00
'yieldFromGenericObjectNotExtendingIterator' => [
' < ? php
2021-01-20 23:41:15 +01:00
/** @extends \ArrayObject<int, int> */
class Foo extends \ArrayObject {}
2019-10-18 17:01:16 +02:00
class A {
/**
* @ var Foo < string >
*/
public Foo $vector ;
/**
* @ param Foo < string > $v
*/
public function __construct ( Foo $v ) {
$this -> vector = $v ;
}
public function getIterator () : Iterator
{
yield from $this -> vector ;
}
} ' ,
[],
[ 'TooManyTemplateParams' ]
],
2019-10-29 20:30:19 +01:00
'coerceEmptyArrayToGeneral' => [
' < ? php
2020-04-04 18:05:01 +02:00
/** @template-covariant T */
2019-10-29 20:30:19 +01:00
class Foo
{
/** @param \Closure(string):T $closure */
public function __construct ( $closure ) {}
}
class Bar
{
2020-02-08 17:17:24 +01:00
/** @var Foo<array> */
private $FooArray ;
2019-10-29 20:30:19 +01:00
2020-02-08 17:17:24 +01:00
public function __construct () {
$this -> FooArray = new Foo ( function ( string $s ) : array {
2020-04-05 07:04:52 +02:00
/** @psalm-suppress MixedAssignment */
2020-02-08 17:17:24 +01:00
$json = \json_decode ( $s , true );
2019-10-29 20:30:19 +01:00
2020-02-08 17:17:24 +01:00
if ( ! \is_array ( $json )) {
return [];
}
2019-10-29 20:30:19 +01:00
2020-02-08 17:17:24 +01:00
return $json ;
});
2019-10-29 20:30:19 +01:00
2020-02-08 17:17:24 +01:00
takesFooArray ( $this -> FooArray );
2019-10-29 20:30:19 +01:00
}
}
/** @param Foo<array> $_ */
function takesFooArray ( $_ ) : void {} ' ,
],
2019-11-01 14:35:16 +01:00
'allowListAcceptance' => [
' < ? php
/** @template T */
class Collection
{
/** @var list<T> */
public $values ;
/** @param list<T> $values */
function __construct ( array $values )
{
$this -> values = $values ;
}
}
2019-11-01 17:33:54 +01:00
/** @return Collection<string> */
function makeStringCollection ()
{
return new Collection ( getStringList ()); // gets typed as Collection<mixed> for some reason
}
/** @return list<string> */
function getStringList () : array
{
return [ " foo " , " baz " ];
} '
],
'allowListAcceptanceIntoArray' => [
' < ? php
/** @template T */
class Collection
{
/** @var array<T> */
public $values ;
/** @param array<T> $values */
function __construct ( array $values )
{
$this -> values = $values ;
}
}
2019-11-01 14:35:16 +01:00
/** @return Collection<string> */
function makeStringCollection ()
{
return new Collection ( getStringList ()); // gets typed as Collection<mixed> for some reason
}
/** @return list<string> */
function getStringList () : array
{
return [ " foo " , " baz " ];
} '
],
2019-11-06 00:37:46 +01:00
'allowInternalNullCheck' => [
' < ? php
/**
* @ template TP as ? scalar
*/
class Entity
{
/**
* @ var TP
*/
private $parent ;
/** @param TP $parent */
public function __construct ( $parent ) {
$this -> parent = $parent ;
}
public function hasNoParent () : bool
{
return $this -> parent === null ; // So TP does contain null
}
} '
],
2019-11-06 19:13:02 +01:00
'useMethodWithExistingGenericParam' => [
' < ? php
class Bar {
public function getFoo () : string {
return " foo " ;
}
}
/**
* @ template TKey
* @ template T
*/
interface Collection {
/**
* @ param Closure ( T = ) : bool $p
* @ return Collection < TKey , T >
*/
public function filter ( Closure $p );
}
/**
* @ param Collection < int , Bar > $c
* @ psalm - return Collection < int , Bar >
*/
function filter ( Collection $c , string $name ) {
return $c -> filter (
function ( Bar $f ) use ( $name ) {
return $f -> getFoo () === " foo " ;
}
);
} '
],
2019-11-07 17:03:41 +01:00
'unboundVariableIsEmptyInInstanceMethod' => [
' < ? php
class A {
/**
* @ template TE
* @ template TR
*
* @ param TE $elt
* @ param TR ... $elts
*
* @ return TE | TR
*/
public function collectInstance ( $elt , ... $elts ) {
$ret = $elt ;
foreach ( $elts as $item ) {
if ( rand ( 0 , 1 )) {
$ret = $item ;
}
}
return $ret ;
}
}
echo ( new A ) -> collectInstance ( " a " ); '
],
'unboundVariableIsEmptyInStaticMethod' => [
' < ? php
class A {
/**
* @ template TE
* @ template TR
*
* @ param TE $elt
* @ param TR ... $elts
*
* @ return TE | TR
*/
public static function collectStatic ( $elt , ... $elts ) {
$ret = $elt ;
foreach ( $elts as $item ) {
if ( rand ( 0 , 1 )) {
$ret = $item ;
}
}
return $ret ;
}
}
echo A :: collectStatic ( " a " ); '
],
2019-11-08 19:11:01 +01:00
'traversableToIterable' => [
' < ? php
/**
* @ template T1 as array - key
* @ template T2
*
* @ param iterable < T1 , T2 > $x
*
* @ return array < T1 , T2 >
*/
function iterableToArray ( iterable $x ) : array {
if ( is_array ( $x )) {
return $x ;
}
else {
return iterator_to_array ( $x );
}
}
/**
* @ param Traversable < int , int > $t
* @ return array < int , int >
*/
function withParams ( Traversable $t ) : array {
return iterableToArray ( $t );
} ' ,
],
2019-11-12 14:01:22 +01:00
'templateStaticWithParam' => [
' < ? php
/**
* @ template T
2020-08-06 01:39:27 +02:00
* @ psalm - consistent - constructor
2019-11-12 14:01:22 +01:00
*/
class ArrayCollection {
/** @var list<T> */
private $elements ;
/**
* @ param list < T > $elements
*/
public function __construct ( array $elements ) {
$this -> elements = $elements ;
}
/**
* @ template U
* @ param callable ( T = ) : U $callback
* @ return static < U >
*/
public function map ( callable $callback ) {
return new static ( array_values ( array_map ( $callback , $this -> elements )));
}
}
/** @param ArrayCollection<int> $ints */
function takesInts ( ArrayCollection $ints ) : void {}
takesInts (( new ArrayCollection ([ " a " , " bc " ])) -> map ( " strlen " )); '
],
2019-11-29 07:21:38 +01:00
'weakReferenceIsTyped' => [
' < ? php
$e = new Exception ;
$r = WeakReference :: create ( $e );
$ex = $r -> get ();
' ,
[ '$ex' => 'Exception|null' ],
],
'weakReferenceIsCovariant' => [
' < ? php
/** @param WeakReference<Throwable> $_ref */
function acceptsThrowableRef ( WeakReference $_ref ) : void {}
acceptsThrowableRef ( WeakReference :: create ( new Exception ));
'
2019-12-09 01:25:40 +01:00
],
'mapTypeParams' => [
' < ? php
/**
* @ template TKey as array - key
* @ template TValue
*/
class Map {
/** @var array<TKey, TValue> */
public $arr ;
/** @param array<TKey, TValue> $arr */
function __construct ( array $arr ) {
$this -> arr = $arr ;
}
}
/**
* @ template TInputKey as array - key
* @ template TInputValue
* @ param Map < TInputKey , TInputValue > $map
* @ return Map < TInputKey , TInputValue >
*/
function copyMapUsingProperty ( Map $map ) : Map {
return new Map ( $map -> arr );
2019-12-09 03:12:18 +01:00
} ' ,
],
'mapStaticClassTemplatedFromClassString' => [
' < ? php
2020-08-06 01:39:27 +02:00
/**
* @ psalm - consistent - constructor
*/
2019-12-09 03:12:18 +01:00
class Base {
/** @return static */
public static function factory () : self {
2020-03-24 22:59:48 +01:00
return new static ();
2019-12-09 03:12:18 +01:00
}
}
/**
* @ template T of Base
* @ param class - string < T > $t
* @ return T
*/
function f ( string $t ) {
return $t :: factory ();
}
/** @template T of Base */
class C {
/** @var class-string<T> */
private string $t ;
/** @param class-string<T> $t */
public function __construct ( $t ) {
$this -> t = $t ;
}
/** @return T */
public function f () : Base {
$t = $this -> t ;
return $t :: factory ();
}
2019-12-09 01:25:40 +01:00
} '
],
2019-12-14 03:56:43 +01:00
'nullableTemplateAs' => [
' < ? php
/**
* @ template T of null | array
*/
class Foo
{
private ? \ArrayObject $arrayObject ;
public function __construct ( ? \ArrayObject $arrayObject )
{
$this -> arrayObject = $arrayObject ;
}
/**
* @ psalm - assert - if - true Foo < array > $this
* @ psalm - assert - if - true ArrayObject $this -> arrayObject
*/
public function hasArray () : bool
{
return $this -> arrayObject instanceof \ArrayObject ;
}
/** @return T */
public function toMaybeArray ()
{
if ( $this -> hasArray ()) {
return $this -> arrayObject -> getArrayCopy ();
}
return null ;
}
} '
],
2019-12-30 13:25:04 +01:00
'uasortCallableInMethod' => [
' < ? php
class C {
/**
* @ template T of object
* @ psalm - param array < T > $collection
* @ psalm - param callable ( T , T ) : int $sorter
* @ psalm - return array < T >
*/
function order ( array $collection , callable $sorter ) : array {
usort ( $collection , $sorter );
return $collection ;
}
} '
],
2020-01-01 19:51:54 +01:00
'intersectOnTOfObject' => [
' < ? php
/**
2020-12-05 17:58:55 +01:00
* @ psalm - template TO of object
2020-01-01 19:51:54 +01:00
*/
2020-12-05 17:58:55 +01:00
interface A {
2020-01-01 19:51:54 +01:00
/**
2020-12-05 17:58:55 +01:00
* @ psalm - param Closure ( TO & A ) : mixed $c
2020-01-01 19:51:54 +01:00
*/
2020-12-05 17:58:55 +01:00
public function setClosure ( Closure $c ) : void ;
2020-01-01 19:51:54 +01:00
}
2020-12-05 17:58:55 +01:00
function foo ( A $i ) : void {
$i -> setClosure (
function ( A $i ) : string {
2020-01-01 19:51:54 +01:00
return " hello " ;
}
);
} '
2020-01-07 06:39:16 +01:00
],
'assertionOnTemplatedClassString' => [
' < ? php
class TEM {
/**
2020-07-22 01:40:35 +02:00
* @ template Entity as object
2020-01-07 06:39:16 +01:00
* @ psalm - param class - string < Entity > $type
* @ psalm - return EQB < Entity >
*/
public function createEQB ( string $type ) {
if ( ! class_exists ( $type )) {
throw new InvalidArgumentException ();
}
return new EQB ( $type );
}
}
/**
2020-07-22 01:40:35 +02:00
* @ template Entity as object
2020-01-07 06:39:16 +01:00
*/
class EQB {
/**
* @ psalm - var class - string < Entity >
*/
protected $type ;
/**
* @ psalm - param class - string < Entity > $type
*/
public function __construct ( string $type ) {
$this -> type = $type ;
}
} '
],
2020-02-08 17:40:22 +01:00
'createEmptyArrayCollection' => [
' < ? php
$a = new ArrayCollection ([]);
/**
* @ psalm - template TKey of array - key
* @ psalm - template T
*/
class ArrayCollection
{
/**
* An array containing the entries of this collection .
*
* @ psalm - var array < TKey , T >
* @ var array
*/
private $elements = [];
/**
* Initializes a new ArrayCollection .
*
* @ param array $elements
*
* @ psalm - param array < TKey , T > $elements
*/
public function __construct ( array $elements = [])
{
$this -> elements = $elements ;
}
/**
* @ param TKey $key
* @ param T $t
*/
public function add ( $key , $t ) : void {
$this -> elements [ $key ] = $t ;
}
} ' ,
[
'$a' => 'ArrayCollection<empty, empty>'
]
],
'newGenericBecomesPropertyTypeValidArg' => [
2020-02-08 17:17:24 +01:00
' < ? php
class B {}
class A {
/** @var ArrayCollection<int, B> */
public ArrayCollection $b_collection ;
public function __construct () {
$this -> b_collection = new ArrayCollection ([]);
$this -> b_collection -> add ( 5 , new B ());
}
}
/**
2020-02-08 17:40:22 +01:00
* @ psalm - template TKey of array - key
2020-02-08 17:17:24 +01:00
* @ psalm - template T
*/
class ArrayCollection
{
/**
* An array containing the entries of this collection .
*
* @ psalm - var array < TKey , T >
* @ var array
*/
private $elements = [];
/**
* Initializes a new ArrayCollection .
*
* @ param array $elements
*
* @ psalm - param array < TKey , T > $elements
*/
public function __construct ( array $elements = [])
{
$this -> elements = $elements ;
}
/**
* @ param TKey $key
* @ param T $t
*/
public function add ( $key , $t ) : void {
$this -> elements [ $key ] = $t ;
}
} '
],
2020-02-08 17:40:22 +01:00
'allowPropertyCoercion' => [
' < ? php
class Test
{
/**
* @ var ArrayCollection < int , DateTime >
*/
private $c ;
public function __construct ()
{
$this -> c = new ArrayCollection ();
$this -> c -> filter ( function ( DateTime $dt ) : bool {
return $dt === $dt ;
});
}
}
/**
* @ psalm - template TKey of array - key
* @ psalm - template T
*/
2020-03-15 21:14:09 +01:00
class ArrayCollection
2020-02-08 17:40:22 +01:00
{
/**
* @ psalm - var array < TKey , T >
* @ var array
*/
private $elements ;
/**
* @ param array $elements
*
* @ psalm - param array < TKey , T > $elements
*/
public function __construct ( array $elements = [])
{
$this -> elements = $elements ;
}
/**
2020-03-15 21:14:09 +01:00
* @ psalm - param Closure ( T = ) : bool $p
* @ psalm - return self < TKey , T >
2020-02-08 17:40:22 +01:00
*/
public function filter ( Closure $p )
{
return $this ;
}
} '
],
2020-03-01 18:27:18 +01:00
'unionClassStringInferenceAndDefaultEmptyArray' => [
' < ? php
class A {}
$packages = Collection :: fromClassString ( A :: class );
/**
* @ template T
*/
class Collection {
/** @var array<T> $items */
protected $items = [];
/**
* @ param array < string , T > $items
*/
public function __construct ( array $items = [])
{
$this -> items = $items ;
}
/**
* @ template C as object
* @ param class - string < C > $classString
* @ param array < string , C > $elements
* @ return Collection < C >
*/
public static function fromClassString ( string $classString , array $elements = []) : Collection
{
return new Collection ( $elements );
}
} ' ,
[
'$packages' => 'Collection<A>'
]
],
2020-03-09 14:59:02 +01:00
'assertSameOnTemplatedProperty' => [
2020-03-05 22:21:41 +01:00
' < ? php
/** @template E as object */
final class Box
{
/** @var E */
private $contents ;
/** @param E $contents */
public function __construct ( object $contents )
{
$this -> contents = $contents ;
}
/** @param E $thing */
public function contains ( object $thing ) : bool
{
if ( $this -> contents !== $thing ) {
return false ;
}
return true ;
}
} '
],
2020-03-09 14:59:02 +01:00
'assertNotNullOnTemplatedProperty' => [
' < ? php
/**
* @ template T of object
*/
final class A {
/**
* @ psalm - var ? callable ( T ) : bool
*/
public $filter ;
}
/** @psalm-var A<A> */
$a = new A ();
if ( null !== $a -> filter ) {} '
],
2020-03-28 22:18:21 +01:00
'setTemplatedPropertyOutsideClass' => [
' < ? php
/**
* @ template TValue as scalar
*/
class Watcher {
/**
* @ psalm - var TValue
*/
public $value ;
/**
* @ psalm - param TValue $value
*/
public function __construct ( $value ) {
$this -> value = $value ;
}
}
/** @psalm-var Watcher<int> $watcher */
$watcher = new Watcher ( 0 );
$watcher -> value = 0 ; '
],
2020-04-03 22:53:56 +02:00
'callableAsClassStringArray' => [
' < ? php
abstract class Id
{
protected string $id ;
final protected function __construct ( string $id )
{
$this -> id = $id ;
}
/**
* @ return static
*/
final public static function fromString ( string $id ) : self
{
return new static ( $id );
}
}
/**
* @ template T of Id
*/
final class Ids
{
/**
* @ psalm - var list < T >
*/
private array $ids ;
/**
* @ psalm - param list < T > $ids
*/
private function __construct ( array $ids )
{
$this -> ids = $ids ;
}
/**
* @ template T1 of Id
* @ psalm - param T1 $class
* @ psalm - param list < string > $ids
* @ psalm - return self < T1 >
*/
public static function fromObjects ( Id $class , array $ids ) : self
{
return new self ( array_map ([ $class , " fromString " ], $ids ));
}
/**
* @ template T1 of Id
* @ psalm - param class - string < T1 > $class
* @ psalm - param list < string > $ids
* @ psalm - return self < T1 >
*/
public static function fromStrings ( string $class , array $ids ) : self
{
return new self ( array_map ([ $class , " fromString " ], $ids ));
}
} '
],
2020-04-18 17:48:22 +02:00
'noCrashTemplateInsideGenerator' => [
' < ? php
namespace Foo ;
/**
* @ template T
*/
final class Set
{
/** @var \Iterator<T> */
private \Iterator $values ;
/**
* @ param \Iterator < T > $values
*/
public function __construct ( \Iterator $values )
{
$this -> values = $values ;
}
/**
* @ param T $element
*
* @ return self < T >
*/
public function __invoke ( $element ) : self
{
return new self (
(
function ( $values , $element ) : \Generator {
/** @var T $value */
foreach ( $values as $value ) {
yield $value ;
}
yield $element ;
}
)( $this -> values , $element ),
);
}
} ' ,
[],
[ 'MissingClosureParamType' ]
],
2020-04-18 21:34:14 +02:00
'templatedPropertyAllowsNull' => [
' < ? php
/**
* @ template TKey as string | null
*/
class A {
/** @var TKey */
public $key ;
/**
* @ param TKey $key
*/
public function __construct ( ? string $key )
{
$this -> key = $key ;
}
} '
],
2020-05-03 05:37:59 +02:00
'templatePropertyWithoutParams' => [
' < ? php
/**
* @ template T of object
*/
class Batch {
/**
* @ var iterable < T >
*/
public iterable $objects = [];
/**
* @ var callable ( T ) : void
*/
public $onEach ;
public function __construct () {
$this -> onEach = function () : void {};
}
}
function handle ( Batch $message , object $o ) : void {
$fn = $message -> onEach ;
$fn ( $o );
} '
],
2020-05-19 23:31:05 +02:00
'changePropertyTypeOfTemplate' => [
' < ? php
class A {
public int $x = 0 ;
}
/**
* @ template T as A
* @ param T $obj
* @ param - out T $obj
*/
function foo ( A & $obj ) : void {
$obj -> x = 1 ;
} '
],
2020-05-20 15:11:11 +02:00
'multipleMatchingObjectsInUnion' => [
' < ? php
/** @template T */
interface Container {
/** @return T */
public function get ();
}
/**
* @ template T
* @ param array < Container < T >> $containers
* @ return T
*/
function unwrap ( array $containers ) {
2020-08-10 15:58:43 +02:00
return array_map (
fn ( $container ) => $container -> get (),
$containers
)[ 0 ];
2020-05-20 15:11:11 +02:00
}
/**
* @ param array < Container < int >| Container < string >> $typed_containers
*/
function takesDifferentTypes ( array $typed_containers ) : void {
$ret = unwrap ( $typed_containers );
if ( is_string ( $ret )) {}
if ( is_int ( $ret )) {}
2020-08-10 15:58:43 +02:00
} ' ,
[],
[],
'7.4'
2020-05-20 15:11:11 +02:00
],
2020-05-25 17:51:10 +02:00
'templateWithLateResolvedType' => [
' < ? php
/**
* @ template A of Enum :: TYPE_ *
*/
class Foo {}
class Enum
{
const TYPE_ONE = 1 ;
const TYPE_TWO = 2 ;
}
/** @var Foo<Enum::TYPE_ONE> $foo */
$foo = new Foo (); '
],
2020-05-26 00:47:59 +02:00
'SKIPPED-extendedPropertyTypeParameterised' => [
2020-05-25 23:10:53 +02:00
' < ? php
namespace App ;
use DateTimeImmutable ;
use Ds\Map ;
abstract class Z
{
public function test () : void
{
$map = $this -> createMap ();
$date = $map -> get ( " test " );
echo $date -> format ( " Y " );
}
/**
* @ return Map < string , DateTimeImmutable >
*/
abstract protected function createMap () : Map ;
} '
],
2020-06-18 15:31:38 +02:00
'looseEquality' => [
' < ? php
/**
* @ psalm - immutable
* @ template T of self :: READ_UNCOMMITTED | self :: READ_COMMITTED | self :: REPEATABLE_READ | self :: SERIALIZABLE
*/
final class TransactionIsolationLevel
{
private const READ_UNCOMMITTED = " read uncommitted " ;
private const READ_COMMITTED = " read committed " ;
private const REPEATABLE_READ = " repeatable read " ;
private const SERIALIZABLE = " serializable " ;
/**
* @ psalm - var T $level
*/
private string $level ;
/**
* @ psalm - param T $level
*/
private function __construct ( string $level )
{
$this -> level = $level ;
}
/**
* @ psalm - return self < self :: READ_UNCOMMITTED >
*/
public static function readUncommitted () : self
{
return new self ( self :: READ_UNCOMMITTED );
}
/**
* @ psalm - return T
*/
public function toString () : string
{
return $this -> level ;
}
/**
* @ psalm - template TResult
* @ psalm - param callable ( self :: READ_UNCOMMITTED ) : TResult $readUncommitted
* @ psalm - return TResult
*/
public function resolve ( callable $readUncommitted ) {
if ( $this -> level == self :: READ_UNCOMMITTED ) {
return $readUncommitted ( $this -> level );
}
throw new \LogicException ( " bad " );
}
} '
],
2020-07-29 23:46:45 +02:00
'narrowTemplateTypeWithInstanceof' => [
' < ? php
class Foo {}
class Bar {}
/** @template FooOrBarOrNull of Foo|Bar|null */
class Resolved
{
/**
* @ var FooOrBarOrNull
*/
private $entity = null ;
/**
* @ psalm - param FooOrBarOrNull $qux
*/
public function __contruct ( ? object $qux )
{
if ( $qux instanceof Foo ) {
$this -> entity = $qux ;
}
}
} '
],
2020-10-15 03:35:46 +02:00
'flippedParamsMethodInside' => [
2020-10-15 03:22:51 +02:00
' < ? php
/**
* @ template A
* @ template B
*/
abstract class Foo
{
/** @return Traversable<A, B> */
public abstract function getTraversable () : Traversable ;
/**
* @ param Foo < B , A > $flipped
* @ return Traversable < B , A >
*/
public function getFlippedTraversable ( Foo $flipped ) : Traversable
{
return $flipped -> getTraversable ();
}
} '
],
2020-10-15 03:35:46 +02:00
'flippedParamsMethodOutside' => [
2020-10-15 03:22:51 +02:00
' < ? php
/**
* @ template B
* @ template A
* @ param Foo < B , A > $flipped
* @ return Traversable < B , A >
*/
function getFlippedTraversable ( Foo $flipped ) : Traversable {
return $flipped -> getTraversable ();
}
/**
* @ template A
* @ template B
*/
abstract class Foo
{
/** @return Traversable<A, B> */
public abstract function getTraversable () : Traversable ;
} '
],
2020-10-15 03:35:46 +02:00
'flippedParamsPropertyInside' => [
' < ? php
/**
* @ template A
* @ template B
*/
abstract class Foo
{
/** @var Traversable<A, B> */
public $traversable ;
/**
* @ param Foo < B , A > $flipped
* @ return Traversable < B , A >
*/
public function getFlippedTraversable ( Foo $flipped ) : Traversable
{
return $flipped -> traversable ;
}
} '
],
'flippedParamsPropertyOutside' => [
' < ? php
/**
* @ template B
* @ template A
* @ param Foo < B , A > $flipped
* @ return Traversable < B , A >
*/
function getFlippedTraversable ( Foo $flipped ) : Traversable {
return $flipped -> traversable ;
}
/**
* @ template A
* @ template B
*/
abstract class Foo
{
/** @var Traversable<A, B> */
public $traversable ;
} '
],
2020-11-13 15:43:30 +01:00
'simpleTemplate' => [
' < ? php
/** @template T */
interface F {}
/** @param F<mixed> $f */
function takesFMixed ( F $f ) : void {}
function sendsF ( F $f ) : void {
takesFMixed ( $f );
} '
],
2020-11-29 23:40:52 +01:00
'arrayCollectionMapInternal' => [
' < ? php
/**
* @ psalm - template TKey of array - key
* @ psalm - template T
* @ psalm - consistent - constructor
*/
class ArrayCollection
{
/** @psalm-var array<TKey,T> */
private $elements ;
/** @psalm-param array<TKey,T> $elements */
public function __construct ( array $elements = [])
{
$this -> elements = $elements ;
}
/**
* @ template TNewKey of array - key
* @ template TNew
* @ psalm - param array < TNewKey , TNew > $elements
* @ psalm - return static < TNewKey , TNew >
*/
protected static function createFrom ( array $elements )
{
return new static ( $elements );
}
/**
* @ psalm - template U
* @ psalm - param Closure ( T = ) : U $func
* @ psalm - return static < TKey , U >
*/
public function map ( Closure $func )
{
$new_elements = array_map ( $func , $this -> elements );
return self :: createFrom ( $new_elements );
}
} '
],
'arrayCollectionMapExternal' => [
' < ? php
/**
* @ psalm - template TKey of array - key
* @ psalm - template T
* @ psalm - consistent - constructor
*/
class ArrayCollection
{
/** @psalm-var array<TKey,T> */
private $elements ;
/** @psalm-param array<TKey,T> $elements */
public function __construct ( array $elements = [])
{
$this -> elements = $elements ;
}
/**
* @ psalm - template U
* @ psalm - param Closure ( T = ) : U $func
* @ psalm - return ArrayCollection < TKey , U >
*/
public function map ( Closure $func )
{
$new_elements = array_map ( $func , $this -> elements );
return Creator :: createFrom ( $new_elements );
}
}
class Creator {
/**
* @ template TNewKey of array - key
* @ template TNew
* @ psalm - param array < TNewKey , TNew > $elements
* @ psalm - return ArrayCollection < TNewKey , TNew >
*/
public static function createFrom ( array $elements ) {
return new ArrayCollection ( $elements );
}
} '
],
2020-11-30 01:07:35 +01:00
'templateWithClassConstants' => [
' < ? php
/**
* @ psalm - immutable
* @ template T of self :: A | self :: B | self :: C
*/
final class Foo
{
public const A = " aa " ;
public const B = " bb " ;
public const C = " cc " ;
/**
* @ psalm - var T $level
*/
private string $level ;
/**
* @ psalm - param T $level
*/
public function __construct ( string $level )
{
$this -> level = $level ;
}
}
/**
* @ psalm - return Foo < Foo :: A >
*/
function getFooA () : Foo {
return new Foo ( Foo :: A );
} '
],
2020-12-29 13:24:33 +01:00
'callTemplatedMethodOnSameClass' => [
' < ? php
/**
* @ template T as object
*/
class Mapper {
/**
* @ param T $e
* @ return T
*/
public function foo ( $e ) {
return $e ;
}
/**
* @ param T $e
* @ return T
*/
public function passthru ( $e ) {
return $this -> foo ( $e );
}
} '
],
2021-01-16 07:12:18 +01:00
'templatedStaticUnion' => [
' < ? php
/**
* @ template T
*/
abstract class A {
/**
* @ var T
*/
private $v ;
/**
* @ param T $v
*/
final public function __construct ( $v ) {
$this -> v = $v ;
}
/**
* @ return static < T >
*/
public function foo () : A {
if ( rand ( 0 , 1 )) {
return new static ( $this -> v );
} else {
return new static ( $this -> v );
}
}
} '
],
2021-02-07 17:07:22 +01:00
'templatedTypeWithLimitGoesIntoTemplatedType' => [
' < ? php
/**
* @ template T as object
*/
abstract class A {}
function takesA ( A $a ) : void {}
function foo ( A $a ) : void {
takesA ( $a );
} ' ,
],
2019-01-26 04:33:42 +01:00
];
}
/**
2019-03-01 21:55:20 +01:00
* @ return iterable < string , array { string , error_message : string , 2 ? : string [], 3 ? : bool , 4 ? : string } >
2019-01-26 04:33:42 +01:00
*/
2020-09-12 17:24:05 +02:00
public function providerInvalidCodeParse () : iterable
2019-01-26 04:33:42 +01:00
{
return [
2019-01-20 00:11:39 +01:00
'restrictTemplateInputWithClassString' => [
2018-06-09 16:14:18 +02:00
' < ? php
2019-01-05 06:15:53 +01:00
/** @template T as object */
2018-06-09 16:14:18 +02:00
class Foo
{
/**
* @ psalm - var class - string
*/
private $type ;
/** @var array<T> */
private $items ;
/**
2019-01-05 06:15:53 +01:00
* @ param T :: class $type
2018-06-09 16:14:18 +02:00
*/
public function __construct ( string $type )
{
if ( ! in_array ( $type , [ A :: class , B :: class ], true )) {
throw new \InvalidArgumentException ;
}
$this -> type = $type ;
$this -> items = [];
}
/** @param T $item */
public function add ( $item ) : void
{
$this -> items [] = $item ;
}
}
class A {}
class B {}
2018-12-13 06:09:01 +01:00
$foo = new Foo ( A :: class );
$foo -> add ( new B ); ' ,
'error_message' => 'InvalidArgument' ,
],
'restrictTemplateInputWithTClassBadInput' => [
' < ? php
/** @template T */
class Foo
{
/**
* @ psalm - var class - string
*/
private $type ;
/** @var array<T> */
private $items ;
/**
* @ param T :: class $type
*/
public function __construct ( string $type )
{
if ( ! in_array ( $type , [ A :: class , B :: class ], true )) {
throw new \InvalidArgumentException ;
}
$this -> type = $type ;
$this -> items = [];
}
/** @param T $item */
public function add ( $item ) : void
{
$this -> items [] = $item ;
}
}
class A {}
class B {}
2018-06-09 16:14:18 +02:00
$foo = new Foo ( A :: class );
$foo -> add ( new B ); ' ,
'error_message' => 'InvalidArgument' ,
],
2018-12-08 20:10:06 +01:00
'templatedClosureProperty' => [
' < ? php
final class State
{}
interface Foo
{}
function type ( string ... $_p ) : void {}
/**
* @ template T
*/
final class AlmostFooMap
{
/**
* @ param callable ( State ) : ( T & Foo ) $closure
*/
public function __construct ( callable $closure )
{
type ( $closure );
}
} ' ,
2020-02-02 18:25:24 +01:00
'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:20:34 - Argument 1 of type expects string, callable(State):(T:AlmostFooMap as mixed)&Foo provided' ,
2018-12-17 21:49:59 +01:00
],
2019-01-24 22:43:22 +01:00
'templateWithNoReturn' => [
' < ? php
/**
* @ template T
*/
class A {
/** @return T */
public function foo () {}
} ' ,
'error_message' => 'InvalidReturnType' ,
],
2019-01-26 22:58:49 +01:00
'templateInvalidDocblockArgument' => [
' < ? php
/** @template T as object */
class Generic {}
/**
* @ template T
* @ param T $p
* @ return Generic < T >
* @ psalm - suppress InvalidReturnType
*/
function violate ( $p ) {} ' ,
'error_message' => 'InvalidTemplateParam' ,
],
2019-01-28 23:09:23 +01:00
'doublyLinkedListBadParam' => [
' < ? php
/** @var SplDoublyLinkedList<int, string> */
$templated_list = new SplDoublyLinkedList ();
$templated_list -> add ( 5 , []); ' ,
'error_message' => 'InvalidArgument' ,
],
2019-02-22 03:54:00 +01:00
'copyScopedClassInNamespacedClass' => [
' < ? php
namespace Foo ;
/**
* @ template Bar as DOMNode
*/
class Bar {} ' ,
'error_message' => 'ReservedWord' ,
],
2019-03-01 05:43:55 +01:00
'duplicateTemplateFunction' => [
' < ? php
/**
* @ template T
*/
class Foo
{
/** @var T */
private $value ;
/**
* @ template T
* @ param T $value
* @ return self < T >
*/
static function of ( $value ) : self
{
return new self ( $value );
}
/**
* @ param T $value
*/
private function __construct ( $value )
{
$this -> value = $value ;
}
} ' ,
'error_message' => 'InvalidDocblock' ,
],
2019-05-06 22:38:08 +02:00
'preventDogCatSnafu' => [
' < ? php
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
/**
* @ template T
*/
class Collection {
/**
* @ param T $t
*/
public function add ( $t ) : void {}
}
/**
* @ param Collection < Animal > $list
*/
function addAnimal ( Collection $list ) : void {
$list -> add ( new Cat ());
}
/**
* @ param Collection < Dog > $list
*/
function takesDogList ( Collection $list ) : void {
addAnimal ( $list ); // this should be an error
} ' ,
'error_message' => 'InvalidArgument' ,
],
2019-05-21 18:11:17 +02:00
'templateEmptyParamCoercionChangeVariable' => [
' < ? php
namespace NS ;
use Countable ;
/** @template T */
class Collection
{
/** @psalm-var iterable<T> */
private $data ;
/** @psalm-param iterable<T> $data */
public function __construct ( iterable $data = []) {
$this -> data = $data ;
}
}
/** @psalm-param Collection<string> $c */
function takesStringCollection ( Collection $c ) : void {}
/** @psalm-param Collection<int> $c */
function takesIntCollection ( Collection $c ) : void {}
$collection = new Collection ();
takesStringCollection ( $collection );
takesIntCollection ( $collection ); ' ,
'error_message' => 'InvalidScalarArgument' ,
],
2019-05-28 20:23:22 +02:00
'argumentExpectsFleshOutTIndexedAccess' => [
' < ? php
/**
* @ template TData as array
*/
abstract class Row {
/**
* @ var TData
*/
protected $data ;
/**
* @ param TData $data
*/
public function __construct ( array $data ) {
$this -> data = $data ;
}
/**
* @ template K as key - of < TData >
*
* @ param K $property
*
* @ return TData [ K ]
*/
public function __get ( string $property ) {
// validation logic would go here
return $this -> data [ $property ];
}
/**
* @ template K as key - of < TData >
*
* @ param K $property
* @ param TData [ K ] $value
*/
public function __set ( string $property , $value ) {
// data updating would go here
$this -> data [ $property ] = $value ;
}
}
/** @extends Row<array{id: int, name: string, height: float}> */
class CharacterRow extends Row {}
$mario = new CharacterRow ([ " id " => 5 , " name " => " Mario " , " height " => 3.5 ]);
$mario -> ame = " Luigi " ; ' ,
2021-01-26 05:41:42 +01:00
'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:47:29 - Argument 1 of CharacterRow::__set expects "height"|"id"|"name", "ame" provided' ,
2019-05-28 20:23:22 +02:00
],
2019-07-10 18:12:51 +02:00
'specialiseTypeBeforeReturning' => [
' < ? php
class Base {}
class Derived extends Base {}
/**
* @ template T of Base
*/
class Foo {
/**
* @ param T $t
*/
public function __construct ( $t ) {}
}
/**
* @ return Foo < Base >
*/
function returnFooBase () {
$f = new Foo ( new Derived ());
takesFooDerived ( $f );
return $f ;
}
/**
* @ param Foo < Derived > $foo
*/
function takesFooDerived ( $foo ) : void {} ' ,
'error_message' => 'InvalidReturnStatement'
],
2019-07-10 20:48:15 +02:00
'possiblySpecialiseTypeBeforeReturning' => [
' < ? php
class Base {}
class Derived extends Base {}
/**
* @ template T of Base
*/
class Foo {
/**
* @ param T $t
*/
public function __construct ( $t ) {}
}
/**
* @ return Foo < Base >
*/
function returnFooBase () {
$f = new Foo ( new Derived ());
if ( rand ( 0 , 1 )) {
takesFooDerived ( $f );
}
return $f ;
}
/**
* @ param Foo < Derived > $foo
*/
function takesFooDerived ( $foo ) : void {} ' ,
'error_message' => 'InvalidReturnStatement'
],
2019-11-06 22:59:08 +01:00
'preventUseWithMoreSpecificParamInt' => [
2019-11-06 17:20:51 +01:00
' < ? php
/** @template T */
abstract class Collection {
/** @param T $elem */
public function add ( $elem ) : void {}
}
/**
* @ template T
* @ param Collection < T > $col
*/
function usesCollection ( Collection $col ) : void {
$col -> add ( 456 );
} ' ,
'error_message' => 'InvalidArgument'
],
2019-11-06 22:59:08 +01:00
'preventUseWithMoreSpecificParamEmptyArray' => [
' < ? php
/** @template T */
abstract class Collection {
/** @param T $elem */
public function add ( $elem ) : void {}
}
/**
* @ template T
* @ param Collection < T > $col
*/
function usesCollection ( Collection $col ) : void {
$col -> add ([]);
} ' ,
'error_message' => 'InvalidArgument'
],
2019-11-11 15:14:34 +01:00
'preventTemplatedCorrectionBeingWrittenTo' => [
' < ? php
namespace NS ;
/**
* @ template TKey
* @ template TValue
*/
class ArrayCollection {
/** @var array<TKey,TValue> */
private $data ;
/** @param array<TKey,TValue> $data */
public function __construct ( array $data ) {
$this -> data = $data ;
}
/**
* @ param TKey $key
* @ param TValue $value
*/
public function addItem ( $key , $value ) : void {
$this -> data [ $key ] = $value ;
}
}
class Item {}
class SubItem extends Item {}
class OtherSubItem extends Item {}
/**
* @ param ArrayCollection < int , Item > $i
*/
function takesCollectionOfItems ( ArrayCollection $i ) : void {
$i -> addItem ( 10 , new OtherSubItem );
}
$subitem_collection = new ArrayCollection ([ new SubItem ]);
takesCollectionOfItems ( $subitem_collection ); ' ,
'error_message' => 'InvalidArgument'
],
2019-12-14 15:40:09 +01:00
'noClassTemplatesInStaticMethods' => [
' < ? php
/**
* @ template T
*/
class C {
/**
* @ param T $t
*/
public static function foo ( $t ) : void {}
} ' ,
'error_message' => 'UndefinedDocblockClass'
],
2020-02-08 17:40:22 +01:00
'newGenericBecomesPropertyTypeInvalidArg' => [
2020-02-08 17:17:24 +01:00
' < ? php
class B {}
class C {}
class A {
/** @var ArrayCollection<int, B> */
public ArrayCollection $b_collection ;
public function __construct () {
$this -> b_collection = new ArrayCollection ([]);
$this -> b_collection -> add ( 5 , new C ());
}
}
/**
* @ psalm - template TKey
* @ psalm - template T
*/
class ArrayCollection
{
/**
* An array containing the entries of this collection .
*
* @ psalm - var array < TKey , T >
* @ var array
*/
private $elements = [];
/**
* Initializes a new ArrayCollection .
*
* @ param array $elements
*
* @ psalm - param array < TKey , T > $elements
*/
public function __construct ( array $elements = [])
{
$this -> elements = $elements ;
}
/**
* @ param TKey $key
* @ param T $t
*/
public function add ( $key , $t ) : void {
$this -> elements [ $key ] = $t ;
}
} ' ,
'error_message' => 'InvalidArgument'
],
2020-04-19 15:15:09 +02:00
'preventIteratorAggregateToIterableWithDifferentTypes' => [
' < ? php
class Foo {}
class Bar {}
/** @param iterable<int, Foo> $foos */
function consume ( iterable $foos ) : void {}
/** @param IteratorAggregate<int, Bar> $t */
function foo ( IteratorAggregate $t ) : void {
consume ( $t );
} ' ,
'error_message' => 'InvalidArgument' ,
],
2020-11-30 01:12:22 +01:00
'preventPassingToBoundParam' => [
' < ? php
/**
* @ template T
*/
class Container
{
/** @var T */
private $t ;
/** @param T $t */
public function __construct ( $t )
{
$this -> t = $t ;
}
/**
* @ param T $t
* @ return T
*/
protected function makeNew ( $t )
{
return $t ;
}
/**
* @ template U
* @ param U $u
*/
public function map ( $u ) : void
{
$this -> makeNew ( $u );
}
} ' ,
'error_message' => 'InvalidArgument' ,
],
2020-11-30 03:36:50 +01:00
'bindRedirectedTemplate' => [
' < ? php
/**
* @ template TIn
* @ template TOut
*/
final class Map
{
/** @param Closure(TIn): TOut $c */
public function __construct ( private Closure $c ) {}
/**
* @ template TIn2 as list < TIn >
* @ param TIn2 $in
* @ return list < TOut >
*/
public function __invoke ( array $in ) : array {
return array_map (
$this -> c ,
$in
);
}
}
$m = new Map ( fn ( int $num ) => ( string ) $num );
$m ([ " a " ]); ' ,
'error_message' => 'InvalidScalarArgument' ,
[],
false ,
'8.0'
],
2020-12-05 17:58:55 +01:00
'bindClosureParamAccurately' => [
' < ? php
/**
* @ template TKey
* @ template TValue
*/
interface Collection {
/**
* @ template T
* @ param Closure ( TValue ) : T $func
* @ return Collection < TKey , T >
*/
public function map ( Closure $func );
}
/**
* @ param Collection < int , string > $c
*/
function f ( Collection $c ) : void {
$fn = function ( int $_p ) : bool { return true ; };
$c -> map ( $fn );
} ' ,
'error_message' => 'InvalidScalarArgument' ,
],
2021-02-07 17:07:22 +01:00
'limitTemplateTypeWithSameName' => [
' < ? php
/**
* @ template T as object
*/
abstract class A {}
function takesA ( A $a ) : void {}
/** @param A<stdClass> $a */
function foo ( A $a ) : void {
takesA ( $a );
} ' ,
'error_message' => 'InvalidArgument' ,
],
'limitTemplateTypeExtended' => [
' < ? php
/**
* @ template T as object
*/
abstract class A {}
/**
* @ extends A < stdClass >
*/
class AChild extends A {}
function takesA ( A $a ) : void {}
$child = new AChild ();
takesA ( $child ); ' ,
'error_message' => 'InvalidArgument' ,
],
2017-04-25 05:45:02 +02:00
];
2017-02-10 04:57:23 +01:00
}
2017-02-10 02:35:17 +01:00
}