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
/**
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 {
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
],
2019-02-09 17:02:24 +01:00
'validPsalmTemplatedFunctionType' => [
2018-07-14 01:09:35 +02:00
' < ? 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 " )); ' ,
],
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 ()); ' ,
],
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 ;}));
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
],
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 {}
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
* @ 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 ],
];
2019-04-09 19:58:49 +02:00
$a = splat_proof ( ... $foo ); ' ,
2018-06-20 16:40:50 +02:00
'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 ]);
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
],
'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 ]);
2019-03-23 19:27:54 +01:00
foreach ( $c -> getIterator () 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-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 {}
2019-03-23 19:27:54 +01:00
bar ( foo ( A :: class )); ' ,
2019-01-02 12:46:10 +01:00
],
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
2019-06-24 16:54:03 +02:00
* @ param class - string < T > $class
2019-01-13 20:29:04 +01:00
* @ 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-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-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 ) {}
2019-03-23 19:27:54 +01:00
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 "
2019-03-23 19:27:54 +01:00
); ' ,
2019-01-26 04:33:42 +01:00
],
'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>' ,
2019-03-23 19:27:54 +01:00
],
2019-01-26 04:33:42 +01:00
],
'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-03-23 19:27:54 +01:00
],
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-03-23 19:27:54 +01:00
],
2019-01-27 20:20:41 +01:00
],
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-03-23 19:27:54 +01:00
],
2019-01-28 23:09:23 +01:00
],
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-06 18:37:05 +01:00
'templateIntersectionLeft' => [
' < ? php
interface I1 {}
interface I2 {}
/**
* @ template T as I1 & I2
* @ param T $a
*/
2019-03-23 19:27:54 +01:00
function templatedBar ( I1 $a ) : void {} ' ,
2019-02-06 18:37:05 +01:00
],
'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-03-23 19:27:54 +01:00
],
2019-02-06 18:37:05 +01:00
],
2019-05-24 19:35:14 +02:00
'templatedClassStringParamAsClass' => [
2019-02-08 19:09:36 +01:00
' < ? 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 ();
2019-03-23 19:27:54 +01:00
} ' ,
2019-02-08 19:09:36 +01:00
],
2019-05-24 19:35:14 +02:00
'templatedClassStringParamAsObject' => [
' < ? php
abstract class C {
public function foo () : void {}
}
class E {
/**
* @ template T as object
* @ param class - string < T > $c_class
*
* @ psalm - return T
*/
public static function get ( string $c_class ) {
return new $c_class ;
}
}
/**
* @ psalm - suppress TypeCoercion
*/
function bat ( string $c_class ) : void {
$c = E :: get ( $c_class );
$c -> bar = " bax " ;
} ' ,
],
2019-02-08 19:34:30 +01:00
'templatedClassStringParamMoreSpecific' => [
2019-02-08 19:09:36 +01:00
' < ? 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-03-23 19:27:54 +01:00
} ' ,
2019-02-08 19:09:36 +01:00
],
2019-03-01 15:20:51 +01:00
'templateOfWithSpace' => [
' < ? php
/**
* @ template T of array < int , mixed >
*/
class Foo
{
}
/**
* @ param Foo < array < int , DateTime >> $a
*/
2019-03-23 19:27:54 +01:00
function bar ( Foo $a ) : void {} ' ,
2019-03-01 15:20:51 +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-03-16 16:15:25 +01:00
'allowUnionTypeParam' => [
' < ? php
/**
* @ template T
* @ param callable ( T ) $x
* @ param array < T > $y
*/
function example ( $x , $y ) : void {}
example (
2019-03-17 15:19:15 +01:00
/**
* @ param int | false $x
*/
2019-03-16 16:15:25 +01:00
function ( $x ) : void {},
[ strpos ( " str " , " str " )]
2019-03-23 19:27:54 +01:00
); ' ,
2019-03-16 16:15:25 +01:00
],
2019-03-17 15:19:15 +01:00
'reflectionClass' => [
' < ? 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
'ignoreTooManyArrayArgs' => [
' < ? php
function takesArray ( array $arr ) : void {}
/**
* @ psalm - suppress TooManyTemplateParams
* @ var array < int , int , int >
*/
$b = [ 1 , 2 , 3 ];
2019-03-23 19:27:54 +01:00
takesArray ( $b ); ' ,
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 {} ' ,
],
'functionTemplateUnionType' => [
' < ? php
/**
* @ template T0 as int | string
* @ param T0 $t
* @ return T0
*/
function foo ( $t ) {
return $t ;
}
$s = foo ( " hello " );
$i = foo ( 5 ); ' ,
'assertions' => [
'$s' => 'string' ,
'$i' => 'int' ,
],
],
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
*
* @ return array < I , V >
*/
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-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>' ,
'$a_or_b' => 'B|A' ,
],
],
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-08 19:23:47 +02:00
'reconcileTraversableTemplatedAndNormal' => [
' < ? php
function foo ( Traversable $t ) : void {
if ( $t instanceof IteratorAggregate ) {
$a = $t -> getIterator ();
$t = $a ;
}
if ( ! $t instanceof Iterator ) {
return ;
}
if ( rand ( 0 , 1 ) && rand ( 0 , 1 )) {
$t -> next ();
}
} ' ,
],
2019-05-17 21:40:52 +02:00
'templateArrayIntersection' => [
' < ? php
/**
* @ template T as object
* @ template S as object
* @ param array < T > $a
* @ param class - string < S > $type
* @ return array < T & S >
*/
function filter ( array $a , string $type ) : array {
$result = [];
foreach ( $a as $item ) {
if ( is_a ( $item , $type )) {
$result [] = $item ;
}
}
return $result ;
}
interface A {}
interface B {}
/** @var array<A> */
$x = [];
$y = filter ( $x , B :: class ); ' ,
[
'$y' => 'array<array-key, A&B>' ,
]
],
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 ; ' ,
[
'$a' => 'string'
]
],
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-24 05:53:48 +02:00
'keyOfTemplate' => [
' < ? php
/**
* @ template T as array
* @ template K as key - of < T >
*
* @ param T $o
* @ param K $name
*
* @ return T [ K ]
*/
function getOffset ( array $o , $name ) {
return $o [ $name ];
}
$a = [ " foo " => " hello " , " bar " => 2 ];
$b = getOffset ( $a , " foo " );
$c = getOffset ( $a , " bar " ); ' ,
[
'$b' => 'string' ,
'$c' => 'int' ,
]
],
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 ;
}
} '
],
'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-05-24 16:20:31 +02:00
'unionTOrClassStringTPassedClassString' => [
' < ? php
/**
* @ psalm - template T of object
* @ psalm - param T | class - string < T > $someType
* @ psalm - return T
*/
function getObject ( $someType ) {
if ( is_object ( $someType )) {
return $someType ;
}
return new $someType ();
}
class C {
function sayHello () : string {
return " hi " ;
}
}
getObject ( C :: class ) -> sayHello (); '
],
'unionTOrClassStringTPassedObject' => [
' < ? php
/**
* @ psalm - template T of object
* @ psalm - param T | class - string < T > $someType
* @ psalm - return T
*/
function getObject ( $someType ) {
if ( is_object ( $someType )) {
return $someType ;
}
return new $someType ();
}
class C {
function sayHello () : string {
return " hi " ;
}
}
getObject ( new C ()) -> sayHello (); '
],
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' ,
]
],
'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-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 ); ' ,
[
'$a' => 'C&I<C>'
]
],
2019-05-25 00:17:48 +02:00
'dontModifyByRefTemplatedArray' => [
' < ? php
class A {}
class B {}
/**
* @ template T of object
* @ param class - string < T > $className
* @ param array < T > $map
* @ param - out array < T > $map
* @ param int $id
* @ return T
*/
function get ( string $className , array & $map , int $id ) {
if ( ! array_key_exists ( $id , $map )) {
$map [ $id ] = new $className ();
}
return $map [ $id ];
}
/**
* @ param array < A > $mapA
*/
function getA ( int $id , array $mapA ) : A {
return get ( A :: class , $mapA , $id );
}
/**
* @ param array < B > $mapB
*/
function getB ( int $id , array $mapB ) : B {
return get ( B :: class , $mapB , $id );
} '
],
2019-05-25 18:44:47 +02:00
'dontGeneraliseBoundParamWithWiderCallable' => [
' < ? php
class C {
public function foo () : void {}
}
/**
* @ psalm - template T
* @ psalm - param T $t
* @ psalm - param callable ( ? T ) : void $callable
* @ return T
*/
function makeConcrete ( $t , callable $callable ) {
$callable ( rand ( 0 , 1 ) ? $t : null );
return $t ;
}
$c = makeConcrete ( new C (), function ( ? C $c ) : void {}); ' ,
[
'$c' => 'C' ,
]
],
2019-05-28 17:54:07 +02:00
'unionClassStringTWithTReturnsObjectWhenCoerced' => [
' < ? php
/**
* @ template T as object
* @ param T | class - string < T > $s
* @ return T
*/
function bar ( $s ) {
if ( is_object ( $s )) {
return $s ;
}
return new $s ();
}
function foo ( string $s ) : object {
/** @psalm-suppress ArgumentTypeCoercion */
return bar ( $s );
} '
],
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-06-13 14:55:36 +02:00
'allowBoolTemplateCoercion' => [
' < ? php
/** @template T */
class TestPromise {
/** @psalm-param T $value */
public function __construct ( $value ) {}
}
/** @return TestPromise<bool> */
function test () : TestPromise {
return new TestPromise ( true );
} ' ,
],
2019-06-19 18:00:07 +02:00
'allowTemplatedIntersectionFirst' => [
' < ? php
class MockObject
{
public function checkExpectations () : void
{
}
}
/**
* @ psalm - template RequestedType
* @ psalm - param class - string < RequestedType > $className
* @ psalm - return RequestedType & MockObject
* @ psalm - suppress MixedInferredReturnType
* @ psalm - suppress MixedReturnStatement
*/
function mock ( string $className )
{
eval ( \ ' " there be dragons " \ ' );
return $instance ;
}
class A {
public function foo () : void {}
}
/**
2019-06-20 16:09:03 +02:00
* @ psalm - template UnknownType
* @ psalm - param class - string < UnknownType > $className
2019-06-19 18:00:07 +02:00
*/
2019-06-20 16:09:03 +02:00
function useMockTemplated ( string $className ) : void
{
mock ( $className ) -> checkExpectations ();
}
mock ( A :: class ) -> foo (); '
],
'allowTemplatedIntersectionFirstTemplatedMock' => [
' < ? php
class MockObject
{
public function checkExpectations () : void
{
}
}
/**
* @ psalm - template RequestedType
* @ psalm - param class - string < RequestedType > $className
* @ psalm - return RequestedType & MockObject
* @ psalm - suppress MixedInferredReturnType
* @ psalm - suppress MixedReturnStatement
*/
function mock ( string $className )
{
eval ( \ ' " there be dragons " \ ' );
return $instance ;
}
class A {
public function foo () : void {}
}
/**
* @ psalm - template UnknownType
* @ psalm - param class - string < UnknownType > $className
*/
function useMockTemplated ( string $className ) : void
{
2019-06-19 18:00:07 +02:00
mock ( $className ) -> checkExpectations ();
}
mock ( A :: class ) -> foo (); '
],
'allowTemplatedIntersectionSecond' => [
' < ? php
class MockObject
{
public function checkExpectations () : void
{
}
}
/**
* @ psalm - template RequestedType
* @ psalm - param class - string < RequestedType > $className
* @ psalm - return MockObject & RequestedType
* @ psalm - suppress MixedInferredReturnType
* @ psalm - suppress MixedReturnStatement
*/
function mock ( string $className )
{
eval ( \ ' " there be dragons " \ ' );
return $instance ;
}
class A {
public function foo () : void {}
}
/**
* @ psalm - param class - string $className
*/
function useMock ( string $className ) : void {
mock ( $className ) -> checkExpectations ();
}
2019-06-20 16:09:03 +02:00
/**
* @ psalm - template UnknownType
* @ psalm - param class - string < UnknownType > $className
*/
function useMockTemplated ( string $className ) : void
{
mock ( $className ) -> checkExpectations ();
}
2019-06-19 18:00:07 +02:00
mock ( A :: class ) -> foo (); '
],
2019-06-24 23:57:01 +02:00
'allowTemplateTypeBeingUsedInsideFunction' => [
' < ? php
/**
* @ template T of DateTime
* @ param callable ( T ) $callable
* @ param T $value
*/
function foo ( callable $callable , DateTime $value ) : void {
$callable ( $value );
} ' ,
],
'callFindAnother' => [
' < ? php
/**
* @ template T as Foo
* @ param T $foo
* @ return T
*/
function loader ( $foo ) {
return $foo :: getAnother ();
}
class Foo {
/** @return static */
public static function getAnother () {
return new static ();
}
} ' ,
],
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 [
'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 {} ' ,
2019-04-26 00:02:19 +02:00
'error_message' => 'MixedArgumentTypeCoercion' ,
2018-05-28 23:26:43 +02:00
],
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
],
'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 ));
} ' ,
2019-04-26 00:02:19 +02:00
'error_message' => 'MixedArgumentTypeCoercion' ,
2019-01-05 16:32:39 +01:00
],
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 ()); ' ,
2019-04-26 00:02:19 +02:00
'error_message' => 'ArgumentTypeCoercion' ,
2019-01-06 18:16:09 +01:00
],
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 ()
); ' ,
2019-04-26 00:02:19 +02:00
'error_message' => 'ArgumentTypeCoercion' ,
2019-01-20 00:35:53 +01:00
],
'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 ()
); ' ,
2019-04-26 00:02:19 +02:00
'error_message' => 'ArgumentTypeCoercion' ,
2019-01-20 00:35:53 +01:00
],
'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 ()
); ' ,
2019-04-26 00:02:19 +02:00
'error_message' => 'ArgumentTypeCoercion' ,
2019-01-20 00:35:53 +01:00
],
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-22 03:54:00 +01:00
'copyScopedClassInFunction' => [
' < ? php
/**
* @ template Throwable as DOMNode
*
* @ param class - string < Throwable > $foo
*/
function Foo ( string $foo ) : string {
return $foo ;
} ' ,
'error_message' => 'ReservedWord' ,
],
'copyScopedClassInNamespacedFunction' => [
' < ? php
namespace Foo ;
class Bar {}
/**
* @ template Bar as DOMNode
*
* @ param class - string < Bar > $foo
*/
function Foo ( string $foo ) : string {
return $foo ;
} ' ,
'error_message' => 'ReservedWord' ,
],
'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 " ; ' ,
'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:47:29 - Argument 1 of CharacterRow::__set expects string(id)|string(name)|string(height), string(ame) provided' ,
],
2019-06-08 17:08:08 +02:00
'constrainTemplateTypeWhenClassStringUsed' => [
' < ? php
class GenericObjectFactory {
/**
* @ psalm - template T
* @ psalm - param class - string < T > $type
* @ psalm - return T
*/
public function getObject ( string $type )
{
return 3 ;
}
} ' ,
'error_message' => 'InvalidReturnStatement'
],
2019-06-25 01:07:29 +02:00
'preventTemplateTypeAsBeingUsedInsideFunction' => [
' < ? php
/**
* @ template T of DateTime
* @ param callable ( T ) $callable
*/
function foo ( callable $callable ) : void {
$callable ( new \DateTime ());
} ' ,
'error_message' => 'InvalidArgument'
],
2019-06-24 16:54:03 +02:00
'preventWrongTemplateBeingPassed' => [
' < ? php
/**
* @ template T of DateTime
* @ template T2 of DateTime
* @ param callable ( T ) : T $parameter
* @ param T2 $value
* @ return T
*/
function foo ( callable $parameter , $value )
{
return $parameter ( $value );
} ' ,
'error_message' => 'InvalidArgument'
],
2019-06-25 01:07:29 +02:00
'preventTemplateTypeReturnMoreGeneral' => [
' < ? php
/**
* @ template T of DateTimeInterface
* @ param T $x
* @ return T
*/
function foo ( $x )
{
return new \DateTime ();
} ' ,
'error_message' => 'InvalidReturnStatement'
],
2017-04-25 05:45:02 +02:00
];
2017-02-10 04:57:23 +01:00
}
2017-02-10 02:35:17 +01:00
}