mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 22:01:48 +01:00
Fixes #9373
This commit is contained in:
parent
98d3514197
commit
ad5bf62094
@ -504,33 +504,6 @@ class SimpleTypeInferer
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($stmt instanceof PhpParser\Node\Expr\PropertyFetch
|
|
||||||
&& $stmt->var instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
||||||
&& $stmt->var->name instanceof PhpParser\Node\Identifier
|
|
||||||
&& $stmt->name instanceof PhpParser\Node\Identifier
|
|
||||||
&& in_array($stmt->name->name, ['name', 'value', true])
|
|
||||||
) {
|
|
||||||
$enum_fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
|
||||||
$stmt->var->class,
|
|
||||||
$aliases,
|
|
||||||
);
|
|
||||||
if ($codebase->classlikes->enumExists($enum_fq_class_name)) {
|
|
||||||
$enum_storage = $codebase->classlike_storage_provider->get($enum_fq_class_name);
|
|
||||||
if (isset($enum_storage->enum_cases[$stmt->var->name->name])) {
|
|
||||||
if ($stmt->name->name === 'value') {
|
|
||||||
$value = $enum_storage->enum_cases[$stmt->var->name->name]->value;
|
|
||||||
if (is_string($value)) {
|
|
||||||
return Type::getString($value);
|
|
||||||
} elseif (is_int($value)) {
|
|
||||||
return Type::getInt(false, $value);
|
|
||||||
}
|
|
||||||
} elseif ($stmt->name->name === 'name') {
|
|
||||||
return Type::getString($stmt->var->name->name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,9 @@ use Psalm\Internal\Scanner\UnresolvedConstant\ArraySpread;
|
|||||||
use Psalm\Internal\Scanner\UnresolvedConstant\ArrayValue;
|
use Psalm\Internal\Scanner\UnresolvedConstant\ArrayValue;
|
||||||
use Psalm\Internal\Scanner\UnresolvedConstant\ClassConstant;
|
use Psalm\Internal\Scanner\UnresolvedConstant\ClassConstant;
|
||||||
use Psalm\Internal\Scanner\UnresolvedConstant\Constant;
|
use Psalm\Internal\Scanner\UnresolvedConstant\Constant;
|
||||||
|
use Psalm\Internal\Scanner\UnresolvedConstant\EnumNameFetch;
|
||||||
|
use Psalm\Internal\Scanner\UnresolvedConstant\EnumPropertyFetch;
|
||||||
|
use Psalm\Internal\Scanner\UnresolvedConstant\EnumValueFetch;
|
||||||
use Psalm\Internal\Scanner\UnresolvedConstant\ScalarValue;
|
use Psalm\Internal\Scanner\UnresolvedConstant\ScalarValue;
|
||||||
use Psalm\Internal\Scanner\UnresolvedConstant\UnresolvedAdditionOp;
|
use Psalm\Internal\Scanner\UnresolvedConstant\UnresolvedAdditionOp;
|
||||||
use Psalm\Internal\Scanner\UnresolvedConstant\UnresolvedBinaryOp;
|
use Psalm\Internal\Scanner\UnresolvedConstant\UnresolvedBinaryOp;
|
||||||
@ -331,6 +334,24 @@ class ConstantTypeResolver
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($c instanceof EnumPropertyFetch) {
|
||||||
|
if ($classlikes->enumExists($c->fqcln)) {
|
||||||
|
$enum_storage = $classlikes->getStorageFor($c->fqcln);;
|
||||||
|
if (isset($enum_storage->enum_cases[$c->case])) {
|
||||||
|
if ($c instanceof EnumValueFetch) {
|
||||||
|
$value = $enum_storage->enum_cases[$c->case]->value;
|
||||||
|
if (is_string($value)) {
|
||||||
|
return new TLiteralString($value);
|
||||||
|
} elseif (is_int($value)) {
|
||||||
|
return new TLiteralInt($value);
|
||||||
|
}
|
||||||
|
} elseif ($c instanceof EnumNameFetch) {
|
||||||
|
return new TLiteralString($c->case);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new TMixed;
|
return new TMixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ use Psalm\Internal\Scanner\UnresolvedConstant\ArraySpread;
|
|||||||
use Psalm\Internal\Scanner\UnresolvedConstant\ArrayValue;
|
use Psalm\Internal\Scanner\UnresolvedConstant\ArrayValue;
|
||||||
use Psalm\Internal\Scanner\UnresolvedConstant\ClassConstant;
|
use Psalm\Internal\Scanner\UnresolvedConstant\ClassConstant;
|
||||||
use Psalm\Internal\Scanner\UnresolvedConstant\Constant;
|
use Psalm\Internal\Scanner\UnresolvedConstant\Constant;
|
||||||
|
use Psalm\Internal\Scanner\UnresolvedConstant\EnumNameFetch;
|
||||||
|
use Psalm\Internal\Scanner\UnresolvedConstant\EnumValueFetch;
|
||||||
use Psalm\Internal\Scanner\UnresolvedConstant\KeyValuePair;
|
use Psalm\Internal\Scanner\UnresolvedConstant\KeyValuePair;
|
||||||
use Psalm\Internal\Scanner\UnresolvedConstant\ScalarValue;
|
use Psalm\Internal\Scanner\UnresolvedConstant\ScalarValue;
|
||||||
use Psalm\Internal\Scanner\UnresolvedConstant\UnresolvedAdditionOp;
|
use Psalm\Internal\Scanner\UnresolvedConstant\UnresolvedAdditionOp;
|
||||||
@ -297,6 +299,23 @@ class ExpressionResolver
|
|||||||
return new ArrayValue($items);
|
return new ArrayValue($items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($stmt instanceof PhpParser\Node\Expr\PropertyFetch
|
||||||
|
&& $stmt->var instanceof PhpParser\Node\Expr\ClassConstFetch
|
||||||
|
&& $stmt->var->name instanceof PhpParser\Node\Identifier
|
||||||
|
&& $stmt->name instanceof PhpParser\Node\Identifier
|
||||||
|
&& in_array($stmt->name->name, ['name', 'value', true])
|
||||||
|
) {
|
||||||
|
$enum_fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
||||||
|
$stmt->var->class,
|
||||||
|
$aliases,
|
||||||
|
);
|
||||||
|
if ($stmt->name->name === 'value') {
|
||||||
|
return new EnumValueFetch($enum_fq_class_name, $stmt->var->name);
|
||||||
|
} elseif ($stmt->name->name === 'name') {
|
||||||
|
return new EnumNameFetch($enum_fq_class_name, $stmt->var->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Psalm\Internal\Scanner\UnresolvedConstant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-immutable
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class EnumNameFetch extends EnumPropertyFetch
|
||||||
|
{
|
||||||
|
public function __construct(string $fqcln, string $case)
|
||||||
|
{
|
||||||
|
parent::__construct($fqcln, $case, 'name');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Psalm\Internal\Scanner\UnresolvedConstant;
|
||||||
|
|
||||||
|
use Psalm\Internal\Scanner\UnresolvedConstantComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-immutable
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
abstract class EnumPropertyFetch extends UnresolvedConstantComponent
|
||||||
|
{
|
||||||
|
public string $fqcln;
|
||||||
|
|
||||||
|
public string $case;
|
||||||
|
|
||||||
|
/** @var 'name'|'value' */
|
||||||
|
public string $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $fqcln
|
||||||
|
* @param string $case
|
||||||
|
* @param 'name'|'value' $name
|
||||||
|
*/
|
||||||
|
public function __construct(string $fqcln, string $case, string $name)
|
||||||
|
{
|
||||||
|
$this->fqcln = $fqcln;
|
||||||
|
$this->case = $case;
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Psalm\Internal\Scanner\UnresolvedConstant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-immutable
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class EnumValueFetch extends EnumPropertyFetch
|
||||||
|
{
|
||||||
|
public function __construct(string $fqcln, string $case)
|
||||||
|
{
|
||||||
|
parent::__construct($fqcln, $case, 'value');
|
||||||
|
}
|
||||||
|
}
|
@ -1338,7 +1338,135 @@ class ConstantTest extends TestCase
|
|||||||
'ignored_issues' => [],
|
'ignored_issues' => [],
|
||||||
'php_version' => '8.1',
|
'php_version' => '8.1',
|
||||||
],
|
],
|
||||||
'classConstantArrayWithEnumCaseKeyEnumDefinedBeforeClass' => [
|
'classConstantArrayWithEnumCaseKey' => [
|
||||||
|
'code' => '<?php
|
||||||
|
enum E {
|
||||||
|
case K1;
|
||||||
|
case K2;
|
||||||
|
}
|
||||||
|
enum BEI: int {
|
||||||
|
case K3 = 1;
|
||||||
|
case K4 = 2;
|
||||||
|
}
|
||||||
|
enum BES: string {
|
||||||
|
case K5 = "a";
|
||||||
|
case K6 = "b";
|
||||||
|
}
|
||||||
|
class A {
|
||||||
|
public const C = [
|
||||||
|
BEI::K3->value => "e",
|
||||||
|
BEI::K4->value => 5,
|
||||||
|
E::K1->name => "c",
|
||||||
|
E::K2->name => 3,
|
||||||
|
BEI::K3->name => "d",
|
||||||
|
BEI::K4->name => 4,
|
||||||
|
BES::K5->name => "f",
|
||||||
|
BES::K6->name => 6,
|
||||||
|
BES::K5->value => "g",
|
||||||
|
BES::K6->value => 7,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$c = A::C;
|
||||||
|
',
|
||||||
|
'assertions' => [
|
||||||
|
'$c===' => "array{1: 'e', 2: 5, K1: 'c', K2: 3, K3: 'd', K4: 4, K5: 'f', K6: 6, a: 'g', b: 7}",
|
||||||
|
],
|
||||||
|
'ignored_issues' => [],
|
||||||
|
'php_version' => '8.2',
|
||||||
|
],
|
||||||
|
'classConstantArrayWithEnumCaseKeyEnumDefinedAfterClass' => [
|
||||||
|
'code' => '<?php
|
||||||
|
class A {
|
||||||
|
public const C = [
|
||||||
|
BEI::K3->value => "e",
|
||||||
|
BEI::K4->value => 5,
|
||||||
|
E::K1->name => "c",
|
||||||
|
E::K2->name => 3,
|
||||||
|
BEI::K3->name => "d",
|
||||||
|
BEI::K4->name => 4,
|
||||||
|
BES::K5->name => "f",
|
||||||
|
BES::K6->name => 6,
|
||||||
|
BES::K5->value => "g",
|
||||||
|
BES::K6->value => 7,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
enum E {
|
||||||
|
case K1;
|
||||||
|
case K2;
|
||||||
|
}
|
||||||
|
enum BEI: int {
|
||||||
|
case K3 = 1;
|
||||||
|
case K4 = 2;
|
||||||
|
}
|
||||||
|
enum BES: string {
|
||||||
|
case K5 = "a";
|
||||||
|
case K6 = "b";
|
||||||
|
}
|
||||||
|
$c = A::C;
|
||||||
|
',
|
||||||
|
'assertions' => [
|
||||||
|
'$c===' => "array{1: 'e', 2: 5, K1: 'c', K2: 3, K3: 'd', K4: 4, K5: 'f', K6: 6, a: 'g', b: 7}",
|
||||||
|
],
|
||||||
|
'ignored_issues' => [],
|
||||||
|
'php_version' => '8.2',
|
||||||
|
],
|
||||||
|
'classConstantArrayWithEnumCaseKeyNamespaced' => [
|
||||||
|
'code' => '<?php
|
||||||
|
namespace OtherNamespace;
|
||||||
|
enum E: int {
|
||||||
|
case K1 = 1;
|
||||||
|
case K2 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace UsedNamespace;
|
||||||
|
enum E: int {
|
||||||
|
case K3 = 3;
|
||||||
|
case K4 = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace AliasedNamespace;
|
||||||
|
enum E: int {
|
||||||
|
case K5 = 5;
|
||||||
|
case K6 = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace SameNamespace;
|
||||||
|
use UsedNamespace\E;
|
||||||
|
use AliasedNamespace\E as E2;
|
||||||
|
|
||||||
|
enum E3: int {
|
||||||
|
case K7 = 7;
|
||||||
|
case K8 = 8;
|
||||||
|
}
|
||||||
|
class A {
|
||||||
|
public const C = [
|
||||||
|
\OtherNamespace\E::K1->name => "a",
|
||||||
|
\OtherNamespace\E::K2->name => 10,
|
||||||
|
\OtherNamespace\E::K1->value => "b",
|
||||||
|
\OtherNamespace\E::K2->value => 11,
|
||||||
|
E::K3->name => "c",
|
||||||
|
E::K4->name => 12,
|
||||||
|
E::K3->value => "d",
|
||||||
|
E::K4->value => 13,
|
||||||
|
E2::K5->name => "e",
|
||||||
|
E2::K6->name => 14,
|
||||||
|
E2::K5->value => "f",
|
||||||
|
E2::K6->value => 15,
|
||||||
|
E3::K7->name => "g",
|
||||||
|
E3::K8->name => 16,
|
||||||
|
E3::K7->value => "h",
|
||||||
|
E3::K8->value => 17,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$c = A::C;
|
||||||
|
',
|
||||||
|
'assertions' => [
|
||||||
|
'$c===' => "array{1: 'b', 2: 11, 3: 'd', 4: 13, 5: 'f', 6: 15, 7: 'h', 8: 17, K1: 'a', K2: 10, K3: 'c', K4: 12, K5: 'e', K6: 14, K7: 'g', K8: 16}",
|
||||||
|
],
|
||||||
|
'ignored_issues' => [],
|
||||||
|
'php_version' => '8.2',
|
||||||
|
],
|
||||||
|
'classConstantArrayWithEnumCaseKeyDirectAccess' => [
|
||||||
'code' => '<?php
|
'code' => '<?php
|
||||||
enum E {
|
enum E {
|
||||||
case K1;
|
case K1;
|
||||||
@ -1412,76 +1540,45 @@ class ConstantTest extends TestCase
|
|||||||
'ignored_issues' => [],
|
'ignored_issues' => [],
|
||||||
'php_version' => '8.2',
|
'php_version' => '8.2',
|
||||||
],
|
],
|
||||||
'classConstantArrayWithEnumCaseKeyEnumDefinedAfterClass' => [
|
'classConstantNestedArrayWithEnumCaseKey' => [
|
||||||
'code' => '<?php
|
'code' => '<?php
|
||||||
|
enum E: string {
|
||||||
|
case K1 = "a";
|
||||||
|
case K2 = "b";
|
||||||
|
case K3 = "c";
|
||||||
|
case K4 = "d";
|
||||||
|
case K5 = "e";
|
||||||
|
case K6 = "f";
|
||||||
|
case K7 = "g";
|
||||||
|
}
|
||||||
class A {
|
class A {
|
||||||
public const C = [
|
public const C = [
|
||||||
E::K1->name => "c",
|
E::K1->name => [
|
||||||
E::K2->name => 3,
|
E::K2->name => [
|
||||||
BEI::K3->name => "d",
|
E::K3->name => "h",
|
||||||
BEI::K4->name => 4,
|
E::K4->name => "i",
|
||||||
BEI::K3->value => "e",
|
],
|
||||||
BEI::K4->value => 5,
|
E::K5->name => [
|
||||||
BES::K5->name => "f",
|
E::K6->name => "j",
|
||||||
BES::K6->name => 6,
|
E::K7->name => "k",
|
||||||
BES::K5->value => "g",
|
],
|
||||||
BES::K6->value => 7,
|
],
|
||||||
|
E::K1->value => [
|
||||||
|
E::K2->value => [
|
||||||
|
E::K3->value => "l",
|
||||||
|
E::K4->value => "m",
|
||||||
|
],
|
||||||
|
E::K5->value => [
|
||||||
|
E::K6->value => "n",
|
||||||
|
E::K7->value => "o",
|
||||||
|
],
|
||||||
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
enum E {
|
$c = A::C;
|
||||||
case K1;
|
|
||||||
case K2;
|
|
||||||
}
|
|
||||||
enum BEI: int {
|
|
||||||
case K3 = 1;
|
|
||||||
case K4 = 2;
|
|
||||||
}
|
|
||||||
enum BES: string {
|
|
||||||
case K5 = "a";
|
|
||||||
case K6 = "b";
|
|
||||||
}
|
|
||||||
$a = A::C[E::K1->name];
|
|
||||||
$b = A::C[E::K2->name];
|
|
||||||
$c = A::C[BEI::K3->name];
|
|
||||||
$d = A::C[BEI::K4->name];
|
|
||||||
$e = A::C[BEI::K3->value];
|
|
||||||
$f = A::C[BEI::K4->value];
|
|
||||||
$g = A::C[BES::K5->name];
|
|
||||||
$h = A::C[BES::K6->name];
|
|
||||||
$i = A::C[BES::K5->value];
|
|
||||||
$j = A::C[BES::K6->value];
|
|
||||||
$k = A::C["K1"];
|
|
||||||
$l = A::C["K2"];
|
|
||||||
$m = A::C["K3"];
|
|
||||||
$n = A::C["K4"];
|
|
||||||
$o = A::C[1];
|
|
||||||
$p = A::C[2];
|
|
||||||
$q = A::C["K5"];
|
|
||||||
$r = A::C["K6"];
|
|
||||||
$s = A::C["a"];
|
|
||||||
$t = A::C["b"];
|
|
||||||
',
|
',
|
||||||
'assertions' => [
|
'assertions' => [
|
||||||
'$a===' => "'c'",
|
'$c===' => "array{K1: array{K2: array{K3: 'h', K4: 'i'}, K5: array{K6: 'j', K7: 'k'}}, a: array{b: array{c: 'l', d: 'm'}, e: array{f: 'n', g: 'o'}}}",
|
||||||
'$b===' => '3',
|
|
||||||
'$c===' => "'d'",
|
|
||||||
'$d===' => '4',
|
|
||||||
'$e===' => "'e'",
|
|
||||||
'$f===' => '5',
|
|
||||||
'$g===' => "'f'",
|
|
||||||
'$h===' => '6',
|
|
||||||
'$i===' => "'g'",
|
|
||||||
'$j===' => '7',
|
|
||||||
'$k===' => "'c'",
|
|
||||||
'$l===' => '3',
|
|
||||||
'$m===' => "'d'",
|
|
||||||
'$n===' => '4',
|
|
||||||
'$o===' => "'e'",
|
|
||||||
'$p===' => '5',
|
|
||||||
'$q===' => "'f'",
|
|
||||||
'$r===' => '6',
|
|
||||||
'$s===' => "'g'",
|
|
||||||
'$t===' => '7',
|
|
||||||
],
|
],
|
||||||
'ignored_issues' => [],
|
'ignored_issues' => [],
|
||||||
'php_version' => '8.2',
|
'php_version' => '8.2',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user