r316104 - [refactor] selection: new CodeRangeASTSelection represents a set of selected

Alex Lorenz via cfe-commits cfe-commits at lists.llvm.org
Wed Oct 18 11:48:58 PDT 2017


Author: arphaman
Date: Wed Oct 18 11:48:58 2017
New Revision: 316104

URL: http://llvm.org/viewvc/llvm-project?rev=316104&view=rev
Log:
[refactor] selection: new CodeRangeASTSelection represents a set of selected
consecutive statements

This commit adds a CodeRangeASTSelection value to the refactoring library. This
value represents a set of selected statements in one body of code.

Differential Revision: https://reviews.llvm.org/D38835

Modified:
    cfe/trunk/include/clang/Tooling/Refactoring/ASTSelection.h
    cfe/trunk/lib/Tooling/Refactoring/ASTSelection.cpp
    cfe/trunk/unittests/Tooling/ASTSelectionTest.cpp

Modified: cfe/trunk/include/clang/Tooling/Refactoring/ASTSelection.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/ASTSelection.h?rev=316104&r1=316103&r2=316104&view=diff
==============================================================================
--- cfe/trunk/include/clang/Tooling/Refactoring/ASTSelection.h (original)
+++ cfe/trunk/include/clang/Tooling/Refactoring/ASTSelection.h Wed Oct 18 11:48:58 2017
@@ -59,6 +59,8 @@ struct SelectedASTNode {
   SelectedASTNode &operator=(SelectedASTNode &&) = default;
 
   void dump(llvm::raw_ostream &OS = llvm::errs()) const;
+
+  using ReferenceType = std::reference_wrapper<const SelectedASTNode>;
 };
 
 /// Traverses the given ASTContext and creates a tree of selected AST nodes.
@@ -68,6 +70,70 @@ struct SelectedASTNode {
 Optional<SelectedASTNode> findSelectedASTNodes(const ASTContext &Context,
                                                SourceRange SelectionRange);
 
+/// An AST selection value that corresponds to a selection of a set of
+/// statements that belong to one body of code (like one function).
+///
+/// For example, the following selection in the source.
+///
+/// \code
+/// void function() {
+///  // selection begin:
+///  int x = 0;
+///  {
+///     // selection end
+///     x = 1;
+///  }
+///  x = 2;
+/// }
+/// \endcode
+///
+/// Would correspond to a code range selection of statements "int x = 0"
+/// and the entire compound statement that follows it.
+///
+/// A \c CodeRangeASTSelection value stores references to the full
+/// \c SelectedASTNode tree and should not outlive it.
+class CodeRangeASTSelection {
+public:
+  CodeRangeASTSelection(CodeRangeASTSelection &&) = default;
+  CodeRangeASTSelection &operator=(CodeRangeASTSelection &&) = default;
+
+  /// Returns the parent hierarchy (top to bottom) for the selected nodes.
+  ArrayRef<SelectedASTNode::ReferenceType> getParents() { return Parents; }
+
+  /// Returns the number of selected statements.
+  size_t size() const {
+    if (!AreChildrenSelected)
+      return 1;
+    return SelectedNode.get().Children.size();
+  }
+
+  const Stmt *operator[](size_t I) const {
+    if (!AreChildrenSelected) {
+      assert(I == 0 && "Invalid index");
+      return SelectedNode.get().Node.get<Stmt>();
+    }
+    return SelectedNode.get().Children[I].Node.get<Stmt>();
+  }
+
+  static Optional<CodeRangeASTSelection>
+  create(SourceRange SelectionRange, const SelectedASTNode &ASTSelection);
+
+private:
+  CodeRangeASTSelection(SelectedASTNode::ReferenceType SelectedNode,
+                        ArrayRef<SelectedASTNode::ReferenceType> Parents,
+                        bool AreChildrenSelected)
+      : SelectedNode(SelectedNode), Parents(Parents.begin(), Parents.end()),
+        AreChildrenSelected(AreChildrenSelected) {}
+
+  /// The reference to the selected node (or reference to the selected
+  /// child nodes).
+  SelectedASTNode::ReferenceType SelectedNode;
+  /// The parent hierarchy (top to bottom) for the selected noe.
+  llvm::SmallVector<SelectedASTNode::ReferenceType, 8> Parents;
+  /// True only when the children of the selected node are actually selected.
+  bool AreChildrenSelected;
+};
+
 } // end namespace tooling
 } // end namespace clang
 

Modified: cfe/trunk/lib/Tooling/Refactoring/ASTSelection.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/ASTSelection.cpp?rev=316104&r1=316103&r2=316104&view=diff
==============================================================================
--- cfe/trunk/lib/Tooling/Refactoring/ASTSelection.cpp (original)
+++ cfe/trunk/lib/Tooling/Refactoring/ASTSelection.cpp Wed Oct 18 11:48:58 2017
@@ -225,3 +225,108 @@ static void dump(const SelectedASTNode &
 }
 
 void SelectedASTNode::dump(llvm::raw_ostream &OS) const { ::dump(*this, OS); }
+
+/// Returns true if the given node has any direct children with the following
+/// selection kind.
+///
+/// Note: The direct children also include children of direct children with the
+/// "None" selection kind.
+static bool hasAnyDirectChildrenWithKind(const SelectedASTNode &Node,
+                                         SourceSelectionKind Kind) {
+  assert(Kind != SourceSelectionKind::None && "invalid predicate!");
+  for (const auto &Child : Node.Children) {
+    if (Child.SelectionKind == Kind)
+      return true;
+    if (Child.SelectionKind == SourceSelectionKind::None)
+      return hasAnyDirectChildrenWithKind(Child, Kind);
+  }
+  return false;
+}
+
+namespace {
+struct SelectedNodeWithParents {
+  SelectedNodeWithParents(SelectedNodeWithParents &&) = default;
+  SelectedNodeWithParents &operator=(SelectedNodeWithParents &&) = default;
+  SelectedASTNode::ReferenceType Node;
+  llvm::SmallVector<SelectedASTNode::ReferenceType, 8> Parents;
+};
+} // end anonymous namespace
+
+/// Finds the set of bottom-most selected AST nodes that are in the selection
+/// tree with the specified selection kind.
+///
+/// For example, given the following selection tree:
+///
+/// FunctionDecl "f" contains-selection
+///   CompoundStmt contains-selection [#1]
+///     CallExpr inside
+///     ImplicitCastExpr inside
+///       DeclRefExpr inside
+///     IntegerLiteral inside
+///     IntegerLiteral inside
+/// FunctionDecl "f2" contains-selection
+///   CompoundStmt contains-selection [#2]
+///     CallExpr inside
+///     ImplicitCastExpr inside
+///       DeclRefExpr inside
+///     IntegerLiteral inside
+///     IntegerLiteral inside
+///
+/// This function will find references to nodes #1 and #2 when searching for the
+/// \c ContainsSelection kind.
+static void findDeepestWithKind(
+    const SelectedASTNode &ASTSelection,
+    llvm::SmallVectorImpl<SelectedNodeWithParents> &MatchingNodes,
+    SourceSelectionKind Kind,
+    llvm::SmallVectorImpl<SelectedASTNode::ReferenceType> &ParentStack) {
+  if (!hasAnyDirectChildrenWithKind(ASTSelection, Kind)) {
+    // This node is the bottom-most.
+    MatchingNodes.push_back(SelectedNodeWithParents{
+        std::cref(ASTSelection), {ParentStack.begin(), ParentStack.end()}});
+    return;
+  }
+  // Search in the children.
+  ParentStack.push_back(std::cref(ASTSelection));
+  for (const auto &Child : ASTSelection.Children)
+    findDeepestWithKind(Child, MatchingNodes, Kind, ParentStack);
+  ParentStack.pop_back();
+}
+
+static void findDeepestWithKind(
+    const SelectedASTNode &ASTSelection,
+    llvm::SmallVectorImpl<SelectedNodeWithParents> &MatchingNodes,
+    SourceSelectionKind Kind) {
+  llvm::SmallVector<SelectedASTNode::ReferenceType, 16> ParentStack;
+  findDeepestWithKind(ASTSelection, MatchingNodes, Kind, ParentStack);
+}
+
+Optional<CodeRangeASTSelection>
+CodeRangeASTSelection::create(SourceRange SelectionRange,
+                              const SelectedASTNode &ASTSelection) {
+  // Code range is selected when the selection range is not empty.
+  if (SelectionRange.getBegin() == SelectionRange.getEnd())
+    return None;
+  llvm::SmallVector<SelectedNodeWithParents, 4> ContainSelection;
+  findDeepestWithKind(ASTSelection, ContainSelection,
+                      SourceSelectionKind::ContainsSelection);
+  // We are looking for a selection in one body of code, so let's focus on
+  // one matching result.
+  if (ContainSelection.size() != 1)
+    return None;
+  SelectedNodeWithParents &Selected = ContainSelection[0];
+  if (!Selected.Node.get().Node.get<Stmt>())
+    return None;
+  const Stmt *CodeRangeStmt = Selected.Node.get().Node.get<Stmt>();
+  if (!isa<CompoundStmt>(CodeRangeStmt)) {
+    // FIXME (Alex L): Canonicalize.
+    return CodeRangeASTSelection(Selected.Node, Selected.Parents,
+                                 /*AreChildrenSelected=*/false);
+  }
+  // FIXME (Alex L): Tweak selection rules for compound statements, see:
+  // https://github.com/apple/swift-clang/blob/swift-4.1-branch/lib/Tooling/
+  // Refactor/ASTSlice.cpp#L513
+  // The user selected multiple statements in a compound statement.
+  Selected.Parents.push_back(Selected.Node);
+  return CodeRangeASTSelection(Selected.Node, Selected.Parents,
+                               /*AreChildrenSelected=*/true);
+}

Modified: cfe/trunk/unittests/Tooling/ASTSelectionTest.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/ASTSelectionTest.cpp?rev=316104&r1=316103&r2=316104&view=diff
==============================================================================
--- cfe/trunk/unittests/Tooling/ASTSelectionTest.cpp (original)
+++ cfe/trunk/unittests/Tooling/ASTSelectionTest.cpp Wed Oct 18 11:48:58 2017
@@ -29,12 +29,16 @@ using FileRange = std::pair<FileLocation
 class SelectionFinderVisitor : public TestVisitor<SelectionFinderVisitor> {
   FileLocation Location;
   Optional<FileRange> SelectionRange;
-  llvm::function_ref<void(Optional<SelectedASTNode>)> Consumer;
+  llvm::function_ref<void(SourceRange SelectionRange,
+                          Optional<SelectedASTNode>)>
+      Consumer;
 
 public:
-  SelectionFinderVisitor(
-      FileLocation Location, Optional<FileRange> SelectionRange,
-      llvm::function_ref<void(Optional<SelectedASTNode>)> Consumer)
+  SelectionFinderVisitor(FileLocation Location,
+                         Optional<FileRange> SelectionRange,
+                         llvm::function_ref<void(SourceRange SelectionRange,
+                                                 Optional<SelectedASTNode>)>
+                             Consumer)
       : Location(Location), SelectionRange(SelectionRange), Consumer(Consumer) {
   }
 
@@ -50,20 +54,35 @@ public:
       SourceLocation Loc = Location.translate(SM);
       SelRange = SourceRange(Loc, Loc);
     }
-    Consumer(findSelectedASTNodes(Context, SelRange));
+    Consumer(SelRange, findSelectedASTNodes(Context, SelRange));
     return false;
   }
 };
 
-void findSelectedASTNodes(
+void findSelectedASTNodesWithRange(
     StringRef Source, FileLocation Location, Optional<FileRange> SelectionRange,
-    llvm::function_ref<void(Optional<SelectedASTNode>)> Consumer,
+    llvm::function_ref<void(SourceRange SelectionRange,
+                            Optional<SelectedASTNode>)>
+        Consumer,
     SelectionFinderVisitor::Language Language =
         SelectionFinderVisitor::Lang_CXX11) {
   SelectionFinderVisitor Visitor(Location, SelectionRange, Consumer);
   EXPECT_TRUE(Visitor.runOver(Source, Language));
 }
 
+void findSelectedASTNodes(
+    StringRef Source, FileLocation Location, Optional<FileRange> SelectionRange,
+    llvm::function_ref<void(Optional<SelectedASTNode>)> Consumer,
+    SelectionFinderVisitor::Language Language =
+        SelectionFinderVisitor::Lang_CXX11) {
+  findSelectedASTNodesWithRange(
+      Source, Location, SelectionRange,
+      [&](SourceRange, Optional<SelectedASTNode> Selection) {
+        Consumer(std::move(Selection));
+      },
+      Language);
+}
+
 void checkNodeImpl(bool IsTypeMatched, const SelectedASTNode &Node,
                    SourceSelectionKind SelectionKind, unsigned NumChildren) {
   ASSERT_TRUE(IsTypeMatched);
@@ -649,4 +668,171 @@ void selectSubscript(NSMutableArray *arr
       SelectionFinderVisitor::Lang_OBJC);
 }
 
+TEST(ASTSelectionFinder, SimpleCodeRangeASTSelection) {
+  StringRef Source = R"(void f(int x, int y) {
+  int z = x;
+  f(2, 3);
+  if (x == 0) {
+    return;
+  }
+  x = 1;
+  return;
+}
+void f2() {
+  int m = 0;
+}
+)";
+  // No selection range.
+  findSelectedASTNodesWithRange(
+      Source, {2, 2}, None,
+      [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) {
+        EXPECT_TRUE(Node);
+        Optional<CodeRangeASTSelection> SelectedCode =
+            CodeRangeASTSelection::create(SelectionRange, std::move(*Node));
+        EXPECT_FALSE(SelectedCode);
+      });
+  findSelectedASTNodesWithRange(
+      Source, {2, 2}, FileRange{{2, 2}, {2, 2}},
+      [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) {
+        EXPECT_TRUE(Node);
+        Optional<CodeRangeASTSelection> SelectedCode =
+            CodeRangeASTSelection::create(SelectionRange, std::move(*Node));
+        EXPECT_FALSE(SelectedCode);
+      });
+  // Range that spans multiple functions is an invalid code range.
+  findSelectedASTNodesWithRange(
+      Source, {2, 2}, FileRange{{7, 2}, {12, 1}},
+      [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) {
+        EXPECT_TRUE(Node);
+        Optional<CodeRangeASTSelection> SelectedCode =
+            CodeRangeASTSelection::create(SelectionRange, std::move(*Node));
+        EXPECT_FALSE(SelectedCode);
+      });
+  // Just 'z = x;':
+  findSelectedASTNodesWithRange(
+      Source, {2, 2}, FileRange{{2, 2}, {2, 13}},
+      [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) {
+        EXPECT_TRUE(Node);
+        Optional<CodeRangeASTSelection> SelectedCode =
+            CodeRangeASTSelection::create(SelectionRange, std::move(*Node));
+        EXPECT_TRUE(SelectedCode);
+        EXPECT_EQ(SelectedCode->size(), 1u);
+        EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0]));
+        ArrayRef<SelectedASTNode::ReferenceType> Parents =
+            SelectedCode->getParents();
+        EXPECT_EQ(Parents.size(), 3u);
+        EXPECT_TRUE(
+            isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>()));
+        // Function 'f' definition.
+        EXPECT_TRUE(isa<FunctionDecl>(Parents[1].get().Node.get<Decl>()));
+        // Function body of function 'F'.
+        EXPECT_TRUE(isa<CompoundStmt>(Parents[2].get().Node.get<Stmt>()));
+      });
+  // From 'f(2,3)' until just before 'x = 1;':
+  findSelectedASTNodesWithRange(
+      Source, {3, 2}, FileRange{{3, 2}, {7, 1}},
+      [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) {
+        EXPECT_TRUE(Node);
+        Optional<CodeRangeASTSelection> SelectedCode =
+            CodeRangeASTSelection::create(SelectionRange, std::move(*Node));
+        EXPECT_TRUE(SelectedCode);
+        EXPECT_EQ(SelectedCode->size(), 2u);
+        EXPECT_TRUE(isa<CallExpr>((*SelectedCode)[0]));
+        EXPECT_TRUE(isa<IfStmt>((*SelectedCode)[1]));
+        ArrayRef<SelectedASTNode::ReferenceType> Parents =
+            SelectedCode->getParents();
+        EXPECT_EQ(Parents.size(), 3u);
+        EXPECT_TRUE(
+            isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>()));
+        // Function 'f' definition.
+        EXPECT_TRUE(isa<FunctionDecl>(Parents[1].get().Node.get<Decl>()));
+        // Function body of function 'F'.
+        EXPECT_TRUE(isa<CompoundStmt>(Parents[2].get().Node.get<Stmt>()));
+      });
+  // From 'f(2,3)' until just before ';' in 'x = 1;':
+  findSelectedASTNodesWithRange(
+      Source, {3, 2}, FileRange{{3, 2}, {7, 8}},
+      [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) {
+        EXPECT_TRUE(Node);
+        Optional<CodeRangeASTSelection> SelectedCode =
+            CodeRangeASTSelection::create(SelectionRange, std::move(*Node));
+        EXPECT_TRUE(SelectedCode);
+        EXPECT_EQ(SelectedCode->size(), 3u);
+        EXPECT_TRUE(isa<CallExpr>((*SelectedCode)[0]));
+        EXPECT_TRUE(isa<IfStmt>((*SelectedCode)[1]));
+        EXPECT_TRUE(isa<BinaryOperator>((*SelectedCode)[2]));
+      });
+  // From the middle of 'int z = 3' until the middle of 'x = 1;':
+  findSelectedASTNodesWithRange(
+      Source, {2, 10}, FileRange{{2, 10}, {7, 5}},
+      [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) {
+        EXPECT_TRUE(Node);
+        EXPECT_TRUE(Node);
+        Optional<CodeRangeASTSelection> SelectedCode =
+            CodeRangeASTSelection::create(SelectionRange, std::move(*Node));
+        EXPECT_TRUE(SelectedCode);
+        EXPECT_EQ(SelectedCode->size(), 4u);
+        EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0]));
+        EXPECT_TRUE(isa<CallExpr>((*SelectedCode)[1]));
+        EXPECT_TRUE(isa<IfStmt>((*SelectedCode)[2]));
+        EXPECT_TRUE(isa<BinaryOperator>((*SelectedCode)[3]));
+      });
+}
+
+TEST(ASTSelectionFinder, OutOfBodyCodeRange) {
+  StringRef Source = R"(
+int codeRange = 2 + 3;
+)";
+  // '2+3' expression.
+  findSelectedASTNodesWithRange(
+      Source, {2, 17}, FileRange{{2, 17}, {2, 22}},
+      [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) {
+        EXPECT_TRUE(Node);
+        Optional<CodeRangeASTSelection> SelectedCode =
+            CodeRangeASTSelection::create(SelectionRange, std::move(*Node));
+        EXPECT_TRUE(SelectedCode);
+        EXPECT_EQ(SelectedCode->size(), 1u);
+        EXPECT_TRUE(isa<BinaryOperator>((*SelectedCode)[0]));
+        ArrayRef<SelectedASTNode::ReferenceType> Parents =
+            SelectedCode->getParents();
+        EXPECT_EQ(Parents.size(), 2u);
+        EXPECT_TRUE(
+            isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>()));
+        // Variable 'codeRange'.
+        EXPECT_TRUE(isa<VarDecl>(Parents[1].get().Node.get<Decl>()));
+      });
+}
+
+TEST(ASTSelectionFinder, SelectVarDeclStmt) {
+  StringRef Source = R"(
+void f() {
+   {
+       int a;
+   }
+}
+)";
+  // 'int a'
+  findSelectedASTNodesWithRange(
+      Source, {4, 8}, FileRange{{4, 8}, {4, 14}},
+      [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) {
+        EXPECT_TRUE(Node);
+        Optional<CodeRangeASTSelection> SelectedCode =
+            CodeRangeASTSelection::create(SelectionRange, std::move(*Node));
+        EXPECT_TRUE(SelectedCode);
+        EXPECT_EQ(SelectedCode->size(), 1u);
+        EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0]));
+        ArrayRef<SelectedASTNode::ReferenceType> Parents =
+            SelectedCode->getParents();
+        EXPECT_EQ(Parents.size(), 4u);
+        EXPECT_TRUE(
+            isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>()));
+        // Function 'f' definition.
+        EXPECT_TRUE(isa<FunctionDecl>(Parents[1].get().Node.get<Decl>()));
+        // Function body of function 'F'.
+        EXPECT_TRUE(isa<CompoundStmt>(Parents[2].get().Node.get<Stmt>()));
+        // Compound statement in body of 'F'.
+        EXPECT_TRUE(isa<CompoundStmt>(Parents[3].get().Node.get<Stmt>()));
+      });
+}
+
 } // end anonymous namespace




More information about the cfe-commits mailing list