Skip to content

Commit

Permalink
Codechange: make TimerGameCalendar Date and Year types strongly typed (
Browse files Browse the repository at this point in the history
  • Loading branch information
TrueBrain committed Aug 12, 2023
1 parent 0238a2b commit 299570b
Show file tree
Hide file tree
Showing 55 changed files with 404 additions and 217 deletions.
4 changes: 2 additions & 2 deletions src/cheat_gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ static int32_t ClickChangeDateCheat(int32_t new_value, int32_t change_direction)
{
/* Don't allow changing to an invalid year, or the current year. */
auto new_year = Clamp(TimerGameCalendar::Year(new_value), MIN_YEAR, MAX_YEAR);
if (new_year == TimerGameCalendar::year) return TimerGameCalendar::year;
if (new_year == TimerGameCalendar::year) return static_cast<int32_t>(TimerGameCalendar::year);

TimerGameCalendar::YearMonthDay ymd;
TimerGameCalendar::ConvertDateToYMD(TimerGameCalendar::date, &ymd);
Expand All @@ -125,7 +125,7 @@ static int32_t ClickChangeDateCheat(int32_t new_value, int32_t change_direction)
InvalidateWindowClassesData(WC_TRUCK_STATION, 0);
InvalidateWindowClassesData(WC_BUILD_OBJECT, 0);
ResetSignalVariant();
return TimerGameCalendar::year;
return static_cast<int32_t>(TimerGameCalendar::year);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/company_gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ struct CompanyFinancesWindow : Window {
auto age = std::min(TimerGameCalendar::year - c->inaugurated_year, TimerGameCalendar::Year(2));
int wid_offset = widget - WID_CF_EXPS_PRICE1;
if (wid_offset <= age) {
DrawYearColumn(r, TimerGameCalendar::year - (age - wid_offset), c->yearly_expenses[age - wid_offset]);
DrawYearColumn(r, TimerGameCalendar::year - (age - wid_offset), c->yearly_expenses[static_cast<int32_t>(age - wid_offset)]);
}
break;
}
Expand Down
4 changes: 2 additions & 2 deletions src/core/format.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ struct fmt::formatter<E, Char, std::enable_if_t<std::is_enum<E>::value>> : fmt::
};

template <typename T, typename Char>
struct fmt::formatter<T, Char, std::enable_if_t<std::is_base_of<StrongTypedefBase, T>::value>> : fmt::formatter<typename T::Type> {
using underlying_type = typename T::Type;
struct fmt::formatter<T, Char, std::enable_if_t<std::is_base_of<StrongTypedefBase, T>::value>> : fmt::formatter<typename T::BaseType> {
using underlying_type = typename T::BaseType;
using parent = typename fmt::formatter<underlying_type>;

constexpr fmt::format_parse_context::iterator parse(fmt::format_parse_context &ctx) {
Expand Down
20 changes: 17 additions & 3 deletions src/core/math_func.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#ifndef MATH_FUNC_HPP
#define MATH_FUNC_HPP

#include "strong_typedef_type.hpp"

/**
* Returns the absolute value of (scalar) variable.
Expand Down Expand Up @@ -162,7 +163,7 @@ static inline uint ClampU(const uint a, const uint min, const uint max)
* for the return type.
* @see Clamp(int, int, int)
*/
template <typename To, typename From>
template <typename To, typename From, std::enable_if_t<std::is_integral<From>::value, int> = 0>
constexpr To ClampTo(From value)
{
static_assert(std::numeric_limits<To>::is_integer, "Do not clamp from non-integer values");
Expand Down Expand Up @@ -213,6 +214,15 @@ constexpr To ClampTo(From value)
return static_cast<To>(std::min<BiggerType>(value, std::numeric_limits<To>::max()));
}

/**
* Specialization of ClampTo for #StrongType::Typedef.
*/
template <typename To, typename From, std::enable_if_t<std::is_base_of<StrongTypedefBase, From>::value, int> = 0>
constexpr To ClampTo(From value)
{
return ClampTo<To>(static_cast<typename From::BaseType>(value));
}

/**
* Returns the (absolute) difference between two (scalar) variables
*
Expand Down Expand Up @@ -254,10 +264,14 @@ static inline bool IsInsideBS(const T x, const size_t base, const size_t size)
* @param max The maximum of the interval
* @see IsInsideBS()
*/
template <typename T>
template <typename T, std::enable_if_t<std::disjunction_v<std::is_convertible<T, size_t>, std::is_base_of<StrongTypedefBase, T>>, int> = 0>
static constexpr inline bool IsInsideMM(const T x, const size_t min, const size_t max) noexcept
{
return (size_t)(x - min) < (max - min);
if constexpr (std::is_base_of_v<StrongTypedefBase, T>) {
return (size_t)(static_cast<typename T::BaseType>(x) - min) < (max - min);
} else {
return (size_t)(x - min) < (max - min);
}
}

/**
Expand Down
259 changes: 192 additions & 67 deletions src/core/strong_typedef_type.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,74 +10,199 @@
#ifndef STRONG_TYPEDEF_TYPE_HPP
#define STRONG_TYPEDEF_TYPE_HPP

/** Non-templated base for #StrongTypedef for use with type trait queries. */
struct StrongTypedefBase {};

/**
* Templated helper to make a type-safe 'typedef' representing a single POD value.
* A normal 'typedef' is not distinct from its base type and will be treated as
* identical in many contexts. This class provides a distinct type that can still
* be assign from and compared to values of its base type.
*
* @note This is meant to be used as a base class, not directly.
* @tparam T Storage type
* @tparam Tthis Type of the derived class (i.e. the concrete usage of this class).
*/
template <class T, class Tthis>
struct StrongTypedef : StrongTypedefBase {
using Type = T;

T value{}; ///< Backing storage field.

debug_inline constexpr StrongTypedef() = default;
debug_inline constexpr StrongTypedef(const StrongTypedef &o) = default;
debug_inline constexpr StrongTypedef(StrongTypedef &&o) = default;

debug_inline constexpr StrongTypedef(const T &value) : value(value) {}

debug_inline constexpr Tthis &operator =(const Tthis &rhs) { this->value = rhs.value; return static_cast<Tthis &>(*this); }
debug_inline constexpr Tthis &operator =(Tthis &&rhs) { this->value = std::move(rhs.value); return static_cast<Tthis &>(*this); }
debug_inline constexpr Tthis &operator =(const T &rhs) { this->value = rhs; return static_cast<Tthis &>(*this); }

explicit constexpr operator T() const { return this->value; }
#include "../3rdparty/fmt/format.h"

constexpr bool operator ==(const Tthis &rhs) const { return this->value == rhs.value; }
constexpr bool operator !=(const Tthis &rhs) const { return this->value != rhs.value; }
constexpr bool operator ==(const T &rhs) const { return this->value == rhs; }
constexpr bool operator !=(const T &rhs) const { return this->value != rhs; }
};

/**
* Extension of #StrongTypedef with operators for addition and subtraction.
* @tparam T Storage type
* @tparam Tthis Type of the derived class (i.e. the concrete usage of this class).
*/
template <class T, class Tthis>
struct StrongIntegralTypedef : StrongTypedef<T, Tthis> {
using StrongTypedef<T, Tthis>::StrongTypedef;

debug_inline constexpr StrongIntegralTypedef() = default;
debug_inline constexpr StrongIntegralTypedef(const StrongIntegralTypedef &o) = default;
debug_inline constexpr StrongIntegralTypedef(StrongIntegralTypedef &&o) = default;

debug_inline constexpr StrongIntegralTypedef(const T &value) : StrongTypedef<T, Tthis>(value) {}

debug_inline constexpr Tthis &operator =(const Tthis &rhs) { this->value = rhs.value; return static_cast<Tthis &>(*this); }
debug_inline constexpr Tthis &operator =(Tthis &&rhs) { this->value = std::move(rhs.value); return static_cast<Tthis &>(*this); }
debug_inline constexpr Tthis &operator =(const T &rhs) { this->value = rhs; return static_cast<Tthis &>(*this); }

constexpr Tthis &operator ++() { this->value++; return static_cast<Tthis &>(*this); }
constexpr Tthis &operator --() { this->value--; return static_cast<Tthis &>(*this); }
constexpr Tthis operator ++(int) { auto res = static_cast<Tthis &>(*this); this->value++; return res; }
constexpr Tthis operator --(int) { auto res = static_cast<Tthis &>(*this); this->value--; return res; }

constexpr Tthis &operator +=(const Tthis &rhs) { this->value += rhs.value; return *static_cast<Tthis *>(this); }
constexpr Tthis &operator -=(const Tthis &rhs) { this->value -= rhs.value; return *static_cast<Tthis *>(this); }
/** Non-templated base for #StrongType::Typedef for use with type trait queries. */
struct StrongTypedefBase {};

constexpr Tthis operator +(const Tthis &rhs) const { return Tthis{ this->value + rhs.value }; }
constexpr Tthis operator -(const Tthis &rhs) const { return Tthis{ this->value - rhs.value }; }
constexpr Tthis operator +(const T &rhs) const { return Tthis{ this->value + rhs }; }
constexpr Tthis operator -(const T &rhs) const { return Tthis{ this->value - rhs }; }
};
namespace StrongType {
/**
* Mix-in which makes the new Typedef comparable with itself and its base type.
*/
struct Compare {
template <typename TType, typename TBaseType>
struct mixin {
friend constexpr bool operator ==(const TType &lhs, const TType &rhs) { return lhs.value == rhs.value; }
friend constexpr bool operator ==(const TType &lhs, const TBaseType &rhs) { return lhs.value == rhs; }

friend constexpr bool operator !=(const TType &lhs, const TType &rhs) { return lhs.value != rhs.value; }
friend constexpr bool operator !=(const TType &lhs, const TBaseType &rhs) { return lhs.value != rhs; }

friend constexpr bool operator <=(const TType &lhs, const TType &rhs) { return lhs.value <= rhs.value; }
friend constexpr bool operator <=(const TType &lhs, const TBaseType &rhs) { return lhs.value <= rhs; }

friend constexpr bool operator <(const TType &lhs, const TType &rhs) { return lhs.value < rhs.value; }
friend constexpr bool operator <(const TType &lhs, const TBaseType &rhs) { return lhs.value < rhs; }

friend constexpr bool operator >=(const TType &lhs, const TType &rhs) { return lhs.value >= rhs.value; }
friend constexpr bool operator >=(const TType &lhs, const TBaseType &rhs) { return lhs.value >= rhs; }

friend constexpr bool operator >(const TType &lhs, const TType &rhs) { return lhs.value > rhs.value; }
friend constexpr bool operator >(const TType &lhs, const TBaseType &rhs) { return lhs.value > rhs; }
};
};

/**
* Mix-in which makes the new Typedef behave more like an integer. This means you can add and subtract from it.
*
* Operators like divide, multiply and module are explicitly denied, as that often makes little sense for the
* new type. If you want to do these actions on the new Typedef, you are better off first casting it to the
* base type.
*/
struct Integer {
template <typename TType, typename TBaseType>
struct mixin {
friend constexpr TType &operator ++(TType &lhs) { lhs.value++; return lhs; }
friend constexpr TType &operator --(TType &lhs) { lhs.value--; return lhs; }
friend constexpr TType operator ++(TType &lhs, int) { TType res = lhs; lhs.value++; return res; }
friend constexpr TType operator --(TType &lhs, int) { TType res = lhs; lhs.value--; return res; }

friend constexpr TType &operator +=(TType &lhs, const TType &rhs) { lhs.value += rhs.value; return lhs; }
friend constexpr TType operator +(const TType &lhs, const TType &rhs) { return TType{ lhs.value + rhs.value }; }
friend constexpr TType operator +(const TType &lhs, const TBaseType &rhs) { return TType{ lhs.value + rhs }; }

friend constexpr TType &operator -=(TType &lhs, const TType &rhs) { lhs.value -= rhs.value; return lhs; }
friend constexpr TType operator -(const TType &lhs, const TType &rhs) { return TType{ lhs.value - rhs.value }; }
friend constexpr TType operator -(const TType &lhs, const TBaseType &rhs) { return TType{ lhs.value - rhs }; }

/* For most new types, the rest of the operators make no sense. For example,
* what does it actually mean to multiply a Year with a value. Or to do a
* bitwise OR on a Date. Or to divide a TileIndex by 2. Conceptually, they
* don't really mean anything. So force the user to first cast it to the
* base type, so the operation no longer returns the new Typedef. */

constexpr TType &operator *=(const TType &rhs) = delete;
constexpr TType operator *(const TType &rhs) = delete;
constexpr TType operator *(const TBaseType &rhs) = delete;

constexpr TType &operator /=(const TType &rhs) = delete;
constexpr TType operator /(const TType &rhs) = delete;
constexpr TType operator /(const TBaseType &rhs) = delete;

constexpr TType &operator %=(const TType &rhs) = delete;
constexpr TType operator %(const TType &rhs) = delete;
constexpr TType operator %(const TBaseType &rhs) = delete;

constexpr TType &operator &=(const TType &rhs) = delete;
constexpr TType operator &(const TType &rhs) = delete;
constexpr TType operator &(const TBaseType &rhs) = delete;

constexpr TType &operator |=(const TType &rhs) = delete;
constexpr TType operator |(const TType &rhs) = delete;
constexpr TType operator |(const TBaseType &rhs) = delete;

constexpr TType &operator ^=(const TType &rhs) = delete;
constexpr TType operator ^(const TType &rhs) = delete;
constexpr TType operator ^(const TBaseType &rhs) = delete;

constexpr TType &operator <<=(const TType &rhs) = delete;
constexpr TType operator <<(const TType &rhs) = delete;
constexpr TType operator <<(const TBaseType &rhs) = delete;

constexpr TType &operator >>=(const TType &rhs) = delete;
constexpr TType operator >>(const TType &rhs) = delete;
constexpr TType operator >>(const TBaseType &rhs) = delete;

constexpr TType operator ~() = delete;
constexpr TType operator -() = delete;
};
};

/**
* Mix-in which makes the new Typedef compatible with another type (which is not the base type).
*
* @note The base type of the new Typedef will be cast to the other type; so make sure they are compatible.
*
* @tparam TCompatibleType The other type to be compatible with.
*/
template <typename TCompatibleType>
struct Compatible {
template <typename TType, typename TBaseType>
struct mixin {
friend constexpr bool operator ==(const TType &lhs, TCompatibleType rhs) { return lhs.value == static_cast<TBaseType>(rhs); }
friend constexpr bool operator !=(const TType &lhs, TCompatibleType rhs) { return lhs.value != static_cast<TBaseType>(rhs); }

friend constexpr bool operator <=(const TType &lhs, TCompatibleType rhs) { return lhs.value <= static_cast<TBaseType>(rhs); }
friend constexpr bool operator <(const TType &lhs, TCompatibleType rhs) { return lhs.value < static_cast<TBaseType>(rhs); }
friend constexpr bool operator >=(const TType &lhs, TCompatibleType rhs) { return lhs.value >= static_cast<TBaseType>(rhs); }
friend constexpr bool operator >(const TType &lhs, TCompatibleType rhs) { return lhs.value > static_cast<TBaseType>(rhs); }

friend constexpr TType operator +(const TType &lhs, TCompatibleType rhs) { return { static_cast<TBaseType>(lhs.value + rhs) }; }
friend constexpr TType operator -(const TType &lhs, TCompatibleType rhs) { return { static_cast<TBaseType>(lhs.value - rhs) }; }
};
};

/**
* Mix-in which makes the new Typedef implicitly convertible to its base type.
*
* Be careful: when allowing implicit conversion, you won't notice if this type is assigned to a compatible, but different, type.
* For example:
*
* StrongType::Typedef<int, struct MyTypeTag, Implicit> a = 1;
* StrongType::Typedef<int, struct MyTypeTag, Implicit> b = 2;
* a = b; // OK
*/
struct Implicit {
template <typename TType, typename TBaseType>
struct mixin {
constexpr operator TBaseType () const { return static_cast<const TType &>(*this).value; }
};
};

/**
* Mix-in which makes the new Typedef explicitly convertible to its base type.
*/
struct Explicit {
template <typename TType, typename TBaseType>
struct mixin {
explicit constexpr operator TBaseType () const { return static_cast<const TType &>(*this).value; }
};
};

/**
* Templated helper to make a type-safe 'typedef' representing a single POD value.
* A normal 'typedef' is not distinct from its base type and will be treated as
* identical in many contexts. This class provides a distinct type that can still
* be assign from and compared to values of its base type.
*
* Example usage:
*
* using MyType = StrongType::Typedef<int, struct MyTypeTag, StrongType::Explicit, StrongType::Compare, StrongType::Integer>;
*
* @tparam TBaseType Type of the derived class (i.e. the concrete usage of this class).
* @tparam TTag An unique struct to keep types of the same TBaseType distinct.
* @tparam TProperties A list of mixins to add to the class.
*/
template <typename TBaseType, typename TTag, typename... TProperties>
struct EMPTY_BASES Typedef : public StrongTypedefBase, public TProperties::template mixin<Typedef<TBaseType, TTag, TProperties...>, TBaseType>... {
using BaseType = TBaseType;

constexpr Typedef() = default;
constexpr Typedef(const Typedef &) = default;
constexpr Typedef(Typedef &&) = default;

constexpr Typedef(const TBaseType &value) : value(value) {}

constexpr Typedef &operator =(const Typedef &rhs) { this->value = rhs.value; return *this; }
constexpr Typedef &operator =(Typedef &&rhs) { this->value = std::move(rhs.value); return *this; }
constexpr Typedef &operator =(const TBaseType &rhs) { this->value = rhs; return *this; }

/* Only allow TProperties classes access to the internal value. Everyone else needs to do an explicit cast. */
friend struct Explicit;
friend struct Implicit;
friend struct Compare;
friend struct Integer;
template <typename TCompatibleType> friend struct Compatible;

/* GCC / MSVC don't pick up on the "friend struct" above, where CLang does.
* As in our CI we compile for all three targets, it is sufficient to have one
* that errors on this; but nobody should be using "value" directly. Instead,
* use a static_cast<> to convert to the base type. */
#ifdef __clang__
protected:
#endif /* __clang__ */
TBaseType value{};
};
}

#endif /* STRONG_TYPEDEF_TYPE_HPP */
4 changes: 2 additions & 2 deletions src/date_gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ struct SetDateWindow : Window {
case WID_SD_YEAR:
for (TimerGameCalendar::Year i = this->min_year; i <= this->max_year; i++) {
SetDParam(0, i);
list.emplace_back(new DropDownListStringItem(STR_JUST_INT, i, false));
list.emplace_back(new DropDownListStringItem(STR_JUST_INT, static_cast<int32_t>(i), false));
}
selected = this->date.year;
selected = static_cast<int32_t>(this->date.year);
break;
}

Expand Down
7 changes: 4 additions & 3 deletions src/date_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ static constexpr TimerGameCalendar::Year ORIGINAL_MAX_YEAR = 2090;
*/
static constexpr TimerGameCalendar::Date DateAtStartOfYear(TimerGameCalendar::Year year)
{
uint number_of_leap_years = (year == 0) ? 0 : ((year - 1) / 4 - (year - 1) / 100 + (year - 1) / 400 + 1);
int32_t year_as_int = static_cast<int32_t>(year);
uint number_of_leap_years = (year == 0) ? 0 : ((year_as_int - 1) / 4 - (year_as_int - 1) / 100 + (year_as_int - 1) / 400 + 1);

return (DAYS_IN_YEAR * year) + number_of_leap_years;
return (DAYS_IN_YEAR * year_as_int) + number_of_leap_years;
}

/**
Expand All @@ -70,7 +71,7 @@ static constexpr TimerGameCalendar::Date DateAtStartOfYear(TimerGameCalendar::Ye
*/
static inline TimerGameCalendar::Year DateToYear(TimerGameCalendar::Date date)
{
return date / DAYS_IN_LEAP_YEAR;
return static_cast<int32_t>(date) / DAYS_IN_LEAP_YEAR;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/depot_gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ struct DepotWindow : Window {
DrawSpriteIgnorePadding((v->vehstatus & VS_STOPPED) ? SPR_FLAG_VEH_STOPPED : SPR_FLAG_VEH_RUNNING, PAL_NONE, flag, false, SA_CENTER);

SetDParam(0, v->unitnumber);
DrawString(text, STR_JUST_COMMA, (uint16_t)(v->max_age - DAYS_IN_LEAP_YEAR) >= v->age ? TC_BLACK : TC_RED);
DrawString(text, STR_JUST_COMMA, (v->max_age - DAYS_IN_LEAP_YEAR) >= v->age ? TC_BLACK : TC_RED);
}
}

Expand Down
Loading

0 comments on commit 299570b

Please sign in to comment.