Skip to content

Commit 2b2f8ac

Browse files
LSP: Implements goto-definition.
1 parent 1035eac commit 2b2f8ac

16 files changed

+720
-92
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Compiler Features:
88
* JSON-AST: Added selector field for errors and events.
99
* Peephole Optimizer: Optimize comparisons in front of conditional jumps and conditional jumps across a single unconditional jump.
1010
* Yul Optimizer: Remove ``sstore`` and ``mstore`` operations that are never read from.
11+
* LSP: Implements goto-definition.
1112

1213
Bugfixes:
1314
* Yul IR Code Generation: Optimize embedded creation code with correct settings. This fixes potential mismatches between the constructor code of a contract compiled in isolation and the bytecode in ``type(C).creationCode``, resp. the bytecode used for ``new C(...)``.

libsolidity/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,10 @@ set(sources
155155
interface/StorageLayout.h
156156
interface/Version.cpp
157157
interface/Version.h
158-
lsp/LanguageServer.cpp
159-
lsp/LanguageServer.h
160158
lsp/FileRepository.cpp
161159
lsp/FileRepository.h
160+
lsp/GotoDefinition.cpp
161+
lsp/GotoDefinition.h
162162
lsp/HandlerBase.cpp
163163
lsp/HandlerBase.h
164164
lsp/LanguageServer.cpp

libsolidity/ast/ASTUtils.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,35 @@
1818

1919
#include <libsolidity/ast/AST.h>
2020
#include <libsolidity/ast/ASTUtils.h>
21+
#include <libsolidity/ast/ASTVisitor.h>
2122

2223
#include <libsolutil/Algorithms.h>
2324

2425
namespace solidity::frontend
2526
{
2627

28+
ASTNode const* locateInnermostASTNode(int _offsetInFile, SourceUnit const& _sourceUnit)
29+
{
30+
ASTNode const* innermostMatch = nullptr;
31+
auto locator = SimpleASTVisitor(
32+
[&](ASTNode const& _node) -> bool
33+
{
34+
// In the AST parent location always covers the whole child location.
35+
// The parent is visited first so to get the innermost node we simply
36+
// take the last one that still contains the offset.
37+
38+
if (!_node.location().containsOffset(_offsetInFile))
39+
return false;
40+
41+
innermostMatch = &_node;
42+
return true;
43+
},
44+
[](ASTNode const&) {}
45+
);
46+
_sourceUnit.accept(locator);
47+
return innermostMatch;
48+
}
49+
2750
bool isConstantVariableRecursive(VariableDeclaration const& _varDecl)
2851
{
2952
solAssert(_varDecl.isConstant(), "Constant variable expected");

libsolidity/ast/ASTUtils.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
namespace solidity::frontend
2222
{
2323

24-
class VariableDeclaration;
24+
class ASTNode;
2525
class Declaration;
2626
class Expression;
27+
class SourceUnit;
28+
class VariableDeclaration;
2729

2830
/// Find the topmost referenced constant variable declaration when the given variable
2931
/// declaration value is an identifier. Works only for constant variable declarations.
@@ -33,4 +35,7 @@ VariableDeclaration const* rootConstVariableDeclaration(VariableDeclaration cons
3335
/// Returns true if the constant variable declaration is recursive.
3436
bool isConstantVariableRecursive(VariableDeclaration const& _varDecl);
3537

38+
/// Returns the innermost AST node that covers the given location or nullptr if not found.
39+
ASTNode const* locateInnermostASTNode(int _offsetInFile, SourceUnit const& _sourceUnit);
40+
3641
}

libsolidity/lsp/GotoDefinition.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
#include <libsolidity/lsp/GotoDefinition.h>
19+
#include <libsolidity/lsp/Transport.h> // for RequestError
20+
#include <libsolidity/lsp/Utils.h>
21+
#include <libsolidity/ast/AST.h>
22+
#include <libsolidity/ast/ASTUtils.h>
23+
24+
#include <fmt/format.h>
25+
26+
#include <memory>
27+
#include <string>
28+
#include <vector>
29+
30+
using namespace solidity::frontend;
31+
using namespace solidity::langutil;
32+
using namespace solidity::lsp;
33+
using namespace std;
34+
35+
void GotoDefinition::operator()(MessageID _id, Json::Value const& _args)
36+
{
37+
auto const [sourceUnitName, lineColumn] = extractSourceUnitNameAndLineColumn(_args);
38+
39+
ASTNode const* sourceNode = m_server.astNodeAtSourceLocation(sourceUnitName, lineColumn);
40+
41+
vector<SourceLocation> locations;
42+
if (auto const* expression = dynamic_cast<Expression const*>(sourceNode))
43+
{
44+
// Handles all expressions that can have one or more declaration annotation.
45+
if (auto const* declaration = referencedDeclaration(expression))
46+
if (auto location = declarationLocation(declaration))
47+
locations.emplace_back(move(location.value()));
48+
}
49+
else if (auto const* identifierPath = dynamic_cast<IdentifierPath const*>(sourceNode))
50+
{
51+
if (auto const* declaration = identifierPath->annotation().referencedDeclaration)
52+
if (auto location = declarationLocation(declaration))
53+
locations.emplace_back(move(location.value()));
54+
}
55+
else if (auto const* importDirective = dynamic_cast<ImportDirective const*>(sourceNode))
56+
{
57+
auto const& path = *importDirective->annotation().absolutePath;
58+
if (fileRepository().sourceUnits().count(path))
59+
locations.emplace_back(SourceLocation{0, 0, make_shared<string const>(path)});
60+
}
61+
62+
Json::Value reply = Json::arrayValue;
63+
for (SourceLocation const& location: locations)
64+
reply.append(toJson(location));
65+
client().reply(_id, reply);
66+
}

libsolidity/lsp/GotoDefinition.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
#include <libsolidity/lsp/HandlerBase.h>
19+
20+
namespace solidity::lsp
21+
{
22+
23+
class GotoDefinition: public HandlerBase
24+
{
25+
public:
26+
explicit GotoDefinition(LanguageServer& _server): HandlerBase(_server) {}
27+
28+
void operator()(MessageID, Json::Value const&);
29+
};
30+
31+
}

libsolidity/lsp/HandlerBase.cpp

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
#include <libsolutil/Exceptions.h>
19+
120
#include <libsolidity/lsp/HandlerBase.h>
221
#include <libsolidity/lsp/LanguageServer.h>
322
#include <libsolidity/lsp/Utils.h>
423
#include <libsolidity/ast/AST.h>
524

625
#include <liblangutil/Exceptions.h>
726

8-
using namespace std;
27+
#include <fmt/format.h>
928

10-
namespace solidity::lsp
11-
{
12-
13-
using namespace langutil;
29+
using namespace solidity::langutil;
30+
using namespace solidity::lsp;
31+
using namespace solidity::util;
32+
using namespace std;
1433

1534
Json::Value HandlerBase::toRange(SourceLocation const& _location) const
1635
{
@@ -33,31 +52,27 @@ Json::Value HandlerBase::toJson(SourceLocation const& _location) const
3352
return item;
3453
}
3554

36-
optional<SourceLocation> HandlerBase::parsePosition(string const& _sourceUnitName, Json::Value const& _position) const
55+
pair<string, LineColumn> HandlerBase::extractSourceUnitNameAndLineColumn(Json::Value const& _args) const
3756
{
38-
if (!fileRepository().sourceUnits().count(_sourceUnitName))
39-
return nullopt;
40-
41-
if (optional<LineColumn> lineColumn = parseLineColumn(_position))
42-
if (optional<int> const offset = CharStream::translateLineColumnToPosition(
43-
fileRepository().sourceUnits().at(_sourceUnitName),
44-
*lineColumn
45-
))
46-
return SourceLocation{*offset, *offset, make_shared<string>(_sourceUnitName)};
47-
return nullopt;
48-
}
57+
string const uri = _args["textDocument"]["uri"].asString();
58+
string const sourceUnitName = fileRepository().clientPathToSourceUnitName(uri);
59+
if (!fileRepository().sourceUnits().count(sourceUnitName))
60+
BOOST_THROW_EXCEPTION(
61+
RequestError(ErrorCode::RequestFailed) <<
62+
errinfo_comment("Unknown file: " + uri)
63+
);
4964

50-
optional<SourceLocation> HandlerBase::parseRange(string const& _sourceUnitName, Json::Value const& _range) const
51-
{
52-
if (!_range.isObject())
53-
return nullopt;
54-
optional<SourceLocation> start = parsePosition(_sourceUnitName, _range["start"]);
55-
optional<SourceLocation> end = parsePosition(_sourceUnitName, _range["end"]);
56-
if (!start || !end)
57-
return nullopt;
58-
solAssert(*start->sourceName == *end->sourceName);
59-
start->end = end->end;
60-
return start;
61-
}
65+
auto const lineColumn = parseLineColumn(_args["position"]);
66+
if (!lineColumn)
67+
BOOST_THROW_EXCEPTION(
68+
RequestError(ErrorCode::RequestFailed) <<
69+
errinfo_comment(fmt::format(
70+
"Unknown position {line}:{column} in file: {file}",
71+
fmt::arg("line", lineColumn.value().line),
72+
fmt::arg("column", lineColumn.value().column),
73+
fmt::arg("file", sourceUnitName)
74+
))
75+
);
6276

77+
return {sourceUnitName, *lineColumn};
6378
}

libsolidity/lsp/HandlerBase.h

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
118
#pragma once
219

320
#include <libsolidity/lsp/FileRepository.h>
@@ -24,22 +41,15 @@ class HandlerBase
2441
Json::Value toRange(langutil::SourceLocation const& _location) const;
2542
Json::Value toJson(langutil::SourceLocation const& _location) const;
2643

27-
std::optional<langutil::SourceLocation> parsePosition(
28-
std::string const& _sourceUnitName,
29-
Json::Value const& _position
30-
) const;
44+
/// @returns source unit name and the line column position as extracted
45+
/// from the JSON-RPC parameters.
46+
std::pair<std::string, langutil::LineColumn> extractSourceUnitNameAndLineColumn(Json::Value const& _params) const;
3147

32-
/// @returns the source location given a source unit name and an LSP Range object,
33-
/// or nullopt on failure.
34-
std::optional<langutil::SourceLocation> parseRange(
35-
std::string const& _sourceUnitName,
36-
Json::Value const& _range
37-
) const;
38-
39-
langutil::CharStreamProvider const& charStreamProvider() const noexcept { return m_server.charStreamProvider(); };
40-
FileRepository const& fileRepository() const noexcept { return m_server.fileRepository(); };
41-
Transport& client() const noexcept { return m_server.client(); };
48+
langutil::CharStreamProvider const& charStreamProvider() const noexcept { return m_server.charStreamProvider(); }
49+
FileRepository const& fileRepository() const noexcept { return m_server.fileRepository(); }
50+
Transport& client() const noexcept { return m_server.client(); }
4251

52+
protected:
4353
LanguageServer& m_server;
4454
};
4555

libsolidity/lsp/LanguageServer.cpp

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#include <libsolidity/lsp/HandlerBase.h>
2525
#include <libsolidity/lsp/Utils.h>
2626

27+
// LSP feature implementations
28+
#include <libsolidity/lsp/GotoDefinition.h>
2729

2830
#include <liblangutil/SourceReferenceExtractor.h>
2931
#include <liblangutil/CharStream.h>
@@ -75,21 +77,18 @@ LanguageServer::LanguageServer(Transport& _transport):
7577
{"initialize", bind(&LanguageServer::handleInitialize, this, _1, _2)},
7678
{"initialized", [](auto, auto) {}},
7779
{"shutdown", [this](auto, auto) { m_state = State::ShutdownRequested; }},
80+
{"textDocument/definition", GotoDefinition(*this) },
7881
{"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _2)},
7982
{"textDocument/didChange", bind(&LanguageServer::handleTextDocumentDidChange, this, _2)},
8083
{"textDocument/didClose", bind(&LanguageServer::handleTextDocumentDidClose, this, _2)},
84+
{"textDocument/implementation", GotoDefinition(*this) },
8185
{"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _2)},
8286
},
8387
m_fileRepository("/" /* basePath */),
8488
m_compilerStack{m_fileRepository.reader()}
8589
{
8690
}
8791

88-
optional<SourceLocation> LanguageServer::parseRange(string const& _sourceUnitName, Json::Value const& _range)
89-
{
90-
return HandlerBase{*this}.parseRange(_sourceUnitName, _range);
91-
}
92-
9392
Json::Value LanguageServer::toRange(SourceLocation const& _location)
9493
{
9594
return HandlerBase(*this).toRange(_location);
@@ -258,8 +257,10 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
258257
Json::Value replyArgs;
259258
replyArgs["serverInfo"]["name"] = "solc";
260259
replyArgs["serverInfo"]["version"] = string(VersionNumber);
261-
replyArgs["capabilities"]["textDocumentSync"]["openClose"] = true;
260+
replyArgs["capabilities"]["definitionProvider"] = true;
261+
replyArgs["capabilities"]["implementationProvider"] = true;
262262
replyArgs["capabilities"]["textDocumentSync"]["change"] = 2; // 0=none, 1=full, 2=incremental
263+
replyArgs["capabilities"]["textDocumentSync"]["openClose"] = true;
263264

264265
m_client.reply(_id, move(replyArgs));
265266
}
@@ -313,7 +314,7 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args)
313314
string text = jsonContentChange["text"].asString();
314315
if (jsonContentChange["range"].isObject()) // otherwise full content update
315316
{
316-
optional<SourceLocation> change = parseRange(sourceUnitName, jsonContentChange["range"]);
317+
optional<SourceLocation> change = parseRange(m_fileRepository, sourceUnitName, jsonContentChange["range"]);
317318
lspAssert(
318319
change && change->hasText(),
319320
ErrorCode::RequestFailed,
@@ -346,19 +347,18 @@ void LanguageServer::handleTextDocumentDidClose(Json::Value const& _args)
346347
compileAndUpdateDiagnostics();
347348
}
348349

349-
ASTNode const* LanguageServer::requestASTNode(std::string const& _sourceUnitName, LineColumn const& _filePos)
350+
ASTNode const* LanguageServer::astNodeAtSourceLocation(std::string const& _sourceUnitName, LineColumn const& _filePos)
350351
{
351352
if (m_compilerStack.state() < CompilerStack::AnalysisPerformed)
352353
return nullptr;
353354

354355
if (!m_fileRepository.sourceUnits().count(_sourceUnitName))
355356
return nullptr;
356357

357-
optional<int> sourcePos = m_compilerStack.charStream(_sourceUnitName)
358-
.translateLineColumnToPosition(_filePos);
359-
if (!sourcePos.has_value())
358+
if (optional<int> sourcePos =
359+
m_compilerStack.charStream(_sourceUnitName).translateLineColumnToPosition(_filePos))
360+
return locateInnermostASTNode(*sourcePos, m_compilerStack.ast(_sourceUnitName));
361+
else
360362
return nullptr;
361-
362-
return locateInnermostASTNode(*sourcePos, m_compilerStack.ast(_sourceUnitName));
363363
}
364364

0 commit comments

Comments
 (0)