diff --git a/llvm/include/llvm/ADT/MutableRange.h b/llvm/include/llvm/ADT/MutableRange.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/ADT/MutableRange.h @@ -0,0 +1,151 @@ +//===-- MutableRange.h ------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements the MutableRange class template which wraps +// over a const container making it effectively mutable. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_ADT_MUTABLERANGE_H +#define LLVM_ADT_MUTABLERANGE_H + +#include "llvm/ADT/SmallVector.h" +#include +#include +#include + +namespace llvm { + +template +class is_associative : public std::false_type {}; +template +class is_associative< + T, typename std::enable_if::type> + : public std::true_type {}; + +/// Creates a wrapper over a const container effectively making it mutable +/// without modifying the underlying container. +/// +/// Specialization for non associative containers. +/// +/// @tparam T underlying container type. +template ::value, int>::type = 0> +class MutableRange { + using SizeType = size_t; + using Iter = typename T::const_iterator; + using ValueType = typename T::value_type; + using MutableT = typename std::remove_const::type; + + static_assert( + std::is_base_of::value, + "T::iterator does not meet the requirments of a forward iterator"); + + /// Describes where to find the underlying object that is pointed to + /// by Index. + struct alignas(sizeof(void *)) MappingType { + enum Operation { + Original = 0, /// Index represents offset from First. + New /// Index represents index into Modified. + /// Is either an addition or replacement. + }; + Operation Type : 1; + SizeType Index : sizeof(void *) * 8 - 1; + + MappingType(const MappingType &) = default; + MappingType(Operation Type, SizeType Index) : Type(Type), Index(Index) {} + + operator SizeType() const { return Index; } + }; + + const Iter First; + const Iter Last; + + std::vector Mappings; + std::vector Modified; + + const ValueType &getValueFromMapping(MappingType Mapping) const { + if (Mapping.Type == MappingType::Original) { + auto Begin = getFirst(); + std::advance(Begin, Mapping); + return *Begin; + } + + return Modified[Mapping]; + } + + Iter getFirst() const { return First; } + +public: + MutableRange(const MutableRange &) = default; + MutableRange(MutableRange &&) = default; + MutableRange(const T &Container) + : MutableRange(Container.begin(), Container.end()) {} + MutableRange(Iter First, Iter Last) : First(First), Last(Last) { + SizeType Length = std::distance(First, Last); + Mappings.reserve(Length); + size_t Count = 0; + auto Begin = Mappings.begin(); + std::transform(Begin, Begin + Length, std::back_inserter(Mappings), + [&Count](const MappingType &) { + return MappingType(MappingType::Original, Count++); + }); + } + + /// Returns an immutable reference to object at Index. + const ValueType &operator[](SizeType Index) const { + return getValueFromMapping(Mappings[Index]); + } + + /// Emplaces a T::value_type at a specified index. + template ValueType &insert(SizeType Index, Ts &&... Args) { + Modified.emplace_back(ValueType(std::forward(Args)...)); + auto Begin = Mappings.begin(); + std::advance(Begin, Index); + Mappings.insert(Begin, MappingType(MappingType::New, Modified.size() - 1)); + return Modified.back(); + } + + /// Appends new element to the back. + template ValueType &emplace_back(Ts &&... Args) { + return insert(Mappings.size(), std::forward(Args)...); + } + + /// Removes an element at the specified Index. + void erase(SizeType Index) { + assert(Index < Mappings.size() && "Index larger than number of elements"); + Mappings.erase(Mappings.begin() + Index); + } + + /// Returns a mutable reference to the index at Index. + ValueType &makeMutable(SizeType Index) { + assert(Index < Mappings.size() && "Index larger than number of elements"); + if (Mappings[Index].Type == MappingType::New) + return Modified[Mappings[Index]]; + + Modified.emplace_back(ValueType(First[Mappings[Index]])); + Mappings[Index] = MappingType(MappingType::New, Modified.size() - 1); + return Modified.back(); + } + + /// Returns the MutableRange as the same type as the original collection. + MutableT collect() const { + MutableT Container; + std::transform(Mappings.begin(), Mappings.end(), + std::back_inserter(Container), + [this](const MappingType &Mapping) { + return getValueFromMapping(Mapping); + }); + return Container; + } +}; + +} // namespace llvm + +#endif // LLVM_ADT_MUTABLERANGE_H diff --git a/llvm/unittests/ADT/CMakeLists.txt b/llvm/unittests/ADT/CMakeLists.txt --- a/llvm/unittests/ADT/CMakeLists.txt +++ b/llvm/unittests/ADT/CMakeLists.txt @@ -39,6 +39,7 @@ MakeUniqueTest.cpp MappedIteratorTest.cpp MapVectorTest.cpp + MutableRangeTest.cpp OptionalTest.cpp PackedVectorTest.cpp PointerEmbeddedIntTest.cpp diff --git a/llvm/unittests/ADT/MutableRangeTest.cpp b/llvm/unittests/ADT/MutableRangeTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/ADT/MutableRangeTest.cpp @@ -0,0 +1,108 @@ +//===-- MutableRangeTest.cpp - MutableRange unit tests --------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/MutableRange.h" +#include "gtest/gtest.h" +#include +#include + +// These could be static_assert, but it makes more sense to have the static +// assertions in this file than MutableRange.h, at that point it might as +// well be a TEST. +TEST(type_traits, is_associative) { + EXPECT_TRUE(llvm::is_associative>::value) + << "set is associative"; + EXPECT_FALSE(llvm::is_associative>::value) + << "vector not associative"; +} + +class MutableRangeTest : public ::testing::Test { + + void TearDown() override { + ASSERT_EQ(IntVec.size(), 3U) << "container was modified"; + ASSERT_EQ(IntVec[0], 1) << "container was modified"; + ASSERT_EQ(IntVec[1], 2) << "container was modified"; + ASSERT_EQ(IntVec[2], 3) << "container was modified"; + } + +public: + const std::vector IntVec{1, 2, 3}; + + llvm::MutableRange getIntRange() { + return llvm::MutableRange(IntVec); + } +}; + +TEST_F(MutableRangeTest, Constructor) { + const auto Range = getIntRange(); + + EXPECT_EQ(Range[0], 1) << "doesn't correctly represent underlying container"; + EXPECT_EQ(Range[1], 2); + EXPECT_EQ(Range[2], 3); + + llvm::MutableRange ConsWithIter(IntVec.begin(), + IntVec.end()); + + EXPECT_EQ(ConsWithIter[0], 1) << "these constructors should be equivalent"; + EXPECT_EQ(ConsWithIter[1], 2); + EXPECT_EQ(ConsWithIter[2], 3); +} + +TEST_F(MutableRangeTest, Insert) { + auto Range = getIntRange(); + + EXPECT_EQ(Range.insert(1, 9), 9) + << "didn't correctly return reference to inserted"; + + EXPECT_EQ(Range[0], 1); + EXPECT_EQ(Range[1], 9) << "inserted in wrong area"; + EXPECT_EQ(Range[2], 2); + EXPECT_EQ(Range[3], 3); + + EXPECT_EQ(Range.emplace_back(4), 4) + << "didn't correctly return reference to appened element"; + + EXPECT_EQ(Range[0], 1); + EXPECT_EQ(Range[1], 9); + EXPECT_EQ(Range[2], 2); + EXPECT_EQ(Range[3], 3); + EXPECT_EQ(Range[4], 4) << "emplace_back didn't push to back"; +} + +TEST_F(MutableRangeTest, Erase) { + auto Range = getIntRange(); + + Range.erase(0); + + EXPECT_EQ(Range[0], 2); + EXPECT_EQ(Range[1], 3) << "erase didn't correctly remove the element"; +} + +TEST_F(MutableRangeTest, MakeMutable) { + auto Range = getIntRange(); + + Range.makeMutable(0) = 10; + + EXPECT_EQ(Range[0], 10) << "makeMutable didn't correctly update"; + EXPECT_EQ(Range[1], 2); + EXPECT_EQ(Range[2], 3); +} + +TEST_F(MutableRangeTest, Collect) { + auto Range = getIntRange(); + + Range.emplace_back(7); + + std::vector NewVec = Range.collect(); + + EXPECT_EQ(NewVec.size(), 4U); + EXPECT_EQ(NewVec[0], 1); + EXPECT_EQ(NewVec[1], 2); + EXPECT_EQ(NewVec[2], 3); + EXPECT_EQ(NewVec[3], 7); +}