[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