From de96494feef6502516a30718b4c2fc4b00341687 Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Sun, 27 Nov 2022 05:19:05 -0400 Subject: [PATCH] Allow omitting argument offsets for map-type overrides in phpstorm.meta Fixes vimeo/psalm#8758 --- .../Internal/Scanner/PhpStormMetaScanner.php | 26 +++++-- tests/StubTest.php | 74 +++++++++++++++++++ tests/fixtures/stubs/phpstorm.meta.php | 13 ++++ 3 files changed, 107 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php b/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php index 10d4156fb77..1e4cb9c15ae 100644 --- a/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php +++ b/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php @@ -89,15 +89,22 @@ public static function handleOverride(array $args, Codebase $codebase): void if ($identifier instanceof PhpParser\Node\Expr\StaticCall && $identifier->class instanceof PhpParser\Node\Name\FullyQualified && $identifier->name instanceof PhpParser\Node\Identifier - && $identifier->getArgs() - && $identifier->getArgs()[0]->value instanceof PhpParser\Node\Scalar\LNumber + && ( + $identifier->getArgs() === [] + || $identifier->getArgs()[0]->value instanceof PhpParser\Node\Scalar\LNumber + ) ) { $meta_fq_classlike_name = implode('\\', $identifier->class->parts); $meta_method_name = strtolower($identifier->name->name); if ($map) { - $offset = $identifier->getArgs()[0]->value->value; + $offset = 0; + if ($identifier->getArgs() + && $identifier->getArgs()[0]->value instanceof PhpParser\Node\Scalar\LNumber + ) { + $offset = $identifier->getArgs()[0]->value->value; + } $codebase->methods->return_type_provider->registerClosure( $meta_fq_classlike_name, @@ -248,13 +255,20 @@ static function ( if ($identifier instanceof PhpParser\Node\Expr\FuncCall && $identifier->name instanceof PhpParser\Node\Name\FullyQualified - && $identifier->getArgs() - && $identifier->getArgs()[0]->value instanceof PhpParser\Node\Scalar\LNumber + && ( + $identifier->getArgs() === [] + || $identifier->getArgs()[0]->value instanceof PhpParser\Node\Scalar\LNumber + ) ) { $function_id = strtolower(implode('\\', $identifier->name->parts)); if ($map) { - $offset = $identifier->getArgs()[0]->value->value; + $offset = 0; + if ($identifier->getArgs() + && $identifier->getArgs()[0]->value instanceof PhpParser\Node\Scalar\LNumber + ) { + $offset = $identifier->getArgs()[0]->value->value; + } $codebase->functions->return_type_provider->registerClosure( $function_id, diff --git a/tests/StubTest.php b/tests/StubTest.php index 736943df32f..ab4b88492f6 100644 --- a/tests/StubTest.php +++ b/tests/StubTest.php @@ -22,6 +22,9 @@ use function getcwd; use function implode; use function reset; +use function strlen; +use function strpos; +use function substr; use const DIRECTORY_SEPARATOR; @@ -321,6 +324,12 @@ class MyClass { */ public function create(string $s) {} + /** + * @return mixed + * @psalm-suppress InvalidReturnType + */ + public function create2(string $s) {} + /** * @param mixed $s * @return mixed @@ -342,6 +351,12 @@ public function bar(array $a) {} */ function create(string $s) {} + /** + * @return mixed + * @psalm-suppress InvalidReturnType + */ + function create2(string $s) {} + /** * @param mixed $s * @return mixed @@ -358,11 +373,19 @@ function bar(array $a) {} $a1 = (new \Ns\MyClass)->creAte("object"); $a2 = (new \Ns\MyClass)->creaTe("exception"); + $y1 = (new \Ns\MyClass)->creAte2("object"); + $y2 = (new \Ns\MyClass)->creaTe2("exception"); + $b1 = \Create("object"); $b2 = \cReate("exception"); $e2 = \creAte(\LogicException::class); + $z1 = \Create2("object"); + $z2 = \cReate2("exception"); + + $x2 = \creAte2(\LogicException::class); + $c1 = (new \Ns\MyClass)->foo(5); $c2 = (new \Ns\MyClass)->bar(["hello"]); @@ -373,6 +396,57 @@ function bar(array $a) {} $context = new Context(); $this->analyzeFile($file_path, $context); + + $this->assertContextVars( + [ + '$a1===' => 'stdClass', + '$a2===' => 'Exception', + + '$y1===' => 'stdClass', + '$y2===' => 'Exception', + + '$b1===' => 'stdClass', + '$b2===' => 'Exception', + + '$e2===' => 'LogicException', + + '$z1===' => 'stdClass', + '$z2===' => 'Exception', + + '$x2===' => 'LogicException', + + '$c1===' => "5", + '$c2===' => "'hello'", + + '$d1===' => "5", + '$d2===' => "'hello'" + ], + $context + ); + } + + /** @param array $assertions */ + private function assertContextVars(array $assertions, Context $context): void + { + $actual_vars = []; + foreach ($assertions as $var => $_) { + $exact = false; + + if ($var && strpos($var, '===') === strlen($var) - 3) { + $var = substr($var, 0, -3); + $exact = true; + } + + if (isset($context->vars_in_scope[$var])) { + $value = $context->vars_in_scope[$var]->getId($exact); + if ($exact) { + $actual_vars[$var . '==='] = $value; + } else { + $actual_vars[$var] = $value; + } + } + } + $this->assertSame($assertions, $actual_vars); } public function testNamespacedStubClass(): void diff --git a/tests/fixtures/stubs/phpstorm.meta.php b/tests/fixtures/stubs/phpstorm.meta.php index 7ba1b8af075..f2d3944f92f 100644 --- a/tests/fixtures/stubs/phpstorm.meta.php +++ b/tests/fixtures/stubs/phpstorm.meta.php @@ -1,6 +1,7 @@ '@', 'exception' => \Exception::class, @@ -12,6 +13,18 @@ 'object' => \stdClass::class, ])); + // tests without argument offset (0 by default) + override(\Ns\MyClass::crEate2(), map([ + '' => '@', + 'exception' => \Exception::class, + 'object' => \stdClass::class, + ])); + override(\create2(), map([ + '' => '@', + 'exception' => \Exception::class, + 'object' => \stdClass::class, + ])); + override(\Ns\MyClass::foO(0), type(0)); override(\Ns\MyClass::Bar(0), elementType(0)); override(\foo(0), type(0));