Skip to content

Commit 9470142

Browse files
committed
[clangd] Implementation of auto type expansion.
Add a tweak for clangd to replace an auto keyword to the deduced type. This way a user can declare something with auto and then have the IDE/clangd replace auto with whatever type clangd thinks it is. In case of long/complext types this makes is reduces writing effort for the user. The functionality is similar to the hover over the auto keyword. Example (from the header): ``` /// Before: /// auto x = Something(); /// ^^^^ /// After: /// MyClass x = Something(); /// ^^^^^^^ ``` Patch by kuhnel! (Christian Kühnel) Differential Revision: https://reviews.llvm.org/D62855 llvm-svn: 365792
1 parent 5cc7c9a commit 9470142

File tree

13 files changed

+489
-4
lines changed

13 files changed

+489
-4
lines changed

clang-tools-extra/clangd/AST.cpp

+30
Original file line numberDiff line numberDiff line change
@@ -169,5 +169,35 @@ llvm::Optional<SymbolID> getSymbolID(const IdentifierInfo &II,
169169
return SymbolID(USR);
170170
}
171171

172+
std::string shortenNamespace(const llvm::StringRef OriginalName,
173+
const llvm::StringRef CurrentNamespace) {
174+
llvm::SmallVector<llvm::StringRef, 8> OriginalParts;
175+
llvm::SmallVector<llvm::StringRef, 8> CurrentParts;
176+
llvm::SmallVector<llvm::StringRef, 8> Result;
177+
OriginalName.split(OriginalParts, "::");
178+
CurrentNamespace.split(CurrentParts, "::");
179+
auto MinLength = std::min(CurrentParts.size(), OriginalParts.size());
180+
181+
unsigned DifferentAt = 0;
182+
while (DifferentAt < MinLength &&
183+
CurrentParts[DifferentAt] == OriginalParts[DifferentAt]) {
184+
DifferentAt++;
185+
}
186+
187+
for (u_int i = DifferentAt; i < OriginalParts.size(); ++i) {
188+
Result.push_back(OriginalParts[i]);
189+
}
190+
return join(Result, "::");
191+
}
192+
193+
std::string printType(const QualType QT, const DeclContext & Context){
194+
PrintingPolicy PP(Context.getParentASTContext().getPrintingPolicy());
195+
PP.SuppressTagKeyword = 1;
196+
return shortenNamespace(
197+
QT.getAsString(PP),
198+
printNamespaceScope(Context) );
199+
}
200+
201+
172202
} // namespace clangd
173203
} // namespace clang

clang-tools-extra/clangd/AST.h

+16
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,22 @@ llvm::Optional<SymbolID> getSymbolID(const IdentifierInfo &II,
6767
const MacroInfo *MI,
6868
const SourceManager &SM);
6969

70+
/// Returns a QualType as string.
71+
std::string printType(const QualType QT, const DeclContext & Context);
72+
73+
/// Try to shorten the OriginalName by removing namespaces from the left of
74+
/// the string that are redundant in the CurrentNamespace. This way the type
75+
/// idenfier become shorter and easier to read.
76+
/// Limitation: It only handles the qualifier of the type itself, not that of
77+
/// templates.
78+
/// FIXME: change type of parameter CurrentNamespace to DeclContext ,
79+
/// take in to account using directives etc
80+
/// Example: shortenNamespace("ns1::MyClass<ns1::OtherClass>", "ns1")
81+
/// --> "MyClass<ns1::OtherClass>"
82+
std::string shortenNamespace(const llvm::StringRef OriginalName,
83+
const llvm::StringRef CurrentNamespace);
84+
85+
7086
} // namespace clangd
7187
} // namespace clang
7288

clang-tools-extra/clangd/Selection.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -364,5 +364,18 @@ const Node *SelectionTree::commonAncestor() const {
364364
return Ancestor;
365365
}
366366

367+
const DeclContext& SelectionTree::Node::getDeclContext() const {
368+
for (const Node* CurrentNode = this; CurrentNode != nullptr;
369+
CurrentNode = CurrentNode->Parent) {
370+
if (const Decl* Current = CurrentNode->ASTNode.get<Decl>()) {
371+
if (CurrentNode != this)
372+
if (auto *DC = dyn_cast<DeclContext>(Current))
373+
return *DC;
374+
return *Current->getDeclContext();
375+
}
376+
}
377+
llvm_unreachable("A tree must always be rooted at TranslationUnitDecl.");
378+
}
379+
367380
} // namespace clangd
368381
} // namespace clang

clang-tools-extra/clangd/Selection.h

+3
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ class SelectionTree {
9393
ast_type_traits::DynTypedNode ASTNode;
9494
// The extent to which this node is covered by the selection.
9595
Selection Selected;
96+
// Walk up the AST to get the DeclContext of this Node,
97+
// which is not the node itself.
98+
const DeclContext& getDeclContext() const;
9699
};
97100

98101
// The most specific common ancestor of all the selected nodes.

clang-tools-extra/clangd/XRefs.cpp

+14-4
Original file line numberDiff line numberDiff line change
@@ -870,20 +870,30 @@ class DeducedTypeVisitor : public RecursiveASTVisitor<DeducedTypeVisitor> {
870870
} // namespace
871871

872872
/// Retrieves the deduced type at a given location (auto, decltype).
873-
bool hasDeducedType(ParsedAST &AST, SourceLocation SourceLocationBeg) {
873+
/// SourceLocationBeg must point to the first character of the token
874+
llvm::Optional<QualType> getDeducedType(ParsedAST &AST,
875+
SourceLocation SourceLocationBeg) {
874876
Token Tok;
875877
auto &ASTCtx = AST.getASTContext();
876878
// Only try to find a deduced type if the token is auto or decltype.
877879
if (!SourceLocationBeg.isValid() ||
878880
Lexer::getRawToken(SourceLocationBeg, Tok, ASTCtx.getSourceManager(),
879881
ASTCtx.getLangOpts(), false) ||
880882
!Tok.is(tok::raw_identifier)) {
881-
return false;
883+
return {};
882884
}
883885
AST.getPreprocessor().LookUpIdentifierInfo(Tok);
884886
if (!(Tok.is(tok::kw_auto) || Tok.is(tok::kw_decltype)))
885-
return false;
886-
return true;
887+
return {};
888+
889+
DeducedTypeVisitor V(SourceLocationBeg);
890+
V.TraverseAST(AST.getASTContext());
891+
return V.DeducedType;
892+
}
893+
894+
/// Retrieves the deduced type at a given location (auto, decltype).
895+
bool hasDeducedType(ParsedAST &AST, SourceLocation SourceLocationBeg) {
896+
return (bool) getDeducedType(AST, SourceLocationBeg);
887897
}
888898

889899
llvm::Optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,

clang-tools-extra/clangd/XRefs.h

+10
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ llvm::Optional<TypeHierarchyItem> getTypeHierarchy(
141141
ParsedAST &AST, Position Pos, int Resolve, TypeHierarchyDirection Direction,
142142
const SymbolIndex *Index = nullptr, PathRef TUPath = PathRef{});
143143

144+
/// Retrieves the deduced type at a given location (auto, decltype).
145+
/// Retuns None unless SourceLocationBeg starts an auto/decltype token.
146+
/// It will return the underlying type.
147+
llvm::Optional<QualType> getDeducedType(ParsedAST &AST,
148+
SourceLocation SourceLocationBeg);
149+
150+
/// Check if there is a deduced type at a given location (auto, decltype).
151+
/// SourceLocationBeg must point to the first character of the token
152+
bool hasDeducedType(ParsedAST &AST, SourceLocation SourceLocationBeg);
153+
144154
} // namespace clangd
145155
} // namespace clang
146156

clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ add_clang_library(clangDaemonTweaks OBJECT
1818
RawStringLiteral.cpp
1919
SwapIfBranches.cpp
2020
ExtractVariable.cpp
21+
ExpandAutoType.cpp
2122

2223
LINK_LIBS
2324
clangAST
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
//===--- ReplaceAutoType.cpp -------------------------------------*- C++-*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
#include "refactor/Tweak.h"
9+
10+
#include "Logger.h"
11+
#include "clang/AST/Type.h"
12+
#include "clang/AST/TypeLoc.h"
13+
#include "clang/Basic/LLVM.h"
14+
#include "llvm/ADT/None.h"
15+
#include "llvm/ADT/Optional.h"
16+
#include "llvm/Support/Debug.h"
17+
#include "llvm/Support/Error.h"
18+
#include <climits>
19+
#include <memory>
20+
#include <string>
21+
#include <AST.h>
22+
#include "XRefs.h"
23+
#include "llvm/ADT/StringExtras.h"
24+
25+
namespace clang {
26+
namespace clangd {
27+
28+
/// Expand the "auto" type to the derived type
29+
/// Before:
30+
/// auto x = Something();
31+
/// ^^^^
32+
/// After:
33+
/// MyClass x = Something();
34+
/// ^^^^^^^
35+
/// FIXME: Handle decltype as well
36+
class ExpandAutoType : public Tweak {
37+
public:
38+
const char *id() const final;
39+
Intent intent() const override { return Intent::Refactor;}
40+
bool prepare(const Selection &Inputs) override;
41+
Expected<Effect> apply(const Selection &Inputs) override;
42+
std::string title() const override;
43+
44+
private:
45+
/// Cache the AutoTypeLoc, so that we do not need to search twice.
46+
llvm::Optional<clang::AutoTypeLoc> CachedLocation;
47+
48+
/// Create an error message with filename and line number in it
49+
llvm::Error createErrorMessage(const std::string& Message,
50+
const Selection &Inputs);
51+
52+
};
53+
54+
REGISTER_TWEAK(ExpandAutoType)
55+
56+
std::string ExpandAutoType::title() const { return "expand auto type"; }
57+
58+
bool ExpandAutoType::prepare(const Selection& Inputs) {
59+
CachedLocation = llvm::None;
60+
if (auto *Node = Inputs.ASTSelection.commonAncestor()) {
61+
if (auto *TypeNode = Node->ASTNode.get<TypeLoc>()) {
62+
if (const AutoTypeLoc Result = TypeNode->getAs<AutoTypeLoc>()) {
63+
CachedLocation = Result;
64+
}
65+
}
66+
}
67+
return (bool) CachedLocation;
68+
}
69+
70+
Expected<Tweak::Effect> ExpandAutoType::apply(const Selection& Inputs) {
71+
auto& SrcMgr = Inputs.AST.getASTContext().getSourceManager();
72+
73+
llvm::Optional<clang::QualType> DeducedType =
74+
getDeducedType(Inputs.AST, CachedLocation->getBeginLoc());
75+
76+
// if we can't resolve the type, return an error message
77+
if (DeducedType == llvm::None || DeducedType->isNull()) {
78+
return createErrorMessage("Could not deduce type for 'auto' type", Inputs);
79+
}
80+
81+
// if it's a lambda expression, return an error message
82+
if (isa<RecordType>(*DeducedType) and
83+
dyn_cast<RecordType>(*DeducedType)->getDecl()->isLambda()) {
84+
return createErrorMessage("Could not expand type of lambda expression",
85+
Inputs);
86+
}
87+
88+
// if it's a function expression, return an error message
89+
// naively replacing 'auto' with the type will break declarations.
90+
// FIXME: there are other types that have similar problems
91+
if (DeducedType->getTypePtr()->isFunctionPointerType()) {
92+
return createErrorMessage("Could not expand type of function pointer",
93+
Inputs);
94+
}
95+
96+
std::string PrettyTypeName = printType(*DeducedType,
97+
Inputs.ASTSelection.commonAncestor()->getDeclContext());
98+
99+
tooling::Replacement
100+
Expansion(SrcMgr, CharSourceRange(CachedLocation->getSourceRange(), true),
101+
PrettyTypeName);
102+
103+
return Tweak::Effect::applyEdit(tooling::Replacements(Expansion));
104+
}
105+
106+
llvm::Error ExpandAutoType::createErrorMessage(const std::string& Message,
107+
const Selection& Inputs) {
108+
auto& SrcMgr = Inputs.AST.getASTContext().getSourceManager();
109+
std::string ErrorMessage =
110+
Message + ": " +
111+
SrcMgr.getFilename(Inputs.Cursor).str() + " Line " +
112+
std::to_string(SrcMgr.getExpansionLineNumber(Inputs.Cursor));
113+
114+
return llvm::createStringError(llvm::inconvertibleErrorCode(),
115+
ErrorMessage.c_str());
116+
}
117+
118+
} // namespace clangd
119+
} // namespace clang
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# RUN: clangd -log=verbose -lit-test < %s | FileCheck -strict-whitespace %s
2+
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
3+
---
4+
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"auto i = 0;"}}}
5+
---
6+
{
7+
"jsonrpc": "2.0",
8+
"id": 1,
9+
"method": "textDocument/codeAction",
10+
"params": {
11+
"textDocument": {
12+
"uri": "test:///main.cpp"
13+
},
14+
"range": {
15+
"start": {
16+
"line": 0,
17+
"character": 0
18+
},
19+
"end": {
20+
"line": 0,
21+
"character": 4
22+
}
23+
},
24+
"context": {
25+
"diagnostics": []
26+
}
27+
}
28+
}
29+
# CHECK: "id": 1,
30+
# CHECK-NEXT: "jsonrpc": "2.0",
31+
# CHECK-NEXT: "result": [
32+
# CHECK-NEXT: {
33+
# CHECK-NEXT: "arguments": [
34+
# CHECK-NEXT: {
35+
# CHECK-NEXT: "file": "file:///clangd-test/main.cpp",
36+
# CHECK-NEXT: "selection": {
37+
# CHECK-NEXT: "end": {
38+
# CHECK-NEXT: "character": 4,
39+
# CHECK-NEXT: "line": 0
40+
# CHECK-NEXT: },
41+
# CHECK-NEXT: "start": {
42+
# CHECK-NEXT: "character": 0,
43+
# CHECK-NEXT: "line": 0
44+
# CHECK-NEXT: }
45+
# CHECK-NEXT: },
46+
# CHECK-NEXT: "tweakID": "ExpandAutoType"
47+
# CHECK-NEXT: }
48+
# CHECK-NEXT: ],
49+
# CHECK-NEXT: "command": "clangd.applyTweak",
50+
# CHECK-NEXT: "title": "expand auto type"
51+
# CHECK-NEXT: }
52+
# CHECK-NEXT: ]
53+
---
54+
{"jsonrpc":"2.0","id":4,"method":"workspace/executeCommand","params":{"command":"clangd.applyTweak","arguments":[{"file":"file:///clangd-test/main.cpp","selection":{"end":{"character":4,"line":0},"start":{"character":0,"line":0}},"tweakID":"ExpandAutoType"}]}}
55+
# CHECK: "newText": "int",
56+
# CHECK-NEXT: "range": {
57+
# CHECK-NEXT: "end": {
58+
# CHECK-NEXT: "character": 4,
59+
# CHECK-NEXT: "line": 0
60+
# CHECK-NEXT: },
61+
# CHECK-NEXT: "start": {
62+
# CHECK-NEXT: "character": 0,
63+
# CHECK-NEXT: "line": 0
64+
# CHECK-NEXT: }
65+
# CHECK-NEXT: }
66+
---
67+
{"jsonrpc":"2.0","id":4,"method":"shutdown"}
68+
---
69+
{"jsonrpc":"2.0","method":"exit"}
70+
---
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//===-- ASTTests.cpp --------------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "AST.h"
10+
#include "gtest/gtest.h"
11+
12+
namespace clang {
13+
namespace clangd {
14+
namespace {
15+
16+
TEST(ExpandAutoType, ShortenNamespace) {
17+
ASSERT_EQ("TestClass", shortenNamespace("TestClass", ""));
18+
19+
ASSERT_EQ("TestClass", shortenNamespace(
20+
"testnamespace::TestClass", "testnamespace"));
21+
22+
ASSERT_EQ(
23+
"namespace1::TestClass",
24+
shortenNamespace("namespace1::TestClass", "namespace2"));
25+
26+
ASSERT_EQ("TestClass",
27+
shortenNamespace("testns1::testns2::TestClass",
28+
"testns1::testns2"));
29+
30+
ASSERT_EQ(
31+
"testns2::TestClass",
32+
shortenNamespace("testns1::testns2::TestClass", "testns1"));
33+
34+
ASSERT_EQ("TestClass<testns1::OtherClass>",
35+
shortenNamespace(
36+
"testns1::TestClass<testns1::OtherClass>", "testns1"));
37+
}
38+
39+
40+
} // namespace
41+
} // namespace clangd
42+
} // namespace clang

clang-tools-extra/clangd/unittests/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ endif()
2323
add_custom_target(ClangdUnitTests)
2424
add_unittest(ClangdUnitTests ClangdTests
2525
Annotations.cpp
26+
ASTTests.cpp
2627
BackgroundIndexTests.cpp
2728
CancellationTests.cpp
2829
CanonicalIncludesTests.cpp

0 commit comments

Comments
 (0)