LSP: Implements goto-definition.

This commit is contained in:
Christian Parpart
2022-03-14 11:59:41 +01:00
parent 1035eacb53
commit 2b2f8acc12
16 changed files with 719 additions and 91 deletions
+2 -2
View File
@@ -155,10 +155,10 @@ set(sources
interface/StorageLayout.h
interface/Version.cpp
interface/Version.h
lsp/LanguageServer.cpp
lsp/LanguageServer.h
lsp/FileRepository.cpp
lsp/FileRepository.h
lsp/GotoDefinition.cpp
lsp/GotoDefinition.h
lsp/HandlerBase.cpp
lsp/HandlerBase.h
lsp/LanguageServer.cpp
+23
View File
@@ -18,12 +18,35 @@
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTUtils.h>
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolutil/Algorithms.h>
namespace solidity::frontend
{
ASTNode const* locateInnermostASTNode(int _offsetInFile, SourceUnit const& _sourceUnit)
{
ASTNode const* innermostMatch = nullptr;
auto locator = SimpleASTVisitor(
[&](ASTNode const& _node) -> bool
{
// In the AST parent location always covers the whole child location.
// The parent is visited first so to get the innermost node we simply
// take the last one that still contains the offset.
if (!_node.location().containsOffset(_offsetInFile))
return false;
innermostMatch = &_node;
return true;
},
[](ASTNode const&) {}
);
_sourceUnit.accept(locator);
return innermostMatch;
}
bool isConstantVariableRecursive(VariableDeclaration const& _varDecl)
{
solAssert(_varDecl.isConstant(), "Constant variable expected");
+6 -1
View File
@@ -21,9 +21,11 @@
namespace solidity::frontend
{
class VariableDeclaration;
class ASTNode;
class Declaration;
class Expression;
class SourceUnit;
class VariableDeclaration;
/// Find the topmost referenced constant variable declaration when the given variable
/// declaration value is an identifier. Works only for constant variable declarations.
@@ -33,4 +35,7 @@ VariableDeclaration const* rootConstVariableDeclaration(VariableDeclaration cons
/// Returns true if the constant variable declaration is recursive.
bool isConstantVariableRecursive(VariableDeclaration const& _varDecl);
/// Returns the innermost AST node that covers the given location or nullptr if not found.
ASTNode const* locateInnermostASTNode(int _offsetInFile, SourceUnit const& _sourceUnit);
}
+66
View File
@@ -0,0 +1,66 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <libsolidity/lsp/GotoDefinition.h>
#include <libsolidity/lsp/Transport.h> // for RequestError
#include <libsolidity/lsp/Utils.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTUtils.h>
#include <fmt/format.h>
#include <memory>
#include <string>
#include <vector>
using namespace solidity::frontend;
using namespace solidity::langutil;
using namespace solidity::lsp;
using namespace std;
void GotoDefinition::operator()(MessageID _id, Json::Value const& _args)
{
auto const [sourceUnitName, lineColumn] = extractSourceUnitNameAndLineColumn(_args);
ASTNode const* sourceNode = m_server.astNodeAtSourceLocation(sourceUnitName, lineColumn);
vector<SourceLocation> locations;
if (auto const* expression = dynamic_cast<Expression const*>(sourceNode))
{
// Handles all expressions that can have one or more declaration annotation.
if (auto const* declaration = referencedDeclaration(expression))
if (auto location = declarationLocation(declaration))
locations.emplace_back(move(location.value()));
}
else if (auto const* identifierPath = dynamic_cast<IdentifierPath const*>(sourceNode))
{
if (auto const* declaration = identifierPath->annotation().referencedDeclaration)
if (auto location = declarationLocation(declaration))
locations.emplace_back(move(location.value()));
}
else if (auto const* importDirective = dynamic_cast<ImportDirective const*>(sourceNode))
{
auto const& path = *importDirective->annotation().absolutePath;
if (fileRepository().sourceUnits().count(path))
locations.emplace_back(SourceLocation{0, 0, make_shared<string const>(path)});
}
Json::Value reply = Json::arrayValue;
for (SourceLocation const& location: locations)
reply.append(toJson(location));
client().reply(_id, reply);
}
+31
View File
@@ -0,0 +1,31 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <libsolidity/lsp/HandlerBase.h>
namespace solidity::lsp
{
class GotoDefinition: public HandlerBase
{
public:
explicit GotoDefinition(LanguageServer& _server): HandlerBase(_server) {}
void operator()(MessageID, Json::Value const&);
};
}
+44 -29
View File
@@ -1,3 +1,22 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <libsolutil/Exceptions.h>
#include <libsolidity/lsp/HandlerBase.h>
#include <libsolidity/lsp/LanguageServer.h>
#include <libsolidity/lsp/Utils.h>
@@ -5,13 +24,13 @@
#include <liblangutil/Exceptions.h>
#include <fmt/format.h>
using namespace solidity::langutil;
using namespace solidity::lsp;
using namespace solidity::util;
using namespace std;
namespace solidity::lsp
{
using namespace langutil;
Json::Value HandlerBase::toRange(SourceLocation const& _location) const
{
if (!_location.hasText())
@@ -33,31 +52,27 @@ Json::Value HandlerBase::toJson(SourceLocation const& _location) const
return item;
}
optional<SourceLocation> HandlerBase::parsePosition(string const& _sourceUnitName, Json::Value const& _position) const
pair<string, LineColumn> HandlerBase::extractSourceUnitNameAndLineColumn(Json::Value const& _args) const
{
if (!fileRepository().sourceUnits().count(_sourceUnitName))
return nullopt;
string const uri = _args["textDocument"]["uri"].asString();
string const sourceUnitName = fileRepository().clientPathToSourceUnitName(uri);
if (!fileRepository().sourceUnits().count(sourceUnitName))
BOOST_THROW_EXCEPTION(
RequestError(ErrorCode::RequestFailed) <<
errinfo_comment("Unknown file: " + uri)
);
if (optional<LineColumn> lineColumn = parseLineColumn(_position))
if (optional<int> const offset = CharStream::translateLineColumnToPosition(
fileRepository().sourceUnits().at(_sourceUnitName),
*lineColumn
))
return SourceLocation{*offset, *offset, make_shared<string>(_sourceUnitName)};
return nullopt;
}
optional<SourceLocation> HandlerBase::parseRange(string const& _sourceUnitName, Json::Value const& _range) const
{
if (!_range.isObject())
return nullopt;
optional<SourceLocation> start = parsePosition(_sourceUnitName, _range["start"]);
optional<SourceLocation> end = parsePosition(_sourceUnitName, _range["end"]);
if (!start || !end)
return nullopt;
solAssert(*start->sourceName == *end->sourceName);
start->end = end->end;
return start;
}
auto const lineColumn = parseLineColumn(_args["position"]);
if (!lineColumn)
BOOST_THROW_EXCEPTION(
RequestError(ErrorCode::RequestFailed) <<
errinfo_comment(fmt::format(
"Unknown position {line}:{column} in file: {file}",
fmt::arg("line", lineColumn.value().line),
fmt::arg("column", lineColumn.value().column),
fmt::arg("file", sourceUnitName)
))
);
return {sourceUnitName, *lineColumn};
}
+24 -14
View File
@@ -1,3 +1,20 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#pragma once
#include <libsolidity/lsp/FileRepository.h>
@@ -24,22 +41,15 @@ public:
Json::Value toRange(langutil::SourceLocation const& _location) const;
Json::Value toJson(langutil::SourceLocation const& _location) const;
std::optional<langutil::SourceLocation> parsePosition(
std::string const& _sourceUnitName,
Json::Value const& _position
) const;
/// @returns source unit name and the line column position as extracted
/// from the JSON-RPC parameters.
std::pair<std::string, langutil::LineColumn> extractSourceUnitNameAndLineColumn(Json::Value const& _params) const;
/// @returns the source location given a source unit name and an LSP Range object,
/// or nullopt on failure.
std::optional<langutil::SourceLocation> parseRange(
std::string const& _sourceUnitName,
Json::Value const& _range
) const;
langutil::CharStreamProvider const& charStreamProvider() const noexcept { return m_server.charStreamProvider(); };
FileRepository const& fileRepository() const noexcept { return m_server.fileRepository(); };
Transport& client() const noexcept { return m_server.client(); };
langutil::CharStreamProvider const& charStreamProvider() const noexcept { return m_server.charStreamProvider(); }
FileRepository const& fileRepository() const noexcept { return m_server.fileRepository(); }
Transport& client() const noexcept { return m_server.client(); }
protected:
LanguageServer& m_server;
};
+13 -13
View File
@@ -24,6 +24,8 @@
#include <libsolidity/lsp/HandlerBase.h>
#include <libsolidity/lsp/Utils.h>
// LSP feature implementations
#include <libsolidity/lsp/GotoDefinition.h>
#include <liblangutil/SourceReferenceExtractor.h>
#include <liblangutil/CharStream.h>
@@ -75,9 +77,11 @@ LanguageServer::LanguageServer(Transport& _transport):
{"initialize", bind(&LanguageServer::handleInitialize, this, _1, _2)},
{"initialized", [](auto, auto) {}},
{"shutdown", [this](auto, auto) { m_state = State::ShutdownRequested; }},
{"textDocument/definition", GotoDefinition(*this) },
{"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _2)},
{"textDocument/didChange", bind(&LanguageServer::handleTextDocumentDidChange, this, _2)},
{"textDocument/didClose", bind(&LanguageServer::handleTextDocumentDidClose, this, _2)},
{"textDocument/implementation", GotoDefinition(*this) },
{"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _2)},
},
m_fileRepository("/" /* basePath */),
@@ -85,11 +89,6 @@ LanguageServer::LanguageServer(Transport& _transport):
{
}
optional<SourceLocation> LanguageServer::parseRange(string const& _sourceUnitName, Json::Value const& _range)
{
return HandlerBase{*this}.parseRange(_sourceUnitName, _range);
}
Json::Value LanguageServer::toRange(SourceLocation const& _location)
{
return HandlerBase(*this).toRange(_location);
@@ -258,8 +257,10 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
Json::Value replyArgs;
replyArgs["serverInfo"]["name"] = "solc";
replyArgs["serverInfo"]["version"] = string(VersionNumber);
replyArgs["capabilities"]["textDocumentSync"]["openClose"] = true;
replyArgs["capabilities"]["definitionProvider"] = true;
replyArgs["capabilities"]["implementationProvider"] = true;
replyArgs["capabilities"]["textDocumentSync"]["change"] = 2; // 0=none, 1=full, 2=incremental
replyArgs["capabilities"]["textDocumentSync"]["openClose"] = true;
m_client.reply(_id, move(replyArgs));
}
@@ -313,7 +314,7 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args)
string text = jsonContentChange["text"].asString();
if (jsonContentChange["range"].isObject()) // otherwise full content update
{
optional<SourceLocation> change = parseRange(sourceUnitName, jsonContentChange["range"]);
optional<SourceLocation> change = parseRange(m_fileRepository, sourceUnitName, jsonContentChange["range"]);
lspAssert(
change && change->hasText(),
ErrorCode::RequestFailed,
@@ -346,7 +347,7 @@ void LanguageServer::handleTextDocumentDidClose(Json::Value const& _args)
compileAndUpdateDiagnostics();
}
ASTNode const* LanguageServer::requestASTNode(std::string const& _sourceUnitName, LineColumn const& _filePos)
ASTNode const* LanguageServer::astNodeAtSourceLocation(std::string const& _sourceUnitName, LineColumn const& _filePos)
{
if (m_compilerStack.state() < CompilerStack::AnalysisPerformed)
return nullptr;
@@ -354,11 +355,10 @@ ASTNode const* LanguageServer::requestASTNode(std::string const& _sourceUnitName
if (!m_fileRepository.sourceUnits().count(_sourceUnitName))
return nullptr;
optional<int> sourcePos = m_compilerStack.charStream(_sourceUnitName)
.translateLineColumnToPosition(_filePos);
if (!sourcePos.has_value())
if (optional<int> sourcePos =
m_compilerStack.charStream(_sourceUnitName).translateLineColumnToPosition(_filePos))
return locateInnermostASTNode(*sourcePos, m_compilerStack.ast(_sourceUnitName));
else
return nullptr;
return locateInnermostASTNode(*sourcePos, m_compilerStack.ast(_sourceUnitName));
}
+2 -7
View File
@@ -59,7 +59,7 @@ public:
FileRepository& fileRepository() noexcept { return m_fileRepository; }
Transport& client() noexcept { return m_client; }
frontend::ASTNode const* requestASTNode(std::string const& _sourceUnitName, langutil::LineColumn const& _filePos);
frontend::ASTNode const* astNodeAtSourceLocation(std::string const& _sourceUnitName, langutil::LineColumn const& _filePos);
langutil::CharStreamProvider const& charStreamProvider() const noexcept { return m_compilerStack; }
private:
@@ -71,6 +71,7 @@ private:
void handleTextDocumentDidOpen(Json::Value const& _args);
void handleTextDocumentDidChange(Json::Value const& _args);
void handleTextDocumentDidClose(Json::Value const& _args);
void handleGotoDefinition(MessageID _id, Json::Value const& _args);
/// Invoked when the server user-supplied configuration changes (initiated by the client).
void changeConfiguration(Json::Value const&);
@@ -79,12 +80,6 @@ private:
void compile();
using MessageHandler = std::function<void(MessageID, Json::Value const&)>;
/// @returns the source location given a source unit name and an LSP Range object,
/// or nullopt on failure.
std::optional<langutil::SourceLocation> parseRange(
std::string const& _sourceUnitName,
Json::Value const& _range
);
Json::Value toRange(langutil::SourceLocation const& _location);
Json::Value toJson(langutil::SourceLocation const& _location);
+62 -14
View File
@@ -1,9 +1,30 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <liblangutil/CharStreamProvider.h>
#include <liblangutil/Exceptions.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/lsp/FileRepository.h>
#include <libsolidity/lsp/Utils.h>
#include <fmt/format.h>
#include <fstream>
namespace solidity::lsp
{
@@ -19,7 +40,7 @@ optional<LineColumn> parseLineColumn(Json::Value const& _lineColumn)
return nullopt;
}
Json::Value toJson(LineColumn _pos)
Json::Value toJson(LineColumn const& _pos)
{
Json::Value json = Json::objectValue;
json["line"] = max(_pos.line, 0);
@@ -36,24 +57,20 @@ Json::Value toJsonRange(LineColumn const& _start, LineColumn const& _end)
return json;
}
vector<Declaration const*> allAnnotatedDeclarations(Expression const* _expression)
Declaration const* referencedDeclaration(Expression const* _expression)
{
vector<Declaration const*> output;
if (auto const* identifier = dynamic_cast<Identifier const*>(_expression))
{
output.push_back(identifier->annotation().referencedDeclaration);
output += identifier->annotation().candidateDeclarations;
}
else if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(_expression))
{
output.push_back(memberAccess->annotation().referencedDeclaration);
}
if (Declaration const* referencedDeclaration = identifier->annotation().referencedDeclaration)
return referencedDeclaration;
return output;
if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(_expression))
if (memberAccess->annotation().referencedDeclaration)
return memberAccess->annotation().referencedDeclaration;
return nullptr;
}
optional<SourceLocation> declarationPosition(Declaration const* _declaration)
optional<SourceLocation> declarationLocation(Declaration const* _declaration)
{
if (!_declaration)
return nullopt;
@@ -67,4 +84,35 @@ optional<SourceLocation> declarationPosition(Declaration const* _declaration)
return nullopt;
}
optional<SourceLocation> parsePosition(
FileRepository const& _fileRepository,
string const& _sourceUnitName,
Json::Value const& _position
)
{
if (!_fileRepository.sourceUnits().count(_sourceUnitName))
return nullopt;
if (optional<LineColumn> lineColumn = parseLineColumn(_position))
if (optional<int> const offset = CharStream::translateLineColumnToPosition(
_fileRepository.sourceUnits().at(_sourceUnitName),
*lineColumn
))
return SourceLocation{*offset, *offset, make_shared<string>(_sourceUnitName)};
return nullopt;
}
optional<SourceLocation> parseRange(FileRepository const& _fileRepository, string const& _sourceUnitName, Json::Value const& _range)
{
if (!_range.isObject())
return nullopt;
optional<SourceLocation> start = parsePosition(_fileRepository, _sourceUnitName, _range["start"]);
optional<SourceLocation> end = parsePosition(_fileRepository, _sourceUnitName, _range["end"]);
if (!start || !end)
return nullopt;
solAssert(*start->sourceName == *end->sourceName);
start->end = end->end;
return start;
}
}
+53 -3
View File
@@ -1,3 +1,21 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#pragma once
#include <liblangutil/SourceLocation.h>
@@ -9,6 +27,13 @@
#include <optional>
#include <vector>
#if !defined(NDEBUG)
#include <fstream>
#define lspDebug(message) (std::ofstream("/tmp/solc.log", std::ios::app) << (message) << std::endl)
#else
#define lspDebug(message) do {} while (0)
#endif
namespace solidity::langutil
{
class CharStreamProvider;
@@ -20,10 +45,35 @@ namespace solidity::lsp
class FileRepository;
std::optional<langutil::LineColumn> parseLineColumn(Json::Value const& _lineColumn);
Json::Value toJson(langutil::LineColumn _pos);
Json::Value toJson(langutil::LineColumn const& _pos);
Json::Value toJsonRange(langutil::LineColumn const& _start, langutil::LineColumn const& _end);
std::vector<frontend::Declaration const*> allAnnotatedDeclarations(frontend::Expression const* _expression);
std::optional<langutil::SourceLocation> declarationPosition(frontend::Declaration const* _declaration);
/// @returns the source location given a source unit name and an LSP Range object,
/// or nullopt on failure.
std::optional<langutil::SourceLocation> parsePosition(
FileRepository const& _fileRepository,
std::string const& _sourceUnitName,
Json::Value const& _position
);
/// @returns the source location given a source unit name and an LSP Range object,
/// or nullopt on failure.
std::optional<langutil::SourceLocation> parseRange(
FileRepository const& _fileRepository,
std::string const& _sourceUnitName,
Json::Value const& _range
);
/// Extracts the resolved declaration of the given expression AST node.
///
/// This may for example be the type declaration of an identifier,
/// or the type declaration of a structured member identifier.
///
/// @returns the resolved type declaration if found, or nullptr otherwise.
frontend::Declaration const* referencedDeclaration(frontend::Expression const* _expression);
/// @returns the location of the declaration's name, if present, or the location of the complete
/// declaration otherwise. If the input declaration is nullptr, std::nullopt is returned instead.
std::optional<langutil::SourceLocation> declarationLocation(frontend::Declaration const* _declaration);
}