2016-12-12 05:41:11 +01:00
|
|
|
<?php
|
|
|
|
namespace Psalm\Tests;
|
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
class MethodCallTest extends TestCase
|
2016-12-12 05:41:11 +01:00
|
|
|
{
|
2018-11-06 03:57:36 +01:00
|
|
|
use Traits\InvalidCodeAnalysisTestTrait;
|
|
|
|
use Traits\ValidCodeAnalysisTestTrait;
|
2016-12-31 15:20:10 +01:00
|
|
|
|
2016-12-17 04:16:29 +01:00
|
|
|
/**
|
2017-04-25 05:45:02 +02:00
|
|
|
* @return array
|
2016-12-17 04:16:29 +01:00
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
public function providerValidCodeParse()
|
2016-12-17 04:16:29 +01:00
|
|
|
{
|
2017-04-25 05:45:02 +02:00
|
|
|
return [
|
2017-07-09 20:36:06 +02:00
|
|
|
'notInCallMapTest' => [
|
|
|
|
'<?php
|
2017-07-25 22:11:02 +02:00
|
|
|
new DOMImplementation();',
|
2017-07-09 20:36:06 +02:00
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
'parentStaticCall' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
/** @return void */
|
|
|
|
public static function foo(){}
|
|
|
|
}
|
2017-07-09 20:36:06 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
class B extends A {
|
|
|
|
/** @return void */
|
|
|
|
public static function bar(){
|
|
|
|
parent::foo();
|
|
|
|
}
|
2017-05-27 02:05:57 +02:00
|
|
|
}',
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'nonStaticInvocation' => [
|
|
|
|
'<?php
|
|
|
|
class Foo {
|
2018-01-11 21:50:45 +01:00
|
|
|
public static function barBar(): void {}
|
2017-04-25 05:45:02 +02:00
|
|
|
}
|
2017-07-09 20:36:06 +02:00
|
|
|
|
2017-05-27 02:05:57 +02:00
|
|
|
(new Foo())->barBar();',
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'staticInvocation' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
2018-01-11 21:50:45 +01:00
|
|
|
public static function fooFoo(): void {}
|
2017-04-25 05:45:02 +02:00
|
|
|
}
|
2017-07-09 20:36:06 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
class B extends A {
|
2017-07-09 20:36:06 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
}
|
2017-07-09 20:36:06 +02:00
|
|
|
|
2017-05-27 02:05:57 +02:00
|
|
|
B::fooFoo();',
|
|
|
|
],
|
2017-11-06 21:37:49 +01:00
|
|
|
'staticCallOnVar' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
2018-01-11 21:50:45 +01:00
|
|
|
public static function bar(): int {
|
2017-11-06 21:37:49 +01:00
|
|
|
return 5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$foo = new A;
|
|
|
|
$b = $foo::bar();',
|
|
|
|
],
|
2017-11-15 03:56:29 +01:00
|
|
|
'uppercasedSelf' => [
|
|
|
|
'<?php
|
|
|
|
class X33{
|
2018-01-11 21:50:45 +01:00
|
|
|
public static function main(): void {
|
2017-11-15 03:56:29 +01:00
|
|
|
echo SELF::class . "\n"; // Class or interface SELF does not exist
|
|
|
|
}
|
|
|
|
}
|
|
|
|
X33::main();',
|
|
|
|
],
|
2018-01-03 16:35:22 +01:00
|
|
|
'dateTimeImmutableStatic' => [
|
|
|
|
'<?php
|
|
|
|
final class MyDate extends DateTimeImmutable {}
|
|
|
|
|
|
|
|
$today = new MyDate();
|
2018-01-05 17:50:27 +01:00
|
|
|
$yesterday = $today->sub(new DateInterval("P1D"));
|
|
|
|
|
|
|
|
$b = (new DateTimeImmutable())->modify("+3 hours");',
|
2018-01-03 16:35:22 +01:00
|
|
|
'assertions' => [
|
|
|
|
'$yesterday' => 'MyDate',
|
2018-01-05 17:50:27 +01:00
|
|
|
'$b' => 'DateTimeImmutable',
|
2018-01-03 16:35:22 +01:00
|
|
|
],
|
|
|
|
],
|
2018-01-22 06:17:16 +01:00
|
|
|
'magicCall' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
2018-07-17 04:48:53 +02:00
|
|
|
public function __call(string $method_name, array $args) {}
|
2018-01-22 06:17:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$a = new A;
|
|
|
|
$a->bar();',
|
|
|
|
],
|
2018-03-04 19:23:40 +01:00
|
|
|
'canBeCalledOnMagic' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
2018-07-17 04:48:53 +02:00
|
|
|
public function __call(string $method, array $args) {}
|
2018-03-04 19:23:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
$a = rand(0, 1) ? new A : new B;
|
|
|
|
|
|
|
|
$a->maybeUndefinedMethod();',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['PossiblyUndefinedMethod'],
|
|
|
|
],
|
2018-08-02 23:14:53 +02:00
|
|
|
'canBeCalledOnMagicWithMethod' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
public function __call(string $method, array $args) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
class B {
|
|
|
|
public function bar() : void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = rand(0, 1) ? new A : new B;
|
|
|
|
|
|
|
|
$a->bar();',
|
|
|
|
'assertions' => [],
|
|
|
|
],
|
2018-04-01 00:57:13 +02:00
|
|
|
'invokeCorrectType' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
public function __invoke(string $p): void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
$q = new A;
|
|
|
|
$q("asda");',
|
|
|
|
],
|
2018-05-12 06:28:21 +02:00
|
|
|
'domDocumentAppendChild' => [
|
|
|
|
'<?php
|
|
|
|
$doc = new DOMDocument("1.0");
|
|
|
|
$node = $doc->createElement("foo");
|
|
|
|
$newnode = $doc->appendChild($node);
|
|
|
|
$newnode->setAttribute("bar", "baz");',
|
|
|
|
],
|
2018-06-04 01:11:07 +02:00
|
|
|
'nonStaticSelfCall' => [
|
|
|
|
'<?php
|
|
|
|
class A11 {
|
|
|
|
public function call() : self {
|
|
|
|
$result = self::method();
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function method() : self {
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$x = new A11();
|
|
|
|
var_export($x->call());',
|
|
|
|
],
|
2018-06-06 05:42:02 +02:00
|
|
|
'simpleXml' => [
|
|
|
|
'<?php
|
|
|
|
$xml = new SimpleXMLElement("<a><b></b></a>");
|
|
|
|
$a = $xml->asXML();
|
|
|
|
$b = $xml->asXML("foo.xml");',
|
|
|
|
'assertions' => [
|
|
|
|
'$a' => 'string|false',
|
|
|
|
'$b' => 'string|bool',
|
|
|
|
],
|
|
|
|
],
|
2018-06-18 16:07:05 +02:00
|
|
|
'datetimeformatNotFalse' => [
|
|
|
|
'<?php
|
|
|
|
$format = random_bytes(10);
|
|
|
|
$dt = new DateTime;
|
|
|
|
$formatted = $dt->format($format);
|
|
|
|
if (false !== $formatted) {}
|
|
|
|
function takesString(string $s) : void {}
|
|
|
|
takesString($formatted);'
|
|
|
|
],
|
2018-07-06 05:02:09 +02:00
|
|
|
'domElement' => [
|
|
|
|
'<?php
|
|
|
|
function foo(DOMElement $e) : ?string {
|
|
|
|
$a = $e->getElementsByTagName("bar");
|
|
|
|
$b = $a->item(0);
|
|
|
|
if (!$b) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return $b->getAttribute("bat");
|
|
|
|
}',
|
|
|
|
],
|
2018-07-17 14:45:42 +02:00
|
|
|
'reflectionParameter' => [
|
|
|
|
'<?php
|
|
|
|
function getTypeName(ReflectionParameter $parameter): string {
|
|
|
|
$type = $parameter->getType();
|
|
|
|
|
|
|
|
if ($type === null) {
|
|
|
|
return "mixed";
|
|
|
|
}
|
|
|
|
|
|
|
|
return $type->getName();
|
|
|
|
}'
|
|
|
|
],
|
2018-11-11 02:56:31 +01:00
|
|
|
'PDOMethod' => [
|
|
|
|
'<?php
|
|
|
|
function md5_and_reverse(string $string) : string {
|
|
|
|
return strrev(md5($string));
|
|
|
|
}
|
|
|
|
|
|
|
|
$db = new PDO("sqlite:sqlitedb");
|
|
|
|
$db->sqliteCreateFunction("md5rev", "md5_and_reverse", 1);',
|
|
|
|
],
|
2018-12-11 00:33:26 +01:00
|
|
|
'dontConvertedMaybeMixedAfterCall' => [
|
|
|
|
'<?php
|
|
|
|
class B {
|
|
|
|
public function foo() : void {}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @param array<B> $b
|
|
|
|
*/
|
|
|
|
function foo(array $a, array $b) : void {
|
|
|
|
$c = array_merge($b, $a);
|
|
|
|
|
|
|
|
foreach ($c as $d) {
|
|
|
|
$d->foo();
|
|
|
|
if ($d instanceof B) {}
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
[],
|
|
|
|
'error_levels' => ['MixedAssignment', 'MixedMethodCall'],
|
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
];
|
2016-12-17 04:16:29 +01:00
|
|
|
}
|
2017-02-12 06:50:37 +01:00
|
|
|
|
|
|
|
/**
|
2017-04-25 05:45:02 +02:00
|
|
|
* @return array
|
2017-02-12 06:50:37 +01:00
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
public function providerInvalidCodeParse()
|
2017-02-12 06:50:37 +01:00
|
|
|
{
|
2017-04-25 05:45:02 +02:00
|
|
|
return [
|
|
|
|
'staticInvocation' => [
|
|
|
|
'<?php
|
|
|
|
class Foo {
|
2018-01-11 21:50:45 +01:00
|
|
|
public function barBar(): void {}
|
2017-04-25 05:45:02 +02:00
|
|
|
}
|
2017-07-09 20:36:06 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
Foo::barBar();',
|
2017-05-27 02:05:57 +02:00
|
|
|
'error_message' => 'InvalidStaticInvocation',
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'parentStaticCall' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
/** @return void */
|
|
|
|
public function foo(){}
|
|
|
|
}
|
2017-07-09 20:36:06 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
class B extends A {
|
|
|
|
/** @return void */
|
|
|
|
public static function bar(){
|
|
|
|
parent::foo();
|
|
|
|
}
|
|
|
|
}',
|
2017-05-27 02:05:57 +02:00
|
|
|
'error_message' => 'InvalidStaticInvocation',
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'mixedMethodCall' => [
|
|
|
|
'<?php
|
|
|
|
class Foo {
|
2018-01-11 21:50:45 +01:00
|
|
|
public static function barBar(): void {}
|
2017-04-25 05:45:02 +02:00
|
|
|
}
|
2017-07-09 20:36:06 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/** @var mixed */
|
|
|
|
$a = (new Foo());
|
2017-07-09 20:36:06 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
$a->barBar();',
|
|
|
|
'error_message' => 'MixedMethodCall',
|
|
|
|
'error_levels' => [
|
|
|
|
'MissingPropertyType',
|
2017-05-27 02:05:57 +02:00
|
|
|
'MixedAssignment',
|
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
2017-11-15 17:34:40 +01:00
|
|
|
'invalidMethodCall' => [
|
|
|
|
'<?php
|
|
|
|
("hello")->someMethod();',
|
|
|
|
'error_message' => 'InvalidMethodCall',
|
|
|
|
],
|
|
|
|
'possiblyInvalidMethodCall' => [
|
|
|
|
'<?php
|
|
|
|
class A1 {
|
|
|
|
public function methodOfA(): void {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @param A1|string $x */
|
2018-05-05 23:30:18 +02:00
|
|
|
function example($x, bool $isObject) : void {
|
2017-11-15 17:34:40 +01:00
|
|
|
if ($isObject) {
|
|
|
|
$x->methodOfA();
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'error_message' => 'PossiblyInvalidMethodCall',
|
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
'selfNonStaticInvocation' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
2018-01-11 21:50:45 +01:00
|
|
|
public function fooFoo(): void {}
|
2017-07-09 20:36:06 +02:00
|
|
|
|
2018-06-04 01:11:07 +02:00
|
|
|
public static function barBar(): void {
|
2017-04-25 05:45:02 +02:00
|
|
|
self::fooFoo();
|
|
|
|
}
|
|
|
|
}',
|
2017-05-27 02:05:57 +02:00
|
|
|
'error_message' => 'NonStaticSelfCall',
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'noParent' => [
|
|
|
|
'<?php
|
|
|
|
class Foo {
|
2018-01-11 21:50:45 +01:00
|
|
|
public function barBar(): void {
|
2017-04-25 05:45:02 +02:00
|
|
|
parent::barBar();
|
|
|
|
}
|
|
|
|
}',
|
2017-05-27 02:05:57 +02:00
|
|
|
'error_message' => 'ParentNotFound',
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'coercedClass' => [
|
|
|
|
'<?php
|
|
|
|
class NullableClass {
|
|
|
|
}
|
2017-07-09 20:36:06 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
class NullableBug {
|
|
|
|
/**
|
2018-11-12 18:03:55 +01:00
|
|
|
* @param class-string|null $className
|
2017-04-25 05:45:02 +02:00
|
|
|
* @return object|null
|
|
|
|
*/
|
|
|
|
public static function mock($className) {
|
|
|
|
if (!$className) { return null; }
|
|
|
|
return new $className();
|
|
|
|
}
|
2017-07-09 20:36:06 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
/**
|
2018-01-05 03:36:16 +01:00
|
|
|
* @return ?NullableClass
|
2017-04-25 05:45:02 +02:00
|
|
|
*/
|
|
|
|
public function returns_nullable_class() {
|
|
|
|
return self::mock("NullableClass");
|
|
|
|
}
|
|
|
|
}',
|
2017-12-07 21:50:25 +01:00
|
|
|
'error_message' => 'LessSpecificReturnStatement',
|
2018-11-12 18:03:55 +01:00
|
|
|
'error_levels' => ['MixedInferredReturnType', 'MixedReturnStatement', 'TypeCoercion'],
|
2017-05-27 02:05:57 +02:00
|
|
|
],
|
2017-11-06 21:37:49 +01:00
|
|
|
'undefinedVariableStaticCall' => [
|
|
|
|
'<?php
|
|
|
|
$foo::bar();',
|
2017-12-06 06:56:00 +01:00
|
|
|
'error_message' => 'UndefinedGlobalVariable',
|
2017-11-06 21:37:49 +01:00
|
|
|
],
|
|
|
|
'staticCallOnString' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
2018-01-11 21:50:45 +01:00
|
|
|
public static function bar(): int {
|
2017-11-06 21:37:49 +01:00
|
|
|
return 5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$foo = "A";
|
2018-11-12 18:03:55 +01:00
|
|
|
/** @psalm-suppress InvalidStringClass */
|
2017-11-06 21:37:49 +01:00
|
|
|
$b = $foo::bar();',
|
|
|
|
'error_message' => 'MixedAssignment',
|
|
|
|
],
|
2018-01-22 06:17:16 +01:00
|
|
|
'possiblyNullFunctionCall' => [
|
|
|
|
'<?php
|
|
|
|
$this->foo();',
|
|
|
|
'error_message' => 'InvalidScope',
|
|
|
|
],
|
|
|
|
'possiblyFalseReference' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
public function bar(): void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = rand(0, 1) ? new A : false;
|
|
|
|
$a->bar();',
|
|
|
|
'error_message' => 'PossiblyFalseReference',
|
|
|
|
],
|
2018-02-14 17:21:43 +01:00
|
|
|
'undefinedParentClass' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @psalm-suppress UndefinedClass
|
|
|
|
*/
|
|
|
|
class B extends A {}
|
|
|
|
|
|
|
|
$b = new B();',
|
2018-04-13 01:42:24 +02:00
|
|
|
'error_message' => 'MissingDependency - src' . DIRECTORY_SEPARATOR . 'somefile.php:7',
|
2018-02-14 17:21:43 +01:00
|
|
|
],
|
2018-02-17 23:16:22 +01:00
|
|
|
'variableMethodCallOnArray' => [
|
|
|
|
'<?php
|
|
|
|
$arr = [];
|
|
|
|
$b = "foo";
|
|
|
|
$arr->$b();',
|
|
|
|
'error_message' => 'InvalidMethodCall',
|
|
|
|
],
|
2018-03-06 17:20:54 +01:00
|
|
|
'intVarStaticCall' => [
|
|
|
|
'<?php
|
|
|
|
$a = 5;
|
|
|
|
$a::bar();',
|
2018-03-06 18:19:50 +01:00
|
|
|
'error_message' => 'UndefinedClass',
|
2018-03-06 17:20:54 +01:00
|
|
|
],
|
|
|
|
'intVarNewCall' => [
|
|
|
|
'<?php
|
|
|
|
$a = 5;
|
|
|
|
new $a();',
|
2018-03-06 18:19:50 +01:00
|
|
|
'error_message' => 'UndefinedClass',
|
2018-03-06 17:20:54 +01:00
|
|
|
],
|
2018-04-01 00:57:13 +02:00
|
|
|
'invokeTypeMismatch' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
public function __invoke(string $p): void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
$q = new A;
|
|
|
|
$q(1);',
|
|
|
|
'error_message' => 'InvalidScalarArgument',
|
|
|
|
],
|
|
|
|
'explicitInvokeTypeMismatch' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
public function __invoke(string $p): void {}
|
|
|
|
}
|
|
|
|
(new A)->__invoke(1);',
|
|
|
|
'error_message' => 'InvalidScalarArgument',
|
|
|
|
],
|
2018-06-27 05:11:16 +02:00
|
|
|
'undefinedMethodPassedAsArg' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
public function __call(string $method, array $args) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
$q = new A;
|
|
|
|
$q->foo(bar());',
|
|
|
|
'error_message' => 'UndefinedFunction'
|
|
|
|
],
|
2018-07-16 17:52:38 +02:00
|
|
|
'noIntersectionMethod' => [
|
|
|
|
'<?php
|
|
|
|
interface A {}
|
|
|
|
interface B {}
|
|
|
|
|
|
|
|
/** @param B&A $p */
|
|
|
|
function f($p): void {
|
|
|
|
$p->zugzug();
|
|
|
|
}',
|
|
|
|
'error_message' => 'UndefinedMethod - src/somefile.php:7 - Method (B&A)::zugzug does not exist'
|
|
|
|
],
|
2018-12-17 23:48:13 +01:00
|
|
|
'noInstanceCallAsStatic' => [
|
|
|
|
'<?php
|
|
|
|
class C {
|
|
|
|
public function foo() : void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
(new C)::foo();',
|
|
|
|
'error_message' => 'InvalidStaticInvocation',
|
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
];
|
2017-02-12 06:50:37 +01:00
|
|
|
}
|
2016-12-12 05:41:11 +01:00
|
|
|
}
|