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 @@ -463,8 +463,13 @@ return Constant.Val.getAsString(Ctx, T); } -std::optional printExprValue(const SelectionTree::Node *N, - const ASTContext &Ctx) { +// Visit the SelectionTree's node along N's ancestors. If a node in the path +// can be converted to an evaluable Expr, CB(Expr) is called to determine if +// we should stop visiting (true). +// Returns the first Expr that CB(Expr) returns true. +const Expr * +visitExprFromSelectionTree(const SelectionTree::Node *N, const ASTContext &Ctx, + llvm::function_ref CB) { for (; N; N = N->Parent) { // Try to evaluate the first evaluatable enclosing expression. if (const Expr *E = N->ASTNode.get()) { @@ -472,15 +477,25 @@ // has nothing to do with our original cursor position. if (!E->getType().isNull() && E->getType()->isVoidType()) break; - if (auto Val = printExprValue(E, Ctx)) - return Val; + if (CB(E)) + return E; } else if (N->ASTNode.get() || N->ASTNode.get()) { // Refuse to cross certain non-exprs. (TypeLoc are OK as part of Exprs). // This tries to ensure we're showing a value related to the cursor. break; } } - return std::nullopt; + return nullptr; +} + +std::optional printExprValue(const SelectionTree::Node *N, + const ASTContext &Ctx) { + std::optional Ret; + visitExprFromSelectionTree(N, Ctx, [&](const Expr *E) { + Ret = printExprValue(E, Ctx); + return Ret.has_value(); + }); + return Ret; } std::optional fieldName(const Expr *E) { @@ -711,6 +726,24 @@ } } + auto Tree = SelectionTree::createRight(AST.getASTContext(), AST.getTokens(), + SM.getFileOffset(Tok.location()), + SM.getFileOffset(Tok.endLocation())); + auto *StartNode = Tree.commonAncestor(); + if (StartNode && StartNode->Selected != SelectionTree::Selection::Complete) + while (StartNode->Children.size() == 1 && + StartNode->Children[0]->Selected == + SelectionTree::Selection::Complete) + StartNode = StartNode->Children.front(); + visitExprFromSelectionTree( + StartNode, AST.getASTContext(), [&](const Expr *E) { + HI.Value = printExprValue(E, AST.getASTContext()); + HI.Type = printType( + E->getType(), AST.getASTContext(), + getPrintingPolicy(AST.getASTContext().getPrintingPolicy())); + return HI.Value || HI.Type; + }); + if (auto Expansion = AST.getTokens().expansionStartingAt(&Tok)) { // We drop expansion that's longer than the threshold. // For extremely long expansion text, it's not readable from hover card 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 @@ -529,6 +529,8 @@ [](HoverInfo &HI) { HI.Name = "MACRO"; HI.Kind = index::SymbolKind::Macro; + HI.Value = "41 (0x29)"; + HI.Type = "int"; HI.Definition = "#define MACRO 41\n\n" "// Expands to\n" "41"; @@ -1792,6 +1794,8 @@ )cpp", [](HoverInfo &HI) { HI.Name = "MACRO"; + HI.Value = "0"; + HI.Type = "int"; HI.Kind = index::SymbolKind::Macro; HI.Definition = "#define MACRO 0\n\n" "// Expands to\n" @@ -3746,6 +3750,140 @@ EXPECT_EQ(H->Type->Type, "int"); EXPECT_EQ(H->Definition, "using foo = type"); } + +TEST(Hover, EvaluateMacros) { + Annotations CXX(R"cpp( + #define X 42 + #define SizeOf sizeof + #define AlignOf alignof + + using u64 = unsigned long long; + // calculate (a ** b) % p + constexpr u64 pow_with_mod(u64 a, u64 b, u64 p) { + u64 ret = 1; + while (b) { + if (b & 1) + ret = (ret * a) % p; + a = (a * a) % p; + b >>= 1; + } + return ret; + } + #define last_n_digit(x, y, n) \ + pow_with_mod(x, y, pow_with_mod(10, n, 2147483647)) + #define declare_struct(X, name, value) \ + struct X { \ + constexpr auto name() { return value; } \ + } + #define gnu_statement_expression(value) \ + ({ \ + declare_struct(Widget, getter, value); \ + Widget().getter(); \ + }) + #define define_lambda_begin(lambda, ...) \ + [&](__VA_ARGS__) { + #define define_lambda_end() } + + #define plus_42 +40 + + void check() { + X$1^; + Size$2^Of(int); + struct Y { + int y; + double z; + }; + Alig$3^nOf(Y); + // 2**32 == 4294967296 + last_n_di$4^git(2, 32, 6); + gnu_statement_exp$5^ression(42); + + constexpr auto value = define_lamb$6^da_begin(lambda, int, char) + // Check if the expansion range is right. + return $7^last_n_digit(10, 3, 3)$8^; + define_lam$9^bda_end(); + 2 pl$10^us_42; + } + )cpp"); + + Config Cfg; + Cfg.Hover.ShowAKA = false; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + + auto TU = TestTU::withCode(CXX.code()); + TU.ExtraArgs.push_back("-std=c++17"); + auto GetHoverAt = [AST(TU.build()), CXX](llvm::StringRef Point) mutable { + return getHover(AST, CXX.point(Point), format::getLLVMStyle(), nullptr); + }; + + std::optional H; + + H = GetHoverAt("1"); + ASSERT_TRUE(H); + EXPECT_EQ(H->Value, "42 (0x2a)"); + EXPECT_EQ(H->Type, HoverInfo::PrintedType("int")); + + H = GetHoverAt("2"); + ASSERT_TRUE(H); + EXPECT_EQ(H->Value, "4"); + // Don't validate type of `sizeof` and `alignof` as we're getting different + // desugared types on different platforms. Same as below. + + H = GetHoverAt("3"); + ASSERT_TRUE(H); + EXPECT_EQ(H->Value, "8"); + + H = GetHoverAt("4"); + ASSERT_TRUE(H); + EXPECT_EQ(H->Value, "967296 (0xec280)"); + EXPECT_EQ(H->Type, HoverInfo::PrintedType("u64")); + + H = GetHoverAt("5"); + ASSERT_TRUE(H); + EXPECT_EQ(H->Value, "42 (0x2a)"); + EXPECT_EQ(H->Type, HoverInfo::PrintedType("int")); + + H = GetHoverAt("6"); + ASSERT_TRUE(H); + EXPECT_FALSE(H->Value) << H->Value; + EXPECT_EQ(H->Type, HoverInfo::PrintedType("class (lambda)")) << H->Type; + + H = GetHoverAt("7"); + ASSERT_TRUE(H); + EXPECT_EQ(H->Value, "0"); + EXPECT_EQ(H->Type, HoverInfo::PrintedType("u64")); + + H = GetHoverAt("8"); + ASSERT_FALSE(H); + + H = GetHoverAt("9"); + ASSERT_FALSE(H->Value) << H->Value; + ASSERT_FALSE(H->Type) << H->Type; + + H = GetHoverAt("10"); + ASSERT_TRUE(H); + EXPECT_EQ(H->Type, HoverInfo::PrintedType("int")); + EXPECT_EQ(H->Value, "40 (0x28)"); + + Annotations C(R"c( + #define alignof _Alignof + void foo() { + al^ignof(struct { int x; char y[10]; }); + } + )c"); + + TU = TestTU::withCode(C.code()); + TU.Filename = "TestTU.c"; + TU.ExtraArgs = { + "-std=c17", + }; + auto AST = TU.build(); + H = getHover(AST, C.point(), format::getLLVMStyle(), nullptr); + + ASSERT_TRUE(H); + EXPECT_EQ(H->Value, "4"); +} + } // namespace } // namespace clangd } // namespace clang