2017-10-27 00:19:19 +02:00
|
|
|
<?php
|
|
|
|
namespace Psalm\Tests;
|
|
|
|
|
|
|
|
class AssertTest extends TestCase
|
|
|
|
{
|
2018-11-06 03:57:36 +01:00
|
|
|
use Traits\ValidCodeAnalysisTestTrait;
|
|
|
|
use Traits\InvalidCodeAnalysisTestTrait;
|
2017-10-27 00:19:19 +02:00
|
|
|
|
|
|
|
/**
|
2019-03-01 21:55:20 +01:00
|
|
|
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
|
2017-10-27 00:19:19 +02:00
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
public function providerValidCodeParse()
|
2017-10-27 00:19:19 +02:00
|
|
|
{
|
|
|
|
return [
|
2018-02-23 21:39:33 +01:00
|
|
|
'assertInstanceOfB' => [
|
2017-10-27 00:19:19 +02:00
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
2017-10-27 00:19:19 +02:00
|
|
|
class A {}
|
|
|
|
class B extends A {
|
2018-01-11 21:50:45 +01:00
|
|
|
public function foo(): void {}
|
2017-10-27 00:19:19 +02:00
|
|
|
}
|
|
|
|
|
2018-01-11 21:50:45 +01:00
|
|
|
function assertInstanceOfB(A $var): void {
|
2017-10-27 00:19:19 +02:00
|
|
|
if (!$var instanceof B) {
|
|
|
|
throw new \Exception();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-23 21:39:33 +01:00
|
|
|
function takesA(A $a): void {
|
|
|
|
assertInstanceOfB($a);
|
|
|
|
$a->foo();
|
|
|
|
}',
|
|
|
|
],
|
2019-01-05 20:50:11 +01:00
|
|
|
'dropInReplacementForAssert' => [
|
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
2019-01-05 20:50:11 +01:00
|
|
|
/**
|
|
|
|
* @param mixed $_b
|
|
|
|
* @psalm-assert !falsy $_b
|
|
|
|
*/
|
|
|
|
function myAssert($_b) : void {
|
|
|
|
if (!$_b) {
|
|
|
|
throw new \Exception("bad");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function bar(?string $s) : string {
|
|
|
|
myAssert($s !== null);
|
|
|
|
return $s;
|
|
|
|
}'
|
|
|
|
],
|
2018-02-25 17:30:45 +01:00
|
|
|
'assertInstanceOfInterface' => [
|
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
2018-02-25 17:30:45 +01:00
|
|
|
class A {
|
|
|
|
public function bar() : void {}
|
|
|
|
}
|
|
|
|
interface I {
|
|
|
|
public function foo(): void;
|
|
|
|
}
|
|
|
|
class B extends A implements I {
|
|
|
|
public function foo(): void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
function assertInstanceOfI(A $var): void {
|
|
|
|
if (!$var instanceof I) {
|
|
|
|
throw new \Exception();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function takesA(A $a): void {
|
|
|
|
assertInstanceOfI($a);
|
|
|
|
$a->bar();
|
|
|
|
$a->foo();
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'assertInstanceOfMultipleInterfaces' => [
|
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
2018-02-25 17:30:45 +01:00
|
|
|
class A {
|
|
|
|
public function bar() : void {}
|
|
|
|
}
|
|
|
|
interface I1 {
|
|
|
|
public function foo1(): void;
|
|
|
|
}
|
|
|
|
interface I2 {
|
|
|
|
public function foo2(): void;
|
|
|
|
}
|
|
|
|
class B extends A implements I1, I2 {
|
|
|
|
public function foo1(): void {}
|
|
|
|
public function foo2(): void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
function assertInstanceOfInterfaces(A $var): void {
|
|
|
|
if (!$var instanceof I1 || !$var instanceof I2) {
|
|
|
|
throw new \Exception();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function takesA(A $a): void {
|
|
|
|
assertInstanceOfInterfaces($a);
|
|
|
|
$a->bar();
|
|
|
|
$a->foo1();
|
|
|
|
}',
|
|
|
|
],
|
2018-02-23 21:39:33 +01:00
|
|
|
'assertInstanceOfBInClassMethod' => [
|
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
2018-02-23 21:39:33 +01:00
|
|
|
class A {}
|
|
|
|
class B extends A {
|
|
|
|
public function foo(): void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
class C {
|
|
|
|
private function assertInstanceOfB(A $var): void {
|
|
|
|
if (!$var instanceof B) {
|
|
|
|
throw new \Exception();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function takesA(A $a): void {
|
|
|
|
$this->assertInstanceOfB($a);
|
|
|
|
$a->foo();
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'assertPropertyNotNull' => [
|
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
2018-02-23 21:39:33 +01:00
|
|
|
class A {
|
|
|
|
public function foo(): void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
class B {
|
|
|
|
/** @var A|null */
|
|
|
|
public $a;
|
|
|
|
|
|
|
|
private function assertNotNullProperty(): void {
|
|
|
|
if (!$this->a) {
|
|
|
|
throw new \Exception();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function takesA(A $a): void {
|
|
|
|
$this->assertNotNullProperty();
|
|
|
|
$a->foo();
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
2018-11-30 05:25:30 +01:00
|
|
|
'assertWithoutRedundantCondition' => [
|
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
2018-11-30 05:25:30 +01:00
|
|
|
/**
|
|
|
|
* @param mixed $data
|
|
|
|
* @throws \Exception
|
|
|
|
*/
|
|
|
|
function assertIsLongString($data): void {
|
|
|
|
if (!is_string($data)) {
|
2019-01-12 17:40:19 +01:00
|
|
|
throw new \Exception;
|
2018-11-30 05:25:30 +01:00
|
|
|
}
|
|
|
|
if (strlen($data) < 100) {
|
2019-01-12 17:40:19 +01:00
|
|
|
throw new \Exception;
|
2018-11-30 05:25:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-01-12 17:40:19 +01:00
|
|
|
* @throws \Exception
|
2018-11-30 05:25:30 +01:00
|
|
|
*/
|
|
|
|
function f(string $s): void {
|
|
|
|
assertIsLongString($s);
|
|
|
|
}',
|
|
|
|
],
|
2018-05-28 21:07:42 +02:00
|
|
|
'assertInstanceOfBAnnotation' => [
|
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
2018-05-28 21:07:42 +02:00
|
|
|
class A {}
|
|
|
|
class B extends A {
|
|
|
|
public function foo(): void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @psalm-assert B $var */
|
|
|
|
function myAssertInstanceOfB(A $var): void {
|
|
|
|
if (!$var instanceof B) {
|
|
|
|
throw new \Exception();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function takesA(A $a): void {
|
|
|
|
myAssertInstanceOfB($a);
|
|
|
|
$a->foo();
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'assertIfTrueAnnotation' => [
|
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
2018-05-28 21:07:42 +02:00
|
|
|
/** @psalm-assert-if-true string $myVar */
|
|
|
|
function isValidString(?string $myVar) : bool {
|
|
|
|
return $myVar !== null && $myVar[0] === "a";
|
|
|
|
}
|
|
|
|
|
|
|
|
$myString = rand(0, 1) ? "abacus" : null;
|
|
|
|
|
|
|
|
if (isValidString($myString)) {
|
|
|
|
echo "Ma chaine " . $myString;
|
|
|
|
}'
|
|
|
|
],
|
|
|
|
'assertIfFalseAnnotation' => [
|
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
2018-05-28 21:07:42 +02:00
|
|
|
/** @psalm-assert-if-false string $myVar */
|
|
|
|
function isInvalidString(?string $myVar) : bool {
|
|
|
|
return $myVar === null || $myVar[0] !== "a";
|
|
|
|
}
|
|
|
|
|
|
|
|
$myString = rand(0, 1) ? "abacus" : null;
|
|
|
|
|
|
|
|
if (isInvalidString($myString)) {
|
|
|
|
// do something
|
|
|
|
} else {
|
|
|
|
echo "Ma chaine " . $myString;
|
|
|
|
}'
|
|
|
|
],
|
2018-08-24 22:48:14 +02:00
|
|
|
'assertServerVar' => [
|
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
2018-08-24 22:48:14 +02:00
|
|
|
/**
|
|
|
|
* @psalm-assert-if-true string $a
|
|
|
|
* @param mixed $a
|
|
|
|
*/
|
|
|
|
function my_is_string($a) : bool
|
|
|
|
{
|
|
|
|
return is_string($a);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (my_is_string($_SERVER["abc"])) {
|
|
|
|
$i = substr($_SERVER["abc"], 1, 2);
|
|
|
|
}',
|
|
|
|
],
|
2018-10-30 14:20:34 +01:00
|
|
|
'assertTemplatedType' => [
|
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
2018-10-30 14:20:34 +01:00
|
|
|
interface Foo {}
|
|
|
|
|
|
|
|
class Bar implements Foo {
|
|
|
|
public function sayHello(): void {
|
|
|
|
echo "Hello";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param mixed $value
|
|
|
|
* @param class-string $type
|
|
|
|
* @template T
|
|
|
|
* @template-typeof T $type
|
|
|
|
* @psalm-assert T $value
|
|
|
|
*/
|
|
|
|
function assertInstanceOf($value, string $type): void {
|
|
|
|
// some code
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns concreate implmenetation of Foo, which in this case is Bar
|
|
|
|
function getImplementationOfFoo(): Foo {
|
|
|
|
return new Bar();
|
|
|
|
}
|
|
|
|
|
|
|
|
$bar = getImplementationOfFoo();
|
|
|
|
assertInstanceOf($bar, Bar::class);
|
|
|
|
|
|
|
|
$bar->sayHello();'
|
|
|
|
],
|
2018-11-14 19:12:31 +01:00
|
|
|
'dontBleedBadAssertVarIntoContext' => [
|
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
2018-11-14 19:12:31 +01:00
|
|
|
class A {
|
|
|
|
public function foo() : bool {
|
|
|
|
return (bool) rand(0, 1);
|
|
|
|
}
|
|
|
|
public function bar() : bool {
|
|
|
|
return (bool) rand(0, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Asserts that a condition is false.
|
|
|
|
*
|
|
|
|
* @param bool $condition
|
|
|
|
* @param string $message
|
|
|
|
*
|
|
|
|
* @psalm-assert false $actual
|
|
|
|
*/
|
|
|
|
function assertFalse($condition, $message = "") : void {}
|
|
|
|
|
|
|
|
function takesA(A $a) : void {
|
|
|
|
assertFalse($a->foo());
|
|
|
|
assertFalse($a->bar());
|
|
|
|
}'
|
|
|
|
],
|
2018-11-14 19:44:20 +01:00
|
|
|
'suppressRedundantCondition' => [
|
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
|
|
|
|
2018-11-14 19:44:20 +01:00
|
|
|
class A {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param class-string $expected
|
|
|
|
* @param mixed $actual
|
|
|
|
* @param string $message
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
* @template-typeof T $expected
|
|
|
|
* @psalm-assert T $actual
|
|
|
|
*/
|
|
|
|
function assertInstanceOf($expected, $actual) : void {
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-suppress RedundantCondition
|
|
|
|
*/
|
|
|
|
function takesA(A $a) : void {
|
|
|
|
assertInstanceOf(A::class, $a);
|
|
|
|
}',
|
|
|
|
],
|
2018-11-16 17:04:45 +01:00
|
|
|
'allowCanBeSameAfterAssertion' => [
|
2018-11-16 06:56:57 +01:00
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
2018-11-16 06:56:57 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Asserts that two variables are the same.
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
* @param T $expected
|
|
|
|
* @param mixed $actual
|
2018-11-16 16:13:52 +01:00
|
|
|
* @psalm-assert =T $actual
|
2018-11-16 06:56:57 +01:00
|
|
|
*/
|
|
|
|
function assertSame($expected, $actual) : void {}
|
|
|
|
|
|
|
|
$a = rand(0, 1) ? "goodbye" : "hello";
|
|
|
|
$b = rand(0, 1) ? "hello" : "goodbye";
|
|
|
|
assertSame($a, $b);
|
|
|
|
|
|
|
|
$c = "hello";
|
|
|
|
$d = rand(0, 1) ? "hello" : "goodbye";
|
|
|
|
assertSame($c, $d);
|
|
|
|
|
|
|
|
$c = "hello";
|
|
|
|
$d = rand(0, 1) ? "hello" : "goodbye";
|
|
|
|
assertSame($d, $c);
|
|
|
|
|
|
|
|
$c = 4;
|
|
|
|
$d = rand(0, 1) ? 4 : 5;
|
|
|
|
assertSame($d, $c);
|
|
|
|
|
2018-11-16 17:50:07 +01:00
|
|
|
$d = rand(0, 1) ? 4 : null;
|
|
|
|
assertSame(null, $d);
|
|
|
|
|
2018-11-16 06:56:57 +01:00
|
|
|
function foo(string $a, string $b) : void {
|
|
|
|
assertSame($a, $b);
|
|
|
|
}',
|
|
|
|
],
|
2018-11-16 17:04:45 +01:00
|
|
|
'allowCanBeNotSameAfterAssertion' => [
|
|
|
|
'<?php
|
2019-01-12 17:40:19 +01:00
|
|
|
namespace Bar;
|
2018-11-16 17:04:45 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Asserts that two variables are the same.
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
* @param T $expected
|
|
|
|
* @param mixed $actual
|
|
|
|
* @psalm-assert !=T $actual
|
|
|
|
*/
|
|
|
|
function assertNotSame($expected, $actual) : void {}
|
|
|
|
|
|
|
|
$a = rand(0, 1) ? "goodbye" : "hello";
|
|
|
|
$b = rand(0, 1) ? "hello" : "goodbye";
|
|
|
|
assertNotSame($a, $b);
|
|
|
|
|
|
|
|
$c = "hello";
|
|
|
|
$d = rand(0, 1) ? "hello" : "goodbye";
|
|
|
|
assertNotSame($c, $d);
|
|
|
|
|
|
|
|
$c = "hello";
|
|
|
|
$d = rand(0, 1) ? "hello" : "goodbye";
|
|
|
|
assertNotSame($d, $c);
|
|
|
|
|
|
|
|
$c = 4;
|
|
|
|
$d = rand(0, 1) ? 4 : 5;
|
|
|
|
assertNotSame($d, $c);
|
|
|
|
|
|
|
|
function foo(string $a, string $b) : void {
|
|
|
|
assertNotSame($a, $b);
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'allowCanBeEqualAfterAssertion' => [
|
|
|
|
'<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Asserts that two variables are the same.
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
* @param T $expected
|
|
|
|
* @param mixed $actual
|
|
|
|
* @psalm-assert ~T $actual
|
|
|
|
*/
|
|
|
|
function assertEqual($expected, $actual) : void {}
|
|
|
|
|
|
|
|
$a = rand(0, 1) ? "goodbye" : "hello";
|
|
|
|
$b = rand(0, 1) ? "hello" : "goodbye";
|
|
|
|
assertEqual($a, $b);
|
|
|
|
|
|
|
|
$c = "hello";
|
|
|
|
$d = rand(0, 1) ? "hello" : "goodbye";
|
|
|
|
assertEqual($c, $d);
|
|
|
|
|
|
|
|
$c = "hello";
|
|
|
|
$d = rand(0, 1) ? "hello" : "goodbye";
|
|
|
|
assertEqual($d, $c);
|
|
|
|
|
|
|
|
$c = 4;
|
|
|
|
$d = rand(0, 1) ? 3.0 : 4.0;
|
|
|
|
assertEqual($d, $c);
|
|
|
|
|
|
|
|
$c = 4.0;
|
|
|
|
$d = rand(0, 1) ? 3 : 4;
|
|
|
|
assertEqual($d, $c);
|
|
|
|
|
|
|
|
function foo(string $a, string $b) : void {
|
|
|
|
assertEqual($a, $b);
|
|
|
|
}',
|
|
|
|
],
|
2019-01-23 05:42:54 +01:00
|
|
|
'assertAllStrings' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @psalm-assert iterable<mixed,string> $i
|
|
|
|
*
|
|
|
|
* @param iterable<mixed,mixed> $i
|
|
|
|
*/
|
|
|
|
function assertAllStrings(iterable $i): void {
|
|
|
|
/** @psalm-suppress MixedAssignment */
|
|
|
|
foreach ($i as $s) {
|
|
|
|
if (!is_string($s)) {
|
|
|
|
throw new \UnexpectedValueException("");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getArray(): array {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
function getIterable(): iterable {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$array = getArray();
|
|
|
|
assertAllStrings($array);
|
|
|
|
|
|
|
|
$iterable = getIterable();
|
|
|
|
assertAllStrings($iterable);',
|
|
|
|
[
|
|
|
|
'$array' => 'array<array-key, string>',
|
|
|
|
'$iterable' => 'iterable<mixed, string>',
|
|
|
|
]
|
|
|
|
],
|
|
|
|
'assertAllArrayOfClass' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @template T
|
|
|
|
*
|
|
|
|
* @psalm-assert iterable<mixed,T> $i
|
|
|
|
*
|
|
|
|
* @param iterable<mixed,mixed> $i
|
|
|
|
* @param class-string<T> $type
|
|
|
|
*/
|
|
|
|
function assertAllInstanceOf(iterable $i, string $type): void {
|
|
|
|
/** @psalm-suppress MixedAssignment */
|
|
|
|
foreach ($i as $elt) {
|
|
|
|
if (!$elt instanceof $type) {
|
|
|
|
throw new \UnexpectedValueException("");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
function getArray(): array {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$array = getArray();
|
|
|
|
assertAllInstanceOf($array, A::class);',
|
|
|
|
[
|
|
|
|
'$array' => 'array<array-key, A>',
|
|
|
|
]
|
|
|
|
],
|
|
|
|
'assertAllIterableOfClass' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @template T
|
|
|
|
*
|
|
|
|
* @psalm-assert iterable<mixed,T> $i
|
|
|
|
*
|
|
|
|
* @param iterable<mixed,mixed> $i
|
|
|
|
* @param class-string<T> $type
|
|
|
|
*/
|
|
|
|
function assertAllInstanceOf(iterable $i, string $type): void {
|
|
|
|
/** @psalm-suppress MixedAssignment */
|
|
|
|
foreach ($i as $elt) {
|
|
|
|
if (!$elt instanceof $type) {
|
|
|
|
throw new \UnexpectedValueException("");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
function getIterable(): iterable {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$iterable = getIterable();
|
|
|
|
assertAllInstanceOf($iterable, A::class);',
|
|
|
|
[
|
|
|
|
'$iterable' => 'iterable<mixed, A>',
|
|
|
|
]
|
|
|
|
],
|
|
|
|
'complicatedAssertAllInstanceOf' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @template T
|
|
|
|
*
|
|
|
|
* @psalm-assert-if-true iterable<mixed,T> $i
|
|
|
|
*
|
|
|
|
* @param iterable<mixed,mixed> $i
|
|
|
|
* @param class-string<T> $type
|
|
|
|
*/
|
|
|
|
function allInstanceOf(iterable $i, string $type): bool {
|
|
|
|
/** @psalm-suppress MixedAssignment */
|
|
|
|
foreach ($i as $elt) {
|
|
|
|
if (!$elt instanceof $type) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface IBlogPost { public function getId(): int; }
|
|
|
|
|
|
|
|
function getData(): iterable {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = getData();
|
|
|
|
|
|
|
|
assert(allInstanceOf($data, IBlogPost::class));
|
|
|
|
|
|
|
|
foreach ($data as $post) {
|
|
|
|
echo $post->getId();
|
|
|
|
}',
|
|
|
|
],
|
2019-03-11 04:38:30 +01:00
|
|
|
'assertArrayReturnTypeNarrowed' => [
|
|
|
|
'<?php
|
|
|
|
/** @return array{0:Exception} */
|
|
|
|
function f(array $a): array {
|
|
|
|
if ($a[0] instanceof Exception) {
|
|
|
|
return $a;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [new Exception("bad")];
|
|
|
|
}'
|
|
|
|
],
|
|
|
|
'assertTypeNarrowedByAssert' => [
|
|
|
|
'<?php
|
|
|
|
/** @return array{0:Exception,1:Exception} */
|
|
|
|
function f(array $ret): array {
|
|
|
|
assert($ret[0] instanceof Exception);
|
|
|
|
assert($ret[1] instanceof Exception);
|
|
|
|
return $ret;
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'assertTypeNarrowedByButOtherFetchesAreMixed' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @return array{0:Exception}
|
|
|
|
* @psalm-suppress MixedArgument
|
|
|
|
*/
|
|
|
|
function f(array $ret): array {
|
|
|
|
assert($ret[0] instanceof Exception);
|
|
|
|
echo strlen($ret[1]);
|
|
|
|
return $ret;
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'assertTypeNarrowedByNestedIsset' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @psalm-suppress MixedMethodCall
|
|
|
|
* @psalm-suppress MixedArgument
|
|
|
|
*/
|
|
|
|
function foo(array $array = []): void {
|
|
|
|
if (array_key_exists("a", $array)) {
|
|
|
|
echo $array["a"];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (array_key_exists("b", $array)) {
|
|
|
|
echo $array["b"]->format("Y-m-d");
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
2019-03-10 22:54:03 +01:00
|
|
|
'assertCheckOnNonZeroArrayOffset' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param array{string,array|null} $a
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
function f(array $a) {
|
|
|
|
assert(is_array($a[1]));
|
|
|
|
return $a[0];
|
|
|
|
}'
|
|
|
|
],
|
2019-03-11 04:38:30 +01:00
|
|
|
'assertOnParseUrlOutput' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param array<"a"|"b"|"c", mixed> $arr
|
|
|
|
*/
|
|
|
|
function uriToPath(array $arr) : string {
|
|
|
|
if (!isset($arr["a"]) || $arr["b"] !== "foo") {
|
|
|
|
throw new \InvalidArgumentException("bad");
|
|
|
|
}
|
|
|
|
|
|
|
|
return (string) $arr["c"];
|
|
|
|
}'
|
|
|
|
],
|
|
|
|
'combineAfterLoopAssert' => [
|
|
|
|
'<?php
|
|
|
|
function foo(array $array) : void {
|
|
|
|
$c = 0;
|
|
|
|
|
|
|
|
if ($array["a"] === "a") {
|
|
|
|
foreach ([rand(0, 1), rand(0, 1)] as $i) {
|
|
|
|
if ($array["b"] === "c") {}
|
|
|
|
$c++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}'
|
|
|
|
],
|
|
|
|
'assertOnXml' => [
|
|
|
|
'<?php
|
|
|
|
function f(array $array) : void {
|
|
|
|
if ($array["foo"] === "ok") {
|
|
|
|
if ($array["bar"] === "a") {}
|
|
|
|
if ($array["bar"] === "b") {}
|
|
|
|
}
|
|
|
|
}'
|
|
|
|
],
|
|
|
|
'assertOnBacktrace' => [
|
|
|
|
'<?php
|
|
|
|
function _validProperty(array $c, array $arr) : void {
|
|
|
|
if (empty($arr["a"])) {}
|
|
|
|
|
|
|
|
if ($c && $c["a"] !== "b") {}
|
|
|
|
}'
|
|
|
|
],
|
|
|
|
'assertOnRemainderOfArray' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @psalm-suppress MixedInferredReturnType
|
|
|
|
* @psalm-suppress MixedReturnStatement
|
|
|
|
*/
|
|
|
|
function foo(string $file_name) : int {
|
|
|
|
while ($data = getData()) {
|
|
|
|
if (is_numeric($data[0])) {
|
|
|
|
for ($i = 1; $i < count($data); $i++) {
|
|
|
|
return $data[$i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 5;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getData() : ?array {
|
|
|
|
return rand(0, 1) ? ["a", "b", "c"] : null;
|
|
|
|
}'
|
|
|
|
]
|
2017-10-27 00:19:19 +02:00
|
|
|
];
|
|
|
|
}
|
2018-02-25 17:30:45 +01:00
|
|
|
|
|
|
|
/**
|
2019-03-01 21:55:20 +01:00
|
|
|
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
|
2018-02-25 17:30:45 +01:00
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
public function providerInvalidCodeParse()
|
2018-02-25 17:30:45 +01:00
|
|
|
{
|
|
|
|
return [
|
|
|
|
'assertInstanceOfMultipleInterfaces' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
public function bar() : void {}
|
|
|
|
}
|
|
|
|
interface I1 {
|
|
|
|
public function foo1(): void;
|
|
|
|
}
|
|
|
|
interface I2 {
|
|
|
|
public function foo2(): void;
|
|
|
|
}
|
|
|
|
class B extends A implements I1, I2 {
|
|
|
|
public function foo1(): void {}
|
|
|
|
public function foo2(): void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
function assertInstanceOfInterfaces(A $var): void {
|
|
|
|
if (!$var instanceof I1 && !$var instanceof I2) {
|
|
|
|
throw new \Exception();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function takesA(A $a): void {
|
|
|
|
assertInstanceOfInterfaces($a);
|
|
|
|
$a->bar();
|
|
|
|
$a->foo1();
|
|
|
|
}',
|
|
|
|
'error_message' => 'UndefinedMethod',
|
|
|
|
],
|
2018-05-28 21:07:42 +02:00
|
|
|
'assertIfTrueNoAnnotation' => [
|
|
|
|
'<?php
|
|
|
|
function isValidString(?string $myVar) : bool {
|
|
|
|
return $myVar !== null && $myVar[0] === "a";
|
|
|
|
}
|
|
|
|
|
|
|
|
$myString = rand(0, 1) ? "abacus" : null;
|
|
|
|
|
|
|
|
if (isValidString($myString)) {
|
|
|
|
echo "Ma chaine " . $myString;
|
|
|
|
}',
|
|
|
|
'error_message' => 'PossiblyNullOperand',
|
|
|
|
],
|
|
|
|
'assertIfFalseNoAnnotation' => [
|
|
|
|
'<?php
|
|
|
|
function isInvalidString(?string $myVar) : bool {
|
|
|
|
return $myVar === null || $myVar[0] !== "a";
|
|
|
|
}
|
|
|
|
|
|
|
|
$myString = rand(0, 1) ? "abacus" : null;
|
|
|
|
|
|
|
|
if (isInvalidString($myString)) {
|
|
|
|
// do something
|
|
|
|
} else {
|
|
|
|
echo "Ma chaine " . $myString;
|
|
|
|
}',
|
|
|
|
'error_message' => 'PossiblyNullOperand',
|
|
|
|
],
|
2018-07-11 17:22:07 +02:00
|
|
|
'assertIfTrueMethodCall' => [
|
|
|
|
'<?php
|
|
|
|
class C {
|
|
|
|
/**
|
|
|
|
* @param mixed $p
|
|
|
|
* @psalm-assert-if-true int $p
|
|
|
|
*/
|
|
|
|
public function isInt($p): bool {
|
|
|
|
return is_int($p);
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @param mixed $p
|
|
|
|
*/
|
|
|
|
public function doWork($p): void {
|
|
|
|
if ($this->isInt($p)) {
|
|
|
|
strlen($p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'error_message' => 'InvalidScalarArgument',
|
|
|
|
],
|
2018-07-11 17:32:12 +02:00
|
|
|
'assertIfStaticTrueMethodCall' => [
|
|
|
|
'<?php
|
|
|
|
class C {
|
|
|
|
/**
|
|
|
|
* @param mixed $p
|
|
|
|
* @psalm-assert-if-true int $p
|
|
|
|
*/
|
|
|
|
public static function isInt($p): bool {
|
|
|
|
return is_int($p);
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @param mixed $p
|
|
|
|
*/
|
|
|
|
public function doWork($p): void {
|
|
|
|
if ($this->isInt($p)) {
|
|
|
|
strlen($p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'error_message' => 'InvalidScalarArgument',
|
|
|
|
],
|
2018-10-30 15:34:02 +01:00
|
|
|
'noFatalForUnknownAssertClass' => [
|
|
|
|
'<?php
|
|
|
|
interface Foo {}
|
|
|
|
|
|
|
|
class Bar implements Foo {
|
|
|
|
public function sayHello(): void {
|
|
|
|
echo "Hello";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param mixed $value
|
|
|
|
* @param class-string $type
|
|
|
|
* @psalm-assert SomeUndefinedClass $value
|
|
|
|
*/
|
|
|
|
function assertInstanceOf($value, string $type): void {
|
|
|
|
// some code
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns concreate implmenetation of Foo, which in this case is Bar
|
|
|
|
function getImplementationOfFoo(): Foo {
|
|
|
|
return new Bar();
|
|
|
|
}
|
|
|
|
|
|
|
|
$bar = getImplementationOfFoo();
|
|
|
|
assertInstanceOf($bar, Bar::class);
|
|
|
|
|
|
|
|
$bar->sayHello();',
|
|
|
|
'error_message' => 'UndefinedClass',
|
|
|
|
],
|
2018-11-14 18:25:17 +01:00
|
|
|
'detectRedundantCondition' => [
|
|
|
|
'<?php
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param class-string $expected
|
|
|
|
* @param mixed $actual
|
|
|
|
* @param string $message
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
* @template-typeof T $expected
|
|
|
|
* @psalm-assert T $actual
|
|
|
|
*/
|
|
|
|
function assertInstanceOf($expected, $actual) : void {
|
|
|
|
}
|
|
|
|
|
|
|
|
function takesA(A $a) : void {
|
2018-11-14 19:12:31 +01:00
|
|
|
assertInstanceOf(A::class, $a);
|
2018-11-14 18:25:17 +01:00
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantCondition'
|
|
|
|
],
|
2018-11-16 00:50:08 +01:00
|
|
|
'detectAssertSameTypeDoesNotContainType' => [
|
|
|
|
'<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Asserts that two variables are the same.
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
* @param T $expected
|
|
|
|
* @param mixed $actual
|
2018-11-16 16:13:52 +01:00
|
|
|
* @psalm-assert =T $actual
|
2018-11-16 00:50:08 +01:00
|
|
|
*/
|
|
|
|
function assertSame($expected, $actual) : void {}
|
|
|
|
|
|
|
|
$a = 5;
|
|
|
|
$b = "hello";
|
|
|
|
assertSame($a, $b);',
|
|
|
|
'error_message' => 'TypeDoesNotContainType'
|
|
|
|
],
|
2018-11-16 06:56:57 +01:00
|
|
|
'detectAssertAlwaysSame' => [
|
|
|
|
'<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Asserts that two variables are the same.
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
* @param T $expected
|
|
|
|
* @param mixed $actual
|
2018-11-16 16:13:52 +01:00
|
|
|
* @psalm-assert =T $actual
|
2018-11-16 06:56:57 +01:00
|
|
|
*/
|
|
|
|
function assertSame($expected, $actual) : void {}
|
|
|
|
|
|
|
|
$a = 5;
|
|
|
|
$b = 5;
|
|
|
|
assertSame($a, $b);',
|
|
|
|
'error_message' => 'RedundantCondition'
|
|
|
|
],
|
2018-11-16 17:04:45 +01:00
|
|
|
'detectNeverCanBeSameAfterAssertion' => [
|
2018-11-16 06:56:57 +01:00
|
|
|
'<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Asserts that two variables are the same.
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
* @param T $expected
|
|
|
|
* @param mixed $actual
|
2018-11-16 16:13:52 +01:00
|
|
|
* @psalm-assert =T $actual
|
2018-11-16 06:56:57 +01:00
|
|
|
*/
|
|
|
|
function assertSame($expected, $actual) : void {}
|
|
|
|
|
|
|
|
$c = "helloa";
|
|
|
|
$d = rand(0, 1) ? "hello" : "goodbye";
|
|
|
|
assertSame($c, $d);',
|
|
|
|
'error_message' => 'TypeDoesNotContainType'
|
|
|
|
],
|
2018-11-16 17:04:45 +01:00
|
|
|
'detectNeverCanBeNotSameAfterAssertion' => [
|
|
|
|
'<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Asserts that two variables are the same.
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
* @param T $expected
|
|
|
|
* @param mixed $actual
|
|
|
|
* @psalm-assert !=T $actual
|
|
|
|
*/
|
|
|
|
function assertNotSame($expected, $actual) : void {}
|
|
|
|
|
|
|
|
$c = "helloa";
|
|
|
|
$d = rand(0, 1) ? "hello" : "goodbye";
|
|
|
|
assertNotSame($c, $d);',
|
|
|
|
'error_message' => 'RedundantCondition'
|
|
|
|
],
|
|
|
|
'detectNeverCanBeEqualAfterAssertion' => [
|
|
|
|
'<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Asserts that two variables are the same.
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
* @param T $expected
|
|
|
|
* @param mixed $actual
|
|
|
|
* @psalm-assert ~T $actual
|
|
|
|
*/
|
|
|
|
function assertEqual($expected, $actual) : void {}
|
|
|
|
|
|
|
|
$c = "helloa";
|
|
|
|
$d = rand(0, 1) ? "hello" : "goodbye";
|
|
|
|
assertEqual($c, $d);',
|
|
|
|
'error_message' => 'TypeDoesNotContainType'
|
|
|
|
],
|
|
|
|
'detectIntFloatNeverCanBeEqualAfterAssertion' => [
|
|
|
|
'<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Asserts that two variables are the same.
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
* @param T $expected
|
|
|
|
* @param mixed $actual
|
|
|
|
* @psalm-assert ~T $actual
|
|
|
|
*/
|
|
|
|
function assertEqual($expected, $actual) : void {}
|
|
|
|
|
|
|
|
$c = 4;
|
|
|
|
$d = rand(0, 1) ? 5.0 : 6.0;
|
|
|
|
assertEqual($c, $d);',
|
|
|
|
'error_message' => 'TypeDoesNotContainType'
|
|
|
|
],
|
|
|
|
'detectFloatIntNeverCanBeEqualAfterAssertion' => [
|
|
|
|
'<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Asserts that two variables are the same.
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
* @param T $expected
|
|
|
|
* @param mixed $actual
|
|
|
|
* @psalm-assert ~T $actual
|
|
|
|
*/
|
|
|
|
function assertEqual($expected, $actual) : void {}
|
|
|
|
|
|
|
|
$c = 4.0;
|
|
|
|
$d = rand(0, 1) ? 5 : 6;
|
|
|
|
assertEqual($c, $d);',
|
|
|
|
'error_message' => 'TypeDoesNotContainType'
|
|
|
|
],
|
2018-02-25 17:30:45 +01:00
|
|
|
];
|
|
|
|
}
|
2017-10-27 00:19:19 +02:00
|
|
|
}
|