[clang-tools-extra] 7385cc3 - [clangd] Support macro evaluation on hover
Younan Zhang via cfe-commits
cfe-commits at lists.llvm.org
Tue May 9 03:50:36 PDT 2023
Author: Younan Zhang
Date: 2023-05-09T18:50:27+08:00
New Revision: 7385cc389abad29eb9044d260b23dd483d674718
URL: https://github.com/llvm/llvm-project/commit/7385cc389abad29eb9044d260b23dd483d674718
DIFF: https://github.com/llvm/llvm-project/commit/7385cc389abad29eb9044d260b23dd483d674718.diff
LOG: [clangd] Support macro evaluation on hover
Creating a SelectionTree at the location where macro expands allows
us to obtain the associated expression, which might then be used to
evaluate compile-time values if possible.
Closes clangd/clangd#1595.
Reviewed By: nridge
Differential Revision: https://reviews.llvm.org/D148457
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 558769f209860..9b789901e77c6 100644
--- a/clang-tools-extra/clangd/Hover.cpp
+++ b/clang-tools-extra/clangd/Hover.cpp
@@ -463,8 +463,23 @@ std::optional<std::string> printExprValue(const Expr *E,
return Constant.Val.getAsString(Ctx, T);
}
-std::optional<std::string> printExprValue(const SelectionTree::Node *N,
- const ASTContext &Ctx) {
+struct PrintExprResult {
+ /// The evaluation result on expression `Expr`.
+ std::optional<std::string> PrintedValue;
+ /// The Expr object that represents the closest evaluable
+ /// expression.
+ const Expr *Expr;
+ /// The node of selection tree where the traversal stops.
+ const SelectionTree::Node *Node;
+};
+
+// Seek the closest evaluable expression along the ancestors of node N
+// in a selection tree. If a node in the path can be converted to an evaluable
+// Expr, a possible evaluation would happen and the associated context
+// is returned.
+// If evaluation couldn't be done, return the node where the traversal ends.
+PrintExprResult printExprValue(const SelectionTree::Node *N,
+ const ASTContext &Ctx) {
for (; N; N = N->Parent) {
// Try to evaluate the first evaluatable enclosing expression.
if (const Expr *E = N->ASTNode.get<Expr>()) {
@@ -473,14 +488,16 @@ std::optional<std::string> printExprValue(const SelectionTree::Node *N,
if (!E->getType().isNull() && E->getType()->isVoidType())
break;
if (auto Val = printExprValue(E, Ctx))
- return Val;
+ return PrintExprResult{/*PrintedValue=*/std::move(Val), /*Expr=*/E,
+ /*Node=*/N};
} 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 PrintExprResult{/*PrintedValue=*/std::nullopt, /*Expr=*/nullptr,
+ /*Node=*/N};
}
std::optional<StringRef> fieldName(const Expr *E) {
@@ -677,6 +694,54 @@ getPredefinedExprHoverContents(const PredefinedExpr &PE, ASTContext &Ctx,
return HI;
}
+HoverInfo evaluateMacroExpansion(unsigned int SpellingBeginOffset,
+ unsigned int SpellingEndOffset,
+ llvm::ArrayRef<syntax::Token> Expanded,
+ ParsedAST &AST) {
+ auto &Context = AST.getASTContext();
+ auto &Tokens = AST.getTokens();
+ auto PP = getPrintingPolicy(Context.getPrintingPolicy());
+ auto Tree = SelectionTree::createRight(Context, Tokens, SpellingBeginOffset,
+ SpellingEndOffset);
+
+ // If macro expands to one single token, rule out punctuator or digraph.
+ // E.g., for the case `array L_BRACKET 42 R_BRACKET;` where L_BRACKET and
+ // R_BRACKET expand to
+ // '[' and ']' respectively, we don't want the type of
+ // 'array[42]' when user hovers on L_BRACKET.
+ if (Expanded.size() == 1)
+ if (tok::getPunctuatorSpelling(Expanded[0].kind()))
+ return {};
+
+ auto *StartNode = Tree.commonAncestor();
+ if (!StartNode)
+ return {};
+ // If the common ancestor is partially selected, do evaluate if it has no
+ // children, thus we can disallow evaluation on incomplete expression.
+ // For example,
+ // #define PLUS_2 +2
+ // 40 PL^US_2
+ // In this case we don't want to present 'value: 2' as PLUS_2 actually expands
+ // to a non-value rather than a binary operand.
+ if (StartNode->Selected == SelectionTree::Selection::Partial)
+ if (!StartNode->Children.empty())
+ return {};
+
+ HoverInfo HI;
+ // Attempt to evaluate it from Expr first.
+ auto ExprResult = printExprValue(StartNode, Context);
+ HI.Value = std::move(ExprResult.PrintedValue);
+ if (auto *E = ExprResult.Expr)
+ HI.Type = printType(E->getType(), Context, PP);
+
+ // If failed, extract the type from Decl if possible.
+ if (!HI.Value && !HI.Type && ExprResult.Node)
+ if (auto *VD = ExprResult.Node->ASTNode.get<VarDecl>())
+ HI.Type = printType(VD->getType(), Context, PP);
+
+ return HI;
+}
+
/// Generate a \p Hover object given the macro \p MacroDecl.
HoverInfo getHoverContents(const DefinedMacro &Macro, const syntax::Token &Tok,
ParsedAST &AST) {
@@ -732,6 +797,13 @@ HoverInfo getHoverContents(const DefinedMacro &Macro, const syntax::Token &Tok,
HI.Definition += "// Expands to\n";
HI.Definition += ExpansionText;
}
+
+ auto Evaluated = evaluateMacroExpansion(
+ /*SpellingBeginOffset=*/SM.getFileOffset(Tok.location()),
+ /*SpellingEndOffset=*/SM.getFileOffset(Tok.endLocation()),
+ /*Expanded=*/Expansion->Expanded, AST);
+ HI.Value = std::move(Evaluated.Value);
+ HI.Type = std::move(Evaluated.Type);
}
return HI;
}
@@ -1293,7 +1365,7 @@ std::optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
addLayoutInfo(*DeclToUse, *HI);
// Look for a close enclosing expression to show the value of.
if (!HI->Value)
- HI->Value = printExprValue(N, AST.getASTContext());
+ HI->Value = printExprValue(N, AST.getASTContext()).PrintedValue;
maybeAddCalleeArgInfo(N, *HI, PP);
maybeAddSymbolProviders(AST, *HI, include_cleaner::Symbol{*DeclToUse});
} else if (const Expr *E = N->ASTNode.get<Expr>()) {
diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp
index ce546f54dd75f..677cd5c4e6323 100644
--- a/clang-tools-extra/clangd/unittests/HoverTests.cpp
+++ b/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 @@ class Foo final {})cpp";
[](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 @@ class Foo final {})cpp";
[](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"
@@ -1850,6 +1854,8 @@ TEST(Hover, All) {
)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"
@@ -3769,6 +3775,232 @@ TEST(Hover, Typedefs) {
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 dg_left_bracket <:
+#define dg_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
diff erent values or desugared types on
diff erent
+ // platforms. Same as below.
+ },
+ },
+ {
+ /*Code=*/R"cpp(
+ struct Y {
+ int y;
+ double z;
+ };
+ Alig^nOf(Y);
+ )cpp",
+ /*Validator=*/
+ [](std::optional<HoverInfo> HI, size_t) {
+ EXPECT_TRUE(HI->Value);
+ EXPECT_TRUE(HI->Type);
+ },
+ },
+ {
+ /*Code=*/R"cpp(
+ // 2**32 == 4294967296
+ last_n_di^git(2, 32, 6);
+ )cpp",
+ /*Validator=*/
+ [](std::optional<HoverInfo> HI, size_t) {
+ EXPECT_EQ(HI->Value, "967296 (0xec280)");
+ EXPECT_EQ(HI->Type, "u64");
+ },
+ },
+ {
+ /*Code=*/R"cpp(
+ gnu_statement_exp^ression(42);
+ )cpp",
+ /*Validator=*/
+ [](std::optional<HoverInfo> HI, size_t) {
+ EXPECT_EQ(HI->Value, "42 (0x2a)");
+ EXPECT_EQ(HI->Type, "int");
+ },
+ },
+ {
+ /*Code=*/R"cpp(
+ 40 + PLU^S_TWO;
+ )cpp",
+ /*Validator=*/
+ [](std::optional<HoverInfo> HI, size_t) {
+ EXPECT_EQ(HI->Value, "2");
+ EXPECT_EQ(HI->Type, "int");
+ },
+ },
+ {
+ /*Code=*/R"cpp(
+ 40 PLU^S_TWO;
+ )cpp",
+ /*Validator=*/
+ [](std::optional<HoverInfo> HI, size_t) {
+ EXPECT_FALSE(HI->Value) << HI->Value;
+ EXPECT_FALSE(HI->Type) << HI->Type;
+ },
+ },
+ {
+ /*Code=*/R"cpp(
+ 40 + TW^O;
+ )cpp",
+ /*Validator=*/
+ [](std::optional<HoverInfo> HI, size_t) {
+ EXPECT_EQ(HI->Value, "2");
+ EXPECT_EQ(HI->Type, "int");
+ },
+ },
+ {
+ /*Code=*/R"cpp(
+ arra^y_decl(int, vector, 10);
+ vector left_b^racket 3 right_b^racket;
+ vector dg_le^ft_bracket 3 dg_righ^t_bracket;
+ )cpp",
+ /*Validator=*/
+ [](std::optional<HoverInfo> HI, size_t Id) {
+ switch (Id) {
+ case 0:
+ EXPECT_EQ(HI->Type, HoverInfo::PrintedType("int[10]"));
+ break;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ EXPECT_FALSE(HI->Type) << HI->Type;
+ EXPECT_FALSE(HI->Value) << HI->Value;
+ break;
+ default:
+ ASSERT_TRUE(false) << "Unhandled id: " << Id;
+ }
+ },
+ },
+ {
+ /*Code=*/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",
+ /*Validator=*/
+ [](std::optional<HoverInfo> HI, size_t Id) {
+ switch (Id) {
+ case 0:
+ EXPECT_FALSE(HI->Value);
+ EXPECT_EQ(HI->Type, HoverInfo::PrintedType("const (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
More information about the cfe-commits
mailing list