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,325 @@ +//===-- 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(bool Feature1, bool Feature2); +/// +/// It is easy to forget the order of arguments. It would be safer to have +/// something like: +/// +/// void foo(Feature1Flag Feature1, Feature2Flag Feature2); +/// +/// However, defining Feature1Flag and Feature2Flag (and Feature3Flag and +/// DoNotOptimizeFlag and EnableHaltingProblemDetectionFlag 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. Mixins are used to +/// imbue objects with additional features: +/// +/// class MyInt : public SemanticTypedef, +/// public LessThanCompare { +/// 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() {} + constexpr SemanticTypedef(const SemanticTypedef &) = default; + explicit constexpr SemanticTypedef(const T &V) : 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 SEMANTIC_TYPEDEF_UNARY_OP(Op, ResultType) \ + friend ResultType operator Op(const Typedef &O) { \ + return ResultType( \ + Op detail::getValue(static_cast(O))); \ + } + +/// Define a macro to declare binary operators and the base class. +#define SEMANTIC_TYPEDEF_MAKE_UNARY_OP(Name, Op, ResultType) \ + /* Operator(Typedef) */ \ + template \ + struct Name { \ + SEMANTIC_TYPEDEF_UNARY_OP(Op, ResultType) \ + }; + +// Define binary operators. + +// Operator(Typedef, Typedef) + +/// Define a macro to create straightforward binary operators. +#define SEMANTIC_TYPEDEF_BINARY_OP(Op, ResultType) \ + friend ResultType operator Op(const Typedef &LHS, \ + const Typedef &RHS) { \ + return ResultType( \ + detail::getValue(static_cast(LHS)) Op \ + detail::getValue(static_cast(RHS))); \ + } + +/// Define a macro to create binary operators implemented in terms of another +/// binary operator. +#define SEMANTIC_TYPEDEF_BINARY_OP_ALT(Alt, Op, ResultType) \ + friend ResultType operator Alt(const Typedef &LHS, \ + const Typedef &RHS) { \ + return ResultType( \ + !(detail::getValue(static_cast(LHS)) Op \ + detail::getValue(static_cast(RHS)))); \ + } + +/// Define a macro to create binary operators that modify and return the LHS. +#define SEMANTIC_TYPEDEF_BINARY_OPEQ(Op) \ + friend Typedef &operator Op(Typedef &LHS, \ + const Typedef &RHS) { \ + (detail::getValue(static_cast(LHS)) Op \ + detail::getValue(static_cast(RHS))); \ + return static_cast(LHS); \ + } + +/// Define a macro to declare binary operators and the base class. +#define SEMANTIC_TYPEDEF_MAKE_BINARY_OP(Name, Op, ResultType) \ + /* Operator(Typedef, Typedef) */ \ + template \ + struct Name { \ + SEMANTIC_TYPEDEF_BINARY_OP(Op, ResultType) \ + }; + +/// Define a macro to declare comparison operators and the base class. +#define SEMANTIC_TYPEDEF_MAKE_COMPARE_OP(Name, Op, Alt) \ + /* Operator(Typedef, Typedef) */ \ + template \ + struct Name { \ + SEMANTIC_TYPEDEF_BINARY_OP(Op, bool) \ + SEMANTIC_TYPEDEF_BINARY_OP_ALT(Alt, Op, bool) \ + }; + +/// Define a macro to declare binary operators and the base class. +#define SEMANTIC_TYPEDEF_MAKE_OPEQ(Name, Op) \ + /* Operator(Typedef, Typedef) */ \ + template \ + struct Name { \ + SEMANTIC_TYPEDEF_BINARY_OPEQ(Op) \ + }; + +// Define operators + +/// Provide logical negation operators to semantic typedefs. +SEMANTIC_TYPEDEF_MAKE_UNARY_OP(LogicalNegation, !, bool) + +/// Provide equality comparison operators to semantic typedefs. +SEMANTIC_TYPEDEF_MAKE_COMPARE_OP(EqualityCompare, ==, !=) + +/// Provide less-than comparison operators to semantic typedefs. +SEMANTIC_TYPEDEF_MAKE_COMPARE_OP(LessThanCompare, <, >=) + +// Note: Not short-circuiting! + +/// Provide logical conjunction operators to semantic typedefs. +SEMANTIC_TYPEDEF_MAKE_BINARY_OP(LogicalConjunction, &&, bool) + +/// Provide logical disjunction operators to semantic typedefs. +SEMANTIC_TYPEDEF_MAKE_BINARY_OP(LogicalDisjunction, ||, bool) + +/// Provide add-assignment operators to semantic typedefs. +SEMANTIC_TYPEDEF_MAKE_OPEQ(AddAssign, +=) + +/// 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 assignment operators to semantic typedefs. +template +struct Assign { + Typedef &operator=(const Typedef &Other) { + detail::getValue(*this) = detail::getValue(Other); + return *this; + } + + Typedef &operator=(Typedef &&Other) { + detail::getValue(*this) = detail::getValue(std::move(Other)); + return *this; + } +}; + +/// 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); + } +}; + +#undef SEMANTIC_TYPEDEF_UNARY_OP +#undef SEMANTIC_TYPEDEF_MAKE_UNARY_OP +#undef SEMANTIC_TYPEDEF_BINARY_OP +#undef SEMANTIC_TYPEDEF_BINARY_OP_ALT +#undef SEMANTIC_TYPEDEF_BINARY_OPEQ +#undef SEMANTIC_TYPEDEF_MAKE_BINARY_OP +#undef SEMANTIC_TYPEDEF_MAKE_COMPARE_OP +#undef SEMANTIC_TYPEDEF_MAKE_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,279 @@ +//===- 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 { +class SemanticTypedefTest : public testing::Test { +public: + // Interoperates with MyInt. + class MyInt : public SemanticTypedef, + public EqualityCompare, + public LessThanCompare, + public Assign, + public StreamOutput { + public: + using SemanticTypedef::SemanticTypedef; + }; + + class MyOtherInt : public SemanticTypedef, + public EqualityCompare, + public LessThanCompare, + public Assign, + public StreamOutput { + public: + using SemanticTypedef::SemanticTypedef; + }; + + struct NonCRTPInt {}; + using MyNonCRTPInt = SemanticTypedef; + + using IntBaseType = UnderlyingType; + + // Interoperates with MyBool. + class MyBool : public SemanticTypedef, + public EqualityCompare, + public LogicalConjunction, + public LogicalDisjunction, + public LogicalNegation, + public Assign, + public StreamOutput { + public: + using SemanticTypedef::SemanticTypedef; + }; + + using BoolBaseType = UnderlyingType; + + // Interoperates with MyAccumulator. + class MyAccumulator : public SemanticTypedef, + public EqualityCompare, + public AddAssign, + public Assign, + public StreamOutput { + public: + using SemanticTypedef::SemanticTypedef; + }; + + using AccumulatorBaseType = UnderlyingType; + + // Interoperates with MyIncrementable. + class MyIncrementable : public SemanticTypedef, + public EqualityCompare, + public Increment, + public Assign, + public StreamOutput { + public: + using SemanticTypedef::SemanticTypedef; + }; + + using IncrementableBaseType = 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) { + MyInt a(10); + MyNonCRTPInt b(20); + + MyInt c(foo(a)); + MyNonCRTPInt d(foo(b)); + + EXPECT_EQ(IntBaseType(c), 10); + EXPECT_EQ(IntBaseType(d), 20); +} + +TEST_F(SemanticTypedefTest, Swap) { + MyInt a(10); + MyInt b(20); + + swap(a, b); + + EXPECT_EQ(IntBaseType(a), 20); + EXPECT_EQ(IntBaseType(b), 10); +} + +TEST_F(SemanticTypedefTest, Equality) { + MyInt a(10); + MyInt b(20); + + EXPECT_TRUE(a != b); + EXPECT_TRUE(b != a); + EXPECT_FALSE(a != a); + EXPECT_FALSE(a == b); + EXPECT_FALSE(b == a); + EXPECT_TRUE(a == a); +} + +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, Assign) { + MyInt a(10); + MyInt b(20); + + a = b; + + EXPECT_TRUE(a == b); +} + +TEST_F(SemanticTypedefTest, IntStreamOutput) { + const MyInt a(10); + + SmallString<5> output; + + raw_svector_ostream OS(output); + + OS << a; + + ASSERT_EQ(OS.str(), StringLiteral("10")); +} + +TEST_F(SemanticTypedefTest, Conjunction) { + MyBool a(true); + MyBool b(false); + + EXPECT_TRUE(a && a); + EXPECT_FALSE(a && b); + EXPECT_FALSE(b && a); + EXPECT_FALSE(b && b); +} + +TEST_F(SemanticTypedefTest, Disjunction) { + MyBool a(true); + MyBool b(false); + + EXPECT_TRUE(a || a); + EXPECT_TRUE(a || b); + EXPECT_TRUE(b || a); + EXPECT_FALSE(b || b); +} + +TEST_F(SemanticTypedefTest, Negation) { + MyBool a(true); + MyBool b(false); + + EXPECT_EQ(!a, false); + EXPECT_EQ(!b, true); +} + +TEST_F(SemanticTypedefTest, BoolStreamOutput) { + MyBool a(true); + MyBool b(false); + + SmallString<5> aOutput; + SmallString<5> bOutput; + + raw_svector_ostream aOS(aOutput); + raw_svector_ostream bOS(bOutput); + + aOS << a; + bOS << b; + + ASSERT_EQ(aOS.str(), StringLiteral("1")); + ASSERT_EQ(bOS.str(), StringLiteral("0")); +} + +TEST_F(SemanticTypedefTest, AddAssign) { + MyAccumulator a(0); + + for (int i = 0; i < 10; ++i) { + a += MyAccumulator(10); + } + + EXPECT_EQ(a, MyAccumulator(100)); +} + +TEST_F(SemanticTypedefTest, AccumulateStreamOutput) { + const MyAccumulator a(10); + + SmallString<5> output; + + raw_svector_ostream OS(output); + + OS << a; + + ASSERT_EQ(OS.str(), StringLiteral("10")); +} + +TEST_F(SemanticTypedefTest, Increment) { + MyIncrementable a(0); + + ASSERT_EQ(++a, MyIncrementable(1)); + ASSERT_EQ(a++, MyIncrementable(1)); + ASSERT_EQ(a, MyIncrementable(2)); +} + +TEST_F(SemanticTypedefTest, IncrementStreamOutput) { + MyIncrementable a(10); + + SmallString<5> output; + + raw_svector_ostream OS(output); + + OS << a; + + ASSERT_EQ(OS.str(), StringLiteral("10")); +} +} // end anonymous namespace