kuhnel created this revision.
kuhnel added a reviewer: sammccall.
Herald added subscribers: cfe-commits, kadircet, arphaman, jkorous, mgorny.
Herald added a project: clang.
Repository:
rG LLVM Github Monorepo
https://reviews.llvm.org/D62855
Files:
clang-tools-extra/clangd/AST.cpp
clang-tools-extra/clangd/AST.h
clang-tools-extra/clangd/XRefs.cpp
clang-tools-extra/clangd/XRefs.h
clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp
clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.h
clang-tools-extra/clangd/test/code-action-request.test
clang-tools-extra/clangd/unittests/TweakTests.cpp
clang-tools-extra/clangd/unittests/XRefsTests.cpp
Index: clang-tools-extra/clangd/unittests/XRefsTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/XRefsTests.cpp
+++ clang-tools-extra/clangd/unittests/XRefsTests.cpp
@@ -1971,6 +1971,28 @@
}
}
+TEST(GetDeductedType, KwAutoExpansion) {
+ struct Test {
+ StringRef AnnotatedCode;
+ const char *DeductedType;
+ } Tests[] = {
+ {"^auto i = 0;", "int"},
+ {"^auto f(){ return 1;};", "int"}
+ };
+ for (Test T : Tests) {
+ Annotations File(T.AnnotatedCode);
+ auto AST = TestTU::withCode(File.code()).build();
+ ASSERT_TRUE(AST.getDiagnostics().empty()) << AST.getDiagnostics().begin()->Message;
+ SourceManagerForFile SM("foo.cpp", File.code());
+
+ for (Position Pos : File.points()) {
+ auto Location = sourceLocationInMainFile(SM.get(), Pos);
+ auto DeducedType = getDeducedType(AST, *Location);
+ EXPECT_EQ(DeducedType->getAsString(), T.DeductedType);
+ }
+ }
+}
+
} // namespace
} // namespace clangd
} // namespace clang
Index: clang-tools-extra/clangd/unittests/TweakTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/TweakTests.cpp
+++ clang-tools-extra/clangd/unittests/TweakTests.cpp
@@ -19,6 +19,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <cassert>
+#include "refactor/tweaks/ExpandAutoType.h"
using llvm::Failed;
using llvm::HasValue;
@@ -217,6 +218,111 @@
checkTransform(ID, Input, Output);
}
+TEST(TweakTest, ExpandAutoType) {
+ llvm::StringLiteral ID = "ExpandAutoType";
+
+ checkAvailable(ID, R"cpp(
+ ^a^u^t^o^ i = 0;
+ )cpp");
+
+ checkNotAvailable(ID, R"cpp(
+ auto ^i^ ^=^ ^0^;^
+ )cpp");
+
+ llvm::StringLiteral Input = R"cpp(
+ [[auto]] i = 0;
+ )cpp";
+ llvm::StringLiteral Output = R"cpp(
+ int i = 0;
+ )cpp";
+ checkTransform(ID, Input, Output);
+
+ // check primitive type
+ Input = R"cpp(
+ au^to i = 0;
+ )cpp";
+ Output = R"cpp(
+ int i = 0;
+ )cpp";
+ checkTransform(ID, Input, Output);
+
+ // check classes and namespaces
+ Input = R"cpp(
+ namespace testns {
+ class TestClass {
+ class SubClass {};
+ };
+ }
+ ^auto C = testns::TestClass::SubClass();
+ )cpp";
+ Output = R"cpp(
+ namespace testns {
+ class TestClass {
+ class SubClass {};
+ };
+ }
+ testns::TestClass::SubClass C = testns::TestClass::SubClass();
+ )cpp";
+ checkTransform(ID, Input, Output);
+
+ // check that namespaces are shortened
+ Input = R"cpp(
+ namespace testns {
+ class TestClass {
+ };
+ void func() { ^auto C = TestClass(); }
+ }
+ )cpp";
+ Output = R"cpp(
+ namespace testns {
+ class TestClass {
+ };
+ void func() { TestClass C = TestClass(); }
+ }
+ )cpp";
+ checkTransform(ID, Input, Output);
+}
+
+TEST(ExpandAutoType, GetNamespaceString) {
+ struct EATWrapper : ExpandAutoType {
+ // to access the protected method
+ using ExpandAutoType::getNamespaceString;
+ };
+ TestTU TU;
+ TU.Filename = "foo.cpp";
+ Annotations Code("namespace firstns{namespace secondns{ au^to i = 0;} }");
+ TU.Code = Code.code();
+ ParsedAST AST = TU.build();
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+ auto Tree = SelectionTree(AST.getASTContext(),
+ *positionToOffset(Code.code(), Code.point()));
+ ASSERT_EQ("firstns::secondns", EATWrapper::getNamespaceString(Tree.commonAncestor()));
+}
+
+TEST(ExpandAutoType, ShortenNamespace) {
+ struct EATWrapper : ExpandAutoType {
+ // to access the protected method
+ using ExpandAutoType::shortenNamespace;
+ };
+
+ ASSERT_EQ("TestClass",
+ EATWrapper::shortenNamespace("TestClass", ""));
+
+ ASSERT_EQ("TestClass",
+ EATWrapper::shortenNamespace("testnamespace::TestClass", "testnamespace"));
+
+ ASSERT_EQ("namespace1::TestClass",
+ EATWrapper::shortenNamespace("namespace1::TestClass", "namespace2"));
+
+ ASSERT_EQ("TestClass",
+ EATWrapper::shortenNamespace("testns1::testns2::TestClass", "testns1::testns2"));
+
+ ASSERT_EQ(
+ "testns2::TestClass",
+ EATWrapper::shortenNamespace("testns1::testns2::TestClass", "testns1"));
+}
+
} // namespace
} // namespace clangd
} // namespace clang
+
Index: clang-tools-extra/clangd/test/code-action-request.test
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/test/code-action-request.test
@@ -0,0 +1,70 @@
+# RUN: clangd -log=verbose -lit-test < %s | FileCheck -strict-whitespace %s
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
+---
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"auto i = 0;"}}}
+---
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "textDocument/codeAction",
+ "params": {
+ "textDocument": {
+ "uri": "test:///main.cpp"
+ },
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 0
+ },
+ "end": {
+ "line": 0,
+ "character": 4
+ }
+ },
+ "context": {
+ "diagnostics": []
+ }
+ }
+}
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "arguments": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "file": "file:///clangd-test/main.cpp",
+# CHECK-NEXT: "selection": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 4,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "tweakID": "ExpandAutoType"
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "command": "clangd.applyTweak",
+# CHECK-NEXT: "title": "expand auto type"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+---
+{"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"}]}}
+# CHECK: "newText": "int",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 4,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+---
+{"jsonrpc":"2.0","id":4,"method":"shutdown"}
+---
+{"jsonrpc":"2.0","method":"exit"}
+---
\ No newline at end of file
Index: clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.h
@@ -0,0 +1,46 @@
+//===--- Tweak.h -------------------------------------------------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_ACTIONS_TWEAKS_EXPANDAUTO_TYPE_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_ACTIONS_TWEAKS_EXPANDAUTO_TYPE_H
+#include "refactor/Tweak.h"
+
+namespace clang {
+namespace clangd {
+/// Expand the "auto" type to the derived type
+/// Before:
+/// auto x = Something();
+/// ^^^^
+/// After:
+/// std::string x = Something();
+/// ^^^^^^^^^^^
+class ExpandAutoType : public Tweak {
+public:
+ const char *id() const override final;
+ bool prepare(const Selection &Inputs) override;
+ Expected<tooling::Replacements> apply(const Selection &Inputs) override;
+ std::string title() const override;
+
+protected:
+ const llvm::Optional<AutoTypeLoc>
+ findAutoType(const SelectionTree::Node *Node);
+
+ static std::string getNamespaceString(const SelectionTree::Node *Node);
+
+ static std::string shortenNamespace(const llvm::StringRef &OriginalName,
+ const llvm::StringRef &CurrentNamespace);
+
+private:
+ // cache the AutoTypeLoc, so that we do not need to search twice
+ llvm::Optional<clang::AutoTypeLoc> CachedLocation;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif
\ No newline at end of file
Index: clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp
@@ -0,0 +1,113 @@
+//===--- ReplaceAutoType.cpp -------------------------------------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#include "Logger.h"
+#include "clang/AST/Type.h"
+#include "clang/AST/TypeLoc.h"
+#include "llvm/ADT/None.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/Error.h"
+#include <climits>
+#include <memory>
+#include "XRefs.h"
+#include "ExpandAutoType.h"
+#include "llvm/ADT/StringExtras.h"
+
+namespace clang {
+namespace clangd {
+
+REGISTER_TWEAK(ExpandAutoType)
+
+bool ExpandAutoType::prepare(const Selection &Inputs) {
+ auto Node = Inputs.ASTSelection.commonAncestor();
+ CachedLocation = findAutoType(Node);
+ return CachedLocation != llvm::None;
+}
+
+Expected<tooling::Replacements> ExpandAutoType::apply(const Selection &Inputs) {
+ auto &SrcMgr = Inputs.AST.getASTContext().getSourceManager();
+
+ llvm::Optional<clang::QualType> DeductedType =
+ getDeducedType(Inputs.AST, CachedLocation->getBeginLoc());
+ if (DeductedType == llvm::None) {
+ log("could not deduct type for 'auto' type, not proposing any changes: %s Line %s",
+ SrcMgr.getFilename(Inputs.Cursor),
+ SrcMgr.getExpansionLineNumber(Inputs.Cursor));
+ return tooling::Replacements();
+ }
+
+ SourceRange OriginalRange(CachedLocation->getBeginLoc(),
+ CachedLocation->getEndLoc());
+ PrintingPolicy PP(Inputs.AST.getASTContext().getPrintingPolicy());
+ PP.SuppressTagKeyword = true;
+ std::string PrettyTypeName = shortenNamespace(
+ DeductedType->getAsString(PP),
+ getNamespaceString(Inputs.ASTSelection.commonAncestor()));
+ tooling::Replacement Expansion(SrcMgr, CharSourceRange(OriginalRange, true),
+ PrettyTypeName);
+
+ return tooling::Replacements(Expansion);
+}
+
+// try to find an 'auto' type location from the Selection
+const llvm::Optional<AutoTypeLoc>
+ExpandAutoType::findAutoType(const SelectionTree::Node *StartNode) {
+ auto Node = StartNode;
+ while (Node != nullptr) {
+ const TypeLoc *TypeNode = Node->ASTNode.get<TypeLoc>();
+ if (TypeNode) {
+ if (const AutoTypeLoc Result = TypeNode->getAs<AutoTypeLoc>()) {
+ return Result;
+ }
+ }
+ Node = Node->Parent;
+ }
+ return llvm::None;
+}
+
+std::string ExpandAutoType::title() const { return "expand auto type"; }
+
+std::string
+ExpandAutoType::shortenNamespace(const llvm::StringRef &OriginalName,
+ const llvm::StringRef &CurrentNamespace) {
+ llvm::SmallVector<llvm::StringRef, 8> OriginalParts;
+ llvm::SmallVector<llvm::StringRef, 8> CurrentParts;
+ llvm::SmallVector<llvm::StringRef, 8> Result;
+ OriginalName.split(OriginalParts, "::");
+ CurrentNamespace.split(CurrentParts, "::");
+ unsigned MinLength = std::min(CurrentParts.size(), OriginalParts.size());
+
+ u_int DifferentAt = 0;
+ while (CurrentParts[DifferentAt] == OriginalParts[DifferentAt] &&
+ DifferentAt < MinLength) {
+ DifferentAt++;
+ }
+
+ for (u_int i = DifferentAt; i < OriginalParts.size(); ++i) {
+ Result.push_back(OriginalParts[i]);
+ }
+ return join(Result, "::");
+}
+
+std::string ExpandAutoType::getNamespaceString(const SelectionTree::Node *StartNode) {
+ auto Node = StartNode;
+ while (Node != nullptr) {
+ LLVM_DEBUG(Node->ASTNode.print());
+ if (const Decl *Current = Node->ASTNode.get<Decl>()) {
+ if (const clang::NamespaceDecl *CurrentNameSpace =
+ dyn_cast<NamespaceDecl>(Current)) {
+ return CurrentNameSpace->getQualifiedNameAsString();
+ }
+ }
+ Node = Node->Parent;
+ }
+ return "";
+}
+
+} // namespace clangd
+} // namespace clang
Index: clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
===================================================================
--- clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
+++ clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
@@ -14,6 +14,7 @@
add_clang_library(clangDaemonTweaks OBJECT
RawStringLiteral.cpp
SwapIfBranches.cpp
+ ExpandAutoType.cpp
LINK_LIBS
clangAST
Index: clang-tools-extra/clangd/XRefs.h
===================================================================
--- clang-tools-extra/clangd/XRefs.h
+++ clang-tools-extra/clangd/XRefs.h
@@ -138,6 +138,11 @@
getTypeHierarchy(ParsedAST &AST, Position Pos, int Resolve,
TypeHierarchyDirection Direction);
+/// Retrieves the deduced type at a given location (auto, decltype).
+/// SourceLocationBeg must point to the first character of the token
+llvm::Optional<QualType> getDeducedType(ParsedAST &AST,
+ SourceLocation SourceLocationBeg);
+
} // namespace clangd
} // namespace clang
Index: clang-tools-extra/clangd/XRefs.cpp
===================================================================
--- clang-tools-extra/clangd/XRefs.cpp
+++ clang-tools-extra/clangd/XRefs.cpp
@@ -847,6 +847,13 @@
/// Retrieves the deduced type at a given location (auto, decltype).
bool hasDeducedType(ParsedAST &AST, SourceLocation SourceLocationBeg) {
+ return getDeducedType(AST, SourceLocationBeg) != llvm::None;
+}
+
+/// Retrieves the deduced type at a given location (auto, decltype).
+/// SourceLocationBeg must point to the first character of the token
+llvm::Optional<QualType> getDeducedType(ParsedAST &AST,
+ SourceLocation SourceLocationBeg) {
Token Tok;
auto &ASTCtx = AST.getASTContext();
// Only try to find a deduced type if the token is auto or decltype.
@@ -854,12 +861,15 @@
Lexer::getRawToken(SourceLocationBeg, Tok, ASTCtx.getSourceManager(),
ASTCtx.getLangOpts(), false) ||
!Tok.is(tok::raw_identifier)) {
- return false;
+ return {};
}
AST.getPreprocessor().LookUpIdentifierInfo(Tok);
if (!(Tok.is(tok::kw_auto) || Tok.is(tok::kw_decltype)))
- return false;
- return true;
+ return {};
+
+ DeducedTypeVisitor V(SourceLocationBeg);
+ V.TraverseAST(AST.getASTContext());
+ return V.DeducedType;
}
llvm::Optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
Index: clang-tools-extra/clangd/AST.h
===================================================================
--- clang-tools-extra/clangd/AST.h
+++ clang-tools-extra/clangd/AST.h
@@ -17,6 +17,7 @@
#include "clang/AST/Decl.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Lex/MacroInfo.h"
+#include "clang/AST/RecursiveASTVisitor.h"
namespace clang {
class SourceManager;
@@ -67,6 +68,9 @@
const MacroInfo *MI,
const SourceManager &SM);
+// TODO: add documentation
+llvm::Optional<QualType> getDeductedType(SourceLocation SearchedLocation, ASTContext &AST);
+
} // namespace clangd
} // namespace clang
Index: clang-tools-extra/clangd/AST.cpp
===================================================================
--- clang-tools-extra/clangd/AST.cpp
+++ clang-tools-extra/clangd/AST.cpp
@@ -169,5 +169,105 @@
return SymbolID(USR);
}
+namespace {
+/// Computes the deduced type at a given location by visiting the relevant
+/// nodes. We use this to display the actual type when hovering over an "auto"
+/// keyword or "decltype()" expression.
+/// FIXME: This could have been a lot simpler by visiting AutoTypeLocs but it
+/// seems that the AutoTypeLocs that can be visited along with their AutoType do
+/// not have the deduced type set. Instead, we have to go to the appropriate
+/// DeclaratorDecl/FunctionDecl and work our back to the AutoType that does have
+/// a deduced type set. The AST should be improved to simplify this scenario.
+class DeducedTypeVisitor : public RecursiveASTVisitor<DeducedTypeVisitor> {
+ SourceLocation SearchedLocation;
+ llvm::Optional<QualType> DeducedType;
+
+public:
+ DeducedTypeVisitor(SourceLocation SearchedLocation)
+ : SearchedLocation(SearchedLocation) {}
+
+ llvm::Optional<QualType> getDeducedType() { return DeducedType; }
+
+ // Handle auto initializers:
+ //- auto i = 1;
+ //- decltype(auto) i = 1;
+ //- auto& i = 1;
+ //- auto* i = &a;
+ bool VisitDeclaratorDecl(DeclaratorDecl *D) {
+ if (!D->getTypeSourceInfo() ||
+ D->getTypeSourceInfo()->getTypeLoc().getBeginLoc() != SearchedLocation)
+ return true;
+
+ if (auto *AT = D->getType()->getContainedAutoType()) {
+ if (!AT->getDeducedType().isNull())
+ DeducedType = AT->getDeducedType();
+ }
+ return true;
+ }
+
+ // Handle auto return types:
+ //- auto foo() {}
+ //- auto& foo() {}
+ //- auto foo() -> int {}
+ //- auto foo() -> decltype(1+1) {}
+ //- operator auto() const { return 10; }
+ bool VisitFunctionDecl(FunctionDecl *D) {
+ if (!D->getTypeSourceInfo())
+ return true;
+ // Loc of auto in return type (c++14).
+ auto CurLoc = D->getReturnTypeSourceRange().getBegin();
+ // Loc of "auto" in operator auto()
+ if (CurLoc.isInvalid() && dyn_cast<CXXConversionDecl>(D))
+ CurLoc = D->getTypeSourceInfo()->getTypeLoc().getBeginLoc();
+ // Loc of "auto" in function with traling return type (c++11).
+ if (CurLoc.isInvalid())
+ CurLoc = D->getSourceRange().getBegin();
+ if (CurLoc != SearchedLocation)
+ return true;
+
+ const AutoType *AT = D->getReturnType()->getContainedAutoType();
+ if (AT && !AT->getDeducedType().isNull()) {
+ DeducedType = AT->getDeducedType();
+ } else if (auto DT = dyn_cast<DecltypeType>(D->getReturnType())) {
+ // auto in a trailing return type just points to a DecltypeType and
+ // getContainedAutoType does not unwrap it.
+ if (!DT->getUnderlyingType().isNull())
+ DeducedType = DT->getUnderlyingType();
+ } else if (!D->getReturnType().isNull()) {
+ DeducedType = D->getReturnType();
+ }
+ return true;
+ }
+
+ // Handle non-auto decltype, e.g.:
+ // - auto foo() -> decltype(expr) {}
+ // - decltype(expr);
+ bool VisitDecltypeTypeLoc(DecltypeTypeLoc TL) {
+ if (TL.getBeginLoc() != SearchedLocation)
+ return true;
+
+ // A DecltypeType's underlying type can be another DecltypeType! E.g.
+ // int I = 0;
+ // decltype(I) J = I;
+ // decltype(J) K = J;
+ const DecltypeType *DT = dyn_cast<DecltypeType>(TL.getTypePtr());
+ while (DT && !DT->getUnderlyingType().isNull()) {
+ DeducedType = DT->getUnderlyingType();
+ DT = dyn_cast<DecltypeType>(DeducedType->getTypePtr());
+ }
+ return true;
+ }
+};
+} // namespace
+
+llvm::Optional<QualType> getDeductedType(SourceLocation SearchedLocation, ASTContext &AST){
+ auto Visitor = DeducedTypeVisitor(SearchedLocation);
+ Visitor.TraverseAST(AST);
+ return Visitor.getDeducedType();
+};
+
+
+
+
} // namespace clangd
} // namespace clang
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits