From ece37d663410f53d26d9106f322d97106e062841 Mon Sep 17 00:00:00 2001 From: Stefan Fisk Date: Wed, 6 Aug 2025 17:17:04 +0200 Subject: [PATCH] Make DOMNamedNodeMap generic Resolves phpstan/phpstan#13365. --- conf/config.neon | 3 +- stubs/dom.stub | 38 ++++++++++++++++++----- tests/PHPStan/Analyser/nsrt/bug-13076.php | 8 ++--- tests/PHPStan/Analyser/nsrt/bug-13365.php | 27 ++++++++++++++++ 4 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-13365.php diff --git a/conf/config.neon b/conf/config.neon index 95cca76752..3b5ebbdce9 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -26,7 +26,8 @@ parameters: bleedingEdge: false checkNonStringableDynamicAccess: false checkParameterCastableToNumberFunctions: false - skipCheckGenericClasses: [] + skipCheckGenericClasses: + - DOMNamedNodeMap stricterFunctionMap: false reportPreciseLineForUnusedFunctionParameter: false internalTag: false diff --git a/stubs/dom.stub b/stubs/dom.stub index 074bdd5102..4b5446144d 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -31,12 +31,12 @@ class DOMNode { /** - * @var DOMNamedNodeMap|null + * @var DOMNamedNodeMap|null */ public $attributes; /** - * @phpstan-assert-if-true =DOMNamedNodeMap $this->attributes + * @phpstan-assert-if-true =DOMNamedNodeMap $this->attributes * @return bool */ public function hasAttributes() {} @@ -46,7 +46,7 @@ class DOMNode class DOMElement extends DOMNode { - /** @var DOMNamedNodeMap */ + /** @var DOMNamedNodeMap */ public $attributes; /** @var DOMDocument */ @@ -69,10 +69,9 @@ class DOMElement extends DOMNode /** * @template-covariant TNode as DOMNode - * @implements Traversable * @implements IteratorAggregate */ -class DOMNodeList implements Traversable, IteratorAggregate, Countable +class DOMNodeList implements IteratorAggregate, Countable { /** @@ -96,7 +95,7 @@ class DOMXPath } -class DOMAttr +class DOMAttr extends DOMNode { /** @var DOMDocument */ @@ -155,11 +154,36 @@ class DOMProcessingInstruction } /** + * @template-covariant TNode as DOMNode + * @implements IteratorAggregate + * * @property-read int $length */ -class DOMNamedNodeMap +class DOMNamedNodeMap implements IteratorAggregate, Countable { + /** + * @return Iterator + */ + public function getIterator(): Iterator {} + /** + * @param string $qualifiedName + * @return TNode|null + */ + public function getNamedItem($qualifiedName): ?DOMNode {} + + /** + * @param string|null $namespace + * @param string $localName + * @return TNode|null + */ + public function getNamedItemNS($namespace, $localName): ?DOMNode {} + + /** + * @param int $index + * @return TNode|null + */ + public function item($index): ?DOMNode {} } class DOMText diff --git a/tests/PHPStan/Analyser/nsrt/bug-13076.php b/tests/PHPStan/Analyser/nsrt/bug-13076.php index dc6d25a8b6..b91cdeb5b7 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-13076.php +++ b/tests/PHPStan/Analyser/nsrt/bug-13076.php @@ -9,18 +9,18 @@ class Foo public function test(\DOMNode $node): void { if ($node->hasAttributes()) { - assertType('DOMNamedNodeMap', $node->attributes); + assertType('DOMNamedNodeMap', $node->attributes); } else { - assertType('DOMNamedNodeMap|null', $node->attributes); + assertType('DOMNamedNodeMap|null', $node->attributes); } } public function testElement(\DOMElement $node): void { if ($node->hasAttributes()) { - assertType('DOMNamedNodeMap', $node->attributes); + assertType('DOMNamedNodeMap', $node->attributes); } else { - assertType('DOMNamedNodeMap', $node->attributes); + assertType('DOMNamedNodeMap', $node->attributes); } } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-13365.php b/tests/PHPStan/Analyser/nsrt/bug-13365.php new file mode 100644 index 0000000000..b2ef7c11f9 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13365.php @@ -0,0 +1,27 @@ +attributes; + + if ($attributes === null) { + return; + } + + assertType('DOMNamedNodeMap', $attributes); + assertType('Iterator', $attributes->getIterator()); + assertType('DOMAttr|null', $attributes->getNamedItem('foo')); + assertType('DOMAttr|null', $attributes->getNamedItemNS('foo', 'bar')); + assertType('DOMAttr|null', $attributes->item(0)); + + foreach ($element->attributes ?? [] as $attr) { + assertType('DOMAttr', $attr); + } + } +}