From cb1b255b900030cb8a3aaeb7a515198701ede5d7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jan 2026 08:52:29 +0100 Subject: [PATCH 1/2] MutatingScope: Remove unnecessary union --- src/Analyser/MutatingScope.php | 11 +---------- src/Type/Accessory/NonEmptyArrayType.php | 11 +---------- tests/PHPStan/Analyser/nsrt/composer-array-bug.php | 6 +++--- tests/PHPStan/Analyser/nsrt/superglobals.php | 2 +- tests/PHPStan/Rules/Functions/data/bug-7156.php | 2 +- 5 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 7bfb8e6689..c29f4bccdd 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3343,16 +3343,7 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, if ($dimType->isInteger()->yes() || $dimType->isString()->yes()) { $exprVarType = $scope->getType($expr->var); if (!$exprVarType instanceof MixedType && !$exprVarType->isArray()->no()) { - $types = [ - new ArrayType(new MixedType(), new MixedType()), - new ObjectType(ArrayAccess::class), - new NullType(), - ]; - if ($dimType->isInteger()->yes()) { - $types[] = new StringType(); - } - $offsetValueType = TypeCombinator::intersect($exprVarType, TypeCombinator::union(...$types)); - + $offsetValueType = $exprVarType; if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) { $offsetValueType = TypeCombinator::intersect( $offsetValueType, diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index d06699b90d..2718af8a53 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -78,17 +78,8 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult { $isArray = $type->isArray(); $isIterableAtLeastOnce = $type->isIterableAtLeastOnce(); - $isNonEmptyArray = $isArray->and($isIterableAtLeastOnce); - if ($isNonEmptyArray->yes()) { - return AcceptsResult::createYes(); - } - - if ($type instanceof CompoundType) { - return $type->isAcceptedBy($this, $strictTypes); - } - - return new AcceptsResult($isNonEmptyArray, []); + return new AcceptsResult($isArray->and($isIterableAtLeastOnce), []); } public function isSuperTypeOf(Type $type): IsSuperTypeOfResult diff --git a/tests/PHPStan/Analyser/nsrt/composer-array-bug.php b/tests/PHPStan/Analyser/nsrt/composer-array-bug.php index 0873226897..cca8e42d9d 100644 --- a/tests/PHPStan/Analyser/nsrt/composer-array-bug.php +++ b/tests/PHPStan/Analyser/nsrt/composer-array-bug.php @@ -47,17 +47,17 @@ public function doFoo(): void } } - assertType("non-empty-array&hasOffsetValue('authors', mixed)", $this->config); + assertType("non-empty-array&hasOffsetValue('authors', mixed)", $this->config); assertType("mixed", $this->config['authors']); if (empty($this->config['authors'])) { unset($this->config['authors']); assertType("array", $this->config); } else { - assertType("non-empty-array&hasOffsetValue('authors', mixed~(0|0.0|''|'0'|array{}|false|null))", $this->config); + assertType("non-empty-array&hasOffsetValue('authors', mixed~(0|0.0|''|'0'|array{}|false|null))", $this->config); } - assertType("array", $this->config); + assertType("array", $this->config); } } diff --git a/tests/PHPStan/Analyser/nsrt/superglobals.php b/tests/PHPStan/Analyser/nsrt/superglobals.php index ee7aadb686..237032015d 100644 --- a/tests/PHPStan/Analyser/nsrt/superglobals.php +++ b/tests/PHPStan/Analyser/nsrt/superglobals.php @@ -38,7 +38,7 @@ public function canBePartlyOverwritten(): void public function canBeNarrowed(): void { if (isset($GLOBALS['foo'])) { - assertType("non-empty-array&hasOffsetValue('foo', mixed~null)", $GLOBALS); + assertType("non-empty-array&hasOffsetValue('foo', mixed~null)", $GLOBALS); assertNativeType("non-empty-array&hasOffset('foo')", $GLOBALS); // https://github.com/phpstan/phpstan/issues/8395 } else { assertType('array', $GLOBALS); diff --git a/tests/PHPStan/Rules/Functions/data/bug-7156.php b/tests/PHPStan/Rules/Functions/data/bug-7156.php index 209a9decf5..4c3f0f6666 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-7156.php +++ b/tests/PHPStan/Rules/Functions/data/bug-7156.php @@ -32,7 +32,7 @@ function foobar2(mixed $data): void throw new \RuntimeException(); } - assertType("non-empty-array&hasOffsetValue('value', string)", $data); + assertType("non-empty-array&hasOffsetValue('value', string)", $data); foo($data); } From 8f73a137c7e58c186a04cc627b68bb6424155f65 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 19 Jan 2026 10:18:19 +0100 Subject: [PATCH 2/2] Update NonEmptyArrayType.php --- src/Type/Accessory/NonEmptyArrayType.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 2718af8a53..d06699b90d 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -78,8 +78,17 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult { $isArray = $type->isArray(); $isIterableAtLeastOnce = $type->isIterableAtLeastOnce(); + $isNonEmptyArray = $isArray->and($isIterableAtLeastOnce); - return new AcceptsResult($isArray->and($isIterableAtLeastOnce), []); + if ($isNonEmptyArray->yes()) { + return AcceptsResult::createYes(); + } + + if ($type instanceof CompoundType) { + return $type->isAcceptedBy($this, $strictTypes); + } + + return new AcceptsResult($isNonEmptyArray, []); } public function isSuperTypeOf(Type $type): IsSuperTypeOfResult