2017-01-15 21:58:40 +01:00
|
|
|
<?php
|
|
|
|
namespace Psalm\Tests;
|
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
class ConstantTest extends TestCase
|
2017-01-15 21:58:40 +01:00
|
|
|
{
|
2018-11-06 03:57:36 +01:00
|
|
|
use Traits\InvalidCodeAnalysisTestTrait;
|
|
|
|
use Traits\ValidCodeAnalysisTestTrait;
|
2017-01-15 21:58:40 +01:00
|
|
|
|
|
|
|
/**
|
2019-03-01 21:55:20 +01:00
|
|
|
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
|
2017-01-15 21:58:40 +01:00
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
public function providerValidCodeParse()
|
2017-01-15 21:58:40 +01:00
|
|
|
{
|
2017-04-25 05:45:02 +02:00
|
|
|
return [
|
|
|
|
'constantInFunction' => [
|
|
|
|
'<?php
|
|
|
|
useTest();
|
|
|
|
const TEST = 2;
|
2017-12-06 06:05:01 +01:00
|
|
|
|
2018-01-11 21:50:45 +01:00
|
|
|
function useTest(): int {
|
2017-04-25 05:45:02 +02:00
|
|
|
return TEST;
|
2017-05-27 02:05:57 +02:00
|
|
|
}',
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'constantInClosure' => [
|
|
|
|
'<?php
|
|
|
|
const TEST = 2;
|
2017-12-06 06:05:01 +01:00
|
|
|
|
2018-01-11 21:50:45 +01:00
|
|
|
$useTest = function(): int {
|
2017-04-25 05:45:02 +02:00
|
|
|
return TEST;
|
|
|
|
};
|
2017-05-27 02:05:57 +02:00
|
|
|
$useTest();',
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'constantDefinedInFunction' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
function defineConstant() {
|
|
|
|
define("CONSTANT", 1);
|
|
|
|
}
|
2017-12-06 06:05:01 +01:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
defineConstant();
|
2017-12-06 06:05:01 +01:00
|
|
|
|
2017-05-27 02:05:57 +02:00
|
|
|
echo CONSTANT;',
|
|
|
|
],
|
2017-12-06 06:05:01 +01:00
|
|
|
'magicConstant' => [
|
|
|
|
'<?php
|
|
|
|
$a = __LINE__;
|
|
|
|
$b = __file__;',
|
|
|
|
'assertions' => [
|
|
|
|
'$a' => 'int',
|
|
|
|
'$b' => 'string',
|
|
|
|
],
|
|
|
|
],
|
2017-12-19 15:48:01 +01:00
|
|
|
'getClassConstantValue' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
const B = [0, 1, 2];
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = A::B[1];',
|
|
|
|
],
|
2018-01-25 16:47:15 +01:00
|
|
|
'staticConstEval' => [
|
|
|
|
'<?php
|
|
|
|
abstract class Enum {
|
|
|
|
/**
|
|
|
|
* @var string[]
|
|
|
|
*/
|
|
|
|
protected const VALUES = [];
|
|
|
|
public static function export(): string
|
|
|
|
{
|
|
|
|
assert(!empty(static::VALUES));
|
|
|
|
$values = array_map(
|
|
|
|
function(string $val): string {
|
|
|
|
return "\'" . $val . "\'";
|
|
|
|
},
|
|
|
|
static::VALUES
|
|
|
|
);
|
|
|
|
return join(",", $values);
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['MixedArgument'],
|
|
|
|
],
|
2018-02-08 02:26:26 +01:00
|
|
|
'undefinedConstant' => [
|
|
|
|
'<?php
|
|
|
|
switch (rand(0, 50)) {
|
|
|
|
case FORTY: // Observed a valid UndeclaredConstant warning
|
|
|
|
$x = "value";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
$x = "other";
|
|
|
|
}
|
|
|
|
|
|
|
|
echo $x;',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['UndefinedConstant'],
|
|
|
|
],
|
2018-05-03 19:20:42 +02:00
|
|
|
'suppressUndefinedClassConstant' => [
|
|
|
|
'<?php
|
|
|
|
class C {}
|
|
|
|
|
|
|
|
/** @psalm-suppress UndefinedConstant */
|
|
|
|
$a = POTATO;
|
|
|
|
|
|
|
|
/** @psalm-suppress UndefinedConstant */
|
|
|
|
$a = C::POTATO;',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['MixedAssignment'],
|
|
|
|
],
|
2018-05-10 19:01:55 +02:00
|
|
|
'hardToDefineClassConstant' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
2018-05-10 20:12:50 +02:00
|
|
|
const C = [
|
|
|
|
self::B => 4,
|
|
|
|
"name" => 3
|
|
|
|
];
|
2018-05-10 19:01:55 +02:00
|
|
|
|
2018-05-10 20:12:50 +02:00
|
|
|
const B = 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
echo A::C[4];',
|
|
|
|
],
|
|
|
|
'sameNamedConstInOtherClass' => [
|
|
|
|
'<?php
|
|
|
|
class B {
|
|
|
|
const B = 4;
|
|
|
|
}
|
|
|
|
class A {
|
|
|
|
const B = "four";
|
|
|
|
const C = [
|
|
|
|
B::B => "one",
|
|
|
|
];
|
2018-05-10 19:01:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
echo A::C[4];',
|
|
|
|
],
|
2018-05-12 05:14:44 +02:00
|
|
|
'onlyMatchingConstantOffset' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
const KEYS = ["one", "two", "three"];
|
|
|
|
const ARR = [
|
|
|
|
"one" => 1,
|
|
|
|
"two" => 2
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (A::KEYS as $key) {
|
|
|
|
if (isset(A::ARR[$key])) {
|
|
|
|
echo A::ARR[$key];
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
2018-05-12 17:17:41 +02:00
|
|
|
'noExceptionsOnMixedArrayKey' => [
|
|
|
|
'<?php
|
|
|
|
function finder(string $id) : ?object {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
return new A();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
return new B();
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
class A {}
|
|
|
|
class B {}
|
|
|
|
class Foo
|
|
|
|
{
|
|
|
|
private const TYPES = [
|
|
|
|
"type1" => A::class,
|
|
|
|
"type2" => B::class,
|
|
|
|
];
|
|
|
|
|
|
|
|
public function bar(array $data): void
|
|
|
|
{
|
|
|
|
if (!isset(self::TYPES[$data["type"]])) {
|
|
|
|
throw new \InvalidArgumentException("Unknown type");
|
|
|
|
}
|
|
|
|
|
|
|
|
$class = self::TYPES[$data["type"]];
|
|
|
|
|
|
|
|
$ret = finder($data["id"]);
|
|
|
|
|
|
|
|
if (!$ret || !$ret instanceof $class) {
|
|
|
|
throw new \InvalidArgumentException;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['MixedArgument', 'MixedArrayOffset', 'MixedAssignment'],
|
|
|
|
],
|
2018-06-28 03:53:25 +02:00
|
|
|
'lateConstantResolution' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
const FOO = "foo";
|
|
|
|
}
|
|
|
|
|
|
|
|
class B {
|
|
|
|
const BAR = [
|
|
|
|
A::FOO
|
|
|
|
];
|
|
|
|
const BAR2 = A::FOO;
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = B::BAR[0];
|
|
|
|
$b = B::BAR2;',
|
|
|
|
'assertions' => [
|
|
|
|
'$a' => 'string',
|
|
|
|
'$b' => 'string',
|
|
|
|
],
|
|
|
|
],
|
2018-07-06 19:35:36 +02:00
|
|
|
'allowConstCheckForDifferentPlatforms' => [
|
|
|
|
'<?php
|
|
|
|
if ("phpdbg" === \PHP_SAPI) {}',
|
|
|
|
],
|
2018-07-10 23:40:34 +02:00
|
|
|
'stdinout' => [
|
|
|
|
'<?php
|
|
|
|
echo fread(STDIN, 100);
|
|
|
|
fwrite(STDOUT, "asd");
|
|
|
|
fwrite(STDERR, "zcx");'
|
|
|
|
],
|
2018-08-21 04:25:10 +02:00
|
|
|
'classStringArrayOffset' => [
|
|
|
|
'<?php
|
|
|
|
class A {}
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
const C = [
|
|
|
|
A::class => 1,
|
|
|
|
B::class => 2,
|
|
|
|
];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param class-string $s
|
|
|
|
*/
|
|
|
|
function foo(string $s) : void {
|
|
|
|
if (isset(C[$s])) {}
|
|
|
|
}',
|
|
|
|
],
|
2018-09-09 17:18:20 +02:00
|
|
|
'resolveClassConstToCurrentClass' => [
|
|
|
|
'<?php
|
|
|
|
interface I {
|
|
|
|
public const C = "a";
|
|
|
|
|
|
|
|
public function getC(): string;
|
|
|
|
}
|
|
|
|
|
|
|
|
class A implements I {
|
|
|
|
public function getC(): string {
|
|
|
|
return self::C;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class B extends A {
|
|
|
|
public const C = [5];
|
2018-11-06 03:57:36 +01:00
|
|
|
|
2018-09-09 17:18:20 +02:00
|
|
|
public function getA(): array {
|
|
|
|
return self::C;
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
2019-01-02 19:46:46 +01:00
|
|
|
'resolveCalculatedConstant' => [
|
|
|
|
'<?php
|
|
|
|
interface Types {
|
|
|
|
public const TWO = "two";
|
|
|
|
}
|
|
|
|
|
|
|
|
interface A {
|
|
|
|
public const TYPE_ONE = "one";
|
|
|
|
public const TYPE_TWO = Types::TWO;
|
|
|
|
}
|
|
|
|
|
|
|
|
class B implements A {
|
|
|
|
public function __construct()
|
|
|
|
{
|
|
|
|
echo self::TYPE_ONE;
|
|
|
|
echo self::TYPE_TWO;
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['MixedArgument'],
|
|
|
|
],
|
2019-01-13 17:54:39 +01:00
|
|
|
'arrayAccessAfterIsset' => [
|
|
|
|
'<?php
|
|
|
|
class C {
|
|
|
|
const A = [
|
|
|
|
"b" => ["c" => false],
|
|
|
|
"c" => ["c" => true],
|
|
|
|
"d" => ["c" => true]
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @var string */
|
|
|
|
$s = "b";
|
|
|
|
|
|
|
|
if (isset(C::A[$s]["c"]) && C::A[$s]["c"] === false) {}',
|
|
|
|
],
|
2019-01-20 15:52:26 +01:00
|
|
|
'namespacedConstantInsideClosure' => [
|
|
|
|
'<?php
|
|
|
|
namespace Foo;
|
|
|
|
|
|
|
|
const FOO_BAR = 1;
|
|
|
|
|
|
|
|
function foo(): \Closure {
|
|
|
|
return function (): int {
|
|
|
|
return FOO_BAR;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function foo2(): int {
|
|
|
|
return FOO_BAR;
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = function (): \Closure {
|
|
|
|
return function (): int {
|
|
|
|
return FOO_BAR;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
$b = function (): int {
|
|
|
|
return FOO_BAR;
|
|
|
|
};',
|
|
|
|
],
|
|
|
|
'rootConstantReferencedInNamespace' => [
|
|
|
|
'<?php
|
|
|
|
namespace Foo;
|
|
|
|
echo DIRECTORY_SEPARATOR;',
|
|
|
|
],
|
2019-02-22 00:19:12 +01:00
|
|
|
'constantDefinedInRootNamespace' => [
|
2019-02-18 19:17:08 +01:00
|
|
|
'<?php
|
|
|
|
namespace {
|
2019-02-22 00:19:12 +01:00
|
|
|
define("ns1\\cons1", 0);
|
2019-02-18 19:17:08 +01:00
|
|
|
|
2019-02-22 00:19:12 +01:00
|
|
|
echo \ns1\cons1;
|
|
|
|
echo ns1\cons1;
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'constantDynamicallyDefinedInNamespaceReferencedInSame' => [
|
|
|
|
'<?php
|
2019-02-18 19:17:08 +01:00
|
|
|
namespace ns2 {
|
2019-02-22 00:19:12 +01:00
|
|
|
define(__NAMESPACE__."\\cons2", 0);
|
2019-02-18 19:17:08 +01:00
|
|
|
|
2019-02-22 00:19:12 +01:00
|
|
|
echo \ns2\cons2;
|
|
|
|
echo cons2;
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'constantDynamicallyDefinedInNamespaceReferencedInRoot' => [
|
|
|
|
'<?php
|
|
|
|
namespace ns2 {
|
|
|
|
define(__NAMESPACE__."\\cons2", 0);
|
2019-02-18 19:17:08 +01:00
|
|
|
}
|
2019-02-22 00:19:12 +01:00
|
|
|
namespace {
|
|
|
|
echo \ns2\cons2;
|
|
|
|
echo ns2\cons2;
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'constantExplicitlyDefinedInNamespaceReferencedInSame' => [
|
|
|
|
'<?php
|
|
|
|
namespace ns2 {
|
|
|
|
define("ns2\\cons2", 0);
|
|
|
|
|
|
|
|
echo \ns2\cons2;
|
|
|
|
echo cons2;
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'constantExplicitlyDefinedInNamespaceReferencedInRoot' => [
|
|
|
|
'<?php
|
2019-02-18 19:17:08 +01:00
|
|
|
namespace ns2 {
|
2019-02-22 00:19:12 +01:00
|
|
|
define("ns2\\cons2", 0);
|
2019-02-18 19:17:08 +01:00
|
|
|
}
|
|
|
|
namespace {
|
2019-02-22 00:19:12 +01:00
|
|
|
echo \ns2\cons2;
|
|
|
|
echo ns2\cons2;
|
2019-02-18 19:17:08 +01:00
|
|
|
}',
|
|
|
|
],
|
2019-03-03 22:43:24 +01:00
|
|
|
'allowConstantToBeDefinedInNamespaceNadReferenced' => [
|
|
|
|
'<?php
|
|
|
|
namespace ns;
|
|
|
|
function func(): void {}
|
|
|
|
define(__NAMESPACE__."\\cons", 0);
|
|
|
|
cons;'
|
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
];
|
2017-01-15 21:58:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-03-01 21:55:20 +01:00
|
|
|
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
|
2017-01-15 21:58:40 +01:00
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
public function providerInvalidCodeParse()
|
2017-01-15 21:58:40 +01:00
|
|
|
{
|
2017-04-25 05:45:02 +02:00
|
|
|
return [
|
|
|
|
'constantDefinedInFunctionButNotCalled' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
function defineConstant() {
|
|
|
|
define("CONSTANT", 1);
|
|
|
|
}
|
2017-12-06 06:05:01 +01:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
echo CONSTANT;',
|
2017-05-27 02:05:57 +02:00
|
|
|
'error_message' => 'UndefinedConstant',
|
|
|
|
],
|
2018-02-16 02:27:42 +01:00
|
|
|
'undefinedClassConstantInParamDefault' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
public function doSomething(int $howManyTimes = self::DEFAULT_TIMES): void {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'UndefinedConstant',
|
|
|
|
],
|
2018-05-12 05:14:44 +02:00
|
|
|
'nonMatchingConstantOffset' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
const KEYS = ["one", "two", "three", "four"];
|
|
|
|
const ARR = [
|
|
|
|
"one" => 1,
|
|
|
|
"two" => 2
|
|
|
|
];
|
|
|
|
|
|
|
|
const ARR2 = [
|
|
|
|
"three" => 3,
|
|
|
|
"four" => 4
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (A::KEYS as $key) {
|
|
|
|
if (isset(A::ARR[$key])) {
|
|
|
|
echo A::ARR2[$key];
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'error_message' => 'InvalidArrayOffset',
|
|
|
|
],
|
2018-05-14 23:39:08 +02:00
|
|
|
'objectLikeConstArrays' => [
|
|
|
|
'<?php
|
|
|
|
class C {
|
|
|
|
const A = 0;
|
|
|
|
const B = 1;
|
|
|
|
|
|
|
|
const ARR = [
|
|
|
|
self::A => "zero",
|
|
|
|
self::B => "two",
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (C::ARR[C::A] === "two") {}',
|
|
|
|
'error_message' => 'TypeDoesNotContainType',
|
|
|
|
],
|
2018-06-25 21:02:46 +02:00
|
|
|
'missingClassConstInArray' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
const B = 1;
|
|
|
|
const C = [B];
|
|
|
|
}',
|
|
|
|
'error_message' => 'UndefinedConstant',
|
|
|
|
],
|
2018-09-09 17:18:20 +02:00
|
|
|
'resolveConstToCurrentClassWithBadReturn' => [
|
|
|
|
'<?php
|
|
|
|
interface I {
|
|
|
|
public const C = "a";
|
|
|
|
|
|
|
|
public function getC(): string;
|
|
|
|
}
|
|
|
|
|
|
|
|
class A implements I {
|
|
|
|
public function getC(): string {
|
|
|
|
return self::C;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class B extends A {
|
|
|
|
public const C = [5];
|
2018-11-06 03:57:36 +01:00
|
|
|
|
2018-09-09 17:18:20 +02:00
|
|
|
public function getC(): string {
|
|
|
|
return self::C;
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'error_message' => 'InvalidReturnStatement',
|
|
|
|
],
|
2019-02-22 00:19:12 +01:00
|
|
|
'outOfScopeDefinedConstant' => [
|
|
|
|
'<?php
|
|
|
|
namespace {
|
|
|
|
define("A\\B", 0);
|
|
|
|
}
|
|
|
|
namespace C {
|
|
|
|
echo A\B;
|
|
|
|
}',
|
2019-03-01 21:55:20 +01:00
|
|
|
'error_message' => 'UndefinedConstant',
|
2019-02-22 00:19:12 +01:00
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
];
|
2017-01-15 22:43:49 +01:00
|
|
|
}
|
2017-01-15 21:58:40 +01:00
|
|
|
}
|