diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp index 63fe6b1d756c0..e54ba2e364a0e 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 printExprValue(const SelectionTree::Node *N, return llvm::None; } +llvm::Optional fieldName(const Expr *E) { + const auto *ME = llvm::dyn_cast(E->IgnoreCasts()); + if (!ME || !llvm::isa(ME->getBase()->IgnoreCasts())) + return llvm::None; + const auto *Field = + llvm::dyn_cast(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 getterVariableName(const CXXMethodDecl *CMD) { + assert(CMD->hasBody()); + if (CMD->getNumParams() != 0 || CMD->isVariadic()) + return llvm::None; + const auto *Body = llvm::dyn_cast(CMD->getBody()); + const auto *OnlyReturn = (Body && Body->size() == 1) + ? llvm::dyn_cast(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 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(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(Body->body_back()); + if (!Ret || !Ret->getRetValue()) + return llvm::None; + const Expr *RetVal = Ret->getRetValue()->IgnoreCasts(); + if (const auto *UO = llvm::dyn_cast(RetVal)) { + if (UO->getOpcode() != UO_Deref) + return llvm::None; + RetVal = UO->getSubExpr()->IgnoreCasts(); + } + if (!llvm::isa(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(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(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(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(ND)) { + // Is this an ordinary, non-static method whose definition is visible? + if (CMD->getDeclName().isIdentifier() && !CMD->isStatic() && + (CMD = llvm::dyn_cast_or_null(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 99fd1c5724476..f9ab78dd753b6 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);