2017-02-10 02:35:17 +01:00
|
|
|
<?php
|
|
|
|
namespace Psalm\Tests;
|
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
class TemplateTest extends TestCase
|
2017-02-10 02:35:17 +01:00
|
|
|
{
|
2017-04-25 05:45:02 +02:00
|
|
|
use Traits\FileCheckerInvalidCodeParseTestTrait;
|
|
|
|
use Traits\FileCheckerValidCodeParseTestTrait;
|
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
|
|
|
*/
|
2017-04-25 05:45:02 +02:00
|
|
|
public function providerFileCheckerValidCodeParse()
|
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 {
|
|
|
|
/** @var string */
|
|
|
|
public $T;
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/**
|
|
|
|
* @param string $T
|
|
|
|
* @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-04-25 05:45:02 +02:00
|
|
|
$cfoo = new Foo("C");
|
|
|
|
$cfoo_bar = $cfoo->bar();
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
$dt = "D";
|
|
|
|
$dfoo = new Foo($dt);',
|
|
|
|
'assertions' => [
|
2017-06-29 16:22:49 +02:00
|
|
|
'$afoo' => 'Foo<A>',
|
|
|
|
'$afoo_bar' => 'A',
|
2017-04-25 05:45:02 +02:00
|
|
|
|
2017-06-29 16:22:49 +02:00
|
|
|
'$bfoo' => 'Foo<B>',
|
|
|
|
'$bfoo_bar' => 'B',
|
2017-04-25 05:45:02 +02:00
|
|
|
|
2017-06-29 16:22:49 +02:00
|
|
|
'$cfoo' => 'Foo<C>',
|
|
|
|
'$cfoo_bar' => 'C',
|
2017-04-25 05:45:02 +02:00
|
|
|
|
2017-06-29 16:22:49 +02:00
|
|
|
'$dfoo' => 'Foo<mixed>',
|
2017-05-27 02:05:57 +02:00
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
2017-07-25 22:11:02 +02:00
|
|
|
'classTemplateExternalClasses' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @template T as object
|
|
|
|
*/
|
|
|
|
class Foo {
|
|
|
|
/** @var string */
|
|
|
|
public $T;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $T
|
|
|
|
* @template-typeof T $T
|
|
|
|
*/
|
|
|
|
public function __construct(string $T) {
|
|
|
|
$this->T = $T;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return T
|
|
|
|
*/
|
|
|
|
public function bar() {
|
|
|
|
$t = $this->T;
|
|
|
|
return new $t();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$efoo = new Foo(Exception::class)
|
|
|
|
$efoo_bar = $efoo->bar();
|
|
|
|
|
|
|
|
$ffoo = new Foo("LogicException");
|
|
|
|
$ffoo_bar = $ffoo->bar();',
|
|
|
|
'assertions' => [
|
|
|
|
'$efoo' => 'Foo<Exception>',
|
|
|
|
'$efoo_bar' => 'Exception',
|
|
|
|
|
|
|
|
'$ffoo' => 'Foo<LogicException>',
|
|
|
|
'$ffoo_bar' => 'LogicException',
|
|
|
|
],
|
|
|
|
],
|
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
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
public function __toString() : string {
|
|
|
|
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
|
|
|
],
|
2017-05-27 02:05:57 +02:00
|
|
|
'error_levels' => ['MixedOperand'],
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'phanTuple' => [
|
|
|
|
'<?php
|
|
|
|
namespace Phan\Library;
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/**
|
|
|
|
* An abstract tuple.
|
|
|
|
*/
|
|
|
|
abstract class Tuple
|
|
|
|
{
|
|
|
|
const ARITY = 0;
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/**
|
|
|
|
* @return int
|
|
|
|
* The arity of this tuple
|
|
|
|
*/
|
|
|
|
public function arity() : int
|
|
|
|
{
|
|
|
|
return (int)static::ARITY;
|
|
|
|
}
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
* An array of all elements in this tuple.
|
|
|
|
*/
|
|
|
|
abstract public function toArray() : array;
|
|
|
|
}
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/**
|
|
|
|
* A tuple of 1 element.
|
|
|
|
*
|
|
|
|
* @template T0
|
|
|
|
* The type of element zero
|
|
|
|
*/
|
|
|
|
class Tuple1 extends Tuple
|
|
|
|
{
|
|
|
|
/** @var int */
|
|
|
|
const ARITY = 1;
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/** @var T0 */
|
|
|
|
public $_0;
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/**
|
|
|
|
* @param T0 $_0
|
|
|
|
* The 0th element
|
|
|
|
*/
|
|
|
|
public function __construct($_0) {
|
|
|
|
$this->_0 = $_0;
|
|
|
|
}
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/**
|
|
|
|
* @return int
|
|
|
|
* The arity of this tuple
|
|
|
|
*/
|
|
|
|
public function arity() : int
|
|
|
|
{
|
|
|
|
return (int)static::ARITY;
|
|
|
|
}
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
* An array of all elements in this tuple.
|
|
|
|
*/
|
|
|
|
public function toArray() : array
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
$this->_0,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/**
|
|
|
|
* A tuple of 2 elements.
|
|
|
|
*
|
|
|
|
* @template T0
|
|
|
|
* The type of element zero
|
|
|
|
*
|
|
|
|
* @template T1
|
|
|
|
* The type of element one
|
|
|
|
*
|
|
|
|
* @inherits Tuple1<T0>
|
|
|
|
*/
|
|
|
|
class Tuple2 extends Tuple1
|
|
|
|
{
|
|
|
|
/** @var int */
|
|
|
|
const ARITY = 2;
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/** @var T1 */
|
|
|
|
public $_1;
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/**
|
|
|
|
* @param T0 $_0
|
|
|
|
* The 0th element
|
|
|
|
*
|
|
|
|
* @param T1 $_1
|
|
|
|
* The 1st element
|
|
|
|
*/
|
|
|
|
public function __construct($_0, $_1) {
|
|
|
|
parent::__construct($_0);
|
|
|
|
$this->_1 = $_1;
|
|
|
|
}
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
* An array of all elements in this tuple.
|
|
|
|
*/
|
|
|
|
public function toArray() : array
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
$this->_0,
|
|
|
|
$this->_1,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
$a = new Tuple2("cool", 5);
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/** @return void */
|
|
|
|
function takes_int(int $i) {}
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/** @return void */
|
|
|
|
function takes_string(string $s) {}
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
takes_string($a->_0);
|
2017-05-27 02:05:57 +02:00
|
|
|
takes_int($a->_1);',
|
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
|
|
|
|
2017-04-25 05:45:02 +02: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
|
|
|
],
|
|
|
|
'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
|
|
|
|
2017-04-25 05:45:02 +02: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
|
|
|
|
2017-04-25 05:45:02 +02: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
|
|
|
|
/**
|
|
|
|
* @template T
|
|
|
|
*
|
|
|
|
* @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
|
|
|
],
|
|
|
|
'genericArrayReverse' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @template TKey
|
|
|
|
* @template TValue
|
|
|
|
*
|
|
|
|
* @param array<TKey, TValue> $arr
|
|
|
|
* @return array<TValue, TKey>
|
|
|
|
*/
|
|
|
|
function my_array_reverse($arr) {
|
|
|
|
return array_reverse($arr);
|
|
|
|
}
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
$b = my_array_reverse(["hello" => 5, "goodbye" => 6]);',
|
|
|
|
'assertions' => [
|
2017-06-29 16:22:49 +02:00
|
|
|
'$b' => 'array<int, string>',
|
2017-05-27 02:05:57 +02:00
|
|
|
],
|
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
];
|
2017-02-10 06:14:44 +01:00
|
|
|
}
|
|
|
|
|
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
|
|
|
*/
|
2017-04-25 05:45:02 +02:00
|
|
|
public function providerFileCheckerInvalidCodeParse()
|
2017-02-10 02:35:17 +01:00
|
|
|
{
|
2017-04-25 05:45:02 +02:00
|
|
|
return [
|
|
|
|
'invalidTemplatedType' => [
|
|
|
|
'<?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
|
|
|
|
2017-04-25 05:45:02 +02: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
|
|
|
|
2017-04-25 05:45:02 +02: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
|
|
|
|
2017-04-25 05:45:02 +02: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',
|
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
];
|
2017-02-10 04:57:23 +01:00
|
|
|
}
|
2017-02-10 02:35:17 +01:00
|
|
|
}
|