diff --git a/fixtures/global_references.php b/fixtures/global_references.php index c76c934..7370d45 100644 --- a/fixtures/global_references.php +++ b/fixtures/global_references.php @@ -41,3 +41,6 @@ TestClass::$staticTestProperty[123]->testProperty; $child = new ChildClass; echo $child->testMethod(); + +// resolve self expression +Something::getInstance()->hello(); diff --git a/fixtures/global_symbols.php b/fixtures/global_symbols.php index ac93b68..f7fec6e 100644 --- a/fixtures/global_symbols.php +++ b/fixtures/global_symbols.php @@ -105,3 +105,18 @@ class ChildClass extends TestClass {} define('TEST_DEFINE_CONSTANT', false); print TEST_DEFINE_CONSTANT ? 'true' : 'false'; + +// resolve self type test +class Something { + + public static function getInstance(): self { + return new self; + } + + /** + * Does nothing + */ + public function hello() { + echo 'Hi!'; + } +} diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index bbf9076..83f7898 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -745,9 +745,19 @@ class DefinitionResolver if (is_string($node->type)) { // Resolve a string like "bool" to a type object $type = $this->typeResolver->resolve($node->type); - } else { - $type = new Types\Object_(new Fqsen('\\' . (string)$node->type)); + } else if ($node->returnType instanceof Node\Name) { + $type = (string)$node->returnType; + + if (strtolower($type) === 'self') { + // handle self reference + $class = getClosestNode($node, Node\Stmt\Class_::class); + + if($class !== null) { + return new Types\Object_(new Fqsen('\\' . $class->name)); + } + } } + $type = new Types\Object_(new Fqsen('\\' . (string)$node->type)); } if ($node->default !== null) { $defaultType = $this->resolveExpressionNodeToType($node->default); @@ -776,6 +786,18 @@ class DefinitionResolver // Resolve a string like "bool" to a type object return $this->typeResolver->resolve($node->returnType); } + if ($node->returnType instanceof Node\Name) { + $type = (string)$node->returnType; + + if (strtolower($type) === 'self') { + // handle self reference + $class = getClosestNode($node, Node\Stmt\Class_::class); + + if ($class !== null) { + return new Types\Object_(new Fqsen('\\' . $class->name)); + } + } + } return new Types\Object_(new Fqsen('\\' . (string)$node->returnType)); } // Unknown return type diff --git a/tests/Server/ServerTestCase.php b/tests/Server/ServerTestCase.php index 1154216..61507be 100644 --- a/tests/Server/ServerTestCase.php +++ b/tests/Server/ServerTestCase.php @@ -85,6 +85,9 @@ abstract class ServerTestCase extends TestCase 'TestClass::testMethod()' => new Location($globalSymbolsUri, new Range(new Position(57, 4), new Position(60, 5))), 'test_function()' => new Location($globalSymbolsUri, new Range(new Position(78, 0), new Position(81, 1))), 'whatever()' => new Location($globalReferencesUri, new Range(new Position(21, 0), new Position(23, 1))), + 'Something' => new Location($globalSymbolsUri, new Range(new Position(109, 0), new Position(121, 1))), + 'Something::getInstance()' => new Location($globalSymbolsUri, new Range(new Position(111, 4), new Position(113, 5))), + 'Something::hello()' => new Location($globalSymbolsUri, new Range(new Position(118, 4), new Position(120, 5))), // Namespaced 'TestNamespace' => new Location($symbolsUri, new Range(new Position( 2, 10), new Position( 2, 23))), @@ -214,6 +217,15 @@ abstract class ServerTestCase extends TestCase 'test_function()' => [ 0 => new Location($globalReferencesUri, new Range(new Position(10, 0), new Position(10, 13))), 1 => new Location($globalReferencesUri, new Range(new Position(31, 13), new Position(31, 40))) + ], + 'Something' => [ + 0 => new Location($globalReferencesUri, new Range(new Position(45, 0), new Position(56, 9))) // Something::getInstance()->hello() + ], + 'Something::getInstance()' => [ + 0 => new Location($globalReferencesUri, new Range(new Position(45, 0), new Position(45, 24))) // Something::getInstance()->hello() + ], + 'Something::hello()' => [ + 0 => new Location($globalReferencesUri, new Range(new Position(45, 0), new Position(45, 33))) // Something::getInstance()->hello() ] ]; // @codingStandardsIgnoreEnd diff --git a/tests/Server/TextDocument/CompletionTest.php b/tests/Server/TextDocument/CompletionTest.php index 29851e0..9cc9aeb 100644 --- a/tests/Server/TextDocument/CompletionTest.php +++ b/tests/Server/TextDocument/CompletionTest.php @@ -179,6 +179,15 @@ class CompletionTest extends TestCase null, '\ChildClass' ), + new CompletionItem( + 'Something', + CompletionItemKind::CLASS_, + null, + null, + null, + null, + '\Something' + ), // Namespaced, `use`d TestClass definition (inserted as TestClass) new CompletionItem( 'TestClass', diff --git a/tests/Server/TextDocument/HoverTest.php b/tests/Server/TextDocument/HoverTest.php index 4da707a..7b4925c 100644 --- a/tests/Server/TextDocument/HoverTest.php +++ b/tests/Server/TextDocument/HoverTest.php @@ -31,6 +31,21 @@ class HoverTest extends ServerTestCase ], $reference->range), $result); } + public function testHoverForSelfReturnTypeDefintion() + { + // class Something + // get hover for Something::getInstance() (returns a instance of Something) + $reference = $this->getReferenceLocations('Something::hello()')[0]; + $result = $this->textDocument->hover( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); + $this->assertEquals(new Hover([ + new MarkedString('php', "range), $result); + } + public function testHoverForClassLikeDefinition() { // class TestClass implements TestInterface diff --git a/tests/Server/Workspace/SymbolTest.php b/tests/Server/Workspace/SymbolTest.php index 65ce804..209e569 100644 --- a/tests/Server/Workspace/SymbolTest.php +++ b/tests/Server/Workspace/SymbolTest.php @@ -59,6 +59,9 @@ class SymbolTest extends ServerTestCase new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('test_function()'), ''), new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('ChildClass'), ''), new SymbolInformation('TEST_DEFINE_CONSTANT', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_DEFINE_CONSTANT'), ''), + new SymbolInformation('Something', SymbolKind::CLASS_, $this->getDefinitionLocation('Something'), ''), + new SymbolInformation('getInstance', SymbolKind::METHOD, $this->getDefinitionLocation('Something::getInstance()'), 'Something'), + new SymbolInformation('hello', SymbolKind::METHOD, $this->getDefinitionLocation('Something::hello()'), 'Something'), new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('whatever()'), ''), new SymbolInformation('SecondTestNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('SecondTestNamespace'), '')