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..395b49c 100644 --- a/fixtures/global_symbols.php +++ b/fixtures/global_symbols.php @@ -105,3 +105,22 @@ 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!'; + } + + public function selfParamTest(self $something) { + $something->hello(); + } +} diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index 5a6f88b..cbf9337 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -758,8 +758,12 @@ 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->type instanceof Node\Name && strtolower((string)$node->type) === 'self') { + // handle self reference + $class = getClosestNode($node->type, Node\Stmt\Class_::class); + if ($class !== null) { + return new Types\Object_(new Fqsen('\\' . $class->name)); + } } } if ($node->default !== null) { @@ -789,6 +793,14 @@ class DefinitionResolver // Resolve a string like "bool" to a type object return $this->typeResolver->resolve($node->returnType); } + if ($node->returnType instanceof Node\Name && strtolower((string)$node->returnType) === '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..4eb5752 100644 --- a/tests/Server/ServerTestCase.php +++ b/tests/Server/ServerTestCase.php @@ -85,6 +85,10 @@ 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(125, 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))), + 'Something::selfParamTest()' => new Location($globalSymbolsUri, new Range(new Position(122, 4), new Position(124, 5))), // Namespaced 'TestNamespace' => new Location($symbolsUri, new Range(new Position( 2, 10), new Position( 2, 23))), @@ -214,6 +218,16 @@ 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() + 1 => new Location($globalSymbolsUri, new Range(new Position(123, 8), new Position(123, 18))) // $something->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..0b32e38 100644 --- a/tests/Server/TextDocument/HoverTest.php +++ b/tests/Server/TextDocument/HoverTest.php @@ -31,6 +31,35 @@ 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 testHoverForSelfParamTypeDefinition() + { + // class Something + // get hover for Something::getInstance() (returns a instance of Something) + $reference = $this->getReferenceLocations('Something')[1]; + $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..3ef4300 100644 --- a/tests/Server/Workspace/SymbolTest.php +++ b/tests/Server/Workspace/SymbolTest.php @@ -59,6 +59,10 @@ 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('selfParamTest', SymbolKind::METHOD, $this->getDefinitionLocation('Something::selfParamTest()'), 'Something'), new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('whatever()'), ''), new SymbolInformation('SecondTestNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('SecondTestNamespace'), '')