diff --git a/conf/config.neon b/conf/config.neon index 1cd6efbf7f9..8fc949c9361 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -30,6 +30,11 @@ parameters: checkParameterCastableToNumberFunctions: false skipCheckGenericClasses: - DOMNamedNodeMap + - ParentIterator + - RecursiveCachingIterator + - RecursiveFilterIterator + - RecursiveRegexIterator + - ReflectionObject stricterFunctionMap: false reportPreciseLineForUnusedFunctionParameter: false checkPrintfParameterTypes: false diff --git a/src/PhpDoc/ReflectionClassStubFilesExtension.php b/src/PhpDoc/ReflectionClassStubFilesExtension.php index f47fe54bbae..a6844801d4a 100644 --- a/src/PhpDoc/ReflectionClassStubFilesExtension.php +++ b/src/PhpDoc/ReflectionClassStubFilesExtension.php @@ -18,11 +18,13 @@ public function getFiles(): array if (!$this->phpVersion->supportsLazyObjects()) { return [ __DIR__ . '/../../stubs/ReflectionClass.stub', + __DIR__ . '/../../stubs/ReflectionObject.stub', ]; } return [ __DIR__ . '/../../stubs/ReflectionClassWithLazyObjects.stub', + __DIR__ . '/../../stubs/ReflectionObjectWithLazyObjects.stub', ]; } diff --git a/stubs/ReflectionObject.stub b/stubs/ReflectionObject.stub new file mode 100644 index 00000000000..6f779116406 --- /dev/null +++ b/stubs/ReflectionObject.stub @@ -0,0 +1,15 @@ + + */ +class ReflectionObject extends ReflectionClass +{ + + /** + * @param T $argument + */ + public function __construct($argument) {} + +} diff --git a/stubs/ReflectionObjectWithLazyObjects.stub b/stubs/ReflectionObjectWithLazyObjects.stub new file mode 100644 index 00000000000..8979b9142c2 --- /dev/null +++ b/stubs/ReflectionObjectWithLazyObjects.stub @@ -0,0 +1,15 @@ + + */ +class ReflectionObject extends ReflectionClass +{ + + /** + * @param T $argument + */ + public function __construct($argument) {} + +} diff --git a/stubs/iterable.stub b/stubs/iterable.stub index baf5ca90837..b2982ba1522 100644 --- a/stubs/iterable.stub +++ b/stubs/iterable.stub @@ -456,6 +456,93 @@ class RegexIterator extends FilterIterator { public function key() {} } +/** + * @template-covariant TKey + * @template-covariant TValue + * @template TIterator as RecursiveIterator + * + * @template-extends FilterIterator + * @template-implements RecursiveIterator + */ +abstract class RecursiveFilterIterator extends FilterIterator implements RecursiveIterator { + /** + * @param TIterator $iterator + */ + public function __construct(RecursiveIterator $iterator) {} + + /** + * @return bool + */ + public function hasChildren() {} + + /** + * @return static + */ + public function getChildren() {} +} + +/** + * @template-covariant TKey + * @template-covariant TValue + * @template TIterator as RecursiveIterator + * + * @template-extends RecursiveFilterIterator + */ +class ParentIterator extends RecursiveFilterIterator { +} + +/** + * @template TKey + * @template TValue + * @template TIterator as Iterator + * + * @template-extends CachingIterator + * @template-implements RecursiveIterator + */ +class RecursiveCachingIterator extends CachingIterator implements RecursiveIterator { + /** + * @param TIterator $iterator + * @param int-mask-of $flags + */ + public function __construct(Iterator $iterator, int $flags = CachingIterator::CALL_TOSTRING) {} + + /** + * @return bool + */ + public function hasChildren() {} + + /** + * @return static + */ + public function getChildren() {} +} + +/** + * @template TKey + * @template TValue + * @template TIterator of RecursiveIterator + * + * @template-extends RegexIterator + * @template-implements RecursiveIterator + */ +class RecursiveRegexIterator extends RegexIterator implements RecursiveIterator { + /** + * @param TIterator $iterator + * @param RegexIterator::MATCH|RegexIterator::GET_MATCH|RegexIterator::ALL_MATCHES|RegexIterator::SPLIT|RegexIterator::REPLACE $mode + */ + public function __construct(RecursiveIterator $iterator, string $pattern, int $mode = RegexIterator::MATCH, int $flags = 0, int $preg_flags = 0) {} + + /** + * @return bool + */ + public function hasChildren() {} + + /** + * @return static + */ + public function getChildren() {} +} + /** * @template-implements Iterator */ diff --git a/tests/PHPStan/Analyser/nsrt/bug-15032.php b/tests/PHPStan/Analyser/nsrt/bug-15032.php new file mode 100644 index 00000000000..086402e959e --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-15032.php @@ -0,0 +1,33 @@ +', $ref); + + $ret = $ref->newInstance(); + assertType('T of object (function Bug15032\createInstance(), argument)', $ret); + + return $ret; +} + +function concreteObject(\Exception $e): void +{ + $ref = new ReflectionObject($e); + assertType('ReflectionObject', $ref); + assertType('class-string', $ref->getName()); + assertType('class-string', $ref->name); + assertType('Exception', $ref->newInstance()); + assertType('Exception', $ref->newInstanceArgs([])); + assertType('Exception', $ref->newInstanceWithoutConstructor()); +} diff --git a/tests/PHPStan/Analyser/nsrt/recursive-iterators.php b/tests/PHPStan/Analyser/nsrt/recursive-iterators.php new file mode 100644 index 00000000000..119f22ad077 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/recursive-iterators.php @@ -0,0 +1,61 @@ + $parent + * @param RecursiveCachingIterator $caching + * @param RecursiveRegexIterator $regex + */ + public function doFoo($parent, $caching, $regex): void + { + foreach ($parent as $key => $value) { + assertType('string', $key); + assertType('SplFileInfo', $value); + } + foreach ($caching as $key => $value) { + assertType('string', $key); + assertType('SplFileInfo', $value); + } + foreach ($regex as $key => $value) { + assertType('string', $key); + assertType('SplFileInfo', $value); + } + + assertType('ParentIterator', $parent->getChildren()); + assertType('RecursiveCachingIterator', $caching->getChildren()); + assertType('RecursiveRegexIterator', $regex->getChildren()); + + assertType('Iterator', $parent->getInnerIterator()); + } + + /** + * @param RecursiveArrayIterator $it + */ + public function doBar($it): void + { + $caching = new RecursiveCachingIterator($it); + assertType('RecursiveCachingIterator>', $caching); + assertType('string', $caching->current()); + assertType('int', $caching->key()); + + $parent = new ParentIterator($it); + assertType('ParentIterator>', $parent); + + $regex = new RecursiveRegexIterator($it, '~foo~'); + assertType('RecursiveRegexIterator>', $regex); + assertType('string', $regex->current()); + assertType('int', $regex->key()); + } + +} diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 910ea477e02..366c39b9964 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -673,7 +673,7 @@ public function testBug9615(): void $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-9615.php'], [ [ - 'Return type mixed of method Bug9615\ExpectComplaintsHere::accept() is not covariant with tentative return type bool of method FilterIterator>::accept().', + 'Return type mixed of method Bug9615\ExpectComplaintsHere::accept() is not covariant with tentative return type bool of method FilterIterator>::accept().', 19, $tipText, ],