From c05b5c2b79f9ab301fee587ee781b9c8e18b8a2f Mon Sep 17 00:00:00 2001
From: Alexey Rusakov <Kitsune-Ral@users.sf.net>
Date: Fri, 16 Jul 2021 20:03:06 +0200
Subject: MembershipType -> Membership, also used for JoinState

Instead of being defined independently, JoinState now uses values from
the Membership enumeration (former MemberEventContent::MembershipType)
that was moved to quotient_common.h for that purpose. Both enumerations
gained a Q_FLAG_NS decoration and operator<< overrides that strip
"Quotient::" prefix when dumping member/join state values to the log -
obviating toCString(JoinState) along the way. Quotient::MembershipType
alias is deprecated from now.
---
 CMakeLists.txt                 |  6 +---
 lib/connection.cpp             |  4 +--
 lib/connection.h               |  1 -
 lib/events/roommemberevent.cpp | 65 ++++++++++++++++++++--------------------
 lib/events/roommemberevent.h   | 26 ++++++++--------
 lib/joinstate.h                | 32 --------------------
 lib/quotient_common.cpp        | 45 ++++++++++++++++++++++++++++
 lib/quotient_common.h          | 67 +++++++++++++++++++++++++++++++++++++-----
 lib/room.cpp                   | 49 ++++++++++++++++--------------
 lib/room.h                     |  9 +++++-
 lib/syncdata.h                 |  2 +-
 lib/user.cpp                   |  2 +-
 12 files changed, 190 insertions(+), 118 deletions(-)
 delete mode 100644 lib/joinstate.h
 create mode 100644 lib/quotient_common.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index deb50aea..49105389 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -122,11 +122,7 @@ endif ()
 
 # Set up source files
 list(APPEND lib_SRCS
-    # This .h is special in that it declares a Q_NAMESPACE but has no .cpp
-    # where staticMetaObject for that namespace would be defined; passing it
-    # to add_library (see below) puts it on the automoc radar, producing
-    # a compilation unit with the needed definition.
-    lib/quotient_common.h
+    lib/quotient_common.cpp
     lib/networkaccessmanager.cpp
     lib/connectiondata.cpp
     lib/connection.cpp
diff --git a/lib/connection.cpp b/lib/connection.cpp
index e076957a..7dd04aaa 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -640,7 +640,7 @@ void Connection::Private::consumeRoomData(SyncDataList&& roomDataList,
             }
             qWarning(MAIN) << "Room" << roomData.roomId
                            << "has just been forgotten but /sync returned it in"
-                           << toCString(roomData.joinState)
+                           << roomData.joinState
                            << "state - suspiciously fast turnaround";
         }
         if (auto* r = q->provideRoom(roomData.roomId, roomData.joinState)) {
@@ -1356,7 +1356,7 @@ void Connection::Private::removeRoom(const QString& roomId)
     for (auto f : { false, true })
         if (auto r = roomMap.take({ roomId, f })) {
             qCDebug(MAIN) << "Room" << r->objectName() << "in state"
-                          << toCString(r->joinState()) << "will be deleted";
+                          << r->joinState() << "will be deleted";
             emit r->beforeDestruction(r);
             r->deleteLater();
         }
diff --git a/lib/connection.h b/lib/connection.h
index 0d22d01f..a7a071f3 100644
--- a/lib/connection.h
+++ b/lib/connection.h
@@ -6,7 +6,6 @@
 #pragma once
 
 #include "ssosession.h"
-#include "joinstate.h"
 #include "qt_connection_util.h"
 #include "quotient_common.h"
 
diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp
index 9634ca3a..8a6bddd8 100644
--- a/lib/events/roommemberevent.cpp
+++ b/lib/events/roommemberevent.cpp
@@ -7,27 +7,26 @@
 #include "converters.h"
 #include "logging.h"
 
-#include <array>
-
-static const std::array<QString, 5> membershipStrings = {
-    { QStringLiteral("invite"), QStringLiteral("join"), QStringLiteral("knock"),
-      QStringLiteral("leave"), QStringLiteral("ban") }
-};
+#include <QtCore/QtAlgorithms>
 
 namespace Quotient {
 template <>
-struct JsonConverter<MembershipType> {
-    static MembershipType load(const QJsonValue& jv)
+struct JsonConverter<Membership> {
+    static Membership load(const QJsonValue& jv)
     {
-        const auto& membershipString = jv.toString();
-        for (auto it = membershipStrings.begin(); it != membershipStrings.end();
-             ++it)
-            if (membershipString == *it)
-                return MembershipType(it - membershipStrings.begin());
-
-        if (!membershipString.isEmpty())
-            qCWarning(EVENTS) << "Unknown MembershipType: " << membershipString;
-        return MembershipType::Undefined;
+        const auto& ms = jv.toString();
+        if (ms.isEmpty())
+        {
+            qCWarning(EVENTS) << "Empty member state:" << ms;
+            return Membership::Invalid;
+        }
+        const auto it =
+            std::find(MembershipStrings.begin(), MembershipStrings.end(), ms);
+        if (it != MembershipStrings.end())
+            return Membership(1U << (it - MembershipStrings.begin()));
+
+        qCWarning(EVENTS) << "Unknown Membership value: " << ms;
+        return Membership::Invalid;
     }
 };
 } // namespace Quotient
@@ -35,7 +34,7 @@ struct JsonConverter<MembershipType> {
 using namespace Quotient;
 
 MemberEventContent::MemberEventContent(const QJsonObject& json)
-    : membership(fromJson<MembershipType>(json["membership"_ls]))
+    : membership(fromJson<Membership>(json["membership"_ls]))
     , isDirect(json["is_direct"_ls].toBool())
     , displayName(fromJson<Omittable<QString>>(json["displayname"_ls]))
     , avatarUrl(fromJson<Omittable<QString>>(json["avatar_url"_ls]))
@@ -48,10 +47,12 @@ MemberEventContent::MemberEventContent(const QJsonObject& json)
 void MemberEventContent::fillJson(QJsonObject* o) const
 {
     Q_ASSERT(o);
-    Q_ASSERT_X(membership != MembershipType::Undefined, __FUNCTION__,
-               "The key 'membership' must be explicit in MemberEventContent");
-    if (membership != MembershipType::Undefined)
-        o->insert(QStringLiteral("membership"), membershipStrings[membership]);
+    if (membership != Membership::Invalid)
+        o->insert(
+            QStringLiteral("membership"),
+            MembershipStrings[qCountTrailingZeroBits(
+                                  std::underlying_type_t<Membership>(membership))
+                              + 1]);
     if (displayName)
         o->insert(QStringLiteral("displayname"), *displayName);
     if (avatarUrl && avatarUrl->isValid())
@@ -67,37 +68,37 @@ bool RoomMemberEvent::changesMembership() const
 
 bool RoomMemberEvent::isInvite() const
 {
-    return membership() == MembershipType::Invite && changesMembership();
+    return membership() == Membership::Invite && changesMembership();
 }
 
 bool RoomMemberEvent::isRejectedInvite() const
 {
-    return membership() == MembershipType::Leave && prevContent()
-           && prevContent()->membership == MembershipType::Invite;
+    return membership() == Membership::Leave && prevContent()
+           && prevContent()->membership == Membership::Invite;
 }
 
 bool RoomMemberEvent::isJoin() const
 {
-    return membership() == MembershipType::Join && changesMembership();
+    return membership() == Membership::Join && changesMembership();
 }
 
 bool RoomMemberEvent::isLeave() const
 {
-    return membership() == MembershipType::Leave && prevContent()
+    return membership() == Membership::Leave && prevContent()
            && prevContent()->membership != membership()
-           && prevContent()->membership != MembershipType::Ban
-           && prevContent()->membership != MembershipType::Invite;
+           && prevContent()->membership != Membership::Ban
+           && prevContent()->membership != Membership::Invite;
 }
 
 bool RoomMemberEvent::isBan() const
 {
-    return membership() == MembershipType::Ban && changesMembership();
+    return membership() == Membership::Ban && changesMembership();
 }
 
 bool RoomMemberEvent::isUnban() const
 {
-    return membership() == MembershipType::Leave && prevContent()
-           && prevContent()->membership == MembershipType::Ban;
+    return membership() == Membership::Leave && prevContent()
+           && prevContent()->membership == Membership::Ban;
 }
 
 bool RoomMemberEvent::isRename() const
diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h
index f2fbe689..f3047159 100644
--- a/lib/events/roommemberevent.h
+++ b/lib/events/roommemberevent.h
@@ -7,23 +7,21 @@
 
 #include "eventcontent.h"
 #include "stateevent.h"
+#include "quotient_common.h"
 
 namespace Quotient {
 class MemberEventContent : public EventContent::Base {
 public:
-    enum MembershipType : unsigned char {
-        Invite = 0,
-        Join,
-        Knock,
-        Leave,
-        Ban,
-        Undefined
-    };
+    using MembershipType
+        [[deprecated("Use Quotient::Membership instead")]] = Membership;
 
-    explicit MemberEventContent(MembershipType mt = Join) : membership(mt) {}
+    explicit MemberEventContent(Membership ms = Membership::Join)
+        : membership(ms)
+    {}
     explicit MemberEventContent(const QJsonObject& json);
 
-    MembershipType membership;
+    Membership membership;
+    /// (Only for invites) Whether the invite is to a direct chat
     bool isDirect = false;
     Omittable<QString> displayName;
     Omittable<QUrl> avatarUrl;
@@ -33,15 +31,15 @@ protected:
     void fillJson(QJsonObject* o) const override;
 };
 
-using MembershipType = MemberEventContent::MembershipType;
+using MembershipType [[deprecated("Use Membership instead")]] = Membership;
 
 class RoomMemberEvent : public StateEvent<MemberEventContent> {
     Q_GADGET
 public:
     DEFINE_EVENT_TYPEID("m.room.member", RoomMemberEvent)
 
-    using MembershipType = MemberEventContent::MembershipType;
-    Q_ENUM(MembershipType)
+    using MembershipType
+        [[deprecated("Use Quotient::Membership instead")]] = Membership;
 
     explicit RoomMemberEvent(const QJsonObject& obj) : StateEvent(typeId(), obj)
     {}
@@ -65,7 +63,7 @@ public:
         : StateEvent(type, fullJson)
     {}
 
-    MembershipType membership() const { return content().membership; }
+    Membership membership() const { return content().membership; }
     QString userId() const { return stateKey(); }
     bool isDirect() const { return content().isDirect; }
     Omittable<QString> newDisplayName() const { return content().displayName; }
diff --git a/lib/joinstate.h b/lib/joinstate.h
deleted file mode 100644
index 805ce73a..00000000
--- a/lib/joinstate.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// SPDX-FileCopyrightText: 2016 Kitsune Ral <Kitsune-Ral@users.sf.net>
-// SPDX-License-Identifier: LGPL-2.1-or-later
-
-#pragma once
-
-#include <QtCore/QFlags>
-
-#include <array>
-
-namespace Quotient {
-enum class JoinState : unsigned int {
-    Join = 0x1,
-    Invite = 0x2,
-    Leave = 0x4,
-};
-
-Q_DECLARE_FLAGS(JoinStates, JoinState)
-
-// We cannot use Q_ENUM outside of a Q_OBJECT and besides, we want
-// to use strings that match respective JSON keys.
-static const std::array<const char*, 3> JoinStateStrings { { "join", "invite",
-                                                             "leave" } };
-
-inline const char* toCString(JoinState js)
-{
-    size_t state = size_t(js), index = 0;
-    while (state >>= 1u)
-        ++index;
-    return JoinStateStrings[index];
-}
-} // namespace Quotient
-Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::JoinStates)
diff --git a/lib/quotient_common.cpp b/lib/quotient_common.cpp
new file mode 100644
index 00000000..805070d1
--- /dev/null
+++ b/lib/quotient_common.cpp
@@ -0,0 +1,45 @@
+#include "quotient_common.h"
+
+#include <QtCore/QDebug>
+
+using namespace Quotient;
+
+template <typename Enum>
+inline QDebug suppressScopeAndDump(QDebug dbg, Enum e)
+{
+    // Suppress "Quotient::" prefix
+    QDebugStateSaver _dss(dbg);
+    dbg.setVerbosity(QDebug::MinimumVerbosity);
+    return qt_QMetaEnum_debugOperator(dbg, std::underlying_type_t<Enum>(e),
+                                      qt_getEnumMetaObject(e),
+                                      qt_getEnumName(e));
+}
+
+template <typename Enum>
+inline QDebug suppressScopeAndDump(QDebug dbg, const QFlags<Enum>& f)
+{
+    // Suppress "Quotient::" prefix
+    QDebugStateSaver _dss(dbg);
+    dbg.setVerbosity(QDebug::MinimumVerbosity);
+    return qt_QMetaEnum_flagDebugOperator_helper(dbg, f);
+}
+
+QDebug operator<<(QDebug dbg, Membership m)
+{
+    return suppressScopeAndDump(dbg, m);
+}
+
+QDebug operator<<(QDebug dbg, MembershipMask mm)
+{
+    return suppressScopeAndDump(dbg, mm) << ")";
+}
+
+QDebug operator<<(QDebug dbg, JoinState js)
+{
+    return suppressScopeAndDump(dbg, js);
+}
+
+QDebug operator<<(QDebug dbg, JoinStates jss)
+{
+    return suppressScopeAndDump(dbg, jss) << ")";
+}
diff --git a/lib/quotient_common.h b/lib/quotient_common.h
index 22fdbe94..789128cf 100644
--- a/lib/quotient_common.h
+++ b/lib/quotient_common.h
@@ -8,15 +8,62 @@
 namespace Quotient {
 Q_NAMESPACE
 
-/** Enumeration with flags defining the network job running policy
- * So far only background/foreground flags are available.
- *
- * \sa Connection::callApi, Connection::run
- */
+// TODO: code like this should be generated from the CS API definition
+
+//! \brief Membership states
+//!
+//! These are used for member events. The names here are case-insensitively
+//! equal to state names used on the wire.
+//! \sa MemberEventContent, RoomMemberEvent
+enum class Membership : unsigned int {
+    // Specific power-of-2 values (1,2,4,...) are important here as syncdata.cpp
+    // depends on that, as well as Join being the first in line
+    Invalid = 0x0,
+    Join = 0x1,
+    Leave = 0x2,
+    Invite = 0x4,
+    Knock = 0x8,
+    Ban = 0x10,
+    Undefined = Invalid
+};
+Q_DECLARE_FLAGS(MembershipMask, Membership)
+Q_FLAG_NS(MembershipMask)
+
+constexpr inline std::array MembershipStrings = {
+    // The order MUST be the same as the order in the original enum
+    "join", "leave", "invite", "knock", "ban"
+};
+
+//! \brief Local user join-state names
+//!
+//! This represents a subset of Membership values that may arrive as the local
+//! user's state grouping for the sync response.
+//! \sa SyncData
+enum class JoinState : std::underlying_type_t<Membership> {
+    Invalid = std::underlying_type_t<Membership>(Membership::Invalid),
+    Join = std::underlying_type_t<Membership>(Membership::Join),
+    Leave = std::underlying_type_t<Membership>(Membership::Leave),
+    Invite = std::underlying_type_t<Membership>(Membership::Invite),
+    Knock = std::underlying_type_t<Membership>(Membership::Knock),
+};
+Q_DECLARE_FLAGS(JoinStates, JoinState)
+Q_FLAG_NS(JoinStates)
+
+constexpr inline std::array JoinStateStrings {
+    MembershipStrings[0], MembershipStrings[1], MembershipStrings[2],
+    MembershipStrings[3] /* same as MembershipStrings, sans "ban" */
+};
+
+//! \brief Network job running policy flags
+//!
+//! So far only background/foreground flags are available.
+//! \sa Connection::callApi, Connection::run
 enum RunningPolicy { ForegroundRequest = 0x0, BackgroundRequest = 0x1 };
 
 Q_ENUM_NS(RunningPolicy)
 
+//! \brief The result of URI resolution using UriResolver
+//! \sa UriResolver
 enum UriResolveResult : short {
     StillResolving = -1,
     UriResolved = 0,
@@ -28,5 +75,11 @@ enum UriResolveResult : short {
 Q_ENUM_NS(UriResolveResult)
 
 } // namespace Quotient
-/// \deprecated Use namespace Quotient instead
-namespace QMatrixClient = Quotient;
+Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::MembershipMask)
+Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::JoinStates)
+
+class QDebug;
+QDebug operator<<(QDebug dbg, Quotient::Membership m);
+QDebug operator<<(QDebug dbg, Quotient::MembershipMask m);
+QDebug operator<<(QDebug dbg, Quotient::JoinState js);
+QDebug operator<<(QDebug dbg, Quotient::JoinStates js);
diff --git a/lib/room.cpp b/lib/room.cpp
index 6b729b8f..54c67c2f 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -460,7 +460,7 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState)
             emit baseStateLoaded();
         return this == r; // loadedRoomState fires only once per room
     });
-    qCDebug(STATE) << "New" << toCString(initialJoinState) << "Room:" << id;
+    qCDebug(STATE) << "New" << initialJoinState << "Room:" << id;
 }
 
 Room::~Room() { delete d; }
@@ -603,6 +603,11 @@ JoinState Room::memberJoinState(User* user) const
                                                           : JoinState::Leave;
 }
 
+Membership Room::memberState(User* user) const
+{
+    return d->getCurrentState<RoomMemberEvent>(user->id())->membership();
+}
+
 JoinState Room::joinState() const { return d->joinState; }
 
 void Room::setJoinState(JoinState state)
@@ -611,8 +616,8 @@ void Room::setJoinState(JoinState state)
     if (state == oldState)
         return;
     d->joinState = state;
-    qCDebug(STATE) << "Room" << id() << "changed state: " << int(oldState)
-                   << "->" << int(state);
+    qCDebug(STATE) << "Room" << id() << "changed state: " << oldState
+                   << "->" << state;
     emit changed(Change::JoinStateChange);
     emit joinStateChanged(oldState, state);
 }
@@ -2513,16 +2518,16 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
                 return false; // Stay low and hope for the best...
             }
             const auto prevMembership = oldRme ? oldRme->membership()
-                                               : MembershipType::Leave;
+                                               : Membership::Leave;
             switch (prevMembership) {
-            case MembershipType::Invite:
+            case Membership::Invite:
                 if (rme.membership() != prevMembership) {
                     d->usersInvited.removeOne(u);
                     Q_ASSERT(!d->usersInvited.contains(u));
                 }
                 break;
-            case MembershipType::Join:
-                if (rme.membership() == MembershipType::Join) {
+            case Membership::Join:
+                if (rme.membership() == Membership::Join) {
                     // rename/avatar change or no-op
                     if (rme.newDisplayName()) {
                         emit memberAboutToRename(u, *rme.newDisplayName());
@@ -2536,7 +2541,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
                         return false;
                     }
                 } else {
-                    if (rme.membership() == MembershipType::Invite)
+                    if (rme.membership() == Membership::Invite)
                         qCWarning(MAIN)
                             << "Membership change from Join to Invite:" << rme;
                     // whatever the new membership, it's no more Join
@@ -2544,16 +2549,16 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
                     emit userRemoved(u);
                 }
                 break;
-            case MembershipType::Ban:
-            case MembershipType::Knock:
-            case MembershipType::Leave:
-                if (rme.membership() == MembershipType::Invite
-                    || rme.membership() == MembershipType::Join) {
+            case Membership::Ban:
+            case Membership::Knock:
+            case Membership::Leave:
+                if (rme.membership() == Membership::Invite
+                    || rme.membership() == Membership::Join) {
                     d->membersLeft.removeOne(u);
                     Q_ASSERT(!d->membersLeft.contains(u));
                 }
                 break;
-            case MembershipType::Undefined:
+            case Membership::Undefined:
                 ; // A warning will be dropped in the post-processing block below
             }
             return true;
@@ -2636,10 +2641,10 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
                 static_cast<const RoomMemberEvent*>(oldStateEvent);
             const auto prevMembership = oldMemberEvent
                                             ? oldMemberEvent->membership()
-                                            : MembershipType::Leave;
+                                            : Membership::Leave;
             switch (evt.membership()) {
-            case MembershipType::Join:
-                if (prevMembership != MembershipType::Join) {
+            case Membership::Join:
+                if (prevMembership != Membership::Join) {
                     d->insertMemberIntoMap(u);
                     emit userAdded(u);
                 } else {
@@ -2651,19 +2656,19 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
                         emit memberAvatarChanged(u);
                 }
                 break;
-            case MembershipType::Invite:
+            case Membership::Invite:
                 if (!d->usersInvited.contains(u))
                     d->usersInvited.push_back(u);
                 if (u == localUser() && evt.isDirect())
                     connection()->addToDirectChats(this, user(evt.senderId()));
                 break;
-            case MembershipType::Knock:
-            case MembershipType::Ban:
-            case MembershipType::Leave:
+            case Membership::Knock:
+            case Membership::Ban:
+            case Membership::Leave:
                 if (!d->membersLeft.contains(u))
                     d->membersLeft.append(u);
                 break;
-            case MembershipType::Undefined:
+            case Membership::Undefined:
                 qCWarning(MEMBERS) << "Ignored undefined membership type";
             }
             return MembersChange;
diff --git a/lib/room.h b/lib/room.h
index d71bff9c..cdbfe58f 100644
--- a/lib/room.h
+++ b/lib/room.h
@@ -11,7 +11,7 @@
 
 #include "connection.h"
 #include "eventitem.h"
-#include "joinstate.h"
+#include "quotient_common.h"
 
 #include "csapi/message_pagination.h"
 
@@ -245,6 +245,8 @@ public:
     /**
      * \brief Check the join state of a given user in this room
      *
+     * \deprecated Use memberState and check against a mask
+     *
      * \note Banned and invited users are not tracked separately for now (Leave
      *       will be returned for them).
      *
@@ -252,6 +254,11 @@ public:
      */
     Q_INVOKABLE Quotient::JoinState memberJoinState(Quotient::User* user) const;
 
+    //! \brief Check the join state of a given user in this room
+    //!
+    //! \return the given user's state with respect to the room
+    Q_INVOKABLE Quotient::Membership memberState(User* user) const;
+
     //! \brief Get a display name (without disambiguation) for the given member
     //!
     //! \sa safeMemberName, htmlSafeMemberName
diff --git a/lib/syncdata.h b/lib/syncdata.h
index e69bac17..0153bfd6 100644
--- a/lib/syncdata.h
+++ b/lib/syncdata.h
@@ -3,7 +3,7 @@
 
 #pragma once
 
-#include "joinstate.h"
+#include "quotient_common.h"
 
 #include "events/stateevent.h"
 
diff --git a/lib/user.cpp b/lib/user.cpp
index 6cc9e161..c97e33a4 100644
--- a/lib/user.cpp
+++ b/lib/user.cpp
@@ -123,7 +123,7 @@ void User::rename(const QString& newName, const Room* r)
     }
     // #481: take the current state and update it with the new name
     auto evtC = r->getCurrentState<RoomMemberEvent>(id())->content();
-    Q_ASSERT_X(evtC.membership == MembershipType::Join, __FUNCTION__,
+    Q_ASSERT_X(evtC.membership == Membership::Join, __FUNCTION__,
                "Attempt to rename a user that's not a room member");
     evtC.displayName = sanitized(newName);
     r->setState<RoomMemberEvent>(id(), move(evtC));
-- 
cgit v1.2.3


From e05402045261a404ad5d8add63b82672d3d9aebb Mon Sep 17 00:00:00 2001
From: Alexey Rusakov <Kitsune-Ral@users.sf.net>
Date: Sat, 17 Jul 2021 13:47:19 +0200
Subject: Fix building with Qt 5.12

---
 lib/quotient_common.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/quotient_common.cpp b/lib/quotient_common.cpp
index 805070d1..5d7a3027 100644
--- a/lib/quotient_common.cpp
+++ b/lib/quotient_common.cpp
@@ -9,7 +9,7 @@ inline QDebug suppressScopeAndDump(QDebug dbg, Enum e)
 {
     // Suppress "Quotient::" prefix
     QDebugStateSaver _dss(dbg);
-    dbg.setVerbosity(QDebug::MinimumVerbosity);
+    dbg.setVerbosity(0 /* QDebug::MinimumVerbosity since Qt 5.13 */);
     return qt_QMetaEnum_debugOperator(dbg, std::underlying_type_t<Enum>(e),
                                       qt_getEnumMetaObject(e),
                                       qt_getEnumName(e));
@@ -20,7 +20,7 @@ inline QDebug suppressScopeAndDump(QDebug dbg, const QFlags<Enum>& f)
 {
     // Suppress "Quotient::" prefix
     QDebugStateSaver _dss(dbg);
-    dbg.setVerbosity(QDebug::MinimumVerbosity);
+    dbg.setVerbosity(0 /* QDebug::MinimumVerbosity since Qt 5.13 */);
     return qt_QMetaEnum_flagDebugOperator_helper(dbg, f);
 }
 
-- 
cgit v1.2.3


From 3c28d13c1a61999e7c3141f3ca08b5b734bd160c Mon Sep 17 00:00:00 2001
From: Alexey Rusakov <Kitsune-Ral@users.sf.net>
Date: Sun, 18 Jul 2021 19:00:07 +0200
Subject: Introduce to_array() to fix building on macOS

A previous incarnation, make_array, existed in basejob.cpp before.
The new direction taken by C++20 is to either deduce the array (but
the used Apple standard library doesn't have deduction guides yet) or
to use to_array() that converts a C array to std::array. This latter
option is taken here, with to_array() defined in quotient_common.h
until we move over to C++20.
---
 lib/jobs/basejob.cpp  | 13 +++----------
 lib/quotient_common.h | 27 ++++++++++++++++++++++-----
 2 files changed, 25 insertions(+), 15 deletions(-)

diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp
index 9a7b9b5e..400a9243 100644
--- a/lib/jobs/basejob.cpp
+++ b/lib/jobs/basejob.cpp
@@ -5,6 +5,7 @@
 #include "basejob.h"
 
 #include "connectiondata.h"
+#include "quotient_common.h"
 
 #include <QtCore/QRegularExpression>
 #include <QtCore/QTimer>
@@ -15,8 +16,6 @@
 #include <QtNetwork/QNetworkReply>
 #include <QtNetwork/QNetworkRequest>
 
-#include <array>
-
 using namespace Quotient;
 using std::chrono::seconds, std::chrono::milliseconds;
 using namespace std::chrono_literals;
@@ -63,12 +62,6 @@ QDebug BaseJob::Status::dumpToLog(QDebug dbg) const
     return dbg << ": " << message;
 }
 
-template <typename... Ts>
-constexpr auto make_array(Ts&&... items)
-{
-    return std::array<std::common_type_t<Ts...>, sizeof...(Ts)>({items...});
-}
-
 class BaseJob::Private {
 public:
     struct JobTimeoutConfig {
@@ -163,8 +156,8 @@ public:
     {
         // FIXME: use std::array {} when Apple stdlib gets deduction guides for it
         static const auto verbs =
-            make_array(QStringLiteral("GET"), QStringLiteral("PUT"),
-                       QStringLiteral("POST"), QStringLiteral("DELETE"));
+            to_array({ QStringLiteral("GET"), QStringLiteral("PUT"),
+                       QStringLiteral("POST"), QStringLiteral("DELETE") });
         const auto verbWord = verbs.at(size_t(verb));
         return verbWord % ' '
                % (reply ? reply->url().toString(QUrl::RemoveQuery)
diff --git a/lib/quotient_common.h b/lib/quotient_common.h
index 789128cf..037d5ded 100644
--- a/lib/quotient_common.h
+++ b/lib/quotient_common.h
@@ -8,6 +8,24 @@
 namespace Quotient {
 Q_NAMESPACE
 
+namespace impl {
+    template <class T, std::size_t N, std::size_t... I>
+    constexpr std::array<std::remove_cv_t<T>, N>
+        to_array_impl(T (&&a)[N], std::index_sequence<I...>)
+    {
+        return { {std::move(a[I])...} };
+    }
+}
+// std::array {} needs explicit template parameters on macOS because
+// Apple stdlib doesn't have deduction guides for std::array; to alleviate that,
+// to_array() is borrowed from C++20 (thanks to cppreference for the possible
+// implementation: https://en.cppreference.com/w/cpp/container/array/to_array)
+template <typename T, size_t N>
+constexpr auto to_array(T (&& items)[N])
+{
+    return impl::to_array_impl(std::move(items), std::make_index_sequence<N>{});
+}
+
 // TODO: code like this should be generated from the CS API definition
 
 //! \brief Membership states
@@ -29,10 +47,9 @@ enum class Membership : unsigned int {
 Q_DECLARE_FLAGS(MembershipMask, Membership)
 Q_FLAG_NS(MembershipMask)
 
-constexpr inline std::array MembershipStrings = {
+constexpr inline auto MembershipStrings = to_array(
     // The order MUST be the same as the order in the original enum
-    "join", "leave", "invite", "knock", "ban"
-};
+    { "join", "leave", "invite", "knock", "ban" });
 
 //! \brief Local user join-state names
 //!
@@ -49,10 +66,10 @@ enum class JoinState : std::underlying_type_t<Membership> {
 Q_DECLARE_FLAGS(JoinStates, JoinState)
 Q_FLAG_NS(JoinStates)
 
-constexpr inline std::array JoinStateStrings {
+constexpr inline auto JoinStateStrings = to_array({
     MembershipStrings[0], MembershipStrings[1], MembershipStrings[2],
     MembershipStrings[3] /* same as MembershipStrings, sans "ban" */
-};
+});
 
 //! \brief Network job running policy flags
 //!
-- 
cgit v1.2.3


From 2187f26ebdc9edf7b3cbfa1d208c03c4384f4135 Mon Sep 17 00:00:00 2001
From: Alexey Rusakov <Kitsune-Ral@users.sf.net>
Date: Sun, 18 Jul 2021 20:56:33 +0200
Subject: Add a missing #include

Without this, it compiles on Linux but on macOS and Windows.
---
 lib/quotient_common.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lib/quotient_common.h b/lib/quotient_common.h
index 037d5ded..d225ad63 100644
--- a/lib/quotient_common.h
+++ b/lib/quotient_common.h
@@ -5,6 +5,8 @@
 
 #include <qobjectdefs.h>
 
+#include <array>
+
 namespace Quotient {
 Q_NAMESPACE
 
-- 
cgit v1.2.3