2017-02-10 02:35:17 +01:00
< ? php
2019-01-26 04:33:42 +01:00
namespace Psalm\Tests\Template ;
use Psalm\Tests\TestCase ;
use Psalm\Tests\Traits ;
2017-02-10 02:35:17 +01:00
2017-04-25 05:45:02 +02:00
class TemplateTest 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
/**
2017-04-25 05:45:02 +02:00
* @ return array
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' ,
'RedundantConditionGivenDocblockType' ,
2018-11-12 18:03:55 +01:00
'TypeCoercion'
2018-03-06 19:59:59 +01:00
],
],
'classTemplateSelfs' => [
' < ? php
/**
* @ template T as object
*/
class Foo {
2018-12-18 05:29:27 +01:00
/** @var T::class */
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
],
2017-04-25 05:45:02 +02:00
'classTemplateContainer' => [
' < ? php
class A {}
2017-06-29 16:22:49 +02:00
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 ;
}
}
2017-06-29 16:22:49 +02:00
2017-04-25 05:45:02 +02:00
$afoo = new Foo ( new A ());
$afoo_bar = $afoo -> bar (); ' ,
'assertions' => [
2017-06-29 16:22:49 +02:00
'$afoo' => 'Foo<A>' ,
'$afoo_bar' => 'A' ,
2017-04-25 05:45:02 +02:00
],
2018-04-20 16:52:23 +02:00
'error_levels' => [ 'MixedOperand' ],
2017-04-25 05:45:02 +02:00
],
'validTemplatedType' => [
' < ? php
2017-07-25 22:11:02 +02:00
namespace FooFoo ;
2017-04-25 05:45:02 +02:00
/**
* @ template T
* @ param T $x
* @ return T
*/
function foo ( $x ) {
return $x ;
}
2017-06-29 16:22:49 +02:00
2018-01-11 21:50:45 +01:00
function bar ( string $a ) : void { }
2017-06-29 16:22:49 +02:00
2017-05-27 02:05:57 +02:00
bar ( foo ( " string " )); ' ,
2017-04-25 05:45:02 +02:00
],
2018-07-14 01:09:35 +02:00
'validPsalmTemplatedType' => [
' < ? php
namespace FooFoo ;
/**
* @ psalm - template T
* @ psalm - param T $x
* @ psalm - return T
*/
function foo ( $x ) {
return $x ;
}
function bar ( string $a ) : void { }
bar ( foo ( " string " )); ' ,
],
2017-04-25 05:45:02 +02:00
'validTemplatedStaticMethodType' => [
' < ? php
2017-07-25 22:11:02 +02:00
namespace FooFoo ;
2017-04-25 05:45:02 +02:00
class A {
/**
* @ template T
* @ param T $x
* @ return T
*/
public static function foo ( $x ) {
return $x ;
}
}
2017-06-29 16:22:49 +02:00
2018-01-11 21:50:45 +01:00
function bar ( string $a ) : void { }
2017-06-29 16:22:49 +02:00
2017-05-27 02:05:57 +02:00
bar ( A :: foo ( " string " )); ' ,
2017-04-25 05:45:02 +02:00
],
'validTemplatedInstanceMethodType' => [
' < ? php
2017-07-25 22:11:02 +02:00
namespace FooFoo ;
2017-04-25 05:45:02 +02:00
class A {
/**
* @ template T
* @ param T $x
* @ return T
*/
public function foo ( $x ) {
return $x ;
}
}
2017-06-29 16:22:49 +02:00
2018-01-11 21:50:45 +01:00
function bar ( string $a ) : void { }
2017-06-29 16:22:49 +02:00
2017-05-27 02:05:57 +02:00
bar (( new A ()) -> foo ( " string " )); ' ,
2017-04-25 05:45:02 +02:00
],
'genericArrayKeys' => [
' < ? php
/**
2019-01-05 06:15:53 +01:00
* @ template T as array - key
2017-04-25 05:45:02 +02:00
*
* @ param array < T , mixed > $arr
* @ return array < int , T >
*/
function my_array_keys ( $arr ) {
return array_keys ( $arr );
}
2017-06-29 16:22:49 +02:00
2017-07-25 22:11:02 +02:00
$a = my_array_keys ([ " hello " => 5 , " goodbye " => new \Exception ()]); ' ,
2017-04-25 05:45:02 +02:00
'assertions' => [
2017-06-29 16:22:49 +02:00
'$a' => 'array<int, string>' ,
2017-05-27 02:05:57 +02:00
],
2017-04-25 05:45:02 +02:00
],
2017-12-07 21:50:25 +01:00
'genericArrayFlip' => [
2017-04-25 05:45:02 +02:00
' < ? php
/**
2019-01-05 06:15:53 +01:00
* @ template TKey as array - key
2019-01-26 22:58:49 +01:00
* @ template TValue as array - key
2017-04-25 05:45:02 +02:00
*
* @ param array < TKey , TValue > $arr
* @ return array < TValue , TKey >
*/
2017-12-07 21:50:25 +01:00
function my_array_flip ( $arr ) {
return array_flip ( $arr );
2017-04-25 05:45:02 +02:00
}
2017-06-29 16:22:49 +02:00
2017-12-07 21:50:25 +01:00
$b = my_array_flip ([ " hello " => 5 , " goodbye " => 6 ]); ' ,
2017-04-25 05:45:02 +02:00
'assertions' => [
2017-06-29 16:22:49 +02:00
'$b' => 'array<int, string>' ,
2017-05-27 02:05:57 +02:00
],
],
2018-11-21 18:38:43 +01:00
'byRefKeyValueArray' => [
' < ? php
/**
* @ template TValue
2019-01-05 06:15:53 +01:00
* @ template TKey as array - key
2018-11-21 18:38:43 +01:00
*
* @ param array < TKey , TValue > $arr
*/
function byRef ( array & $arr ) : void {}
$b = [ " a " => 5 , " c " => 6 ];
byRef ( $b ); ' ,
'assertions' => [
'$b' => 'array<string, int>' ,
],
],
'byRefMixedKeyArray' => [
' < ? php
/**
* @ template TValue
*
* @ param array < mixed , TValue > $arr
*/
function byRef ( array & $arr ) : void {}
$b = [ " a " => 5 , " c " => 6 ];
byRef ( $b ); ' ,
'assertions' => [
'$b' => 'array<mixed, int>' ,
],
],
'mixedArrayPop' => [
2017-11-20 06:12:17 +01:00
' < ? php
/**
* @ template TValue
*
2019-01-05 06:15:53 +01:00
* @ param array < array - key , TValue > $arr
2017-12-07 21:50:25 +01:00
* @ return TValue | null
2017-11-20 06:12:17 +01:00
*/
function my_array_pop ( array & $arr ) {
return array_pop ( $arr );
}
/** @var mixed */
$b = [ " a " => 5 , " c " => 6 ];
$a = my_array_pop ( $b ); ' ,
'assertions' => [
'$a' => 'mixed' ,
2019-01-05 06:15:53 +01:00
'$b' => 'array<array-key, mixed>' ,
2017-11-20 06:12:17 +01:00
],
'error_levels' => [ 'MixedAssignment' , 'MixedArgument' ],
],
2018-11-21 18:38:43 +01:00
'genericArrayPop' => [
' < ? php
/**
* @ template TValue
2019-01-05 06:15:53 +01:00
* @ template TKey as array - key
2018-11-21 18:38:43 +01:00
*
* @ param array < TKey , TValue > $arr
* @ return TValue | null
*/
function my_array_pop ( array & $arr ) {
return array_pop ( $arr );
}
$b = [ " a " => 5 , " c " => 6 ];
$a = my_array_pop ( $b ); ' ,
'assertions' => [
'$a' => 'null|int' ,
'$b' => 'array<string, int>' ,
],
],
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-11 22:17:21 +02:00
'templateCallableReturnType' => [
' < ? php
namespace NS ;
/**
* @ template T
* @ psalm - param callable () : T $action
* @ psalm - return T
*/
function retry ( int $maxRetries , callable $action ) {
return $action ();
}
function takesInt ( int $p ) : void {};
takesInt ( retry ( 1 , function () : int { return 1 ; })); ' ,
],
'templateClosureReturnType' => [
' < ? php
namespace NS ;
/**
* @ template T
* @ psalm - param \Closure () : T $action
* @ psalm - return T
*/
function retry ( int $maxRetries , callable $action ) {
return $action ();
}
function takesInt ( int $p ) : void {};
takesInt ( retry ( 1 , function () : int { return 1 ; })); ' ,
],
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 ;}));
takesCollectionOfItems ( $c -> map ( function ( Item $i ) : Item { return $i ;})); '
],
2019-01-10 22:59:44 +01:00
'replaceChildTypeWithGenerator' => [
2018-05-28 23:26:43 +02:00
' < ? php
/**
2019-01-05 06:15:53 +01:00
* @ template TKey as array - key
2018-05-28 23:26:43 +02:00
* @ template TValue
* @ param Traversable < TKey , TValue > $t
* @ return array < TKey , TValue >
*/
function f ( Traversable $t ) : array {
$ret = [];
foreach ( $t as $k => $v ) $ret [ $k ] = $v ;
return $ret ;
}
/** @return Generator<int, stdClass> */
function g () : Generator { yield new stdClass ; }
takesArrayOfStdClass ( f ( g ()));
/** @param array<stdClass> $p */
function takesArrayOfStdClass ( array $p ) : void {} ' ,
],
2018-06-13 14:38:07 +02:00
'noRepeatedTypeException' => [
' < ? php
/** @template T */
class Foo
{
/**
* @ psalm - var class - string
*/
private $type ;
/** @var array<T> */
private $items ;
/**
* @ param class - string $type
* @ template - typeof T $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 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
{
$type = $items [ 0 ] instanceof A ? A :: class : B :: class ;
return new Foo ( $type );
}
}
class A {}
class B {} '
],
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
* @ psalm - suppress MixedTypeCoercion
2018-06-19 19:19:34 +02:00
*/
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-06-20 16:40:50 +02:00
'splatTemplateParam' => [
' < ? php
/**
2019-01-05 06:15:53 +01:00
* @ template TKey as array - key
2018-06-20 16:40:50 +02:00
* @ template TValue
*
* @ param array < TKey , TValue > $arr
* @ param array $arr2
* @ return array < TKey , TValue >
*/
function splat_proof ( array $arr , array $arr2 ) {
return $arr ;
}
$foo = [
[ 1 , 2 , 3 ],
[ 1 , 2 ],
];
$a = splat_proof ( ... $foo ); ' ,
'assertions' => [
'$a' => 'array<int, int>' ,
],
],
2018-11-21 18:38:43 +01:00
'passArrayByRef' => [
' < ? php
function acceptsStdClass ( stdClass $_p ) : void {}
$q = [ new stdClass ];
acceptsStdClass ( fNoRef ( $q ));
acceptsStdClass ( fRef ( $q ));
acceptsStdClass ( fNoRef ( $q ));
/**
2019-01-05 06:15:53 +01:00
* @ template TKey as array - key
2018-11-21 18:38:43 +01:00
* @ template TValue
*
* @ param array < TKey , TValue > $_arr
* @ return null | TValue
* @ psalm - ignore - nullable - return
*/
function fRef ( array & $_arr ) {
return array_shift ( $_arr );
}
/**
2019-01-05 06:15:53 +01:00
* @ template TKey as array - key
2018-11-21 18:38:43 +01:00
* @ template TValue
*
* @ param array < TKey , TValue > $_arr
* @ return null | TValue
* @ psalm - ignore - nullable - return
*/
function fNoRef ( array $_arr ) {
return array_shift ( $_arr );
} ' ,
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 ;
}
/** @psalm-suppress LessSpecificImplementedReturnType */
public function getIterator () : \Traversable {
return new \ArrayIterator ( $this -> data );
}
}
/** @var ICollection<string, int> */
$c = new Collection ([ " a " => 1 ]);
foreach ( $c as $k => $v ) { atan ( $v ); strlen ( $k ); } '
],
'templatedInterfaceGetIteratorIteration' => [
' < ? 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 ;
}
/** @psalm-suppress LessSpecificImplementedReturnType */
public function getIterator () : \Traversable {
return new \ArrayIterator ( $this -> data );
}
}
/** @var ICollection<string, int> */
$c = new Collection ([ " a " => 1 ]);
foreach ( $c -> getIterator () as $k => $v ) { atan ( $v ); strlen ( $k ); } '
],
2018-11-22 00:38:09 +01:00
'implictIteratorTemplating' => [
' < ? php
2019-01-10 23:58:32 +01:00
/**
2019-01-24 23:55:03 +01:00
* @ template - implements IteratorAggregate < int , int >
2019-01-10 23:58:32 +01:00
*/
2018-11-22 00:38:09 +01:00
class SomeIterator implements IteratorAggregate
{
function getIterator ()
{
yield 1 ;
}
}
/** @param \IteratorAggregate<mixed, int> $i */
function takesIteratorOfInts ( \IteratorAggregate $i ) : void {
foreach ( $i as $j ) {
echo $j ;
}
}
takesIteratorOfInts ( new SomeIterator ()); '
],
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 ;
/**
* @ param T & Foo $closure
*/
public function __construct ( Foo $bar )
{
$this -> bar = $bar ;
}
/**
* @ return T & Foo
*/
public function makeFoo ()
{
return $this -> bar ;
}
} '
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-17 21:49:59 +01:00
'classTemplateAsCorrect' => [
' < ? php
class Foo {}
class FooChild extends Foo {}
/**
* @ template T as Foo
* @ param T $x
* @ return T
*/
function bar ( $x ) {
return $x ;
}
bar ( new Foo ());
bar ( new FooChild ()); ' ,
],
2019-01-16 15:23:18 +01:00
'classTemplateOfCorrect' => [
' < ? php
class Foo {}
class FooChild extends Foo {}
/**
* @ template T of Foo
* @ param T $x
* @ return T
*/
function bar ( $x ) {
return $x ;
}
bar ( new Foo ());
bar ( new FooChild ()); ' ,
],
2018-12-17 21:49:59 +01:00
'classTemplateAsInterface' => [
' < ? php
interface Foo {}
interface FooChild extends Foo {}
class FooImplementer implements Foo {}
/**
* @ template T as Foo
* @ param T $x
* @ return T
*/
function bar ( $x ) {
return $x ;
}
function takesFoo ( Foo $f ) : void {
bar ( $f );
}
function takesFooChild ( FooChild $f ) : void {
bar ( $f );
}
function takesFooImplementer ( FooImplementer $f ) : void {
bar ( $f );
} ' ,
],
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 ();
} ' ,
],
'templateFunctionVar' => [
' < ? php
namespace A\B ;
class C {
public function bar () : void {}
}
interface D {}
/**
* @ template T as C
* @ return T
*/
function foo ( $some_t ) : C {
/** @var T */
$a = $some_t ;
$a -> bar ();
/** @var T&D */
$b = $some_t ;
$b -> bar ();
/** @var D&T */
$b = $some_t ;
$b -> bar ();
return $a ;
} ' ,
'assertions' => [],
'error_levels' => [ 'MixedAssignment' , 'MissingParamType' ],
],
2019-01-02 12:46:10 +01:00
'returnClassString' => [
' < ? php
/**
* @ template T
* @ param T :: class $s
* @ return T :: class
*/
function foo ( string $s ) : string {
return $s ;
}
/**
* @ param A :: class $s
*/
function bar ( string $s ) : void {
}
class A {}
bar ( foo ( A :: class )); '
],
2019-01-05 06:15:53 +01:00
'callStaticMethodOnTemplatedClassName' => [
' < ? php
/**
* @ template T
* @ param class - string $class
* @ template - typeof T $class
*/
function foo ( string $class , array $args ) : void {
$class :: bar ( $args );
} ' ,
'assertions' => [],
'error_levels' => [ 'MixedMethodCall' ],
],
2019-01-13 20:29:04 +01:00
'returnTemplatedClassClassName' => [
' < ? php
class I {
/**
* @ template T as Foo
* @ param class - string $class
* @ template - typeof T $class
* @ return T | null
*/
2019-01-26 22:58:49 +01:00
public function loader ( string $class ) {
2019-01-13 20:29:04 +01:00
return $class :: load ();
}
}
class Foo {
/** @return static */
public static function load () {
return new static ();
}
}
class FooChild extends Foo {}
$a = ( new I ) -> loader ( FooChild :: class ); ' ,
'assertions' => [
'$a' => 'null|FooChild' ,
],
],
2019-01-05 16:32:39 +01:00
'upcastIterableToTraversable' => [
' < ? php
/**
* @ template T as iterable
* @ param T :: class $class
*/
function foo ( string $class ) : void {
$a = new $class ();
foreach ( $a as $b ) {}
} ' ,
'assertions' => [],
'error_levels' => [ 'MixedAssignment' ],
],
'upcastGenericIterableToGenericTraversable' => [
' < ? php
/**
* @ template T as iterable < int >
* @ param T :: class $class
*/
function foo ( string $class ) : void {
$a = new $class ();
foreach ( $a as $b ) {}
} ' ,
'assertions' => [],
'error_levels' => [],
],
2019-01-06 18:16:09 +01:00
'bindFirstTemplatedClosureParameter' => [
' < ? php
/**
* @ template T
*
* @ param Closure ( T ) : void $t1
* @ param T $t2
*/
function apply ( Closure $t1 , $t2 ) : void
{
$t1 ( $t2 );
}
apply ( function ( int $_i ) : void {}, 5 );
apply ( function ( string $_i ) : void {}, " hello " );
apply ( function ( stdClass $_i ) : void {}, new stdClass );
class A {}
class AChild extends A {}
apply ( function ( A $_i ) : void {}, new AChild ()); ' ,
],
2019-01-08 22:55:53 +01:00
'getPropertyOnClass' => [
' < ? php
class Foo {
/** @var int */
public $id = 0 ;
}
/**
* @ 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 >
*/
public function getType ()
{
return $this -> type ;
}
/**
* @ param T $object
*/
public function bar ( Foo $object ) : void
{
if ( $this -> getType () !== get_class ( $object )) {
return ;
}
echo $object -> id ;
}
2019-01-13 20:40:21 +01:00
}
class FooChild extends Foo {}
/** @param Collection<Foo> $c */
function handleCollectionOfFoo ( Collection $c ) : void {
if ( $c -> getType () === FooChild :: class ) {}
2019-01-08 22:55:53 +01:00
} ' ,
],
'getEquateClass' => [
' < ? php
class Foo {
/** @var int */
public $id = 0 ;
}
/**
* @ template T as Foo
*/
class Container {
/**
* @ var T
*/
private $obj ;
/**
* @ 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-01-10 18:13:49 +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>' ,
'$b' => 'mixed'
],
'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-01-10 22:59:44 +01:00
],
2019-01-26 04:33:42 +01:00
'callableReturnsItself' => [
2019-01-10 22:59:44 +01:00
' < ? php
2019-01-26 04:33:42 +01:00
$a =
/**
* @ param callable () : string $s
* @ return string
*/
function ( callable $s ) {
return $s ();
};
2019-01-10 22:59:44 +01:00
/**
2019-01-26 04:33:42 +01:00
* @ template T1
* @ param callable ( callable () : T1 ) : T1 $s
* @ return void
2019-01-10 22:59:44 +01:00
*/
2019-01-26 04:33:42 +01:00
function takesReturnTCallable ( callable $s ) {}
takesReturnTCallable ( $a ); '
2019-01-10 22:59:44 +01:00
],
2019-01-26 04:33:42 +01:00
'nonBindingParamReturn' => [
2019-01-10 23:58:32 +01:00
' < ? php
/**
* @ template T
2019-01-26 04:33:42 +01:00
*
* @ param Closure () : T $t1
* @ param T $t2
2019-01-10 23:58:32 +01:00
*/
2019-01-26 04:33:42 +01:00
function foo ( Closure $t1 , $t2 ) : void {}
foo (
function () : int {
return 5 ;
},
" hello "
); '
],
'templatedInterfaceMethodInheritReturnType' => [
' < ? php
class Foo {}
2019-01-10 23:58:32 +01:00
2019-01-26 04:33:42 +01:00
class SomeIterator implements IteratorAggregate
2019-01-10 23:58:32 +01:00
{
2019-01-26 04:33:42 +01:00
public function getIterator () {
yield new Foo ;
2019-01-10 23:58:32 +01:00
}
}
2019-01-26 04:33:42 +01:00
$i = ( new SomeIterator ()) -> getIterator (); ' ,
[
'$i' => 'Traversable<mixed, mixed>' ,
]
],
'upcastArrayToIterable' => [
' < ? php
2019-01-10 23:58:32 +01:00
/**
2019-01-26 04:33:42 +01:00
* @ template K
* @ template V
* @ param iterable < K , V > $collection
* @ return V
* @ psalm - suppress InvalidReturnType
2019-01-19 22:01:43 +01:00
*/
2019-01-26 04:33:42 +01:00
function first ( $collection ) {}
2019-01-19 22:01:43 +01:00
2019-01-26 04:33:42 +01:00
$one = first ([ 1 , 2 , 3 ]); ' ,
2019-01-19 22:01:43 +01:00
[
2019-01-26 04:33:42 +01:00
'$one' => 'int' ,
2019-01-19 22:01:43 +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-01-27 21:08:17 +01:00
'understandTemplatedCalculationInOtherFunction' => [
' < ? php
/**
* @ template T as Exception
* @ param T :: class $type
* @ return T
*/
function a ( string $type ) : Exception {
return new $type ;
}
/**
* @ template T as InvalidArgumentException
* @ param T :: class $type
* @ return T
*/
function b ( string $type ) : InvalidArgumentException {
return a ( $type );
} ' ,
],
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-02-01 16:55:28 +01:00
'objectReturn' => [
' < ? php
/**
* @ template T as object
*
* @ param class - string < T > $foo
*
* @ return T
*/
function Foo ( string $foo ) : object {
return new $foo ;
}
echo Foo ( DateTime :: class ) -> format ( " c " ); ' ,
],
2019-02-01 20:13:21 +01:00
'genericInterface' => [
' < ? php
/**
* @ template T as object
* @ param class - string < T > $t
* @ return T
*/
function generic ( string $t ) {
return f ( $t ) -> get ();
}
/** @template T as object */
interface I {
/** @return T */
public function get () {}
}
/**
* @ template T as object
* @ template - implements I < T >
*/
class C implements I {
/**
* @ var T
*/
public $t ;
/**
* @ param T $t
*/
public function __construct ( object $t ) {
$this -> t = $t ;
}
/**
* @ return T
*/
public function get () {
return $this -> t ;
}
}
/**
* @ template T as object
* @ param class - string < T > $t
* @ return I < T >
*/
function f ( string $t ) {
return new C ( new $t );
} ' ,
],
2019-02-06 18:37:05 +01:00
'templateIntersectionLeft' => [
' < ? php
interface I1 {}
interface I2 {}
/**
* @ template T as I1 & I2
* @ param T $a
*/
function templatedBar ( I1 $a ) : void {} '
],
'templateIntersectionRight' => [
' < ? php
interface I1 {}
interface I2 {}
/**
* @ template T as I1 & I2
* @ param T $b
*/
2019-02-06 21:41:20 +01:00
function templatedBar ( I2 $b ) : void {} ' ,
],
'matchMostSpecificTemplate' => [
' < ? php
/**
* @ template TReturn
* @ param callable () : ( \Generator < mixed , mixed , mixed , TReturn >| TReturn ) $gen
* @ return array < int , TReturn >
*/
function call ( callable $gen ) : array {
$return = $gen ();
if ( $return instanceof Generator ) {
return [ $gen -> getReturn ()];
}
return [ $gen ];
}
$arr = call (
/**
* @ return Generator < mixed , mixed , mixed , string >
*/
function () {
yield 1 ;
return " hello " ;
}
); ' ,
[
'$arr' => 'array<int, string>' ,
]
2019-02-06 18:37:05 +01:00
],
2019-02-08 19:09:36 +01:00
'templatedClassStringParam' => [
' < ? php
abstract class C {
public function foo () : void {}
}
class E {
/**
* @ template T as C
* @ param class - string < T > $c_class
*
* @ return C
* @ psalm - return T
*/
public static function get ( string $c_class ) : C {
$c = new $c_class ;
$c -> foo ();
return $c ;
}
}
/**
* @ param class - string < C > $c_class
*/
function bar ( string $c_class ) : void {
$c = E :: get ( $c_class );
$c -> foo ();
}
/**
* @ psalm - suppress TypeCoercion
*/
function bat ( string $c_class ) : void {
$c = E :: get ( $c_class );
$c -> foo ();
} '
],
'SKIPPED-templatedClassStringParamMoreSpecific' => [
' < ? php
abstract class C {
public function foo () : void {}
}
class D extends C {
public function faa () : void {}
}
class E {
/**
* @ template T as C
* @ param class - string < T > $c_class
*
* @ return C
* @ psalm - return T
*/
public static function get ( string $c_class ) : C {
$c = new $c_class ;
$c -> foo ();
return $c ;
}
}
/**
* @ param class - string < D > $d_class
*/
function moreSpecific ( string $d_class ) : void {
$d = E :: get ( $d_class );
$d -> foo ();
$d -> faa ();
} '
],
2019-01-26 04:33:42 +01:00
];
}
/**
* @ return array
*/
public function providerInvalidCodeParse ()
{
return [
'invalidTemplatedType' => [
2019-01-19 22:01:43 +01:00
' < ? php
2019-01-26 04:33:42 +01:00
namespace FooFoo ;
2019-01-19 22:01:43 +01:00
/**
* @ template T
2019-01-26 04:33:42 +01:00
* @ param T $x
* @ return T
2017-04-25 05:45:02 +02:00
*/
function foo ( $x ) {
return $x ;
}
2017-06-29 16:22:49 +02:00
2018-01-11 21:50:45 +01:00
function bar ( string $a ) : void { }
2017-06-29 16:22:49 +02:00
2017-04-25 05:45:02 +02:00
bar ( foo ( 4 )); ' ,
2017-05-27 02:05:57 +02:00
'error_message' => 'InvalidScalarArgument' ,
2017-04-25 05:45:02 +02:00
],
'invalidTemplatedStaticMethodType' => [
' < ? php
2017-07-25 22:11:02 +02:00
namespace FooFoo ;
2017-04-25 05:45:02 +02:00
class A {
/**
* @ template T
* @ param T $x
* @ return T
*/
public static function foo ( $x ) {
return $x ;
}
}
2017-06-29 16:22:49 +02:00
2018-01-11 21:50:45 +01:00
function bar ( string $a ) : void { }
2017-06-29 16:22:49 +02:00
2017-04-25 05:45:02 +02:00
bar ( A :: foo ( 4 )); ' ,
2017-05-27 02:05:57 +02:00
'error_message' => 'InvalidScalarArgument' ,
2017-04-25 05:45:02 +02:00
],
'invalidTemplatedInstanceMethodType' => [
' < ? php
2017-07-25 22:11:02 +02:00
namespace FooFoo ;
2017-04-25 05:45:02 +02:00
class A {
/**
* @ template T
* @ param T $x
* @ return T
*/
public function foo ( $x ) {
return $x ;
}
}
2017-06-29 16:22:49 +02:00
2018-01-11 21:50:45 +01:00
function bar ( string $a ) : void { }
2017-06-29 16:22:49 +02:00
2017-04-25 05:45:02 +02:00
bar (( new A ()) -> foo ( 4 )); ' ,
2017-05-27 02:05:57 +02:00
'error_message' => 'InvalidScalarArgument' ,
],
2018-05-28 23:26:43 +02:00
'replaceChildTypeNoHint' => [
' < ? php
/**
2019-01-26 22:58:49 +01:00
* @ template TKey as array - key
2018-05-28 23:26:43 +02:00
* @ template TValue
* @ param Traversable < TKey , TValue > $t
* @ return array < TKey , TValue >
*/
function f ( Traversable $t ) : array {
$ret = [];
foreach ( $t as $k => $v ) $ret [ $k ] = $v ;
return $ret ;
}
function g () : Generator { yield new stdClass ; }
takesArrayOfStdClass ( f ( g ()));
/** @param array<stdClass> $p */
function takesArrayOfStdClass ( array $p ) : void {} ' ,
'error_message' => 'MixedTypeCoercion' ,
],
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-06 20:19:29 +01:00
'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:20 - Argument 1 of type expects string, callable(State):(T as mixed)&Foo provided' ,
2018-12-17 21:49:59 +01:00
],
'classTemplateAsIncorrectClass' => [
' < ? php
class Foo {}
class NotFoo {}
/**
* @ template T as Foo
* @ param T $x
* @ return T
*/
function bar ( $x ) {
return $x ;
}
bar ( new NotFoo ()); ' ,
'error_message' => 'InvalidArgument' ,
],
'classTemplateAsIncorrectInterface' => [
' < ? php
interface Foo {}
interface NotFoo {}
/**
* @ template T as Foo
* @ param T $x
* @ return T
*/
function bar ( $x ) {
return $x ;
}
function takesNotFoo ( NotFoo $f ) : void {
bar ( $f );
} ' ,
'error_message' => 'InvalidArgument' ,
],
2018-12-18 05:29:27 +01:00
'templateFunctionMethodCallWithoutMethod' => [
' < ? php
namespace A\B ;
class C {}
/**
* @ template T as C
* @ param T $some_t
*/
function foo ( $some_t ) : void {
$some_t -> bar ();
} ' ,
'error_message' => 'PossiblyUndefinedMethod' ,
],
'templateFunctionMethodCallWithoutAsType' => [
' < ? php
namespace A\B ;
/**
* @ template T
* @ param T $some_t
*/
function foo ( $some_t ) : void {
$some_t -> bar ();
} ' ,
'error_message' => 'MixedMethodCall' ,
],
2019-01-05 16:32:39 +01:00
'forbidLossOfInformationWhenCoercing' => [
' < ? php
/**
* @ template T as iterable < int >
* @ param T :: class $class
*/
function foo ( string $class ) : void {}
function bar ( Traversable $t ) : void {
foo ( get_class ( $t ));
} ' ,
'error_message' => 'MixedTypeCoercion' ,
],
2019-01-06 18:16:09 +01:00
'bindFirstTemplatedClosureParameter' => [
' < ? php
/**
* @ template T
*
* @ param Closure ( T ) : void $t1
* @ param T $t2
*/
function apply ( Closure $t1 , $t2 ) : void
{
$t1 ( $t2 );
}
apply ( function ( int $_i ) : void {}, " hello " ); ' ,
'error_message' => 'InvalidScalarArgument' ,
],
'bindFirstTemplatedClosureParameterTypeCoercion' => [
' < ? php
/**
* @ template T
*
* @ param Closure ( T ) : void $t1
* @ param T $t2
*/
function apply ( Closure $t1 , $t2 ) : void
{
$t1 ( $t2 );
}
class A {}
class AChild extends A {}
apply ( function ( AChild $_i ) : void {}, new A ()); ' ,
'error_message' => 'TypeCoercion' ,
],
2019-01-13 00:18:23 +01:00
2019-01-14 21:50:25 +01:00
'callableDoesNotReturnItself' => [
' < ? php
$b =
/**
* @ param callable () : int $s
* @ return string
*/
function ( callable $s ) {
return " # " . $s ();
};
/**
* @ template T1
* @ param callable ( callable () : T1 ) : T1 $s
* @ return void
*/
function takesReturnTCallable ( callable $s ) {}
takesReturnTCallable ( $b ); ' ,
'error_message' => 'InvalidScalarArgument' ,
],
2019-01-20 00:35:53 +01:00
'multipleArgConstraintWithMoreRestrictiveFirstArg' => [
' < ? php
class A {}
class AChild extends A {}
/**
* @ template T
* @ param callable ( T ) : void $c1
* @ param callable ( T ) : void $c2
* @ param T $a
*/
function foo ( callable $c1 , callable $c2 , $a ) : void {
$c1 ( $a );
$c2 ( $a );
}
foo (
function ( AChild $_a ) : void {},
function ( A $_a ) : void {},
new A ()
); ' ,
'error_message' => 'TypeCoercion' ,
],
'multipleArgConstraintWithMoreRestrictiveSecondArg' => [
' < ? php
class A {}
class AChild extends A {}
/**
* @ template T
* @ param callable ( T ) : void $c1
* @ param callable ( T ) : void $c2
* @ param T $a
*/
function foo ( callable $c1 , callable $c2 , $a ) : void {
$c1 ( $a );
$c2 ( $a );
}
foo (
function ( A $_a ) : void {},
function ( AChild $_a ) : void {},
new A ()
); ' ,
'error_message' => 'TypeCoercion' ,
],
'multipleArgConstraintWithLessRestrictiveThirdArg' => [
' < ? php
class A {}
class AChild extends A {}
/**
* @ template T
* @ param callable ( T ) : void $c1
* @ param callable ( T ) : void $c2
* @ param T $a
*/
function foo ( callable $c1 , callable $c2 , $a ) : void {
$c1 ( $a );
$c2 ( $a );
}
foo (
function ( AChild $_a ) : void {},
function ( AChild $_a ) : void {},
new A ()
); ' ,
'error_message' => 'TypeCoercion' ,
],
2019-01-20 00:51:18 +01:00
'possiblyInvalidArgumentWithUnionFirstArg' => [
' < ? php
/**
* @ template T
* @ param T $a
* @ param T $b
* @ return T
*/
function foo ( $a , $b ) {
return rand ( 0 , 1 ) ? $a : $b ;
}
echo foo ([], " hello " ); ' ,
'error_message' => 'PossiblyInvalidArgument' ,
],
'possiblyInvalidArgumentWithUnionSecondArg' => [
' < ? php
/**
* @ template T
* @ param T $a
* @ param T $b
* @ return T
*/
function foo ( $a , $b ) {
return rand ( 0 , 1 ) ? $a : $b ;
}
echo foo ( " hello " , []); ' ,
'error_message' => 'PossiblyInvalidArgument' ,
],
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-05 23:43:46 +01:00
'classTemplateUnionType' => [
2019-02-05 23:26:28 +01:00
' < ? php
/**
* @ template T0 as int | string
*/
class Foo {} ' ,
'error_message' => 'InvalidDocblock'
],
2019-02-05 23:43:46 +01:00
'functionTemplateUnionType' => [
' < ? php
/**
* @ template T0 as int | string
*/
function foo () : void {} ' ,
'error_message' => 'InvalidDocblock'
],
2017-04-25 05:45:02 +02:00
];
2017-02-10 04:57:23 +01:00
}
2017-02-10 02:35:17 +01:00
}