zyounan updated this revision to Diff 518144. zyounan added a comment. Refactor tests. Obtain type from VarDecl.
Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D148457/new/ https://reviews.llvm.org/D148457 Files: clang-tools-extra/clangd/Hover.cpp clang-tools-extra/clangd/unittests/HoverTests.cpp
Index: clang-tools-extra/clangd/unittests/HoverTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/HoverTests.cpp +++ clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -18,6 +18,7 @@ #include "clang/Format/Format.h" #include "clang/Index/IndexSymbol.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" #include "gtest/gtest.h" #include <functional> @@ -529,6 +530,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"; @@ -560,6 +563,7 @@ [](HoverInfo &HI) { HI.Name = "DECL_STR"; HI.Kind = index::SymbolKind::Macro; + HI.Type = HoverInfo::PrintedType("const char *"); HI.Definition = "#define DECL_STR(NAME, VALUE) const char *v_##NAME = " "STRINGIFY(VALUE)\n\n" "// Expands to\n" @@ -1792,6 +1796,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 +3752,213 @@ EXPECT_EQ(H->Type->Type, "int"); EXPECT_EQ(H->Definition, "using foo = type<true, int, double>"); } + +TEST(Hover, EvaluateMacros) { + llvm::StringRef PredefinedCXX = R"cpp( +#define X 42 +#define SizeOf sizeof +#define AlignOf alignof +#define PLUS_TWO +2 +#define TWO 2 + +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 left_bracket [ +#define right_bracket ] +#define array_decl(type, name, size) type name left_bracket size right_bracket + )cpp"; + + struct { + llvm::StringRef Code; + const std::function<void(std::optional<HoverInfo>, size_t /*Id*/)> + Validator; + } Cases[] = { + { + /*Code=*/R"cpp( + X^; + )cpp", + /*Validator=*/ + [](std::optional<HoverInfo> HI, size_t) { + EXPECT_EQ(HI->Value, "42 (0x2a)"); + EXPECT_EQ(HI->Type, HoverInfo::PrintedType("int")); + }, + }, + { + /*Code=*/R"cpp( + Size^Of(int); + )cpp", + /*Validator=*/ + [](std::optional<HoverInfo> HI, size_t) { + EXPECT_TRUE(HI->Value); + EXPECT_TRUE(HI->Type); + // Don't validate type or value of `sizeof` and `alignof` as we're + // getting different values or desugared types on different + // platforms. Same as below. + }, + }, + { + R"cpp( + struct Y { + int y; + double z; + }; + Alig$3^nOf(Y); + )cpp", + [](std::optional<HoverInfo> HI, size_t) { + EXPECT_TRUE(HI->Value); + EXPECT_TRUE(HI->Type); + }, + }, + { + R"cpp( + // 2**32 == 4294967296 + last_n_di^git(2, 32, 6); + )cpp", + [](std::optional<HoverInfo> HI, size_t) { + EXPECT_EQ(HI->Value, "967296 (0xec280)"); + EXPECT_EQ(HI->Type, "u64"); + }, + }, + { + R"cpp( + gnu_statement_exp^ression(42); + )cpp", + [](std::optional<HoverInfo> HI, size_t) { + EXPECT_EQ(HI->Value, "42 (0x2a)"); + EXPECT_EQ(HI->Type, "int"); + }, + }, + { + R"cpp( + 40 + PLU^S_TWO; + )cpp", + [](std::optional<HoverInfo> HI, size_t) { + EXPECT_EQ(HI->Value, "2"); + EXPECT_EQ(HI->Type, "int"); + }, + }, + { + R"cpp( + 40 + TW^O; + )cpp", + [](std::optional<HoverInfo> HI, size_t) { + EXPECT_EQ(HI->Value, "2"); + EXPECT_EQ(HI->Type, "int"); + }, + }, + { + R"cpp( + arra^y_decl(int, vector, 10); + vector left_b^racket 3 right_b^racket; + )cpp", + [](std::optional<HoverInfo> HI, size_t Id) { + switch (Id) { + case 0: + EXPECT_EQ(HI->Type, HoverInfo::PrintedType("int[10]")); + break; + case 1: + EXPECT_TRUE(HI->Type); + EXPECT_FALSE(HI->Value); + break; + case 2: + EXPECT_TRUE(HI->Type); + EXPECT_FALSE(HI->Value) << HI->Value; + break; + default: + ASSERT_TRUE(false) << "Unhandled id: " << Id; + } + }, + }, + { + R"cpp( + constexpr auto value = define_lamb^da_begin(lambda, int, char) + // Check if the expansion range is right. + return ^last_n_digit(10, 3, 3)^; + define_lam^bda_end(); + )cpp", + [](std::optional<HoverInfo> HI, size_t Id) { + switch (Id) { + case 0: + EXPECT_FALSE(HI->Value); + EXPECT_EQ(HI->Type, HoverInfo::PrintedType("class (lambda)")); + break; + case 1: + EXPECT_EQ(HI->Value, "0"); + EXPECT_EQ(HI->Type, HoverInfo::PrintedType("u64")); + break; + case 2: + EXPECT_FALSE(HI); + break; + case 3: + EXPECT_FALSE(HI->Type) << HI->Type; + EXPECT_FALSE(HI->Value) << HI->Value; + break; + default: + ASSERT_TRUE(false) << "Unhandled id: " << Id; + } + }, + }, + }; + + Config Cfg; + Cfg.Hover.ShowAKA = false; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + for (const auto &C : Cases) { + Annotations Code( + (PredefinedCXX + "void function() {\n" + C.Code + "}\n").str()); + auto TU = TestTU::withCode(Code.code()); + TU.ExtraArgs.push_back("-std=c++17"); + auto AST = TU.build(); + for (auto [Index, Position] : llvm::enumerate(Code.points())) { + C.Validator(getHover(AST, Position, format::getLLVMStyle(), nullptr), + Index); + } + } + + Annotations C(R"c( + #define alignof _Alignof + void foo() { + al^ignof(struct { int x; char y[10]; }); + } + )c"); + + auto TU = TestTU::withCode(C.code()); + TU.Filename = "TestTU.c"; + TU.ExtraArgs = { + "-std=c17", + }; + auto AST = TU.build(); + auto H = getHover(AST, C.point(), format::getLLVMStyle(), nullptr); + + ASSERT_TRUE(H); + EXPECT_EQ(H->Value, "4"); +} + } // namespace } // namespace clangd } // namespace clang Index: clang-tools-extra/clangd/Hover.cpp =================================================================== --- clang-tools-extra/clangd/Hover.cpp +++ clang-tools-extra/clangd/Hover.cpp @@ -463,8 +463,13 @@ return Constant.Val.getAsString(Ctx, T); } -std::optional<std::string> 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 and the result +// true indicates to stop visiting. +// Returns the node where the traversal ends. +const SelectionTree::Node * +visitExprFromSelectionTree(const SelectionTree::Node *N, const ASTContext &Ctx, + llvm::function_ref<bool(const Expr *)> CB) { for (; N; N = N->Parent) { // Try to evaluate the first evaluatable enclosing expression. if (const Expr *E = N->ASTNode.get<Expr>()) { @@ -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)) + break; } else if (N->ASTNode.get<Decl>() || N->ASTNode.get<Stmt>()) { // 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 N; +} + +std::optional<std::string> printExprValue(const SelectionTree::Node *N, + const ASTContext &Ctx) { + std::optional<std::string> Ret; + visitExprFromSelectionTree(N, Ctx, [&](const Expr *E) { + Ret = printExprValue(E, Ctx); + return Ret.has_value(); + }); + return Ret; } std::optional<StringRef> fieldName(const Expr *E) { @@ -677,6 +692,50 @@ return HI; } +HoverInfo evaluateMacroExpansion(const syntax::Token &Tok, ParsedAST &AST) { + // FIXME: Devise a strategy to determine if a macro expansion results in + // a full expression for evaluation. Currently we simply transform the + // expanded tokens into AST nodes. This suggests the expression we + // evaluate may not be fully covered by the expansion, which might confuse + // users. + HoverInfo HI; + auto &Context = AST.getASTContext(); + auto &Tokens = AST.getTokens(); + auto &SM = AST.getSourceManager(); + auto Tree = SelectionTree::createRight(Context, Tokens, + SM.getFileOffset(Tok.location()), + SM.getFileOffset(Tok.endLocation())); + + // We start from the node with complete selection. + 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(); + + // Attempt to evaluate it from Expr first. + auto *LastNode = 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 failed, extract the type from Decl if possible. + if (!HI.Value && !HI.Type && LastNode) { + if (auto *VD = LastNode->ASTNode.get<VarDecl>()) { + HI.Type = + printType(VD->getType(), AST.getASTContext(), + getPrintingPolicy(AST.getASTContext().getPrintingPolicy())); + } + } + + return HI; +} + /// Generate a \p Hover object given the macro \p MacroDecl. HoverInfo getHoverContents(const DefinedMacro &Macro, const syntax::Token &Tok, ParsedAST &AST) { @@ -711,6 +770,10 @@ } } + auto Evaluated = evaluateMacroExpansion(Tok, AST); + HI.Value = std::move(Evaluated.Value); + HI.Type = std::move(Evaluated.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
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits