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
*/
2018-11-06 03:57:36 +01:00
public function providerValidCodeParse ()
2017-02-10 02:35:17 +01:00
{
2017-04-25 05:45:02 +02:00
return [
'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
/**
2018-11-12 18:03:55 +01:00
* @ param class - string $T
2017-04-25 05:45:02 +02:00
* @ template - typeof T $T
*/
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
*/
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
2017-04-25 05:45:02 +02:00
/** @var Foo<A> */
$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' ,
2019-03-23 19:27:54 +01:00
'TypeCoercion' ,
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 ;
/**
2018-11-12 18:03:55 +01:00
* @ param class - string $T
2018-03-06 19:59:59 +01:00
* @ template - typeof T $T
*/
public function __construct ( string $T ) {
$this -> T = $T ;
}
/**
* @ return T
*/
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 ;
/**
2018-11-12 18:03:55 +01:00
* @ param class - string $T
2017-07-25 22:11:02 +02:00
* @ template - typeof T $T
*/
public function __construct ( string $T ) {
$this -> T = $T ;
}
/**
* @ return T
*/
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
*/
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 ) {
return new static ( array_map ( $func , $this -> data ));
}
}
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
{
/**
* @ template T
* @ param T $item
*/
public function add ( $item ) : void
{
$foo = $this -> ensureFoo ([ $item ]);
$foo -> add ( $item );
}
/**
* @ template T
* @ 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 (
/** @param Collection<mixed,I> $elt */
function ( Collection $elt ) : bool { return ( bool ) rand ( 0 , 1 ); }
);
$c -> filter (
/** @param Collection<mixed,I> $elt */
function ( Collection $elt ) : bool { return true ; }
); ' ,
],
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 ;
/** @template T */
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
} ' ,
],
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
],
2019-01-27 20:20:41 +01:00
'templateObjectLikeValues' => [
' < ? 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' => [
'$c===' => 'C<string(hello)>' ,
],
],
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)>' ,
],
],
'SKIPPED-templateDefaultClassConstant' => [
' < ? 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-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 {
/**
* @ 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 ;
/**
* @ param array < I , V > $records
*/
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 )) {
$obj = $obj2 ;
}
$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 >
*/
private function appender ( string $obj2 ) : array
{
$arr = [];
foreach ( $this -> records as $key => $obj ) {
if ( rand ( 0 , 1 )) {
$obj = new $obj2 ;
}
$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 (); ' ,
[
'$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 ) {
return $this -> data [ $property ] ? ? null ;
}
/**
* @ 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
{
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 {
/**
* @ psalm - return callable () : ? T
*/
public function bar () {
return
/** @psalm-return ?T */
function () {
/** @psalm-var ?T */
$data = null ;
return $data ;
};
}
} ' ,
2019-07-09 20:48:19 +02:00
],
'allowBoundedType' => [
' < ? php
class Base {}
class Child extends Base {}
/**
* @ template T
*/
class Foo
{
/** @param Closure():T $t */
public function __construct ( Closure $t ) {}
}
/**
* @ return Foo < Base >
*/
function returnFooBase () : Foo {
$f = new Foo ( function () { return new Child (); });
return $f ;
} ' ,
],
'allowMoreSpecificArray' => [
' < ? 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 [ " foo " => " bar " ]; });
}
} '
],
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
*/
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
class Foo {}
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
/** @template T */
class Foo
{
/** @param \Closure(string):T $closure */
public function __construct ( $closure ) {}
}
class Bar
{
/** @var Foo<array> */
private $FooArray ;
public function __construct ()
{
$this -> FooArray = new Foo ( function ( string $s ) : array {
/** @psalm-suppress MixedAssignment */
$json = \json_decode ( $s , true );
if ( ! \is_array ( $json )) {
return [];
}
return $json ;
});
takesFooArray ( $this -> FooArray );
}
}
/** @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-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
*/
public function providerInvalidCodeParse ()
{
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 );
}
} ' ,
2019-02-27 22:00:44 +01:00
'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:20:34 - Argument 1 of type expects string, callable(State):(T 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-15 00:17:38 +02:00
'preventCovariantParamUsage' => [
' < ? php
/**
* @ template - covariant T
*/
class Covariant {
/**
* @ param T $value
*/
public function set ( $value ) : void {}
} ' ,
'error_message' => 'InvalidTemplateParam' ,
],
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 " ; ' ,
2019-10-17 07:14:33 +02:00
'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:47:29 - Argument 1 of CharacterRow::__set expects string(height)|string(id)|string(name), string(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-07-10 19:49:11 +02:00
'specializeTypeInPropertyAssignment' => [
' < ? php
/** @template T */
class Foo {
/** @var \Closure():T $closure */
private $closure ;
/** @param \Closure():T $closure */
public function __construct ( $closure )
{
$this -> closure = $closure ;
}
}
class Bar {
/** @var Foo<array> */
private $FooArray ;
public function __construct () {
$this -> FooArray = new Foo ( function () : array { return [ " foo " => " bar " ]; });
expectsShape ( $this -> FooArray );
}
}
/** @param Foo<array{foo: string}> $_ */
function expectsShape ( $_ ) : void {} ' ,
'error_message' => 'MixedArgumentTypeCoercion'
],
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'
],
2017-04-25 05:45:02 +02:00
];
2017-02-10 04:57:23 +01:00
}
2017-02-10 02:35:17 +01:00
}