diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h --- a/clang-tools-extra/clangd/Hover.h +++ b/clang-tools-extra/clangd/Hover.h @@ -77,6 +77,9 @@ llvm::Optional Size; /// Contains the offset of fields within the enclosing class. llvm::Optional Offset; + // Set when symbol is inside function call. Contains information extracted + // from the callee definition about the argument this is passed as. + llvm::Optional CalleeArgInfo; /// Produce a user-readable information. markup::Document present() const; diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -289,30 +289,35 @@ : PVD->getDefaultArg(); } +HoverInfo::Param toHoverInfoParam(const ParmVarDecl *PVD, + const PrintingPolicy &Policy) { + HoverInfo::Param Out; + if (!PVD->getType().isNull()) { + Out.Type = printType(PVD->getType(), Policy); + } else { + std::string Param; + llvm::raw_string_ostream OS(Param); + PVD->dump(OS); + OS.flush(); + elog("Got param with null type: {0}", Param); + } + if (!PVD->getName().empty()) + Out.Name = PVD->getNameAsString(); + if (const Expr *DefArg = getDefaultArg(PVD)) { + Out.Default.emplace(); + llvm::raw_string_ostream OS(*Out.Default); + DefArg->printPretty(OS, nullptr, Policy); + } + return Out; +} + // Populates Type, ReturnType, and Parameters for function-like decls. void fillFunctionTypeAndParams(HoverInfo &HI, const Decl *D, const FunctionDecl *FD, const PrintingPolicy &Policy) { HI.Parameters.emplace(); for (const ParmVarDecl *PVD : FD->parameters()) { - HI.Parameters->emplace_back(); - auto &P = HI.Parameters->back(); - if (!PVD->getType().isNull()) { - P.Type = printType(PVD->getType(), Policy); - } else { - std::string Param; - llvm::raw_string_ostream OS(Param); - PVD->dump(OS); - OS.flush(); - elog("Got param with null type: {0}", Param); - } - if (!PVD->getName().empty()) - P.Name = PVD->getNameAsString(); - if (const Expr *DefArg = getDefaultArg(PVD)) { - P.Default.emplace(); - llvm::raw_string_ostream Out(*P.Default); - DefArg->printPretty(Out, nullptr, Policy); - } + HI.Parameters->emplace_back(toHoverInfoParam(PVD, Policy)); } // We don't want any type info, if name already contains it. This is true for @@ -683,6 +688,42 @@ } } +// If N is passed as argument to a function, fill HI.CalleeArgInfo with +// information about that argument. +void maybeAddCalleeArgInfo(const SelectionTree::Node *N, HoverInfo &HI, + const PrintingPolicy &Policy) { + if (!N) + return; + const auto &OuterNode = N->outerImplicit(); + if (!OuterNode.Parent) + return; + auto *CE = OuterNode.Parent->ASTNode.get(); + if (!CE) + return; + const FunctionDecl *FD = CE->getDirectCallee(); + // For non-function-call-like operatators (e.g. operator+, operator<<) it's + // not immediattely obvious what the "passed as" would refer to and, given + // fixed function signature, the value would be very low anyway, so we choose + // to not support that. + // Both variadic functions and operator() (especially relevant for lambdas) + // should be supported in the future. + if (!FD || FD->isOverloadedOperator() || FD->isVariadic()) + return; + + // Find argument index for N. + for (unsigned I = 0; I < CE->getNumArgs() && I < FD->getNumParams(); ++I) { + auto *Arg = CE->getArg(I); + if (Arg != OuterNode.ASTNode.get()) + continue; + + // Extract matching argument from function declaration. + if (const ParmVarDecl *PVD = FD->getParamDecl(I)) { + HI.CalleeArgInfo.emplace(toHoverInfoParam(PVD, Policy)); + } + return; + } +} + } // namespace llvm::Optional getHover(ParsedAST &AST, Position Pos, @@ -745,6 +786,7 @@ // Look for a close enclosing expression to show the value of. if (!HI->Value) HI->Value = printExprValue(N, AST.getASTContext()); + maybeAddCalleeArgInfo(N, *HI, AST.getASTContext().getPrintingPolicy()); } else if (const Expr *E = N->ASTNode.get()) { HI = getHoverContents(E, AST); } @@ -825,6 +867,14 @@ Output.addParagraph().appendText( llvm::formatv("Size: {0} byte{1}", *Size, *Size == 1 ? "" : "s").str()); + if (CalleeArgInfo) { + Output.addRuler(); + std::string Buffer; + llvm::raw_string_ostream OS(Buffer); + OS << "Passed as " << *CalleeArgInfo; + Output.addParagraph().appendText(OS.str()); + } + if (!Documentation.empty()) parseDocumentation(Documentation, Output); @@ -849,6 +899,7 @@ // non-c++ projects or projects that are not making use of namespaces. Output.addCodeBlock(ScopeComment + DefinitionWithAccess); } + return Output; } diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -696,6 +696,51 @@ HI.Parameters->back().Name = "v"; HI.AccessSpecifier = "public"; }}, + {// Extra info for function call. + R"cpp( + void fun(int arg_a, int &arg_b) {}; + void code() { + int a = 1, b = 2; + fun(a, [[^b]]); + } + )cpp", + [](HoverInfo &HI) { + HI.Name = "b"; + HI.Kind = index::SymbolKind::Variable; + HI.NamespaceScope = ""; + HI.Definition = "int b = 2"; + HI.LocalScope = "code::"; + HI.Value = "2"; + HI.Type = "int"; + HI.CalleeArgInfo.emplace(); + HI.CalleeArgInfo->Name = "arg_b"; + HI.CalleeArgInfo->Type = "int &"; + }}, + {// Extra info for method call. + R"cpp( + class C { + public: + void fun(int arg_a = 3, int arg_b = 4) {} + }; + void code() { + int a = 1, b = 2; + C c; + c.fun([[^a]], b); + } + )cpp", + [](HoverInfo &HI) { + HI.Name = "a"; + HI.Kind = index::SymbolKind::Variable; + HI.NamespaceScope = ""; + HI.Definition = "int a = 1"; + HI.LocalScope = "code::"; + HI.Value = "1"; + HI.Type = "int"; + HI.CalleeArgInfo.emplace(); + HI.CalleeArgInfo->Name = "arg_a"; + HI.CalleeArgInfo->Type = "int"; + HI.CalleeArgInfo->Default = "3"; + }}, }; for (const auto &Case : Cases) { SCOPED_TRACE(Case.Code); @@ -729,6 +774,7 @@ EXPECT_EQ(H->Size, Expected.Size); EXPECT_EQ(H->Offset, Expected.Offset); EXPECT_EQ(H->AccessSpecifier, Expected.AccessSpecifier); + EXPECT_EQ(H->CalleeArgInfo, Expected.CalleeArgInfo); } } @@ -2022,6 +2068,29 @@ // In namespace ns1 private: union foo {})", + }, + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Variable; + HI.Name = "foo"; + HI.Definition = "int foo = 3"; + HI.LocalScope = "test::Bar::"; + HI.Value = "3"; + HI.Type = "int"; + HI.CalleeArgInfo.emplace(); + HI.CalleeArgInfo->Name = "arg_a"; + HI.CalleeArgInfo->Type = "int"; + HI.CalleeArgInfo->Default = "7"; + }, + R"(variable foo + +Type: int +Value = 3 + +Passed as int arg_a = 7 + +// In test::Bar +int foo = 3)", }}; for (const auto &C : Cases) {