[clang-tools-extra] a975fde - [clang] Annotate trivial getters and setters on hover.
Sam McCall via cfe-commits
cfe-commits at lists.llvm.org
Fri Apr 3 22:15:35 PDT 2020
Author: Sam McCall
Date: 2020-04-04T07:15:12+02:00
New Revision: a975fde23a5e64721d75022b2a072a0b19f4b279
URL: https://github.com/llvm/llvm-project/commit/a975fde23a5e64721d75022b2a072a0b19f4b279
DIFF: https://github.com/llvm/llvm-project/commit/a975fde23a5e64721d75022b2a072a0b19f4b279.diff
LOG: [clang] Annotate trivial getters and setters on hover.
Summary: (Only if their definitions are visible and they have no other docs)
Reviewers: kadircet
Subscribers: jkorous, arphaman, usaxena95, cfe-commits
Tags: #clang
Differential Revision: https://reviews.llvm.org/D77408
Added:
Modified:
clang-tools-extra/clangd/Hover.cpp
clang-tools-extra/clangd/unittests/HoverTests.cpp
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp
index 63fe6b1d756c..e54ba2e364a0 100644
--- a/clang-tools-extra/clangd/Hover.cpp
+++ b/clang-tools-extra/clangd/Hover.cpp
@@ -21,9 +21,11 @@
#include "clang/AST/ASTTypeTraits.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
+#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
+#include "clang/AST/OperationKinds.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/Type.h"
#include "clang/Basic/SourceLocation.h"
@@ -370,6 +372,97 @@ llvm::Optional<std::string> printExprValue(const SelectionTree::Node *N,
return llvm::None;
}
+llvm::Optional<StringRef> fieldName(const Expr *E) {
+ const auto *ME = llvm::dyn_cast<MemberExpr>(E->IgnoreCasts());
+ if (!ME || !llvm::isa<CXXThisExpr>(ME->getBase()->IgnoreCasts()))
+ return llvm::None;
+ const auto *Field =
+ llvm::dyn_cast<FieldDecl>(ME->getMemberDecl());
+ if (!Field || !Field->getDeclName().isIdentifier())
+ return llvm::None;
+ return Field->getDeclName().getAsIdentifierInfo()->getName();
+}
+
+// If CMD is of the form T foo() { return FieldName; } then returns "FieldName".
+llvm::Optional<StringRef> getterVariableName(const CXXMethodDecl *CMD) {
+ assert(CMD->hasBody());
+ if (CMD->getNumParams() != 0 || CMD->isVariadic())
+ return llvm::None;
+ const auto *Body = llvm::dyn_cast<CompoundStmt>(CMD->getBody());
+ const auto *OnlyReturn = (Body && Body->size() == 1)
+ ? llvm::dyn_cast<ReturnStmt>(Body->body_front())
+ : nullptr;
+ if (!OnlyReturn || !OnlyReturn->getRetValue())
+ return llvm::None;
+ return fieldName(OnlyReturn->getRetValue());
+}
+
+// If CMD is one of the forms:
+// void foo(T arg) { FieldName = arg; }
+// R foo(T arg) { FieldName = arg; return *this; }
+// then returns "FieldName"
+llvm::Optional<StringRef> setterVariableName(const CXXMethodDecl *CMD) {
+ assert(CMD->hasBody());
+ if (CMD->isConst() || CMD->getNumParams() != 1 || CMD->isVariadic())
+ return llvm::None;
+ const ParmVarDecl *Arg = CMD->getParamDecl(0);
+ if (Arg->isParameterPack())
+ return llvm::None;
+
+ const auto *Body = llvm::dyn_cast<CompoundStmt>(CMD->getBody());
+ if (!Body || Body->size() == 0 || Body->size() > 2)
+ return llvm::None;
+ // If the second statement exists, it must be `return this` or `return *this`.
+ if (Body->size() == 2) {
+ auto *Ret = llvm::dyn_cast<ReturnStmt>(Body->body_back());
+ if (!Ret || !Ret->getRetValue())
+ return llvm::None;
+ const Expr *RetVal = Ret->getRetValue()->IgnoreCasts();
+ if (const auto *UO = llvm::dyn_cast<UnaryOperator>(RetVal)) {
+ if (UO->getOpcode() != UO_Deref)
+ return llvm::None;
+ RetVal = UO->getSubExpr()->IgnoreCasts();
+ }
+ if (!llvm::isa<CXXThisExpr>(RetVal))
+ return llvm::None;
+ }
+ // The first statement must be an assignment of the arg to a field.
+ const Expr *LHS, *RHS;
+ if (const auto *BO = llvm::dyn_cast<BinaryOperator>(Body->body_front())) {
+ if (BO->getOpcode() != BO_Assign)
+ return llvm::None;
+ LHS = BO->getLHS();
+ RHS = BO->getRHS();
+ } else if (const auto *COCE =
+ llvm::dyn_cast<CXXOperatorCallExpr>(Body->body_front())) {
+ if (COCE->getOperator() != OO_Equal || COCE->getNumArgs() != 2)
+ return llvm::None;
+ LHS = COCE->getArg(0);
+ RHS = COCE->getArg(1);
+ } else {
+ return llvm::None;
+ }
+ auto *DRE = llvm::dyn_cast<DeclRefExpr>(RHS->IgnoreCasts());
+ if (!DRE || DRE->getDecl() != Arg)
+ return llvm::None;
+ return fieldName(LHS);
+}
+
+std::string synthesizeDocumentation(const NamedDecl *ND) {
+ if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(ND)) {
+ // Is this an ordinary, non-static method whose definition is visible?
+ if (CMD->getDeclName().isIdentifier() && !CMD->isStatic() &&
+ (CMD = llvm::dyn_cast_or_null<CXXMethodDecl>(CMD->getDefinition())) &&
+ CMD->hasBody()) {
+ if (const auto GetterField = getterVariableName(CMD))
+ return llvm::formatv("Trivial accessor for `{0}`.", *GetterField);
+ if (const auto SetterField = setterVariableName(CMD))
+ return llvm::formatv("Trivial setter for `{0}`.", *SetterField);
+ }
+ }
+ return "";
+}
+
/// Generate a \p Hover object given the declaration \p D.
HoverInfo getHoverContents(const NamedDecl *D, const SymbolIndex *Index) {
HoverInfo HI;
@@ -387,6 +480,8 @@ HoverInfo getHoverContents(const NamedDecl *D, const SymbolIndex *Index) {
const auto *CommentD = getDeclForComment(D);
HI.Documentation = getDeclComment(Ctx, *CommentD);
enhanceFromIndex(HI, *CommentD, Index);
+ if (HI.Documentation.empty())
+ HI.Documentation = synthesizeDocumentation(D);
HI.Kind = index::getSymbolInfo(D).Kind;
diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp
index 99fd1c572447..f9ab78dd753b 100644
--- a/clang-tools-extra/clangd/unittests/HoverTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp
@@ -616,6 +616,58 @@ class Foo {})cpp";
HI.LocalScope = "foo::";
HI.Type = "int";
}},
+
+ {// Getter
+ R"cpp(
+ struct X { int Y; float [[^y]]() { return Y; } };
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "y";
+ HI.Kind = index::SymbolKind::InstanceMethod;
+ HI.NamespaceScope = "";
+ HI.Definition = "float y()";
+ HI.LocalScope = "X::";
+ HI.Documentation = "Trivial accessor for `Y`.";
+ HI.Type = "float ()";
+ HI.ReturnType = "float";
+ HI.Parameters.emplace();
+ }},
+ {// Setter
+ R"cpp(
+ struct X { int Y; void [[^setY]](float v) { Y = v; } };
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "setY";
+ HI.Kind = index::SymbolKind::InstanceMethod;
+ HI.NamespaceScope = "";
+ HI.Definition = "void setY(float v)";
+ HI.LocalScope = "X::";
+ HI.Documentation = "Trivial setter for `Y`.";
+ HI.Type = "void (float)";
+ HI.ReturnType = "void";
+ HI.Parameters.emplace();
+ HI.Parameters->emplace_back();
+ HI.Parameters->back().Type = "float";
+ HI.Parameters->back().Name = "v";
+ }},
+ {// Setter (builder)
+ R"cpp(
+ struct X { int Y; X& [[^setY]](float v) { Y = v; return *this; } };
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "setY";
+ HI.Kind = index::SymbolKind::InstanceMethod;
+ HI.NamespaceScope = "";
+ HI.Definition = "X &setY(float v)";
+ HI.LocalScope = "X::";
+ HI.Documentation = "Trivial setter for `Y`.";
+ HI.Type = "struct X &(float)";
+ HI.ReturnType = "struct X &";
+ HI.Parameters.emplace();
+ HI.Parameters->emplace_back();
+ HI.Parameters->back().Type = "float";
+ HI.Parameters->back().Name = "v";
+ }},
};
for (const auto &Case : Cases) {
SCOPED_TRACE(Case.Code);
More information about the cfe-commits
mailing list