diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php index d753c05d5..e5b039987 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php @@ -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; } diff --git a/src/Psalm/Internal/Codebase/ConstantTypeResolver.php b/src/Psalm/Internal/Codebase/ConstantTypeResolver.php index a67ed0ff5..7031db255 100644 --- a/src/Psalm/Internal/Codebase/ConstantTypeResolver.php +++ b/src/Psalm/Internal/Codebase/ConstantTypeResolver.php @@ -11,6 +11,9 @@ use Psalm\Internal\Scanner\UnresolvedConstant\ArraySpread; use Psalm\Internal\Scanner\UnresolvedConstant\ArrayValue; use Psalm\Internal\Scanner\UnresolvedConstant\ClassConstant; 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\UnresolvedAdditionOp; 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; } diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php index 021b89308..9344d218d 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php @@ -15,6 +15,8 @@ use Psalm\Internal\Scanner\UnresolvedConstant\ArraySpread; use Psalm\Internal\Scanner\UnresolvedConstant\ArrayValue; use Psalm\Internal\Scanner\UnresolvedConstant\ClassConstant; 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\ScalarValue; use Psalm\Internal\Scanner\UnresolvedConstant\UnresolvedAdditionOp; @@ -297,6 +299,23 @@ class ExpressionResolver 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; } diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumNameFetch.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumNameFetch.php new file mode 100644 index 000000000..762a66157 --- /dev/null +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumNameFetch.php @@ -0,0 +1,15 @@ +fqcln = $fqcln; + $this->case = $case; + $this->name = $name; + } + + +} diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumValueFetch.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumValueFetch.php new file mode 100644 index 000000000..7fd587a38 --- /dev/null +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumValueFetch.php @@ -0,0 +1,15 @@ + [], 'php_version' => '8.1', ], - 'classConstantArrayWithEnumCaseKeyEnumDefinedBeforeClass' => [ + 'classConstantArrayWithEnumCaseKey' => [ + 'code' => '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' => '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' => '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_version' => '8.2', ], - 'classConstantArrayWithEnumCaseKeyEnumDefinedAfterClass' => [ + 'classConstantNestedArrayWithEnumCaseKey' => [ 'code' => 'name => "c", - E::K2->name => 3, - BEI::K3->name => "d", - BEI::K4->name => 4, - BEI::K3->value => "e", - BEI::K4->value => 5, - BES::K5->name => "f", - BES::K6->name => 6, - BES::K5->value => "g", - BES::K6->value => 7, + E::K1->name => [ + E::K2->name => [ + E::K3->name => "h", + E::K4->name => "i", + ], + E::K5->name => [ + E::K6->name => "j", + E::K7->name => "k", + ], + ], + 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 { - 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"]; + $c = A::C; ', 'assertions' => [ - '$a===' => "'c'", - '$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', + '$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'}}}", ], 'ignored_issues' => [], 'php_version' => '8.2',