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.
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
}
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);
+}
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) {
}
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);
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