diff options
Diffstat (limited to 'js/public/ComparisonOperators.h')
-rw-r--r-- | js/public/ComparisonOperators.h | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/js/public/ComparisonOperators.h b/js/public/ComparisonOperators.h new file mode 100644 index 0000000000..c7f03fa4ca --- /dev/null +++ b/js/public/ComparisonOperators.h @@ -0,0 +1,237 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Support comparison operations on wrapper types -- e.g. |JS::Rooted<T>|, + * |JS::Handle<T>|, and so on -- against raw |T| values, against pointers or + * |nullptr| if the wrapper is a pointer wrapper, and against other wrappers + * around compatible types. + */ + +#ifndef js_ComparisonOperators_h +#define js_ComparisonOperators_h + +#include <type_traits> // std::false_type, std::true_type, std::enable_if_t, std::is_pointer_v, std::remove_pointer_t + +// To define |operator==| and |operator!=| for a wrapper class |W| (which may +// or may not be a template class) that contains a |T|: +// +// * Specialize |JS::detail::DefineComparisonOps| for |W|: +// - Make it inherit from |std::true_type|. +// - Include within your specialization a |static get(const W& v)| function +// that returns the value (which may be an lvalue reference) of the |T| in +// |W|. +// * If needed, add |using JS::detail::wrapper_comparison::operator==;| and +// |using JS::detail::wrapper_comparison::operator!=;| to the namespace +// directly containing |W| at the end of this header. (If you are not in +// SpiderMonkey code and have questionably decided to define your own +// wrapper class, add these to its namespace somewhere in your code.) +// +// The first step opts the wrapper class into comparison support and defines a +// generic means of extracting a comparable |T| out of an instance. +// +// The second step ensures that symmetric |operator==| and |operator!=| are +// exposed for the wrapper, accepting two wrappers or a wrapper and a suitable +// raw value. +// +// Failure to perform *both* steps will likely result in errors like +// 'invalid operands to binary expression' or 'no match for operator==' +// when comparing an instance of your wrapper. + +namespace JS { + +namespace detail { + +// By default, comparison ops are not supported for types. +template <typename T> +struct DefineComparisonOps : std::false_type {}; + +// Define functions for the core equality operations, that the actual operators +// can all invoke. + +// Compare two wrapper types. Assumes both wrapper types support comparison +// operators. +template <typename W, typename OW> +inline bool WrapperEqualsWrapper(const W& wrapper, const OW& other) { + return JS::detail::DefineComparisonOps<W>::get(wrapper) == + JS::detail::DefineComparisonOps<OW>::get(other); +} + +// Compare a wrapper against a value of its unwrapped element type (or against a +// value that implicitly converts to that unwrapped element type). Assumes its +// wrapper argument supports comparison operators. +template <typename W> +inline bool WrapperEqualsUnwrapped(const W& wrapper, + const typename W::ElementType& value) { + return JS::detail::DefineComparisonOps<W>::get(wrapper) == value; +} + +// Compare a wrapper containing a pointer against a pointer to const element +// type. Assumes its wrapper argument supports comparison operators. +template <typename W> +inline bool WrapperEqualsPointer( + const W& wrapper, + const typename std::remove_pointer_t<typename W::ElementType>* ptr) { + return JS::detail::DefineComparisonOps<W>::get(wrapper) == ptr; +} + +// It is idiomatic C++ to define operators on user-defined types in the +// namespace of their operands' types (not at global scope, which isn't examined +// if at point of operator use another operator definition shadows the global +// definition). But our wrappers live in *multiple* namespaces (|namespace js| +// and |namespace JS| in SpiderMonkey), so we can't literally do that without +// defining ambiguous overloads. +// +// Instead, we define the operators *once* in a namespace containing nothing +// else at all. Then we |using| the operators into each namespace containing +// a wrapper type. |using| creates *aliases*, so two |using|s of the same +// operator contribute only one overload to overload resolution. +namespace wrapper_comparison { + +// Comparisons between potentially-differing wrappers. +template <typename W, typename OW> +inline typename std::enable_if_t<JS::detail::DefineComparisonOps<W>::value && + JS::detail::DefineComparisonOps<OW>::value, + bool> +operator==(const W& wrapper, const OW& other) { + return JS::detail::WrapperEqualsWrapper(wrapper, other); +} + +template <typename W, typename OW> +inline typename std::enable_if_t<JS::detail::DefineComparisonOps<W>::value && + JS::detail::DefineComparisonOps<OW>::value, + bool> +operator!=(const W& wrapper, const OW& other) { + return !JS::detail::WrapperEqualsWrapper(wrapper, other); +} + +// Comparisons between a wrapper and its unwrapped element type. +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator==(const W& wrapper, const typename W::ElementType& value) { + return WrapperEqualsUnwrapped(wrapper, value); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator!=(const W& wrapper, const typename W::ElementType& value) { + return !WrapperEqualsUnwrapped(wrapper, value); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator==(const typename W::ElementType& value, const W& wrapper) { + return WrapperEqualsUnwrapped(wrapper, value); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator!=(const typename W::ElementType& value, const W& wrapper) { + return !WrapperEqualsUnwrapped(wrapper, value); +} + +// For wrappers around a pointer type, comparisons between a wrapper object +// and a const element pointer. +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value && + std::is_pointer_v<typename W::ElementType>, + bool> +operator==(const W& wrapper, + const typename std::remove_pointer_t<typename W::ElementType>* ptr) { + return WrapperEqualsPointer(wrapper, ptr); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value && + std::is_pointer_v<typename W::ElementType>, + bool> +operator!=(const W& wrapper, + const typename std::remove_pointer_t<typename W::ElementType>* ptr) { + return !WrapperEqualsPointer(wrapper, ptr); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value && + std::is_pointer_v<typename W::ElementType>, + bool> +operator==(const typename std::remove_pointer_t<typename W::ElementType>* ptr, + const W& wrapper) { + return WrapperEqualsPointer(wrapper, ptr); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value && + std::is_pointer_v<typename W::ElementType>, + bool> +operator!=(const typename std::remove_pointer_t<typename W::ElementType>* ptr, + const W& wrapper) { + return !WrapperEqualsPointer(wrapper, ptr); +} + +// For wrappers around a pointer type, comparisons between a wrapper object +// and |nullptr|. +// +// These overloads are a workaround for gcc hazard build bugs. Per spec, +// |nullptr -> const T*| for the wrapper-pointer operators immediately above +// this is a standard conversion sequence (consisting of a single pointer +// conversion). Meanwhile, |nullptr -> T* const&| for the wrapper-element +// operators just above that, is a pointer conversion to |T*|, then an identity +// conversion of the |T* const| to a reference. The former conversion sequence +// is a proper subsequence of the latter, so it *should* be a better conversion +// sequence and thus should be the better overload. But gcc doesn't implement +// things this way, so we add overloads directly targeting |nullptr| as an exact +// match, preferred to either of those overloads. +// +// We should be able to remove these overloads when gcc hazard builds use modern +// clang. +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator==(const W& wrapper, std::nullptr_t) { + return WrapperEqualsUnwrapped(wrapper, nullptr); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator!=(const W& wrapper, std::nullptr_t) { + return !WrapperEqualsUnwrapped(wrapper, nullptr); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator==(std::nullptr_t, const W& wrapper) { + return WrapperEqualsUnwrapped(wrapper, nullptr); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator!=(std::nullptr_t, const W& wrapper) { + return !WrapperEqualsUnwrapped(wrapper, nullptr); +} + +} // namespace wrapper_comparison + +} // namespace detail + +} // namespace JS + +// Expose wrapper-supporting |operator==| and |operator!=| in the namespaces of +// all SpiderMonkey's wrapper classes that support comparisons. + +namespace JS { + +using JS::detail::wrapper_comparison::operator==; +using JS::detail::wrapper_comparison::operator!=; + +} // namespace JS + +namespace js { + +using JS::detail::wrapper_comparison::operator==; +using JS::detail::wrapper_comparison::operator!=; + +} // namespace js + +#endif // js_ComparisonOperators_h |