[PATCH] Introduce Replacement deduplication and conflict detection function
Edwin Vane
edwin.vane at intel.com
Wed Aug 7 11:33:13 PDT 2013
Hi klimek, djasper, silvas,
This patch adds tooling::deduplicate() which removes duplicates from and
looks for conflicts in a vector of Replacements.
Questions for reviewers:
* Use std::pair<unsigned, unsigned> instead of Range to describe the
Replacements involved in a conflict? .first and .second would be
offset and location respectively.
* Make conflict detection optional? (i.e. dedup only)
* There's a FIXME saying to replace std::set<Replacement> with
std::vector<> doing dedup in the tool. With the acceptance of this
patch should that FIXME be resolved?
http://llvm-reviews.chandlerc.com/D1314
Files:
include/clang/Tooling/Refactoring.h
lib/Tooling/Refactoring.cpp
unittests/Tooling/RefactoringTest.cpp
Index: include/clang/Tooling/Refactoring.h
===================================================================
--- include/clang/Tooling/Refactoring.h
+++ include/clang/Tooling/Refactoring.h
@@ -122,6 +122,8 @@
bool operator()(const Replacement &R1, const Replacement &R2) const;
};
+ bool operator==(const Replacement &Other) const;
+
private:
void setFromSourceLocation(SourceManager &Sources, SourceLocation Start,
unsigned Length, StringRef ReplacementText);
@@ -155,6 +157,14 @@
/// applied.
unsigned shiftedCodePosition(const Replacements& Replaces, unsigned Position);
+/// \brief Removes duplicate Replacements and reports if Replacements conflict
+/// with one another.
+///
+/// This function will sort \p Replaces so that conflicts can be reported simply
+/// by offset into \p Replaces and number of elements in the conflict.
+void deduplicate(std::vector<Replacement> &Replaces,
+ std::vector<Range> &Conflicts);
+
/// \brief A tool to run refactorings.
///
/// This is a refactoring specific version of \see ClangTool. FrontendActions
Index: lib/Tooling/Refactoring.cpp
===================================================================
--- lib/Tooling/Refactoring.cpp
+++ lib/Tooling/Refactoring.cpp
@@ -90,6 +90,12 @@
return R1.ReplacementText < R2.ReplacementText;
}
+bool Replacement::operator==(const Replacement &Other) const {
+ return ReplacementRange.getOffset() == Other.ReplacementRange.getOffset() &&
+ ReplacementRange.getLength() == Other.ReplacementRange.getLength() &&
+ FilePath == Other.FilePath && ReplacementText == Other.ReplacementText;
+}
+
void Replacement::setFromSourceLocation(SourceManager &Sources,
SourceLocation Start, unsigned Length,
StringRef ReplacementText) {
@@ -179,6 +185,44 @@
return NewPosition;
}
+void deduplicate(std::vector<Replacement> &Replaces,
+ std::vector<Range> &Conflicts) {
+ if (Replaces.empty())
+ return;
+
+ // Deduplicate
+ std::sort(Replaces.begin(), Replaces.end(), Replacement::Less());
+ std::vector<Replacement>::iterator End =
+ std::unique(Replaces.begin(), Replaces.end());
+ Replaces.erase(End, Replaces.end());
+
+ // Detect conflicts
+ Range ConflictRange(Replaces.front().getOffset(),
+ Replaces.front().getLength());
+ unsigned ConflictStart = 0;
+ unsigned ConflictLength = 1;
+ for (unsigned i = 1; i < Replaces.size(); ++i) {
+ Range Current(Replaces[i].getOffset(), Replaces[i].getLength());
+ if (ConflictRange.overlapsWith(Current)) {
+ // Extend conflicted range
+ ConflictRange = Range(ConflictRange.getOffset(),
+ Current.getOffset() + Current.getLength() -
+ ConflictRange.getOffset());
+ ++ConflictLength;
+ } else {
+ if (ConflictLength > 1)
+ Conflicts.push_back(Range(ConflictStart, ConflictLength));
+ ConflictRange = Current;
+ ConflictStart = i;
+ ConflictLength = 1;
+ }
+ }
+
+ if (ConflictLength > 1)
+ Conflicts.push_back(Range(ConflictStart, ConflictLength));
+}
+
+
RefactoringTool::RefactoringTool(const CompilationDatabase &Compilations,
ArrayRef<std::string> SourcePaths)
: ClangTool(Compilations, SourcePaths) {}
Index: unittests/Tooling/RefactoringTest.cpp
===================================================================
--- unittests/Tooling/RefactoringTest.cpp
+++ unittests/Tooling/RefactoringTest.cpp
@@ -347,5 +347,76 @@
EXPECT_FALSE(Range(0, 10).contains(Range(0, 11)));
}
+TEST(DeduplicateTest, removesDuplicates) {
+ std::vector<Replacement> Input;
+ Input.push_back(Replacement("fileA", 50, 0, " foo "));
+ Input.push_back(Replacement("fileA", 10, 3, " bar "));
+ Input.push_back(Replacement("fileA", 10, 2, " bar ")); // Length differs
+ Input.push_back(Replacement("fileA", 9, 3, " bar ")); // Offset differs
+ Input.push_back(Replacement("fileA", 50, 0, " foo ")); // Duplicate
+ Input.push_back(Replacement("fileA", 51, 3, " bar "));
+ Input.push_back(Replacement("fileB", 51, 3, " bar ")); // Filename differs!
+ Input.push_back(Replacement("fileA", 51, 3, " moo ")); // Replacement text
+ // differs!
+
+ std::vector<Replacement> Expected;
+ Expected.push_back(Replacement("fileA", 9, 3, " bar "));
+ Expected.push_back(Replacement("fileA", 10, 2, " bar "));
+ Expected.push_back(Replacement("fileA", 10, 3, " bar "));
+ Expected.push_back(Replacement("fileA", 50, 0, " foo "));
+ Expected.push_back(Replacement("fileA", 51, 3, " bar "));
+ Expected.push_back(Replacement("fileA", 51, 3, " moo "));
+ Expected.push_back(Replacement("fileB", 51, 3, " bar "));
+
+ std::vector<Range> Conflicts; // Ignored for this test
+ deduplicate(Input, Conflicts);
+
+ ASSERT_TRUE(Expected == Input);
+}
+
+TEST(DeduplicateTest, detectsConflicts) {
+ {
+ std::vector<Replacement> Input;
+ Input.push_back(Replacement("fileA", 0, 5, " foo "));
+ Input.push_back(Replacement("fileA", 0, 5, " foo ")); // Duplicate not a
+ // conflict.
+ Input.push_back(Replacement("fileA", 2, 6, " bar "));
+ Input.push_back(Replacement("fileA", 7, 3, " moo "));
+
+ std::vector<Range> Conflicts;
+ deduplicate(Input, Conflicts);
+
+ // One duplicate is removed and the remaining three items form one
+ // conflicted range.
+ ASSERT_EQ(3u, Input.size());
+ ASSERT_EQ(1u, Conflicts.size());
+ ASSERT_EQ(0u, Conflicts.front().getOffset());
+ ASSERT_EQ(3u, Conflicts.front().getLength());
+ }
+ {
+ std::vector<Replacement> Input;
+
+ // Expected sorted order is shown. It is the sorted order to which the
+ // returned conflict info refers to.
+ Input.push_back(Replacement("fileA", 0, 5, " foo ")); // 0
+ Input.push_back(Replacement("fileA", 5, 5, " bar ")); // 1
+ Input.push_back(Replacement("fileA", 5, 5, " moo ")); // 2
+ Input.push_back(Replacement("fileA", 15, 5, " golf ")); // 4
+ Input.push_back(Replacement("fileA", 16, 5, " bag ")); // 5
+ Input.push_back(Replacement("fileA", 10, 3, " club ")); // 6
+
+ std::vector<Range> Conflicts;
+ deduplicate(Input, Conflicts);
+
+ // No duplicates
+ ASSERT_EQ(6u, Input.size());
+ ASSERT_EQ(2u, Conflicts.size());
+ ASSERT_EQ(1u, Conflicts[0].getOffset());
+ ASSERT_EQ(2u, Conflicts[0].getLength());
+ ASSERT_EQ(4u, Conflicts[1].getOffset());
+ ASSERT_EQ(2u, Conflicts[1].getLength());
+ }
+}
+
} // end namespace tooling
} // end namespace clang
-------------- next part --------------
A non-text attachment was scrubbed...
Name: D1314.1.patch
Type: text/x-patch
Size: 6742 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20130807/9824c80f/attachment.bin>
More information about the cfe-commits
mailing list