Q(Untyped)Bindable: Print warnings when operations fail

Instead of silently failing, we now print an explanatory warning to aid
with debugging.

Task-number: QTBUG-89512
Change-Id: I36dd2ce452af12d0523c19286919095e366bd390
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
bb10
Fabian Kosmale 2021-04-22 11:21:17 +02:00
parent d225c6afe7
commit 98b4f4bc4d
3 changed files with 97 additions and 6 deletions

View File

@ -42,9 +42,12 @@
#include <qscopedvaluerollback.h>
#include <QScopeGuard>
#include <QtCore/qloggingcategory.h>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQPropertyBinding, "qt.qproperty.binding");
using namespace QtPrivate;
void QPropertyBindingPrivatePtr::destroyAndFreeMemory()
@ -2131,6 +2134,36 @@ bool isPropertyInBindingWrapper(const QUntypedPropertyData *property)
bindingStatus.currentCompatProperty->property == property;
}
namespace BindableWarnings {
void printUnsuitableBindableWarning(QAnyStringView prefix, BindableWarnings::Reason reason)
{
switch (reason) {
case QtPrivate::BindableWarnings::NonBindableInterface:
qCWarning(lcQPropertyBinding).noquote() << prefix.toString()
<< "The QBindable does not allow interaction with the binding.";
break;
case QtPrivate::BindableWarnings::ReadOnlyInterface:
qCWarning(lcQPropertyBinding).noquote() << prefix.toString()
<< "The QBindable is read-only.";
break;
default:
case QtPrivate::BindableWarnings::InvalidInterface:
qCWarning(lcQPropertyBinding).noquote() << prefix.toString()
<< "The QBindable is invalid.";
break;
}
}
void printMetaTypeMismatch(QMetaType actual, QMetaType expected)
{
qCWarning(lcQPropertyBinding) << "setBinding: Could not set binding as the property expects it to be of type"
<< actual.name()
<< "but got" << expected.name() << "instead.";
}
} // namespace BindableWarnings end
} // namespace QtPrivate end
QT_END_NAMESPACE

View File

@ -43,9 +43,7 @@
#include <QtCore/qglobal.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qstring.h>
#include <functional>
#include <type_traits>
#include <variant>
#include <QtCore/qpropertyprivate.h>
@ -555,6 +553,15 @@ public:
}
namespace QtPrivate {
// used in Q(Untyped)Bindable to print warnings about various binding errors
namespace BindableWarnings {
enum Reason { InvalidInterface, NonBindableInterface, ReadOnlyInterface };
Q_CORE_EXPORT void printUnsuitableBindableWarning(QAnyStringView prefix, Reason reason);
Q_CORE_EXPORT void printMetaTypeMismatch(QMetaType actual, QMetaType expected);
}
}
class QUntypedBindable
{
protected:
@ -600,6 +607,11 @@ public:
{
if (iface)
iface->setObserver(data, observer);
#ifndef QT_NO_DEBUG
else
QtPrivate::BindableWarnings::printUnsuitableBindableWarning("observe:",
QtPrivate::BindableWarnings::InvalidInterface);
#endif
}
template<typename Functor>
@ -619,16 +631,31 @@ public:
QUntypedPropertyBinding binding() const
{
if (!iface->getBinding)
if (!isBindable()) {
#ifndef QT_NO_DEBUG
QtPrivate::BindableWarnings::printUnsuitableBindableWarning("binding: ",
QtPrivate::BindableWarnings::NonBindableInterface);
#endif
return QUntypedPropertyBinding();
}
return iface->getBinding(data);
}
bool setBinding(const QUntypedPropertyBinding &binding)
{
if (!iface->setBinding)
if (isReadOnly()) {
#ifndef QT_NO_DEBUG
const auto errorType = iface ? QtPrivate::BindableWarnings::ReadOnlyInterface :
QtPrivate::BindableWarnings::InvalidInterface;
QtPrivate::BindableWarnings::printUnsuitableBindableWarning("setBinding: Could not set binding via bindable interface.", errorType);
#endif
return false;
if (!binding.isNull() && binding.valueMetaType() != iface->metaType())
}
if (!binding.isNull() && binding.valueMetaType() != iface->metaType()) {
#ifndef QT_NO_DEBUG
QtPrivate::BindableWarnings::printMetaTypeMismatch(iface->metaType(), binding.valueMetaType());
#endif
return false;
}
iface->setBinding(data, binding);
return true;
}
@ -675,7 +702,16 @@ public:
QPropertyBinding<T> setBinding(const QPropertyBinding<T> &binding)
{
Q_ASSERT(!iface || binding.isNull() || binding.valueMetaType() == iface->metaType());
return (iface && iface->setBinding) ? static_cast<QPropertyBinding<T> &&>(iface->setBinding(data, binding)) : QPropertyBinding<T>();
if (iface && iface->setBinding)
return static_cast<QPropertyBinding<T> &&>(iface->setBinding(data, binding));
#ifndef QT_NO_DEBUG
if (!iface)
QtPrivate::BindableWarnings::printUnsuitableBindableWarning("setBinding", QtPrivate::BindableWarnings::InvalidInterface);
else
QtPrivate::BindableWarnings::printUnsuitableBindableWarning("setBinding: Could not set binding via bindable interface.", QtPrivate::BindableWarnings::ReadOnlyInterface);
#endif
return QPropertyBinding<T>();
}
#ifndef Q_CLANG_QDOC
template <typename Functor>

View File

@ -1065,6 +1065,25 @@ void tst_QProperty::quntypedBindableApi()
QCOMPARE(iprop.value(), 42);
QUntypedBindable propLess;
QVERIFY(propLess.takeBinding().isNull());
QUntypedBindable invalidBindable;
#ifndef QT_NO_DEBUG
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "setBinding: Could not set binding via bindable interface. The QBindable is invalid.");
#endif
invalidBindable.setBinding(Qt::makePropertyBinding(iprop));
QUntypedBindable readOnlyBindable(static_cast<const QProperty<int> *>(&iprop) );
QVERIFY(readOnlyBindable.isReadOnly());
#ifndef QT_NO_DEBUG
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "setBinding: Could not set binding via bindable interface. The QBindable is read-only.");
#endif
readOnlyBindable.setBinding(Qt::makePropertyBinding(iprop));
QProperty<float> fprop;
#ifndef QT_NO_DEBUG
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "setBinding: Could not set binding as the property expects it to be of type int but got float instead.");
#endif
bindable.setBinding(Qt::makePropertyBinding(fprop));
}
void tst_QProperty::readonlyConstQBindable()
@ -1216,6 +1235,9 @@ void tst_QProperty::qobjectBindableSignalTakingNewValue()
void tst_QProperty::testNewStuff()
{
MyQObject testReadOnly;
#ifndef QT_NO_DEBUG
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "setBinding: Could not set binding via bindable interface. The QBindable is read-only.");
#endif
testReadOnly.bindableFoo().setBinding([](){return 42;});
auto bindable = const_cast<const MyQObject&>(testReadOnly).bindableFoo();
QVERIFY(bindable.hasBinding());