diff --git a/llvm/include/llvm/ADT/SemanticTypedef.h b/llvm/include/llvm/ADT/SemanticTypedef.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/ADT/SemanticTypedef.h @@ -0,0 +1,390 @@ +//===-- SemanticTypedef.h - Typedefs for a type-safe world ------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// 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 +// +//===----------------------------------------------------------------------===// +// +/// \file +/// This file provides utilities for creating semantic typedefs. +/// +/// Semantic typedefs are useful when one wants to distinguish between types +/// that otherwise would have the same signature. For example: +/// +/// void foo(int Size, int Alignment); +/// +/// It is easy to forget the order of arguments. It would be safer to have +/// something like: +/// +/// void frobnicate(SizeType Size, AlignmentType Alignment); +/// +/// But we can do even better. Are Size and Alignment specified in bits or +/// bytes? We could make it explicit: +/// +/// void frobnicate(BitSizeType Size, ByteAlignmentType Alignment); +/// +/// However, defining BitSizeSizeType and BytesAlignmentType (and ByteDistance +/// and VectorElementLength and...) is tedious, cumbersome and error-prone. +/// The semantic typedef utilites here make this at least a little bit easier. +/// +/// Typical use is as follows: +/// +/// class MyInt : public SemanticTypedef { +/// using SemanticTypedef::SemanticTypedef; +/// }; +/// +/// MyInt a(10); +/// +/// int A = int(MyInt); +/// +/// It is not required that SemanticTypedef be used with CRTP, but it is often +/// most conveninent. One may use any type as a SemanticTypedef tag: +/// +/// struct MyIntTag {}; +/// using MyInt = SemanticTypedef; +/// +/// A large downside to not using CRTP is that it's not possible to use the +/// pre-defined mixin classes to add operators. One has to define each operator +/// specially for the new type. +/// +/// Using the same tag again will make a second alias interoperate with the first +/// but still prevent implicit conversions to the underlying type: +/// +/// using MyOtherInt = SemanticTypedef; +/// +/// Of course this removes some safety: +/// +/// void foo(MyInt expected); +/// +/// MyOtherInt maybeWrong(10); +/// foo(maybeWrong); // Oops? +/// +/// Metafunctions exist to obtain the underlying type and tag of a typedef: +/// +/// using Underlying = UnderlyingType; +/// using Tag = TagType; +/// +/// The base SemanticTypedef class does not provide any functionality other than +/// explicit conversion between it and its underlying type along with defaulted +/// constructors and assignment operators. Mixins are used to imbue objects +/// with additional features: +/// +/// class MyInt : public SemanticTypedef, +/// public LeCmp { +/// using SemanticTypedef::SemanticTypedef; +/// }; +/// +/// MyInt a(10); +/// MyInt b(20); +/// +/// bool LtTrue = a < b; +/// bool GeTrue = b >= a; +/// bool LtFalse = b < a; +/// bool GeFalse = a >= b; +/// +/// Inspired by Jonathan Mueller's post on strong typedef: +/// +/// https://foonathan.net/blog/2016/10/19/strong-typedefs.html +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_ADT_SEMANTICTYPEDEF_H +#define LLVM_ADT_SEMANTICTYPEDEF_H + +#include "llvm/Support/raw_ostream.h" + +#include +#include + +namespace llvm { + +//////////////////////////////////////////////////////////////////////////////// +// Define SemanticTypedef. +//////////////////////////////////////////////////////////////////////////////// + +/// This is the basic SemanticTypedef template, providing only explicit +/// conversion between itself and its underlying type. +template +class SemanticTypedef { + T Value; + +public: + constexpr SemanticTypedef() : Value() {} + explicit SemanticTypedef(const T &V) + noexcept(std::is_nothrow_copy_constructible::value) : + Value(V) {} + explicit SemanticTypedef(T &&V) + noexcept(std::is_nothrow_move_constructible::value) : + Value(std::move(V)) {} + + explicit operator T &() noexcept { + return Value; + } + + explicit operator const T &() const noexcept { + return Value; + } + + friend void swap(SemanticTypedef &A, SemanticTypedef &B) { + using std::swap; + swap(static_cast(A), static_cast(B)); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +// Internal utilities. +//////////////////////////////////////////////////////////////////////////////// + +namespace detail { +/// Implementation of UnderlyingType. +template +T UnderlyingTypeImpl(const SemanticTypedef &); + +/// Implementation of TagType. +template +Tag TagTypeImpl(const SemanticTypedef &); +} // end namespace detail + +/// Get the underlying type of a semantic typedef. +template +using UnderlyingType = decltype(detail::UnderlyingTypeImpl(std::declval())); + +/// Get the tag of a semantic typedef. +template +using TagType = decltype(detail::TagTypeImpl(std::declval())); + +namespace detail { +/// Implmentation of getValue. +template +T &getValue(SemanticTypedef &V) { + return static_cast(V); +} + +/// Get the wrapped value of a semantic typedef object. +template +const T &getValue(const SemanticTypedef &V) { + return static_cast(V); +} +} // end namespace detail + +//////////////////////////////////////////////////////////////////////////////// +// Define mixins to add functionality to typedefs. +//////////////////////////////////////////////////////////////////////////////// + +// The operators shouldn't take a SemanticTypedef because then any +// SemanticTypedef would match and we want to restrict these to the specific +// typedef that opted into them. In order to do that, we define them as friends +// within the mixin class. This makes them accessible only by ADL from the type +// that defined them (i.e. the typedef subclass). See the referenced blog post +// above. +// +// Define unary operators. + +// Operator(Typedef) + +/// Define a macro to create unary operators. +#define TYPEDEF_UNARY_OP(Op) \ + friend ResultType operator Op(const Typedef &O) { \ + return ResultType( \ + Op detail::getValue(O)); \ + } + +/// Define a macro to declare binary operators and the base class. +#define MAKE_TYPEDEF_UNARY_OP(Name, Op, Result) \ + /* Operator(Typedef) */ \ + template \ + struct Name { \ + TYPEDEF_UNARY_OP(Op) \ + }; + +// Define binary operators. + +// Operator(Typedef, Typedef) + +/// Define a macro to create straightforward binary operators. +#define TYPEDEF_TYPEDEF_BINARY_OP(Op) \ + friend ResultType operator Op(const Typedef &LHS, \ + const Typedef &RHS) { \ + return ResultType( \ + detail::getValue(LHS) Op detail::getValue(RHS)); \ + } + +/// Define a macro to create binary operators that modify and return the LHS. +#define TYPEDEF_TYPEDEF_BINARY_OPEQ(Op) \ + friend Typedef &operator Op(Typedef &LHS, \ + const Typedef &RHS) { \ + detail::getValue(LHS) Op detail::getValue(RHS); \ + return LHS; \ + } + +/// Define a macro to declare binary operators and the base class. +#define MAKE_TYPEDEF_TYPEDEF_BINARY_OP(Name, Op, Result) \ + /* Operator(Typedef, Typedef) */ \ + template \ + struct Name { \ + TYPEDEF_TYPEDEF_BINARY_OP(Op) \ + }; + +/// Define a macro to declare binary operators and the base class. +#define MAKE_TYPEDEF_TYPEDEF_OPEQ(Name, Op) \ + /* Operator(Typedef, Typedef) */ \ + template \ + struct Name { \ + TYPEDEF_TYPEDEF_BINARY_OPEQ(Op) \ + }; + +// Define operators +namespace typedef_operators { +MAKE_TYPEDEF_UNARY_OP(LogNot, !, bool) +MAKE_TYPEDEF_UNARY_OP(BitNot, ~, Typedef) +MAKE_TYPEDEF_UNARY_OP(Pos, +, Typedef) +MAKE_TYPEDEF_UNARY_OP(Neg, -, Typedef) + +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(LtCmp, <, bool) +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(LeCmp, <=, bool) +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(EqCmp, ==, bool) +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(NeCmp, !=, bool) +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(GeCmp, >=, bool) +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(GtCmp, >, bool) + +// Note: Not short-circuiting! +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(LogAnd, &&, bool) +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(LogOr, ||, bool) + +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(BitAnd, &, Typedef) +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(BitOr, |, Typedef) +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(BitXor, ^, Typedef) +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(BitShl, <<, Typedef) +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(BitShr, >>, Typedef) + +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(Add, +, Typedef) +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(Sub, -, Typedef) +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(Mul, *, Typedef) +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(Div, /, Typedef) +MAKE_TYPEDEF_TYPEDEF_BINARY_OP(Mod, %, Typedef) + +/// Provide add-assignment operators to semantic typedefs. +MAKE_TYPEDEF_TYPEDEF_OPEQ(AddAssign, +=) +MAKE_TYPEDEF_TYPEDEF_OPEQ(SubAssign, -=) +MAKE_TYPEDEF_TYPEDEF_OPEQ(MulAssign, *=) +MAKE_TYPEDEF_TYPEDEF_OPEQ(DivAssign, /=) +MAKE_TYPEDEF_TYPEDEF_OPEQ(ModAssign, %=) +MAKE_TYPEDEF_TYPEDEF_OPEQ(BitAndAssign, &=) +MAKE_TYPEDEF_TYPEDEF_OPEQ(BitOrAssign, |=) +MAKE_TYPEDEF_TYPEDEF_OPEQ(BitXorAssign, ^=) +MAKE_TYPEDEF_TYPEDEF_OPEQ(BitShlAssign, <<=) +MAKE_TYPEDEF_TYPEDEF_OPEQ(BitShrAssign, >>=) + +/// Provide increment operators to semantic typedefs. +template +struct Increment { + Typedef &operator++() { + ++detail::getValue(static_cast(*this)); + return static_cast(*this); + } + + Typedef operator++(int) { + Typedef ReturnValue(static_cast(*this)); + ++*this; + return ReturnValue; + } +}; + +/// Provide decrement operators to semantic typedefs. +template +struct Decrement { + Typedef &operator--() { + --detail::getValue(static_cast(*this)); + return static_cast(*this); + } + + Typedef operator--(int) { + Typedef ReturnValue(static_cast(*this)); + --*this; + return ReturnValue; + } +}; + +/// Provide streaming operators to semantic typedefs. +template +struct StreamOutput { + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const Typedef &V) { + return OS << detail::getValue(V); + } +}; + +/// Provide arithmetic to semantic typedefs. +template +struct Arithmetic : private Pos, + private Neg, + private Add, + private Sub, + private Mul, + private Div, + private Mod {}; + +/// Provide arithmetic-assign operators to semantic typedefs. +template +struct ArithmeticAssign : private AddAssign, + private SubAssign, + private MulAssign, + private DivAssign, + private ModAssign {}; + +/// Provide increment and decrement to semantic typedefs. +template +struct IncDec : public Increment, + public Decrement {}; + +/// Provide bitwise operators to semantic typedefs. +template +struct Bitwise : private BitNot, + private BitAnd, + private BitOr, + private BitXor, + private BitShl, + private BitShr {}; + +/// Provide bitwise-assign operators to semantic typedefs. +template +struct BitwiseAssign : private BitAndAssign, + private BitOrAssign, + private BitXorAssign, + private BitShlAssign, + private BitShrAssign {}; + +/// Provide logical operators to semantic typedefs. +template +struct Logical : private LogNot, + private LogAnd, + private LogOr {}; + +/// Provide operators useful for sorting to semantic typedefs. +template +struct Sortable : private LtCmp {}; + +/// Provide compare operators to semantic typedefs. +template +struct Compare : private Sortable, + private LeCmp, + private EqCmp, + private NeCmp, + private GeCmp, + private GtCmp {}; +} // end namespace typedef_operators + +#undef TYPEDEF_UNARY_OP +#undef MAKE_TYPEDEFUNARY_OP +#undef TYPEDEF_TYPEDEF_BINARY_OP +#undef TYPEDEF_TYPEDEF_BINARY_OP_ALT +#undef TYPEDEF_TYPEDEF_BINARY_OPEQ +#undef MAKE_TYPEDEF_TYPEDEF_BINARY_OP +#undef MAKE_TYPEDEF_TYPEDEF_OPEQ + +} // end namespace llvm + +#endif 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 @@ -52,6 +52,7 @@ SCCIteratorTest.cpp STLExtrasTest.cpp ScopeExitTest.cpp + SemanticTypedef.cpp SequenceTest.cpp SetVectorTest.cpp SimpleIListTest.cpp diff --git a/llvm/unittests/ADT/SemanticTypedef.cpp b/llvm/unittests/ADT/SemanticTypedef.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/ADT/SemanticTypedef.cpp @@ -0,0 +1,320 @@ +//===- unittests/ADT/SemanticTypedef.cpp - Semantic typedef tests ---------===// +// +// The LLVM Compiler Infrastructure +// +// 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/SemanticTypedef.h" +#include "llvm/ADT/SmallString.h" + +#include "gtest/gtest.h" + +#include +#include + +using namespace llvm; + +namespace { +using namespace typedef_operators; + +class SemanticTypedefTest : public testing::Test { +public: + // Interoperates with MyInt. + class MyInt : public SemanticTypedef, + private Arithmetic, + private ArithmeticAssign, + public IncDec, + private Bitwise, + private BitwiseAssign, + private Compare, + private StreamOutput { + public: + using SemanticTypedef::SemanticTypedef; + }; + + class MyOtherInt : public SemanticTypedef, + private Arithmetic, + private ArithmeticAssign, + public IncDec, + private Bitwise, + private BitwiseAssign, + private Compare, + private StreamOutput { + public: + using SemanticTypedef::SemanticTypedef; + }; + + struct NonCRTPInt {}; + using MyNonCRTPInt = SemanticTypedef; + + using IntBaseType = UnderlyingType; + + // Interoperates with MyBool. + class MyBool : public SemanticTypedef, + private EqCmp, + private Logical, + StreamOutput { + public: + using SemanticTypedef::SemanticTypedef; + }; + + using BoolBaseType = UnderlyingType; + + SemanticTypedefTest() {} + + // Test copy constructor. + MyInt foo(MyInt a) { + return a; + } + + MyNonCRTPInt foo(MyNonCRTPInt a) { + return a; + } +}; + +TEST_F(SemanticTypedefTest, Conversions) { + MyInt a(10); + MyOtherInt b(20); + MyNonCRTPInt c(30); + + EXPECT_EQ(IntBaseType(a), 10); + EXPECT_EQ(IntBaseType(b), 20); + + bool aToUnderlying = std::is_convertible>::value; + bool underlyingToa = std::is_convertible, + decltype(a)>::value; + + EXPECT_FALSE(aToUnderlying); + EXPECT_FALSE(underlyingToa); + + bool aTob = std::is_convertible::value; + bool bToa = std::is_convertible::value; + + EXPECT_FALSE(aTob); + EXPECT_FALSE(bToa); + + bool aToc = std::is_convertible::value; + bool cToa = std::is_convertible::value; + + EXPECT_FALSE(aToc); + EXPECT_FALSE(cToa); +} + +TEST_F(SemanticTypedefTest, Copy) { + int aValue = 10; + int bValue = 20; + + MyInt a(aValue); + MyNonCRTPInt b(bValue); + + MyInt c(foo(a)); + MyNonCRTPInt d(foo(b)); + + EXPECT_EQ(IntBaseType(c), aValue); + EXPECT_EQ(IntBaseType(d), bValue); +} + +TEST_F(SemanticTypedefTest, Assign) { + int aValue = 10; + int bValue = 20; + + MyInt a(10); + MyInt b(20); + + aValue = bValue; + a = b; + + EXPECT_EQ(IntBaseType(a), aValue); +} + +TEST_F(SemanticTypedefTest, Swap) { + int aValue = 10; + int bValue = 20; + + MyInt a(aValue); + MyInt b(bValue); + + std::swap(aValue, bValue); + swap(a, b); + + EXPECT_EQ(IntBaseType(a), aValue); + EXPECT_EQ(IntBaseType(b), bValue); +} + +TEST_F(SemanticTypedefTest, Arithmetic) { + int aValue = 10; + int bValue = 20; + + MyInt a(aValue); + MyInt b(bValue); + + EXPECT_EQ(IntBaseType(+a), aValue); + EXPECT_EQ(IntBaseType(-a), -aValue); + EXPECT_EQ(IntBaseType(a + b), aValue + bValue); + EXPECT_EQ(IntBaseType(a - b), aValue - bValue); + EXPECT_EQ(IntBaseType(a * b), aValue * bValue); + EXPECT_EQ(IntBaseType(a % b), aValue % bValue); + + a += b; + aValue += bValue; + EXPECT_EQ(IntBaseType(a), aValue); + + a -= b; + aValue -= bValue; + EXPECT_EQ(IntBaseType(a), aValue); + + a *= b; + aValue *= bValue; + EXPECT_EQ(IntBaseType(a), aValue); + + a /= b; + aValue /= bValue; + EXPECT_EQ(IntBaseType(a), aValue); + + a %= b; + aValue %= bValue; + EXPECT_EQ(IntBaseType(a), aValue); +} + +TEST_F(SemanticTypedefTest, IncDec) { + MyInt a(0); + + ASSERT_EQ(++a, MyInt(1)); + ASSERT_EQ(a++, MyInt(1)); + ASSERT_EQ(a, MyInt(2)); + + ASSERT_EQ(--a, MyInt(1)); + ASSERT_EQ(a--, MyInt(1)); + ASSERT_EQ(a, MyInt(0)); +} + +TEST_F(SemanticTypedefTest, Bitwise) { + int aValue = 10; + int bValue = 20; + + MyInt a(aValue); + MyInt b(bValue); + + EXPECT_EQ(IntBaseType(~a), ~aValue); + EXPECT_EQ(IntBaseType(a & b), aValue & bValue); + EXPECT_EQ(IntBaseType(a | b), aValue | bValue); + EXPECT_EQ(IntBaseType(a ^ b), aValue ^ bValue); + EXPECT_EQ(IntBaseType(a << b), aValue << bValue); + EXPECT_EQ(IntBaseType(a >> b), aValue >> bValue); + + a &= b; + aValue &= bValue; + EXPECT_EQ(IntBaseType(a), aValue); + + a |= b; + aValue |= bValue; + EXPECT_EQ(IntBaseType(a), aValue); + + a ^= b; + aValue ^= bValue; + EXPECT_EQ(IntBaseType(a), aValue); + + a <<= b; + aValue <<= bValue; + EXPECT_EQ(IntBaseType(a), aValue); + + a >>= b; + aValue >>= bValue; + EXPECT_EQ(IntBaseType(a), aValue); +} + +TEST_F(SemanticTypedefTest, Compare) { + int aValue = 10; + int bValue = 20; + + MyInt a(aValue); + MyInt b(bValue); + + EXPECT_EQ(IntBaseType(a < b), aValue < bValue); + EXPECT_EQ(IntBaseType(a <= b), aValue <= bValue); + EXPECT_EQ(IntBaseType(a == b), aValue == bValue); + EXPECT_EQ(IntBaseType(a != b), aValue != bValue); + EXPECT_EQ(IntBaseType(a >= b), aValue >= bValue); + EXPECT_EQ(IntBaseType(a > b), aValue > bValue); +} + +TEST_F(SemanticTypedefTest, LessThan) { + MyInt a(10); + MyInt b(20); + + EXPECT_TRUE(a < b); + EXPECT_FALSE(b < a); + EXPECT_FALSE(a < a); + EXPECT_FALSE(a >= b); + EXPECT_TRUE(b >= a); + EXPECT_TRUE(a >= a); +} + +TEST_F(SemanticTypedefTest, IntStreamOutput) { + int aValue = 10; + MyInt a(aValue); + + SmallString<5> aValueOutput; + SmallString<5> aOutput; + + raw_svector_ostream aValueOS(aOutput); + raw_svector_ostream aOS(aOutput); + + aValueOS << aValue; + aOS << a; + + ASSERT_EQ(aOS.str(), aValueOS.str()); +} + +TEST_F(SemanticTypedefTest, Logical) { + bool aValue = true; + bool bValue = true; + + MyBool a(aValue); + MyBool b(bValue); + + EXPECT_EQ(a && a, aValue && aValue); + EXPECT_EQ(a && b, aValue && bValue); + EXPECT_EQ(b && a, bValue && aValue); + EXPECT_EQ(b && b, bValue && bValue); + + EXPECT_EQ(a || a, aValue || aValue); + EXPECT_EQ(a || b, aValue || bValue); + EXPECT_EQ(b || a, bValue || aValue); + EXPECT_EQ(b || b, bValue || bValue); + + EXPECT_EQ(!a, !aValue); + EXPECT_EQ(!b, !bValue); +} + +TEST_F(SemanticTypedefTest, BoolStreamOutput) { + bool aValue = true; + bool bValue = true; + + MyBool a(aValue); + MyBool b(bValue); + + SmallString<5> aValueOutput; + SmallString<5> aOutput; + SmallString<5> bValueOutput; + SmallString<5> bOutput; + + raw_svector_ostream aValueOS(aOutput); + raw_svector_ostream aOS(aOutput); + raw_svector_ostream bValueOS(bOutput); + raw_svector_ostream bOS(bOutput); + + aValueOS << aValue; + aOS << a; + bValueOS << bValue; + bOS << b; + + ASSERT_EQ(aOS.str(), aValueOS.str()); + ASSERT_EQ(bOS.str(), bValueOS.str()); +} +} // end anonymous namespace