From dc9221b6afe3a4e433fd60273763906fb2abee1b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 3 Jan 2020 11:31:10 +0100 Subject: [PATCH] Stubs - no need to repeat inherited methods --- src/PhpDoc/StubPhpDocProvider.php | 2 +- src/Reflection/ClassReflection.php | 2 +- .../Php/PhpClassReflectionExtension.php | 37 ++++++- stubs/ArrayObject.php | 25 ----- stubs/iterable.php | 55 ----------- .../PHPStan/Levels/data/stubs-methods-3.json | 22 +++++ .../PHPStan/Levels/data/stubs-methods-4.json | 22 +++++ tests/PHPStan/Levels/data/stubs-methods.php | 96 +++++++++++++++++++ tests/notAutoloaded/stubs-methods.php | 20 ++++ 9 files changed, 197 insertions(+), 84 deletions(-) create mode 100644 tests/PHPStan/Levels/data/stubs-methods-3.json create mode 100644 tests/PHPStan/Levels/data/stubs-methods-4.json diff --git a/src/PhpDoc/StubPhpDocProvider.php b/src/PhpDoc/StubPhpDocProvider.php index 9c4dc507da..0da956cf06 100644 --- a/src/PhpDoc/StubPhpDocProvider.php +++ b/src/PhpDoc/StubPhpDocProvider.php @@ -171,7 +171,7 @@ public function findFunctionPhpDoc(string $functionName): ?ResolvedPhpDocBlock return null; } - private function isKnownClass(string $className): bool + public function isKnownClass(string $className): bool { $this->initializeKnownElements(); diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 8294ae89b4..843781838c 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -817,7 +817,7 @@ private function getTemplateTags(): array /** * @return array */ - private function getAncestors(): array + public function getAncestors(): array { $ancestors = $this->ancestors; diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 8d62776f58..a4e40e9590 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -429,7 +429,7 @@ private function createMethod( $stubPhpDocParameterTypes = []; $stubPhpDocParameterVariadicity = []; if (count($variantNames) === 1) { - $stubPhpDoc = $this->stubPhpDocProvider->findMethodPhpDoc($declaringClassName, $methodReflection->getName()); + $stubPhpDoc = $this->findMethodPhpDocIncludingAncestors($declaringClassName, $methodReflection->getName()); if ($stubPhpDoc !== null) { $stubPhpDocString = $stubPhpDoc->getPhpDocString(); $templateTypeMap = $declaringClass->getActiveTemplateTypeMap(); @@ -486,7 +486,7 @@ private function createMethod( } $declaringTraitName = $this->findMethodTrait($methodReflection); - $resolvedPhpDoc = $this->stubPhpDocProvider->findMethodPhpDoc($declaringClassName, $methodReflection->getName()); + $resolvedPhpDoc = $this->findMethodPhpDocIncludingAncestors($declaringClassName, $methodReflection->getName()); $stubPhpDocString = null; if ($resolvedPhpDoc === null) { if ($declaringClass->getFileName() !== false) { @@ -866,4 +866,37 @@ private function getPhpDocReturnType(ClassReflection $phpDocBlockClassReflection return null; } + private function findMethodPhpDocIncludingAncestors(string $declaringClassName, string $methodName): ?ResolvedPhpDocBlock + { + $resolved = $this->stubPhpDocProvider->findMethodPhpDoc($declaringClassName, $methodName); + if ($resolved !== null) { + return $resolved; + } + if (!$this->stubPhpDocProvider->isKnownClass($declaringClassName)) { + return null; + } + if (!$this->reflectionProvider->hasClass($declaringClassName)) { + return null; + } + + $ancestors = $this->reflectionProvider->getClass($declaringClassName)->getAncestors(); + foreach ($ancestors as $ancestor) { + if ($ancestor->getName() === $declaringClassName) { + continue; + } + if (!$ancestor->hasNativeMethod($methodName)) { + continue; + } + + $resolved = $this->stubPhpDocProvider->findMethodPhpDoc($ancestor->getName(), $methodName); + if ($resolved === null) { + continue; + } + + return $resolved; + } + + return null; + } + } diff --git a/stubs/ArrayObject.php b/stubs/ArrayObject.php index 5e90565dd7..9f717de224 100644 --- a/stubs/ArrayObject.php +++ b/stubs/ArrayObject.php @@ -50,31 +50,6 @@ class ArrayObject implements IteratorAggregate, ArrayAccess */ public function __construct($input = null, $flags = 0, $iterator_class = "ArrayIterator") { } - /** - * @param TKey $index - * @return bool - */ - public function offsetExists($index) { } - - /** - * @param TKey $index - * @return TValue - */ - public function offsetGet($index) { } - - /** - * @param TKey $index - * @param TValue $newval - * @return void - */ - public function offsetSet($index, $newval) { } - - /** - * @param TKey $index - * @return void - */ - public function offsetUnset($index) { } - /** * @param TValue $value * @return void diff --git a/stubs/iterable.php b/stubs/iterable.php index d9d5499e3d..ce011cd266 100644 --- a/stubs/iterable.php +++ b/stubs/iterable.php @@ -56,16 +56,6 @@ public function key(); class Generator implements Iterator { - /** - * @return TValue - */ - public function current() {} - - /** - * @return TKey - */ - public function key() {} - /** * @return TReturn */ @@ -95,16 +85,6 @@ class SimpleXMLElement implements Traversable interface SeekableIterator extends Iterator { - /** - * @return TValue - */ - public function current(); - - /** - * @return TKey - */ - public function key(); - } /** @@ -122,31 +102,6 @@ class ArrayIterator implements SeekableIterator, ArrayAccess, Countable, Seriali */ public function __construct($array = array(), $flags = 0) { } - /** - * @param TKey $index - * @return bool - */ - public function offsetExists($index) { } - - /** - * @param TKey $index - * @return TValue - */ - public function offsetGet($index) { } - - /** - * @param TKey $index - * @param TValue $newval - * @return void - */ - public function offsetSet($index, $newval) { } - - /** - * @param TKey $index - * @return void - */ - public function offsetUnset($index) { } - /** * @param TValue $value * @return void @@ -170,16 +125,6 @@ public function uasort($cmp_function) { } */ public function uksort($cmp_function) { } - /** - * @return TValue - */ - public function current(); - - /** - * @return TKey - */ - public function key(); - } class DOMDocument diff --git a/tests/PHPStan/Levels/data/stubs-methods-3.json b/tests/PHPStan/Levels/data/stubs-methods-3.json new file mode 100644 index 0000000000..0d942f5dac --- /dev/null +++ b/tests/PHPStan/Levels/data/stubs-methods-3.json @@ -0,0 +1,22 @@ +[ + { + "message": "Return type (string) of method StubsIntegrationTest\\AnotherInterfaceExtendingInterfaceWithStubPhpDoc::doFoo() should be compatible with return type (int) of method StubsIntegrationTest\\InterfaceWithStubPhpDoc::doFoo()", + "line": 68, + "ignorable": true + }, + { + "message": "Anonymous function should return int but returns string.", + "line": 74, + "ignorable": true + }, + { + "message": "Return type (string) of method StubsIntegrationTest\\YetAnotherClassExtendingInterfaceWithStubPhpDoc::doFoo() should be compatible with return type (int) of method StubsIntegrationTest\\InterfaceWithStubPhpDoc::doFoo()", + "line": 119, + "ignorable": true + }, + { + "message": "Anonymous function should return int but returns string.", + "line": 128, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/stubs-methods-4.json b/tests/PHPStan/Levels/data/stubs-methods-4.json new file mode 100644 index 0000000000..f1199a4cdd --- /dev/null +++ b/tests/PHPStan/Levels/data/stubs-methods-4.json @@ -0,0 +1,22 @@ +[ + { + "message": "Strict comparison using === between int and array() will always evaluate to false.", + "line": 47, + "ignorable": true + }, + { + "message": "Strict comparison using === between int and array() will always evaluate to false.", + "line": 58, + "ignorable": true + }, + { + "message": "Strict comparison using === between int and array() will always evaluate to false.", + "line": 89, + "ignorable": true + }, + { + "message": "Strict comparison using === between int and array() will always evaluate to false.", + "line": 108, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/stubs-methods.php b/tests/PHPStan/Levels/data/stubs-methods.php index e23a74239a..49308e923b 100644 --- a/tests/PHPStan/Levels/data/stubs-methods.php +++ b/tests/PHPStan/Levels/data/stubs-methods.php @@ -31,3 +31,99 @@ function (FooChild $fooChild) { $string = $fooChild->doFoo('test'); $fooChild->doFoo($string); }; + +interface InterfaceWithStubPhpDoc +{ + + /** + * @return string + */ + public function doFoo(); + +} + +function (InterfaceWithStubPhpDoc $stub): int +{ + $stub->doFoo() === []; + return $stub->doFoo(); // stub wins +}; + +interface InterfaceExtendingInterfaceWithStubPhpDoc extends InterfaceWithStubPhpDoc +{ + +} + +function (InterfaceExtendingInterfaceWithStubPhpDoc $stub): int +{ + $stub->doFoo() === []; + return $stub->doFoo(); // stub wins +}; + +interface AnotherInterfaceExtendingInterfaceWithStubPhpDoc extends InterfaceWithStubPhpDoc +{ + + /** + * @return string + */ + public function doFoo(); + +} + +function (AnotherInterfaceExtendingInterfaceWithStubPhpDoc $stub): int +{ + return $stub->doFoo(); // implementation wins - string -> int mismatch reported +}; + +class ClassExtendingInterfaceWithStubPhpDoc implements InterfaceWithStubPhpDoc +{ + + public function doFoo() + { + throw new \Exception(); + } + +} + +function (ClassExtendingInterfaceWithStubPhpDoc $stub): int +{ + $stub->doFoo() === []; + return $stub->doFoo(); // stub wins +}; + +class AnotherClassExtendingInterfaceWithStubPhpDoc implements InterfaceWithStubPhpDoc +{ + + /** + * @return string + */ + public function doFoo() + { + throw new \Exception(); + } + +} + +function (AnotherClassExtendingInterfaceWithStubPhpDoc $stub): int +{ + $stub->doFoo() === []; + return $stub->doFoo(); // stub wins +}; + +/** This one is missing in the stubs */ +class YetAnotherClassExtendingInterfaceWithStubPhpDoc implements InterfaceWithStubPhpDoc +{ + + /** + * @return string + */ + public function doFoo() + { + throw new \Exception(); + } + +} + +function (YetAnotherClassExtendingInterfaceWithStubPhpDoc $stub): int +{ + return $stub->doFoo(); // implementation wins - string -> int mismatch reported +}; diff --git a/tests/notAutoloaded/stubs-methods.php b/tests/notAutoloaded/stubs-methods.php index e3c786385c..adb2f140a9 100644 --- a/tests/notAutoloaded/stubs-methods.php +++ b/tests/notAutoloaded/stubs-methods.php @@ -32,3 +32,23 @@ public function doFoo(Collection $collection): void } } + +interface InterfaceWithStubPhpDoc +{ + + /** + * @return int + */ + public function doFoo(); + +} + +class ClassExtendingInterfaceWithStubPhpDoc implements InterfaceWithStubPhpDoc +{ + +} + +class AnotherClassExtendingInterfaceWithStubPhpDoc implements InterfaceWithStubPhpDoc +{ + +}