| 1 | // Copyright (C) 2021 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
| 3 | |
| 4 | #pragma once |
| 5 | |
| 6 | #include <QtCore/qglobal.h> |
| 7 | |
| 8 | #include <functional> |
| 9 | #include <optional> |
| 10 | #include <type_traits> |
| 11 | |
| 12 | // TODO: Express the documentation such that QDoc would be able to see |
| 13 | // it and process it correctly. This probably means that we would like |
| 14 | // to associate the definition with a namespace, albeit we could use |
| 15 | // the header file too, and put the documentation in an empty cpp |
| 16 | // file. This is delayed as there currently isn't much namespacing for |
| 17 | // anything in QDoc and such a namespacing should be added gradually |
| 18 | // and attentively. |
| 19 | |
| 20 | // TODO: Review the semantics for construction and optmize it. Should we copy the |
| 21 | // value? Should we only allow rvalues? |
| 22 | |
| 23 | // TODO: There is an high chance that we will need to "compose" |
| 24 | // refinitions later on when dealing, for example, with the basics of |
| 25 | // user-provided paths. |
| 26 | // For example, when requiring that each user-inputted path is purged |
| 27 | // as per QFileInfo definition of purging. |
| 28 | // For example, it might be that instead of passing QString around we |
| 29 | // might pass some Path type that is a purged QString. |
| 30 | // Then, any other refinement over paths will need to use that as a |
| 31 | // base type. |
| 32 | // To avoid the clutter that comes from that, if such will be the |
| 33 | // case, we will need to change the definition of refine and value if |
| 34 | // the passed in type was refined already. |
| 35 | // That is, such that if we have: |
| 36 | // |
| 37 | // QDOC_REFINE_TYPE(QString, Path) { ... } |
| 38 | // QDOC_REFINE_TYPE(Path, Foo) { ... } |
| 39 | // |
| 40 | // Foo refines a QString and Foo.value returns a QString. This should |
| 41 | // in general be trivial as long as we add a way to identify, such as |
| 42 | // a tag that refinements derive from, what type was declared through |
| 43 | // QDOC_REFINED_TYPEDEF and what type was not. |
| 44 | |
| 45 | // TODO: Provide a way to generate a standard documentation for all |
| 46 | // members of a type generated by QDOC_REFINED_TYPEDEF without having |
| 47 | // to copy-paste include command everywhere. |
| 48 | // The main problem of doing this is that the preprocessor strips |
| 49 | // comments away, making it impossible to generate comments, and hence |
| 50 | // QDoc documentation, with the preprocessor. |
| 51 | |
| 52 | /*! |
| 53 | * \macro QDOC_REFINED_TYPEDEF(_type, _name) |
| 54 | * \relates refined_typedef.hpp |
| 55 | * |
| 56 | * Declares a wrapper type for \c {_type}, with identifier \c {_name}, |
| 57 | * that represents a subset of \c {_type} for which some conditions |
| 58 | * hold. |
| 59 | * |
| 60 | * For example: |
| 61 | * |
| 62 | * \code |
| 63 | QDOC_REFINED_TYPEDEF(std::size_t, Fin5) { |
| 64 | return (value < 5) : std::make_optional<Fin5>{value} : std::nullopt; |
| 65 | } |
| 66 | * \endcode |
| 67 | * |
| 68 | * Represents the subset of \c {std::size_t} that contains the value 0, 1, |
| 69 | * 2, 3 and 4, that is, the general finite set of cardinality 5. |
| 70 | * |
| 71 | * As the example shows, usages of the macro require some type, an |
| 72 | * identifier and some code. |
| 73 | * |
| 74 | * The type that is provided is the type that will be wrapped. |
| 75 | * Do note that we expect a type with no-qualifiers and that is not a |
| 76 | * pointer type. Types passed with those kind of qualifiers will be |
| 77 | * simplified to their base type. |
| 78 | * |
| 79 | * That is, for example, \c {int*}, \c {const int}, \c {const int&}, |
| 80 | * \c {int&} all counts as \c {int}. |
| 81 | * |
| 82 | * The identifier that is passed is used as the name for the newly |
| 83 | * declared type that wraps the original type. |
| 84 | * |
| 85 | * The code block that is passed will be run when an instance of the |
| 86 | * newly created wrapper type is being obtained. |
| 87 | * If the wrapper type is T, the codeblock must return a \c |
| 88 | * {std::optional<T>}. |
| 89 | * The code block should perform any check that ensures that the |
| 90 | * guarantees provided by the wrapper type holds and return a value if |
| 91 | * such is the case, otherwise returning \c {std::nullopt}. |
| 92 | * |
| 93 | * Inside the code block, the identifier \e {value} is implicitly |
| 94 | * bound to an element of the wrapped type that the instance is being |
| 95 | * built from and for which the guarantees provided by the wrapper |
| 96 | * type must hold. |
| 97 | * |
| 98 | * When a call to QDOC_REFINED_TYPEDEF is successful, a type with the |
| 99 | * provided identifier is declared. |
| 100 | * |
| 101 | * Let T be a type declared trough a call of QDOC_REFINED_TYPEDEF and |
| 102 | * W be the type that it wraps. |
| 103 | * |
| 104 | * An instance of T can be obtained by calling T::refine with an |
| 105 | * element of W. |
| 106 | * |
| 107 | * If the element of W respects the guarantees that T provides, then |
| 108 | * the call will return an optional that contains an instance of T, |
| 109 | * othewise it will return an empty optional. |
| 110 | * |
| 111 | * When an instance of T is obtained, it will wrap the element of W that |
| 112 | * was used to obtain it. |
| 113 | * |
| 114 | * The wrapped value can be accessed trough the \c {value} method. |
| 115 | * |
| 116 | * For example, considering \c {Fin5}, we could obtain an instance of |
| 117 | * it as follows: |
| 118 | * |
| 119 | * \code |
| 120 | * auto instance = *(Fin5::refine(std::size_t{1})); |
| 121 | * \endcode |
| 122 | * |
| 123 | * With that instance available we can retrieve the original value as |
| 124 | * follows: |
| 125 | * |
| 126 | * \code |
| 127 | * instance.value(); // The value 1 |
| 128 | * \endcode |
| 129 | */ |
| 130 | |
| 131 | #define QDOC_REFINED_TYPEDEF(_type, _name) \ |
| 132 | struct _name { \ |
| 133 | public: \ |
| 134 | using wrapped_type = std::remove_reference_t<std::remove_cv_t<std::remove_pointer_t<_type>>>; \ |
| 135 | \ |
| 136 | inline static constexpr auto has_equality_operator_v = std::is_invocable_r_v<bool, std::equal_to<>, wrapped_type, wrapped_type>; \ |
| 137 | inline static constexpr auto has_less_than_operator_v = std::is_invocable_r_v<bool, std::less_equal<>, wrapped_type, wrapped_type>; \ |
| 138 | inline static constexpr auto has_strictly_less_than_operator_v = std::is_invocable_r_v<bool, std::less<>, wrapped_type, wrapped_type>; \ |
| 139 | inline static constexpr auto has_greater_than_operator_v = std::is_invocable_r_v<bool, std::greater_equal<>, wrapped_type, wrapped_type>; \ |
| 140 | inline static constexpr auto has_strictly_greater_than_operator_v = std::is_invocable_r_v<bool, std::greater<>, wrapped_type, wrapped_type>; \ |
| 141 | \ |
| 142 | public: \ |
| 143 | static std::optional<_name> refine(wrapped_type value); \ |
| 144 | \ |
| 145 | [[nodiscard]] const wrapped_type& value() const noexcept { return _value; } \ |
| 146 | \ |
| 147 | _name(const _name&) = default; \ |
| 148 | _name& operator=(const _name&) = default; \ |
| 149 | \ |
| 150 | _name(_name&&) = default; \ |
| 151 | _name& operator=(_name&&) = default; \ |
| 152 | \ |
| 153 | operator wrapped_type() const { return _value; } \ |
| 154 | \ |
| 155 | public: \ |
| 156 | \ |
| 157 | template< \ |
| 158 | typename = std::enable_if_t< \ |
| 159 | has_equality_operator_v \ |
| 160 | > \ |
| 161 | > \ |
| 162 | bool operator==(const _name& rhs) const noexcept { return _value == rhs._value; } \ |
| 163 | \ |
| 164 | template< \ |
| 165 | typename = std::enable_if_t< \ |
| 166 | has_equality_operator_v \ |
| 167 | > \ |
| 168 | > \ |
| 169 | bool operator!=(const _name& rhs) const noexcept { return !(_value == rhs._value); } \ |
| 170 | \ |
| 171 | template< \ |
| 172 | typename = std::enable_if_t< \ |
| 173 | has_less_than_operator_v \ |
| 174 | > \ |
| 175 | > \ |
| 176 | bool operator<=(const _name& rhs) const noexcept { return _value <= rhs._value; } \ |
| 177 | \ |
| 178 | \ |
| 179 | template< \ |
| 180 | typename = std::enable_if_t< \ |
| 181 | has_strictly_less_than_operator_v \ |
| 182 | > \ |
| 183 | > \ |
| 184 | bool operator<(const _name& rhs) const noexcept { return _value < rhs._value; } \ |
| 185 | \ |
| 186 | template< \ |
| 187 | typename = std::enable_if_t< \ |
| 188 | has_greater_than_operator_v \ |
| 189 | > \ |
| 190 | > \ |
| 191 | bool operator>=(const _name& rhs) const noexcept { return _value >= rhs._value; } \ |
| 192 | \ |
| 193 | template< \ |
| 194 | typename = std::enable_if_t< \ |
| 195 | has_strictly_greater_than_operator_v \ |
| 196 | > \ |
| 197 | > \ |
| 198 | bool operator>(const _name& rhs) const noexcept { return _value > rhs._value; } \ |
| 199 | \ |
| 200 | private: \ |
| 201 | _name(wrapped_type value) : _value{std::move(value)} {} \ |
| 202 | \ |
| 203 | private: \ |
| 204 | wrapped_type _value; \ |
| 205 | }; \ |
| 206 | \ |
| 207 | inline std::optional<_name> _name::refine(wrapped_type value) |
| 208 | |