forked from OSchip/llvm-project
[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 llvm-svn: 316104
This commit is contained in:
parent
54ffd1cfe8
commit
cd6c78386f
|
@ -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
|
||||
|
||||
|
|
|
@ -225,3 +225,108 @@ static void dump(const SelectedASTNode &Node, llvm::raw_ostream &OS,
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -29,12 +29,16 @@ using FileRange = std::pair<FileLocation, 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,18 +54,33 @@ public:
|
|||
SourceLocation Loc = Location.translate(SM);
|
||||
SelRange = SourceRange(Loc, Loc);
|
||||
}
|
||||
Consumer(findSelectedASTNodes(Context, SelRange));
|
||||
Consumer(SelRange, findSelectedASTNodes(Context, SelRange));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void findSelectedASTNodesWithRange(
|
||||
StringRef Source, FileLocation Location, Optional<FileRange> SelectionRange,
|
||||
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) {
|
||||
SelectionFinderVisitor Visitor(Location, SelectionRange, Consumer);
|
||||
EXPECT_TRUE(Visitor.runOver(Source, Language));
|
||||
findSelectedASTNodesWithRange(
|
||||
Source, Location, SelectionRange,
|
||||
[&](SourceRange, Optional<SelectedASTNode> Selection) {
|
||||
Consumer(std::move(Selection));
|
||||
},
|
||||
Language);
|
||||
}
|
||||
|
||||
void checkNodeImpl(bool IsTypeMatched, const SelectedASTNode &Node,
|
||||
|
@ -649,4 +668,171 @@ void selectSubscript(NSMutableArray *array, I *i) {
|
|||
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
|
||||
|
|
Loading…
Reference in New Issue