diff --git a/src/Psalm/Internal/Codebase/ConstantTypeResolver.php b/src/Psalm/Internal/Codebase/ConstantTypeResolver.php index a67ed0ff5d0..ad79ff8a531 100644 --- a/src/Psalm/Internal/Codebase/ConstantTypeResolver.php +++ b/src/Psalm/Internal/Codebase/ConstantTypeResolver.php @@ -11,6 +11,9 @@ 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 @@ public static function resolve( } } + 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 Type::getString($value)->getSingleAtomic(); + } elseif (is_int($value)) { + return Type::getInt(false, $value)->getSingleAtomic(); + } + } elseif ($c instanceof EnumNameFetch) { + return Type::getString($c->case)->getSingleAtomic(); + } + } + } + } + return new TMixed; } diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php index 021b8930895..20c75007515 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\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; @@ -34,6 +36,7 @@ use function class_exists; use function function_exists; use function implode; +use function in_array; use function interface_exists; use function strtolower; @@ -297,6 +300,24 @@ public static function getUnresolvedClassConstExpr( return new ArrayValue($items); } + if ($stmt instanceof PhpParser\Node\Expr\PropertyFetch + && $stmt->var instanceof PhpParser\Node\Expr\ClassConstFetch + && $stmt->var->class instanceof PhpParser\Node\Name + && $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->name); + } elseif ($stmt->name->name === 'name') { + return new EnumNameFetch($enum_fq_class_name, $stmt->var->name->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 00000000000..29639fb3ceb --- /dev/null +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumNameFetch.php @@ -0,0 +1,11 @@ +fqcln = $fqcln; + $this->case = $case; + } +} diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumValueFetch.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumValueFetch.php new file mode 100644 index 00000000000..1ee9c003dd8 --- /dev/null +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumValueFetch.php @@ -0,0 +1,11 @@ + [], 'php_version' => '8.1', ], + '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' => '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, + ]; + } + $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' => [ + '$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', + ], + 'ignored_issues' => [], + 'php_version' => '8.2', + ], + 'classConstantNestedArrayWithEnumCaseKey' => [ + 'code' => '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", + ], + ] + ]; + } + $c = A::C; + ', + 'assertions' => [ + '$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', + ], + 'constantArrayWithEnumCaseKey' => [ + '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, + ]; + $a = C[E::K1->name]; + $b = C[E::K2->name]; + $c = C[BEI::K3->name]; + $d = C[BEI::K4->name]; + $e = C[BEI::K3->value]; + $f = C[BEI::K4->value]; + $g = C[BES::K5->name]; + $h = C[BES::K6->name]; + $i = C[BES::K5->value]; + $j = C[BES::K6->value]; + $k = C["K1"]; + $l = C["K2"]; + $m = C["K3"]; + $n = C["K4"]; + $o = C[1]; + $p = C[2]; + $q = C["K5"]; + $r = C["K6"]; + $s = C["a"]; + $t = C["b"]; + ', + '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', + ], + 'ignored_issues' => [], + 'php_version' => '8.2', + ], 'classConstWithParamOut' => [ 'code' => '