Index: include/clang/Tooling/Refactoring/ASTSelection.h =================================================================== --- include/clang/Tooling/Refactoring/ASTSelection.h +++ include/clang/Tooling/Refactoring/ASTSelection.h @@ -59,6 +59,8 @@ SelectedASTNode &operator=(SelectedASTNode &&) = default; void dump(llvm::raw_ostream &OS = llvm::errs()) const; + + using ReferenceType = std::reference_wrapper; }; /// Traverses the given ASTContext and creates a tree of selected AST nodes. @@ -68,6 +70,70 @@ Optional 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 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(); + } + return SelectedNode.get().Children[I].Node.get(); + } + + static Optional + create(SourceRange SelectionRange, const SelectedASTNode &ASTSelection); + +private: + CodeRangeASTSelection(SelectedASTNode::ReferenceType SelectedNode, + ArrayRef 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 Parents; + /// True only when the children of the selected node are actually selected. + bool AreChildrenSelected; +}; + } // end namespace tooling } // end namespace clang Index: lib/Tooling/Refactoring/ASTSelection.cpp =================================================================== --- lib/Tooling/Refactoring/ASTSelection.cpp +++ lib/Tooling/Refactoring/ASTSelection.cpp @@ -225,3 +225,108 @@ } 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 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 &MatchingNodes, + SourceSelectionKind Kind, + llvm::SmallVectorImpl &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 &MatchingNodes, + SourceSelectionKind Kind) { + llvm::SmallVector ParentStack; + findDeepestWithKind(ASTSelection, MatchingNodes, Kind, ParentStack); +} + +Optional +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 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()) + return None; + const Stmt *CodeRangeStmt = Selected.Node.get().Node.get(); + if (!isa(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); +} Index: unittests/Tooling/ASTSelectionTest.cpp =================================================================== --- unittests/Tooling/ASTSelectionTest.cpp +++ unittests/Tooling/ASTSelectionTest.cpp @@ -29,12 +29,16 @@ class SelectionFinderVisitor : public TestVisitor { FileLocation Location; Optional SelectionRange; - llvm::function_ref)> Consumer; + llvm::function_ref)> + Consumer; public: - SelectionFinderVisitor( - FileLocation Location, Optional SelectionRange, - llvm::function_ref)> Consumer) + SelectionFinderVisitor(FileLocation Location, + Optional SelectionRange, + llvm::function_ref)> + Consumer) : Location(Location), SelectionRange(SelectionRange), Consumer(Consumer) { } @@ -50,20 +54,35 @@ 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 SelectionRange, - llvm::function_ref)> Consumer, + llvm::function_ref)> + 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 SelectionRange, + llvm::function_ref)> Consumer, + SelectionFinderVisitor::Language Language = + SelectionFinderVisitor::Lang_CXX11) { + findSelectedASTNodesWithRange( + Source, Location, SelectionRange, + [&](SourceRange, Optional Selection) { + Consumer(std::move(Selection)); + }, + Language); +} + void checkNodeImpl(bool IsTypeMatched, const SelectedASTNode &Node, SourceSelectionKind SelectionKind, unsigned NumChildren) { ASSERT_TRUE(IsTypeMatched); @@ -649,4 +668,171 @@ 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 Node) { + EXPECT_TRUE(Node); + Optional SelectedCode = + CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); + EXPECT_FALSE(SelectedCode); + }); + findSelectedASTNodesWithRange( + Source, {2, 2}, FileRange{{2, 2}, {2, 2}}, + [](SourceRange SelectionRange, Optional Node) { + EXPECT_TRUE(Node); + Optional 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 Node) { + EXPECT_TRUE(Node); + Optional 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 Node) { + EXPECT_TRUE(Node); + Optional SelectedCode = + CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); + EXPECT_TRUE(SelectedCode); + EXPECT_EQ(SelectedCode->size(), 1u); + EXPECT_TRUE(isa((*SelectedCode)[0])); + ArrayRef Parents = + SelectedCode->getParents(); + EXPECT_EQ(Parents.size(), 3u); + EXPECT_TRUE( + isa(Parents[0].get().Node.get())); + // Function 'f' definition. + EXPECT_TRUE(isa(Parents[1].get().Node.get())); + // Function body of function 'F'. + EXPECT_TRUE(isa(Parents[2].get().Node.get())); + }); + // From 'f(2,3)' until just before 'x = 1;': + findSelectedASTNodesWithRange( + Source, {3, 2}, FileRange{{3, 2}, {7, 1}}, + [](SourceRange SelectionRange, Optional Node) { + EXPECT_TRUE(Node); + Optional SelectedCode = + CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); + EXPECT_TRUE(SelectedCode); + EXPECT_EQ(SelectedCode->size(), 2u); + EXPECT_TRUE(isa((*SelectedCode)[0])); + EXPECT_TRUE(isa((*SelectedCode)[1])); + ArrayRef Parents = + SelectedCode->getParents(); + EXPECT_EQ(Parents.size(), 3u); + EXPECT_TRUE( + isa(Parents[0].get().Node.get())); + // Function 'f' definition. + EXPECT_TRUE(isa(Parents[1].get().Node.get())); + // Function body of function 'F'. + EXPECT_TRUE(isa(Parents[2].get().Node.get())); + }); + // From 'f(2,3)' until just before ';' in 'x = 1;': + findSelectedASTNodesWithRange( + Source, {3, 2}, FileRange{{3, 2}, {7, 8}}, + [](SourceRange SelectionRange, Optional Node) { + EXPECT_TRUE(Node); + Optional SelectedCode = + CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); + EXPECT_TRUE(SelectedCode); + EXPECT_EQ(SelectedCode->size(), 3u); + EXPECT_TRUE(isa((*SelectedCode)[0])); + EXPECT_TRUE(isa((*SelectedCode)[1])); + EXPECT_TRUE(isa((*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 Node) { + EXPECT_TRUE(Node); + EXPECT_TRUE(Node); + Optional SelectedCode = + CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); + EXPECT_TRUE(SelectedCode); + EXPECT_EQ(SelectedCode->size(), 4u); + EXPECT_TRUE(isa((*SelectedCode)[0])); + EXPECT_TRUE(isa((*SelectedCode)[1])); + EXPECT_TRUE(isa((*SelectedCode)[2])); + EXPECT_TRUE(isa((*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 Node) { + EXPECT_TRUE(Node); + Optional SelectedCode = + CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); + EXPECT_TRUE(SelectedCode); + EXPECT_EQ(SelectedCode->size(), 1u); + EXPECT_TRUE(isa((*SelectedCode)[0])); + ArrayRef Parents = + SelectedCode->getParents(); + EXPECT_EQ(Parents.size(), 2u); + EXPECT_TRUE( + isa(Parents[0].get().Node.get())); + // Variable 'codeRange'. + EXPECT_TRUE(isa(Parents[1].get().Node.get())); + }); +} + +TEST(ASTSelectionFinder, SelectVarDeclStmt) { + StringRef Source = R"( +void f() { + { + int a; + } +} +)"; + // 'int a' + findSelectedASTNodesWithRange( + Source, {4, 8}, FileRange{{4, 8}, {4, 14}}, + [](SourceRange SelectionRange, Optional Node) { + EXPECT_TRUE(Node); + Optional SelectedCode = + CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); + EXPECT_TRUE(SelectedCode); + EXPECT_EQ(SelectedCode->size(), 1u); + EXPECT_TRUE(isa((*SelectedCode)[0])); + ArrayRef Parents = + SelectedCode->getParents(); + EXPECT_EQ(Parents.size(), 4u); + EXPECT_TRUE( + isa(Parents[0].get().Node.get())); + // Function 'f' definition. + EXPECT_TRUE(isa(Parents[1].get().Node.get())); + // Function body of function 'F'. + EXPECT_TRUE(isa(Parents[2].get().Node.get())); + // Compound statement in body of 'F'. + EXPECT_TRUE(isa(Parents[3].get().Node.get())); + }); +} + } // end anonymous namespace