From 7af771a006ad02ef7e90dc9e4aa1846a10856e7a Mon Sep 17 00:00:00 2001 From: Brown Date: Sat, 18 Apr 2020 12:39:00 -0400 Subject: [PATCH] Fix #3132 - resolve array access in constant properly --- src/Psalm/Internal/Codebase/ClassLikes.php | 33 ++++++++++++++++--- .../Internal/PhpVisitor/ReflectorVisitor.php | 18 ++++++++++ .../UnresolvedConstant/ArrayOffsetFetch.php | 20 +++++++++++ tests/ConstantTest.php | 18 ++++++++++ 4 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php diff --git a/src/Psalm/Internal/Codebase/ClassLikes.php b/src/Psalm/Internal/Codebase/ClassLikes.php index 255a27f0d..ee84e3066 100644 --- a/src/Psalm/Internal/Codebase/ClassLikes.php +++ b/src/Psalm/Internal/Codebase/ClassLikes.php @@ -1540,10 +1540,6 @@ class ClassLikes throw new \InvalidArgumentException('Must specify $visibility'); } - if (isset($type_candidates[$constant_name])) { - return $type_candidates[$constant_name]; - } - if (isset($fallbacks[$constant_name])) { return new Type\Union([ $this->resolveConstantType( @@ -1554,6 +1550,10 @@ class ClassLikes ]); } + if (isset($type_candidates[$constant_name])) { + return $type_candidates[$constant_name]; + } + return null; } @@ -1750,6 +1750,31 @@ class ClassLikes } } + if ($c instanceof UnresolvedConstant\ArrayOffsetFetch) { + $var_type = $this->resolveConstantType( + $c->array, + $statements_analyzer, + $visited_constant_ids + [$c_id => true] + ); + + $offset_type = $this->resolveConstantType( + $c->offset, + $statements_analyzer, + $visited_constant_ids + [$c_id => true] + ); + + if ($var_type instanceof Type\Atomic\ObjectLike + && ($offset_type instanceof Type\Atomic\TLiteralInt + || $offset_type instanceof Type\Atomic\TLiteralString) + ) { + $union = $var_type->properties[$offset_type->value] ?? null; + + if ($union && $union->isSingle()) { + return \array_values($union->getAtomicTypes())[0]; + } + } + } + if ($c instanceof UnresolvedConstant\Constant) { if ($statements_analyzer) { $found_type = $statements_analyzer->getConstType( diff --git a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php index e62e189b7..0e5dcb5a7 100644 --- a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php @@ -3492,6 +3492,24 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse return new UnresolvedConstant\ScalarValue($aliases->namespace); } + if ($stmt instanceof PhpParser\Node\Expr\ArrayDimFetch && $stmt->dim) { + $left = self::getUnresolvedClassConstExpr( + $stmt->var, + $aliases, + $fq_classlike_name + ); + + $right = self::getUnresolvedClassConstExpr( + $stmt->dim, + $aliases, + $fq_classlike_name + ); + + if ($left && $right) { + return new UnresolvedConstant\ArrayOffsetFetch($left, $right); + } + } + if ($stmt instanceof PhpParser\Node\Expr\ClassConstFetch) { if ($stmt->class instanceof PhpParser\Node\Name && $stmt->name instanceof PhpParser\Node\Identifier diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php new file mode 100644 index 000000000..ddfc84026 --- /dev/null +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php @@ -0,0 +1,20 @@ +array = $left; + $this->offset = $right; + } +} diff --git a/tests/ConstantTest.php b/tests/ConstantTest.php index 3d8fc3947..ec3be6044 100644 --- a/tests/ConstantTest.php +++ b/tests/ConstantTest.php @@ -782,6 +782,24 @@ class ConstantTest extends TestCase A::test();' ], + 'referenceClassConstantWithSelf' => [ + ' self::KEYS[\'hi\']]; + public const KEYS = [\'hi\' => CONSTANTS::THERE]; + } + + class CONSTANTS { + public const THERE = \'there\'; + } + + echo B::VALUES["there"];' + ], ]; }