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
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to