diff --git a/src/Psalm/Codebase.php b/src/Psalm/Codebase.php index 623899f04ea..abd96cc753a 100644 --- a/src/Psalm/Codebase.php +++ b/src/Psalm/Codebase.php @@ -415,7 +415,7 @@ public function reloadFiles(ProjectAnalyzer $project_analyzer, array $candidate_ $this->file_reference_provider->updateReferenceCache($this, $referenced_files); - $this->populator->populateCodebase($this); + $this->populator->populateCodebase(); } /** @return void */ @@ -479,7 +479,7 @@ public function scanFiles(int $threads = 1) $has_changes = $this->scanner->scanFiles($this->classlikes, $threads); if ($has_changes) { - $this->populator->populateCodebase($this); + $this->populator->populateCodebase(); } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ClassConstFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ClassConstFetchAnalyzer.php index 503ec5c8a54..a00da7e2cc8 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ClassConstFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ClassConstFetchAnalyzer.php @@ -189,22 +189,24 @@ public static function analyze( $class_visibility = \ReflectionProperty::IS_PUBLIC; } - $class_constants = $codebase->classlikes->getConstantsForClass( + $class_constant_type = $codebase->classlikes->getConstantForClass( $fq_class_name, - $class_visibility + $stmt->name->name, + $class_visibility, + $statements_analyzer ); - if (!isset($class_constants[$stmt->name->name])) { - $all_class_constants = []; - + if (!$class_constant_type) { if ($fq_class_name !== $context->self) { - $all_class_constants = $codebase->classlikes->getConstantsForClass( + $class_constant_type = $codebase->classlikes->getConstantForClass( $fq_class_name, - \ReflectionProperty::IS_PRIVATE + $stmt->name->name, + \ReflectionProperty::IS_PRIVATE, + $statements_analyzer ); } - if ($all_class_constants && isset($all_class_constants[$stmt->name->name])) { + if ($class_constant_type) { if (IssueBuffer::accepts( new InaccessibleClassConstant( 'Constant ' . $const_id . ' is not visible in this context', @@ -294,10 +296,8 @@ public static function analyze( } } - if (isset($class_constants[$stmt->name->name]) - && ($first_part_lc !== 'static' || $class_const_storage->final) - ) { - $stmt->inferredType = clone $class_constants[$stmt->name->name]; + if ($first_part_lc !== 'static' || $class_const_storage->final) { + $stmt->inferredType = clone $class_constant_type; $context->vars_in_scope[$const_id] = $stmt->inferredType; } else { $stmt->inferredType = Type::getMixed(); diff --git a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php index 2a20a71ee13..3da0c00fb7f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php @@ -1182,18 +1182,17 @@ private static function fleshOutAtomicType( return new Type\Atomic\TLiteralClassString($return_type->fq_classlike_name); } - $class_constants = $codebase->classlikes->getConstantsForClass( + $class_constant = $codebase->classlikes->getConstantForClass( $return_type->fq_classlike_name, + $return_type->const_name, \ReflectionProperty::IS_PRIVATE ); - if (isset($class_constants[$return_type->const_name])) { - $const_type = $class_constants[$return_type->const_name]; + if ($class_constant) { + if ($class_constant->isSingle()) { + $class_constant = clone $class_constant; - if ($const_type->isSingle()) { - $const_type = clone $const_type; - - return array_values($const_type->getTypes())[0]; + return array_values($class_constant->getTypes())[0]; } } } @@ -1209,15 +1208,14 @@ private static function fleshOutAtomicType( } if ($evaluate && $codebase->classOrInterfaceExists($return_type->fq_classlike_name)) { - $class_constants = $codebase->classlikes->getConstantsForClass( + $class_constant_type = $codebase->classlikes->getConstantForClass( $return_type->fq_classlike_name, + $return_type->const_name, \ReflectionProperty::IS_PRIVATE ); - if (isset($class_constants[$return_type->const_name])) { - $const_type = $class_constants[$return_type->const_name]; - - foreach ($const_type->getTypes() as $const_type_atomic) { + if ($class_constant_type) { + foreach ($class_constant_type->getTypes() as $const_type_atomic) { if ($const_type_atomic instanceof Type\Atomic\ObjectLike || $const_type_atomic instanceof Type\Atomic\TArray ) { diff --git a/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php b/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php index 268d702bbf0..1106d21efa2 100644 --- a/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php @@ -1639,15 +1639,19 @@ public static function getSimpleType( return Type::getLiteralClassString($const_fq_class_name); } - if ($existing_class_constants === null) { + if ($existing_class_constants === null + && $file_source instanceof StatementsAnalyzer + ) { try { - $foreign_class_constants = $codebase->classlikes->getConstantsForClass( + $foreign_class_constant = $codebase->classlikes->getConstantForClass( $const_fq_class_name, - \ReflectionProperty::IS_PRIVATE + $stmt->name->name, + \ReflectionProperty::IS_PRIVATE, + $file_source ); - if (isset($foreign_class_constants[$stmt->name->name])) { - return clone $foreign_class_constants[$stmt->name->name]; + if ($foreign_class_constant) { + return clone $foreign_class_constant; } return null; @@ -1898,9 +1902,9 @@ private function analyzeConstAssignment(PhpParser\Node\Stmt\Const_ $stmt, Contex * @return Type\Union|null */ public function getConstType( - $const_name, - $is_fully_qualified, - Context $context + string $const_name, + bool $is_fully_qualified, + ?Context $context ) { $aliased_constants = $this->getAliases()->constants; @@ -1926,7 +1930,7 @@ public function getConstType( } } - if ($context->hasVariable($fq_const_name, $this)) { + if ($context && $context->hasVariable($fq_const_name, $this)) { return $context->vars_in_scope[$fq_const_name]; } diff --git a/src/Psalm/Internal/Codebase/ClassLikes.php b/src/Psalm/Internal/Codebase/ClassLikes.php index a6cc72fc336..8c351ca1702 100644 --- a/src/Psalm/Internal/Codebase/ClassLikes.php +++ b/src/Psalm/Internal/Codebase/ClassLikes.php @@ -21,6 +21,7 @@ use Psalm\Internal\FileManipulation\FileManipulationBuffer; use Psalm\Internal\Provider\ClassLikeStorageProvider; use Psalm\Internal\Provider\FileReferenceProvider; +use Psalm\Internal\Scanner\UnresolvedConstant; use Psalm\Issue\PossiblyUnusedMethod; use Psalm\Issue\PossiblyUnusedParam; use Psalm\Issue\PossiblyUnusedProperty; @@ -1427,6 +1428,191 @@ public function getConstantsForClass($class_name, $visibility) throw new \InvalidArgumentException('Must specify $visibility'); } + public function getConstantForClass( + string $class_name, + string $constant_name, + int $visibility, + ?\Psalm\Internal\Analyzer\StatementsAnalyzer $statements_analyzer = null + ) : ?Type\Union { + $class_name = strtolower($class_name); + + $storage = $this->classlike_storage_provider->get($class_name); + + if ($visibility === ReflectionProperty::IS_PUBLIC) { + $type_candidates = $storage->public_class_constants; + + $fallbacks = $storage->public_class_constant_nodes; + } elseif ($visibility === ReflectionProperty::IS_PROTECTED) { + $type_candidates = array_merge( + $storage->public_class_constants, + $storage->protected_class_constants + ); + + $fallbacks = array_merge( + $storage->public_class_constant_nodes, + $storage->protected_class_constant_nodes + ); + } elseif ($visibility === ReflectionProperty::IS_PRIVATE) { + $type_candidates = array_merge( + $storage->public_class_constants, + $storage->protected_class_constants, + $storage->private_class_constants + ); + + $fallbacks = array_merge( + $storage->public_class_constant_nodes, + $storage->protected_class_constant_nodes, + $storage->private_class_constant_nodes + ); + } else { + 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($fallbacks[$constant_name], $statements_analyzer)]); + } + + return null; + } + + /** + * @param string|int|float|bool|null $value + */ + private static function getLiteralTypeFromScalarValue($value) : Type\Atomic + { + if (\is_string($value)) { + return new Type\Atomic\TLiteralString($value); + } elseif (\is_int($value)) { + return new Type\Atomic\TLiteralInt($value); + } elseif (\is_float($value)) { + return new Type\Atomic\TLiteralFloat($value); + } elseif ($value === false) { + return new Type\Atomic\TFalse; + } elseif ($value === true) { + return new Type\Atomic\TTrue; + } else { + return new Type\Atomic\TNull; + } + } + + private function resolveConstantType( + \Psalm\Internal\Scanner\UnresolvedConstantComponent $c, + \Psalm\Internal\Analyzer\StatementsAnalyzer $statements_analyzer = null + ) : Type\Atomic { + if ($c instanceof UnresolvedConstant\ScalarValue) { + return self::getLiteralTypeFromScalarValue($c->value); + } + + if ($c instanceof UnresolvedConstant\UnresolvedBinaryOp) { + $left = $this->resolveConstantType($c->left, $statements_analyzer); + $right = $this->resolveConstantType($c->right, $statements_analyzer); + + if ($left instanceof Type\Atomic\TMixed || $right instanceof Type\Atomic\TMixed) { + return new Type\Atomic\TMixed; + } + + if ($c instanceof UnresolvedConstant\UnresolvedConcatOp) { + if ($left instanceof Type\Atomic\TLiteralString && $right instanceof Type\Atomic\TLiteralString) { + return new Type\Atomic\TLiteralString($left->value . $right->value); + } + + return new Type\Atomic\TMixed; + } + + if ($c instanceof UnresolvedConstant\UnresolvedAdditionOp + || $c instanceof UnresolvedConstant\UnresolvedSubtractionOp + || $c instanceof UnresolvedConstant\UnresolvedDivisionOp + || $c instanceof UnresolvedConstant\UnresolvedMultiplicationOp + || $c instanceof UnresolvedConstant\UnresolvedBinaryOr + ) { + if (($left instanceof Type\Atomic\TLiteralFloat || $left instanceof Type\Atomic\TLiteralInt) + && ($right instanceof Type\Atomic\TLiteralFloat || $right instanceof Type\Atomic\TLiteralInt) + ) { + if ($c instanceof UnresolvedConstant\UnresolvedAdditionOp) { + return self::getLiteralTypeFromScalarValue($left->value + $right->value); + } + + if ($c instanceof UnresolvedConstant\UnresolvedSubtractionOp) { + return self::getLiteralTypeFromScalarValue($left->value - $right->value); + } + + if ($c instanceof UnresolvedConstant\UnresolvedDivisionOp) { + return self::getLiteralTypeFromScalarValue($left->value / $right->value); + } + + if ($c instanceof UnresolvedConstant\UnresolvedBinaryOr) { + return self::getLiteralTypeFromScalarValue($left->value | $right->value); + } + + return self::getLiteralTypeFromScalarValue($left->value * $right->value); + } + + return new Type\Atomic\TMixed; + } + + return new Type\Atomic\TMixed; + } + + if ($c instanceof UnresolvedConstant\ArrayValue) { + $properties = []; + + foreach ($c->entries as $i => $entry) { + if ($entry->key) { + $key_type = $this->resolveConstantType($entry->key, $statements_analyzer); + } else { + $key_type = new Type\Atomic\TLiteralInt($i); + } + + if ($key_type instanceof Type\Atomic\TLiteralInt + || $key_type instanceof Type\Atomic\TLiteralString + ) { + $key_value = $key_type->value; + } else { + return new Type\Atomic\TArray([Type::getArrayKey(), Type::getMixed()]); + } + + $value_type = new Type\Union([$this->resolveConstantType($entry->value, $statements_analyzer)]); + + $properties[$key_value] = $value_type; + } + + return new Type\Atomic\ObjectLike($properties); + } + + if ($c instanceof UnresolvedConstant\ClassConstant) { + $found_type = $this->getConstantForClass( + $c->fqcln, + $c->name, + ReflectionProperty::IS_PRIVATE, + $statements_analyzer + ); + + if ($found_type) { + return \array_values($found_type->getTypes())[0]; + } + } + + if ($c instanceof UnresolvedConstant\Constant) { + if ($statements_analyzer) { + $found_type = $statements_analyzer->getConstType( + $c->name, + $c->is_fully_qualified, + null + ); + + if ($found_type) { + return \array_values($found_type->getTypes())[0]; + } + } + } + + return new Type\Atomic\TMixed; + } + /** * @param string $class_name * @param string $const_name diff --git a/src/Psalm/Internal/Codebase/Populator.php b/src/Psalm/Internal/Codebase/Populator.php index 12ac99e4a8c..d299d0f178f 100644 --- a/src/Psalm/Internal/Codebase/Populator.php +++ b/src/Psalm/Internal/Codebase/Populator.php @@ -78,7 +78,7 @@ public function __construct( /** * @return void */ - public function populateCodebase(\Psalm\Codebase $codebase) + public function populateCodebase() { $this->progress->debug('ClassLikeStorage is populating' . "\n"); @@ -122,47 +122,6 @@ public function populateCodebase(\Psalm\Codebase $codebase) $class_storage->dependent_classlikes += $dependee_storage->dependent_classlikes; } - - if ($class_storage->aliases) { - foreach ($class_storage->public_class_constant_nodes as $const_name => $node) { - $const_type = \Psalm\Internal\Analyzer\StatementsAnalyzer::getSimpleType( - $codebase, - $node, - $class_storage->aliases, - null, - null, - $class_storage->name - ); - - $class_storage->public_class_constants[$const_name] = $const_type ?: Type::getMixed(); - } - - foreach ($class_storage->protected_class_constant_nodes as $const_name => $node) { - $const_type = \Psalm\Internal\Analyzer\StatementsAnalyzer::getSimpleType( - $codebase, - $node, - $class_storage->aliases, - null, - null, - $class_storage->name - ); - - $class_storage->protected_class_constants[$const_name] = $const_type ?: Type::getMixed(); - } - - foreach ($class_storage->private_class_constant_nodes as $const_name => $node) { - $const_type = \Psalm\Internal\Analyzer\StatementsAnalyzer::getSimpleType( - $codebase, - $node, - $class_storage->aliases, - null, - null, - $class_storage->name - ); - - $class_storage->private_class_constants[$const_name] = $const_type ?: Type::getMixed(); - } - } } if ($this->config->allow_phpstorm_generics) { @@ -614,8 +573,8 @@ private function populateInterfaceDataFromParentInterfaces( $parent_interface_storage->invalid_dependencies ); - foreach ($parent_interface_storage->public_class_constant_nodes as $name => $_) { - $storage->public_class_constants[$name] = Type::getMixed(); + foreach ($parent_interface_storage->public_class_constant_nodes as $name => $node) { + $storage->public_class_constant_nodes[$name] = $node; } if ($parent_interface_storage->template_types) { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php new file mode 100644 index 00000000000..45d2b0ec092 --- /dev/null +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php @@ -0,0 +1,17 @@ + */ + public $entries; + + /** @param array $entries */ + public function __construct(array $entries) + { + $this->entries = $entries; + } +} diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php new file mode 100644 index 00000000000..89d1e65c968 --- /dev/null +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php @@ -0,0 +1,20 @@ +fqcln = $fqcln; + $this->name = $name; + } +} diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php new file mode 100644 index 00000000000..dac88fff8c9 --- /dev/null +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php @@ -0,0 +1,20 @@ +name = $name; + $this->is_fully_qualified = $is_fully_qualified; + } +} diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/KeyValuePair.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/KeyValuePair.php new file mode 100644 index 00000000000..509b73e7534 --- /dev/null +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/KeyValuePair.php @@ -0,0 +1,20 @@ +key = $key; + $this->value = $value; + } +} diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php new file mode 100644 index 00000000000..3c05ec880ef --- /dev/null +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php @@ -0,0 +1,17 @@ +value = $value; + } +} diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php new file mode 100644 index 00000000000..494308789e8 --- /dev/null +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php @@ -0,0 +1,7 @@ +left = $left; + $this->right = $right; + } +} diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBinaryOr.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBinaryOr.php new file mode 100644 index 00000000000..a2ef0cc1725 --- /dev/null +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBinaryOr.php @@ -0,0 +1,7 @@ +value, + $this->aliases, + $fq_classlike_name + ); + if ($stmt->isProtected()) { - $storage->protected_class_constant_nodes[$const->name->name] = $const->value; + if ($unresolved_const_expr) { + $storage->protected_class_constant_nodes[$const->name->name] = $unresolved_const_expr; + } else { + $storage->protected_class_constants[$const->name->name] = Type::getMixed(); + } } elseif ($stmt->isPrivate()) { - $storage->private_class_constant_nodes[$const->name->name] = $const->value; + if ($unresolved_const_expr) { + $storage->private_class_constant_nodes[$const->name->name] = $unresolved_const_expr; + } else { + $storage->private_class_constants[$const->name->name] = Type::getMixed(); + } } else { - $storage->public_class_constant_nodes[$const->name->name] = $const->value; + if ($unresolved_const_expr) { + $storage->public_class_constant_nodes[$const->name->name] = $unresolved_const_expr; + } else { + $storage->public_class_constants[$const->name->name] = Type::getMixed(); + } } } @@ -3220,6 +3240,140 @@ private function visitClassConstDeclaration( } } + public function getUnresolvedClassConstExpr( + PhpParser\Node\Expr $stmt, + Aliases $aliases, + string $fq_classlike_name + ) : ?UnresolvedConstantComponent { + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) { + $left = self::getUnresolvedClassConstExpr( + $stmt->left, + $aliases, + $fq_classlike_name + ); + + $right = self::getUnresolvedClassConstExpr( + $stmt->right, + $aliases, + $fq_classlike_name + ); + + if (!$left || !$right) { + return null; + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Plus) { + return new UnresolvedConstant\UnresolvedAdditionOp($left, $right); + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Minus) { + return new UnresolvedConstant\UnresolvedSubtractionOp($left, $right); + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Mul) { + return new UnresolvedConstant\UnresolvedMultiplicationOp($left, $right); + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Div) { + return new UnresolvedConstant\UnresolvedDivisionOp($left, $right); + } + + if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat) { + return new UnresolvedConstant\UnresolvedConcatOp($left, $right); + } + } + + if ($stmt instanceof PhpParser\Node\Expr\ConstFetch) { + if (strtolower($stmt->name->parts[0]) === 'false') { + return new UnresolvedConstant\ScalarValue(false); + } elseif (strtolower($stmt->name->parts[0]) === 'true') { + return new UnresolvedConstant\ScalarValue(true); + } elseif (strtolower($stmt->name->parts[0]) === 'null') { + return new UnresolvedConstant\ScalarValue(null); + } elseif ($stmt->name->parts[0] === '__NAMESPACE__') { + return new UnresolvedConstant\ScalarValue($aliases->namespace); + } + + return new UnresolvedConstant\Constant( + implode('\\', $stmt->name->parts), + $stmt->name instanceof PhpParser\Node\Name\FullyQualified + ); + } + + if ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Namespace_) { + return new UnresolvedConstant\ScalarValue($aliases->namespace); + } + + if ($stmt instanceof PhpParser\Node\Expr\ClassConstFetch) { + if ($stmt->class instanceof PhpParser\Node\Name + && $stmt->name instanceof PhpParser\Node\Identifier + && $fq_classlike_name + && $stmt->class->parts !== ['static'] + && $stmt->class->parts !== ['parent'] + ) { + if ($stmt->class->parts === ['self']) { + $const_fq_class_name = $fq_classlike_name; + } else { + $const_fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject( + $stmt->class, + $aliases + ); + } + + return new UnresolvedConstant\ClassConstant($const_fq_class_name, $stmt->name->name); + } + + return null; + } + + if ($stmt instanceof PhpParser\Node\Scalar\String_ + || $stmt instanceof PhpParser\Node\Scalar\LNumber + || $stmt instanceof PhpParser\Node\Scalar\DNumber + ) { + return new UnresolvedConstant\ScalarValue($stmt->value); + } + + if ($stmt instanceof PhpParser\Node\Expr\Array_) { + $items = []; + + foreach ($stmt->items as $item) { + if ($item === null) { + return null; + } + + if ($item->key) { + $item_key_type = self::getUnresolvedClassConstExpr( + $item->key, + $aliases, + $fq_classlike_name + ); + + if (!$item_key_type) { + return null; + } + } else { + $item_key_type = null; + } + + $item_value_type = self::getUnresolvedClassConstExpr( + $item->value, + $aliases, + $fq_classlike_name + ); + + if (!$item_value_type) { + return null; + } + + $items[] = new UnresolvedConstant\KeyValuePair($item_key_type, $item_value_type); + } + + return new UnresolvedConstant\ArrayValue($items); + } + + return null; + } + /** * @param PhpParser\Node\Expr\Include_ $stmt * diff --git a/src/Psalm/Storage/ClassLikeStorage.php b/src/Psalm/Storage/ClassLikeStorage.php index 4a005bef191..4354f736451 100644 --- a/src/Psalm/Storage/ClassLikeStorage.php +++ b/src/Psalm/Storage/ClassLikeStorage.php @@ -46,21 +46,21 @@ class ClassLikeStorage /** * A lookup table for nodes of unresolvable public class constants * - * @var array + * @var array */ public $public_class_constant_nodes = []; /** * A lookup table for nodes of unresolvable protected class constants * - * @var array + * @var array */ public $protected_class_constant_nodes = []; /** * A lookup table for nodes of unresolvable private class constants * - * @var array + * @var array */ public $private_class_constant_nodes = []; diff --git a/src/Psalm/Type/Atomic/TScalarClassConstant.php b/src/Psalm/Type/Atomic/TScalarClassConstant.php index 904474d0d64..cce3094f4a9 100644 --- a/src/Psalm/Type/Atomic/TScalarClassConstant.php +++ b/src/Psalm/Type/Atomic/TScalarClassConstant.php @@ -144,12 +144,14 @@ public function check( return false; } - $class_constants = $source->getCodebase()->classlikes->getConstantsForClass( + $class_constant_type = $source->getCodebase()->classlikes->getConstantForClass( $fq_classlike_name, - \ReflectionProperty::IS_PRIVATE + $this->const_name, + \ReflectionProperty::IS_PRIVATE, + null ); - if (!isset($class_constants[$this->const_name])) { + if (!$class_constant_type) { if (\Psalm\IssueBuffer::accepts( new \Psalm\Issue\UndefinedConstant( 'Constant ' . $fq_classlike_name . '::' . $this->const_name . ' is not defined', diff --git a/src/Psalm/Type/Reconciler.php b/src/Psalm/Type/Reconciler.php index 60e9f74788a..f54e5254b0e 100644 --- a/src/Psalm/Type/Reconciler.php +++ b/src/Psalm/Type/Reconciler.php @@ -414,13 +414,15 @@ private static function getValueForKey( if (strpos($base_key, '::')) { list($fq_class_name, $const_name) = explode('::', $base_key); - $all_class_constants = $codebase->classlikes->getConstantsForClass( + $class_constant = $codebase->classlikes->getConstantForClass( $fq_class_name, - \ReflectionProperty::IS_PRIVATE + $const_name, + \ReflectionProperty::IS_PRIVATE, + null ); - if (isset($all_class_constants[$const_name])) { - $existing_keys[$base_key] = clone $all_class_constants[$const_name]; + if ($class_constant) { + $existing_keys[$base_key] = clone $class_constant; } else { return null; } diff --git a/tests/ConstantTest.php b/tests/ConstantTest.php index e36facc7b88..2ffa4f54fcd 100644 --- a/tests/ConstantTest.php +++ b/tests/ConstantTest.php @@ -400,6 +400,35 @@ public static function bar() : bool { } }' ], + 'resolveOutOfOrderClassConstants' => [ + ' [ + '