From 493e2defd3ae73442183903957275955b3338760 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 20 Jul 2025 01:00:59 +0200 Subject: [PATCH 1/4] Fix objectType::getOffsetValueType --- src/Type/ObjectType.php | 8 ++--- tests/PHPStan/Analyser/nsrt/bug-12125.php | 37 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12125.php diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 25ee53d4e9..78e4473410 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -1228,14 +1228,14 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic public function getOffsetValueType(Type $offsetType): Type { - if (!$this->isExtraOffsetAccessibleClass()->no()) { - return new MixedType(); - } - if ($this->isInstanceOf(ArrayAccess::class)->yes()) { return RecursionGuard::run($this, fn (): Type => $this->getMethod('offsetGet', new OutOfClassScope())->getOnlyVariant()->getReturnType()); } + if (!$this->isExtraOffsetAccessibleClass()->no()) { + return new MixedType(); + } + return new ErrorType(); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12125.php b/tests/PHPStan/Analyser/nsrt/bug-12125.php new file mode 100644 index 0000000000..815e3cb701 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12125.php @@ -0,0 +1,37 @@ +|array $bug */ +$bug = []; + +assertType('stdClass|null', $bug['key'] ?? null); + +interface MyInterface { + /** @return array | ArrayAccess */ + public function getStrings(): array | ArrayAccess; +} + +function myFunction(MyInterface $container): string { + $strings = $container->getStrings(); + assertType('array|ArrayAccess', $strings); + assertType('string|null', $strings['test']); + return $strings['test']; +} + +function myOtherFunction(MyInterface $container): string { + $strings = $container->getStrings(); + assertType('array|ArrayAccess', $strings); + if (isset($strings['test'])) { + assertType('string', $strings['test']); + return $strings['test']; + } else { + throw new Exception(); + } +} From ba4a7cface7392eb2c1dac0a60620d4385f526c1 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 20 Jul 2025 11:54:01 +0200 Subject: [PATCH 2/4] Add test --- tests/PHPStan/Analyser/nsrt/bug-9575.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-9575.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-9575.php b/tests/PHPStan/Analyser/nsrt/bug-9575.php new file mode 100644 index 0000000000..fb19202e4f --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-9575.php @@ -0,0 +1,22 @@ + + 1 + +XML; + +$xml = new SimpleXMLElement($string); +foreach($xml->foo[0]->attributes() as $a => $b) { + echo $a,'="',$b,"\"\n"; +} + +assertType('(SimpleXMLElement|null)', $xml->foo); +assertType('(SimpleXMLElement|null)', $xml->foo[0]); +assertType('(SimpleXMLElement|null)', $xml->foobar); +assertType('(SimpleXMLElement|null)', $xml->foo->attributes()); From 01f7ac0580c902e7c97ad9a12487c0b43185c1c3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 20 Jul 2025 11:58:36 +0200 Subject: [PATCH 3/4] Add test --- tests/PHPStan/Analyser/nsrt/bug-13144.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-13144.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-13144.php b/tests/PHPStan/Analyser/nsrt/bug-13144.php new file mode 100644 index 0000000000..fb47f4d862 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13144.php @@ -0,0 +1,23 @@ + 1, 'b' => 2]); + +assertType('ArrayObject', $arr); // correctly inferred as `ArrayObject` + +$a = $arr['a']; // ok +$b = $arr['b']; // ok + +assertType('int|null', $a); // correctly inferred as `int|null` +assertType('int|null', $b); // correctly inferred as `int|null` + + +['a' => $a, 'b' => $b] = $arr; // ok + +assertType('int|null', $a); // incorrectly inferred as `mixed` +assertType('int|null', $b); // incorrectly inferred as `mixed` From 08c8bd43e6bbd6ea1a9b8e6f634020b1cb10a0a7 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 20 Jul 2025 12:00:38 +0200 Subject: [PATCH 4/4] Add test --- tests/PHPStan/Analyser/nsrt/bug-9456.php | 71 ++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-9456.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-9456.php b/tests/PHPStan/Analyser/nsrt/bug-9456.php new file mode 100644 index 0000000000..22b5e4dad8 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-9456.php @@ -0,0 +1,71 @@ += 8.0 + +namespace Bug9456; + +use ArrayAccess; + +use function PHPStan\Testing\assertType; + +/** + * @template TKey of array-key + * @template TValue of mixed + * + * @implements ArrayAccess + */ +class Collection implements ArrayAccess { + /** + * @param (callable(TValue, TKey): bool)|TValue|string $key + * @param TValue|string|null $operator + * @param TValue|null $value + * @return static, static> + */ + public function partition($key, $operator = null, $value = null) {} // @phpstan-ignore-line + + /** + * @param TKey $key + * @return TValue + */ + public function offsetGet($key): mixed { return null; } // @phpstan-ignore-line + + /** + * @param TKey $key + * @return bool + */ + public function offsetExists($key): bool { return true; } + + /** + * @param TKey|null $key + * @param TValue $value + * @return void + */ + public function offsetSet($key, $value): void {} + + /** + * @param TKey $key + * @return void + */ + public function offsetUnset($key): void {} +} + +class HelloWorld +{ + /** + * @param Collection $collection + */ + public function sayHello(Collection $collection): void + { + $result = $collection->partition('key'); + + assertType( + 'Bug9456\Collection, Bug9456\Collection>', + $result + ); + assertType('Bug9456\Collection', $result[0]); + assertType('Bug9456\Collection', $result[1]); + + [$one, $two] = $collection->partition('key'); + + assertType('Bug9456\Collection', $one); + assertType('Bug9456\Collection', $two); + } +}