diff --git a/src/Analyser/ExprHandler/ArrayHandler.php b/src/Analyser/ExprHandler/ArrayHandler.php index ad28fe76aa0..f8bb606474e 100644 --- a/src/Analyser/ExprHandler/ArrayHandler.php +++ b/src/Analyser/ExprHandler/ArrayHandler.php @@ -6,8 +6,10 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\ArrowFunction; +use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Identifier; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; @@ -219,17 +221,18 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex */ private function getCallableDependencies(MutatingScope $scope, Array_ $expr, array $itemResults, ExpressionResult $result): ?Dependencies { - if (count($expr->items) !== 2 || !isset($expr->items[0])) { + if (count($expr->items) !== 2 || !isset($expr->items[0], $expr->items[1])) { + return null; + } + + if ($scope->getFunctionCallStack() === []) { return null; } - // a class constant, property default or enum case value is not called where it is - // declared - testing it would reflect whatever class its first item happens to name if ( - $scope->isInClass() - && $scope->getFunction() === null - && !$scope->isInAnonymousFunction() - && $scope->getFunctionCallStack() === [] + !$expr->items[0]->value instanceof ClassConstFetch + || !$expr->items[0]->value->name instanceof Identifier + || $expr->items[0]->value->name->toLowerString() !== 'class' ) { return null; } diff --git a/tests/PHPStan/Analyser/data/bug-15292.php b/tests/PHPStan/Analyser/data/bug-15292.php index a81a8dea61e..5d626e971db 100644 --- a/tests/PHPStan/Analyser/data/bug-15292.php +++ b/tests/PHPStan/Analyser/data/bug-15292.php @@ -40,7 +40,37 @@ public function isAllowed(string $item): bool return in_array($item, self::ALLOWED_MODALITIES, true) || in_array($item, $this->allowedProperty, true) || in_array($item, Modality::ALLOWED, true) - || in_array('Bug15292\BigContainer::string_ident', [$item], true); + || in_array('Bug15292\BigContainer::string_ident', [$item], true) + || in_array($item, ['Bug15292\BigContainer', 'packstation'], true); } + public function checkMethodBody(string $item): bool + { + $modes = ['Bug15292\BigContainer', 'packstation']; + return in_array($item, $modes, true); + } + + /** + * @param list $modes + */ + public function checkParamDefault(array $modes = ['Bug15292\BigContainer', 'packstation']): bool + { + return count($modes) > 0; + } + + /** + * @return list + */ + public function getModes(): array + { + return ['Bug15292\BigContainer', 'packstation']; + } + +} + +function checkFunctionBody(): void +{ + $modes = ['Bug15292\BigContainer', 'packstation']; + $fn = static fn (): array => ['Bug15292\BigContainer', 'packstation']; } + diff --git a/turbo-ext/src/ArrayHandler.cpp b/turbo-ext/src/ArrayHandler.cpp index 7211f9d1d85..57a711f6b20 100644 --- a/turbo-ext/src/ArrayHandler.cpp +++ b/turbo-ext/src/ArrayHandler.cpp @@ -466,24 +466,14 @@ class ArrayHandler if (first == NULL) return zv::Val::null(); ZVAL_DEREF(first); if (Z_TYPE_P(first) == IS_NULL) return zv::Val::null(); + zval *second = zend_hash_index_find(Z_ARRVAL_P(items), 1); + if (second == NULL) return zv::Val::null(); + ZVAL_DEREF(second); + if (Z_TYPE_P(second) == IS_NULL) return zv::Val::null(); - // a class constant, property default or enum case value is not called where it is - // declared - testing it would reflect whatever class its first item happens to name - bool inClass; - if (UNEXPECTED(!pt_mutating_scope_is_in_class(Z_OBJ_P(scope), inClass))) return zv::Val(); - if (inClass) { - zv::Val function = pt_mutating_scope_get_function(Z_OBJ_P(scope)); - if (UNEXPECTED(function.isUndef())) return zv::Val(); - if (function.isNull()) { - bool inAnonymousFunction; - if (UNEXPECTED(!pt_mutating_scope_is_in_anonymous_function(Z_OBJ_P(scope), inAnonymousFunction))) return zv::Val(); - if (!inAnonymousFunction) { - zv::Val functionCallStack = pt_type_call(Z_OBJ_P(scope), PT_LC("getfunctioncallstack"), 0, NULL); - if (UNEXPECTED(functionCallStack.isUndef())) return zv::Val(); - if (Z_TYPE_P(functionCallStack.raw()) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(functionCallStack.raw())) == 0) return zv::Val::null(); - } - } - } + zv::Val functionCallStack = pt_type_call(Z_OBJ_P(scope), PT_LC("getfunctioncallstack"), 0, NULL); + if (UNEXPECTED(functionCallStack.isUndef())) return zv::Val(); + if (Z_TYPE_P(functionCallStack.raw()) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(functionCallStack.raw())) == 0) return zv::Val::null(); if (UNEXPECTED(Z_TYPE_P(first) != IS_OBJECT)) { zend_throw_error(NULL, "Attempt to read property \"value\" on %s", zend_zval_value_name(first)); @@ -495,6 +485,18 @@ class ArrayHandler zend_type_error("spl_object_id(): Argument #1 ($object) must be of type object, %s given", zend_zval_value_name(value)); return zv::Val(); } + + zend_class_entry *classConstFetchCe = pt_class(PT_CLASS_CLASS_CONST_FETCH); + if (UNEXPECTED(classConstFetchCe == NULL)) return zv::Val(); + if (!instanceof_function(Z_OBJCE_P(value), classConstFetchCe)) return zv::Val::null(); + static pt_property_site nameSite, identifierNameSite; + zval *name = nodeProperty(nameSite, value, PT_LC("name")); + if (name == NULL || Z_TYPE_P(name) != IS_OBJECT) return zv::Val::null(); + zend_class_entry *identifierCe = pt_class(PT_CLASS_IDENTIFIER); + if (UNEXPECTED(identifierCe == NULL)) return zv::Val(); + if (!instanceof_function(Z_OBJCE_P(name), identifierCe)) return zv::Val::null(); + zval *idName = nodeProperty(identifierNameSite, name, PT_LC("name")); + if (idName == NULL || Z_TYPE_P(idName) != IS_STRING || !zend_string_equals_literal_ci(Z_STR_P(idName), "class")) return zv::Val::null(); zval *firstItemResult = zend_hash_index_find(Z_ARRVAL_P(itemResults), Z_OBJ_HANDLE_P(value)); if (firstItemResult == NULL || Z_TYPE_P(firstItemResult) == IS_NULL) return zv::Val::null(); zv::Val firstItemType = pt_expression_result_get_type(firstItemResult);