forked from sorbet/sorbet
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[LSP] Go to implementation (sorbet#4598)
* Implement go to implementation for abstract classes * New use cases for the go toimplentation: - Go to implementation from abstract class site - Go to implementation from abstract class reference - Go to implementation from abstract method call site Also change updates tests * Replace has_value() with value_or(false) for "Go to implementation" experiment feature in lsp test * Move getSubclasses to the ClassOrModuleRef * Address PR feedback * Run linter * Remove allLocs * Fix remaining use cases * Run linter * Fix server capabilities * Fix test * Another attempt to fix recorded tests * Add additional safety check * Add tests for the "Go to Implementations" * Run formatting * Address comments * PR comments: part 2 * Move local functions into anonymous namespace
- Loading branch information
Showing
28 changed files
with
473 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ | |
|
||
.idea/ | ||
.vscode/ | ||
.vim/ | ||
|
||
/website/lib/core/metadata.js | ||
/website/lib/core/MetadataBlog.js | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
#include "main/lsp/requests/go_to_implementation.h" | ||
#include "core/lsp/QueryResponse.h" | ||
#include "main/lsp/json_types.h" | ||
#include "main/lsp/lsp.h" | ||
#include <cstddef> | ||
#include <memory> | ||
|
||
using namespace std; | ||
|
||
namespace sorbet::realmain::lsp { | ||
|
||
namespace { | ||
struct MethodImplementationResults { | ||
vector<core::Loc> locations; | ||
unique_ptr<ResponseError> error; | ||
}; | ||
|
||
unique_ptr<ResponseError> makeInvalidParamsError(std::string error) { | ||
return make_unique<ResponseError>((int)LSPErrorCodes::InvalidParams, error); | ||
} | ||
|
||
const MethodImplementationResults findMethodImplementations(const core::GlobalState &gs, core::SymbolRef method) { | ||
MethodImplementationResults res; | ||
if (!method.data(gs)->isMethod() || !method.data(gs)->isAbstract()) { | ||
res.error = makeInvalidParamsError( | ||
"Go to implementation can be used only for methods or references of abstract classes"); | ||
return res; | ||
} | ||
|
||
vector<core::Loc> locations; | ||
auto owner = method.data(gs)->owner; | ||
if (!owner.isClassOrModule()) { | ||
res.error = makeInvalidParamsError("Abstract method can only be inside a class or module"); | ||
return res; | ||
} | ||
|
||
auto owningClassSymbolRef = owner.asClassOrModuleRef(); | ||
auto childClasses = owningClassSymbolRef.getSubclasses(gs, false); | ||
auto methodName = method.data(gs)->name; | ||
for (const auto &childClass : childClasses) { | ||
auto methodImplementation = childClass.data(gs)->findMember(gs, methodName); | ||
locations.push_back(methodImplementation.data(gs)->loc()); | ||
} | ||
|
||
res.locations = locations; | ||
return res; | ||
} | ||
|
||
core::SymbolRef findOverridedMethod(const core::GlobalState &gs, const core::SymbolRef method) { | ||
auto ownerClass = method.data(gs)->owner.asClassOrModuleRef(); | ||
|
||
for (auto mixin : ownerClass.data(gs)->mixins()) { | ||
if (!mixin.data(gs)->isClassOrModule() && !mixin.data(gs)->isAbstract()) { | ||
continue; | ||
} | ||
return mixin.data(gs)->findMember(gs, method.data(gs)->name); | ||
} | ||
return core::Symbols::noSymbol(); | ||
} | ||
} // namespace | ||
|
||
GoToImplementationTask::GoToImplementationTask(const LSPConfiguration &config, MessageId id, | ||
std::unique_ptr<ImplementationParams> params) | ||
: LSPRequestTask(config, move(id), LSPMethod::TextDocumentImplementation), params(move(params)) {} | ||
|
||
unique_ptr<ResponseMessage> GoToImplementationTask::runRequest(LSPTypecheckerDelegate &typechecker) { | ||
auto response = make_unique<ResponseMessage>("2.0", id, LSPMethod::TextDocumentImplementation); | ||
|
||
const core::GlobalState &gs = typechecker.state(); | ||
auto queryResult = | ||
queryByLoc(typechecker, params->textDocument->uri, *params->position, LSPMethod::TextDocumentImplementation); | ||
|
||
if (queryResult.error) { | ||
// An error happened while setting up the query. | ||
response->error = move(queryResult.error); | ||
return response; | ||
} | ||
|
||
if (queryResult.responses.empty()) { | ||
return response; | ||
} | ||
|
||
vector<unique_ptr<Location>> result; | ||
auto queryResponse = move(queryResult.responses[0]); | ||
if (auto def = queryResponse->isDefinition()) { | ||
// User called "Go to Implementation" from the abstract function definition | ||
core::SymbolRef method = def->symbol; | ||
if (!method.data(gs)->isMethod()) { | ||
response->error = make_unique<ResponseError>( | ||
(int)LSPErrorCodes::InvalidParams, | ||
"Go to implementation can be used only for methods or references of abstract classes"); | ||
return response; | ||
} | ||
|
||
core::SymbolRef overridedMethod = method; | ||
if (method.data(gs)->isOverride()) { | ||
overridedMethod = findOverridedMethod(gs, method); | ||
} | ||
auto locationsOrError = findMethodImplementations(gs, overridedMethod); | ||
|
||
if (locationsOrError.error != nullptr) { | ||
response->error = move(locationsOrError.error); | ||
return response; | ||
} else { | ||
for (const auto &location : locationsOrError.locations) { | ||
addLocIfExists(gs, result, location); | ||
} | ||
} | ||
} else if (auto constant = queryResponse->isConstant()) { | ||
// User called "Go to Implementation" from the abstract class reference | ||
auto classSymbol = constant->symbol; | ||
|
||
if (!classSymbol.data(gs)->isClassOrModule() || !classSymbol.data(gs)->isClassOrModuleAbstract()) { | ||
response->error = make_unique<ResponseError>( | ||
(int)LSPErrorCodes::InvalidParams, | ||
"Go to implementation can be used only for methods or references of abstract classes"); | ||
return response; | ||
} | ||
|
||
auto classOrModuleRef = classSymbol.asClassOrModuleRef(); | ||
auto childClasses = classOrModuleRef.getSubclasses(gs, false); | ||
for (const auto &childClass : childClasses) { | ||
for (auto loc : childClass.data(gs)->locs()) { | ||
addLocIfExists(gs, result, loc); | ||
} | ||
} | ||
|
||
} else if (auto send = queryResponse->isSend()) { | ||
auto mainResponse = move(send->dispatchResult->main); | ||
|
||
// User called "Go to Implementation" from the abstract function call | ||
if (mainResponse.errors.size() != 0) { | ||
response->error = makeInvalidParamsError("Failed to fetch implementations"); | ||
return response; | ||
} | ||
|
||
auto calledMethod = mainResponse.method; | ||
core::SymbolRef overridedMethod = calledMethod; | ||
if (calledMethod.data(gs)->isOverride()) { | ||
overridedMethod = findOverridedMethod(gs, overridedMethod); | ||
} | ||
|
||
auto locationsOrError = findMethodImplementations(gs, overridedMethod); | ||
|
||
if (locationsOrError.error != nullptr) { | ||
response->error = move(locationsOrError.error); | ||
return response; | ||
} else { | ||
for (const auto &location : locationsOrError.locations) { | ||
addLocIfExists(gs, result, location); | ||
} | ||
} | ||
} | ||
|
||
response->result = move(result); | ||
return response; | ||
} | ||
} // namespace sorbet::realmain::lsp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
#ifndef RUBY_TYPER_LSP_REQUESTS_GO_TO_IMPLEMENTATION_H | ||
#define RUBY_TYPER_LSP_REQUESTS_GO_TO_IMPLEMENTATION_H | ||
|
||
#include "main/lsp/LSPTask.h" | ||
|
||
namespace sorbet::realmain::lsp { | ||
class ImplementationParams; | ||
|
||
class GoToImplementationTask final : public LSPRequestTask { | ||
std::unique_ptr<ImplementationParams> params; | ||
|
||
public: | ||
GoToImplementationTask(const LSPConfiguration &config, MessageId id, std::unique_ptr<ImplementationParams> params); | ||
|
||
std::unique_ptr<ResponseMessage> runRequest(LSPTypecheckerDelegate &typechecker) override; | ||
}; | ||
|
||
} // namespace sorbet::realmain::lsp | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.