[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