diff options
author | Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io> | 2018-02-13 14:45:46 +0100 |
---|---|---|
committer | Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io> | 2018-03-02 16:00:23 +0000 |
commit | 095fd339a238fe2ad5ec6080119882436d95fde3 (patch) | |
tree | 2cf8db3a692b4a622194168282d0ef2ce3f996b2 | |
parent | e9e157032ab383c894c38517a047860232e45482 (diff) |
Check slot-signal association when connecting
When connecting a signal with a slot there is a process
to associate the proper signal signature, but the slot
signature was not verified.
This missing verification step lead to wrongly associate
the slots and the signal signatures, for example:
def on_clicked(checked=True):
...
QGroupBox.clicked.connect(on_clicked)
will wrongly connect the slot "on_clicked" with the
signal "clicked()" (without any argument),
when the proper signal is "clicked(bool)".
This can be solved by manually specifying the arguments:
QGroupBox.clicked[bool].connect(self.clicked)
We can add an additional verification step
to associate the proper signal if the slot has
a certain number of arguments.
There is an existing test that checks the compatibility
of this change with all the ways to connect
signals and slots.
A few additional cases were added.
Task-number: PYSIDE-104
Change-Id: Ic5b06fa3bb91903f7d506e0e2c52a6f7d3dc4570
Reviewed-by: Christian Tismer <tismer@stackless.com>
-rw-r--r-- | sources/pyside2/libpyside/pysidesignal.cpp | 59 | ||||
-rw-r--r-- | sources/pyside2/tests/signals/signal_signature_test.py | 40 |
2 files changed, 95 insertions, 4 deletions
diff --git a/sources/pyside2/libpyside/pysidesignal.cpp b/sources/pyside2/libpyside/pysidesignal.cpp index 04b1cf1f4..a901d10af 100644 --- a/sources/pyside2/libpyside/pysidesignal.cpp +++ b/sources/pyside2/libpyside/pysidesignal.cpp @@ -413,11 +413,64 @@ PyObject* signalInstanceConnect(PyObject* self, PyObject* args, PyObject* kwds) sourceWalk = reinterpret_cast<PySideSignalInstance*>(sourceWalk->d->next); } } else { - //try the first signature + // Check signature of the slot (method or function) to match signal + int slotArgs = -1; + bool useSelf = false; + bool isMethod = PyMethod_Check(slot); + bool isFunction = PyFunction_Check(slot); + bool matchedSlot = false; + + QByteArray functionName; + PySideSignalInstance *it = source; + + if (isMethod || isFunction) { + PyObject *function = isMethod ? PyMethod_GET_FUNCTION(slot) : slot; + PyCodeObject *objCode = reinterpret_cast<PyCodeObject *>(PyFunction_GET_CODE(function)); + PyFunctionObject *function_obj = reinterpret_cast<PyFunctionObject *>(function); + functionName = Shiboken::String::toCString(function_obj->func_name); + useSelf = isMethod; + slotArgs = objCode->co_flags & CO_VARARGS ? -1 : objCode->co_argcount; + if (useSelf) + slotArgs -= 1; + + // Get signature args + bool isShortCircuit = false; + int signatureArgs = 0; + QStringList argsSignature; + + argsSignature = PySide::Signal::getArgsFromSignature(it->d->signature, + &isShortCircuit); + signatureArgs = argsSignature.length(); + + // Iterate the possible types of connection for this signal and compare + // it with slot arguments + if (signatureArgs != slotArgs) { + while (it->d->next != nullptr) { + it = it->d->next; + argsSignature = PySide::Signal::getArgsFromSignature(it->d->signature, + &isShortCircuit); + signatureArgs = argsSignature.length(); + if (signatureArgs == slotArgs) { + matchedSlot = true; + break; + } + } + } + } + + // Adding references to pyArgs PyList_Append(pyArgs, source->d->source); - Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(source->d->signature)); - PyList_Append(pyArgs, signature); + if (matchedSlot) { + // If a slot matching the same number of arguments was found, + // include signature to the pyArgs + Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(it->d->signature)); + PyList_Append(pyArgs, signature); + } else { + // Try the first by default if the slot was not found + Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(source->d->signature)); + PyList_Append(pyArgs, signature); + } PyList_Append(pyArgs, slot); match = true; } diff --git a/sources/pyside2/tests/signals/signal_signature_test.py b/sources/pyside2/tests/signals/signal_signature_test.py index 349619aac..e94c1722d 100644 --- a/sources/pyside2/tests/signals/signal_signature_test.py +++ b/sources/pyside2/tests/signals/signal_signature_test.py @@ -34,7 +34,11 @@ import unittest from PySide2.QtCore import * from helper import UsesQCoreApplication +called = False +name = "Old" class Obj(QObject): + dummySignalArgs = Signal(str) + numberSignal = Signal(int) def __init__(self): QObject.__init__(self) self.signal = '' @@ -42,8 +46,20 @@ class Obj(QObject): def connectNotify(self, signal): self.signal = signal + @staticmethod + def static_method(): + global called + called = True + + @staticmethod + def static_method_args(arg="default"): + global name + name = arg + def callback(arg=None): pass +def callback_empty(): + pass class TestConnectNotifyWithNewStyleSignals(UsesQCoreApplication): '''Test case for signal signature received by QObject::connectNotify().''' @@ -65,12 +81,34 @@ class TestConnectNotifyWithNewStyleSignals(UsesQCoreApplication): def testNewStyle(self): sender = Obj() - sender.destroyed.connect(callback) + sender.destroyed.connect(callback_empty) self.assertEqual(sender.signal.methodSignature(), 'destroyed()') sender.destroyed[QObject].connect(callback) self.assertEqual(sender.signal.methodSignature(), 'destroyed(QObject*)') + def testStaticSlot(self): + global called + sender = Obj() + sender.connect(sender, SIGNAL("dummySignal()"), Obj.static_method) + sender.emit(SIGNAL("dummySignal()")) + self.assertTrue(called) + + + def testStaticSlotArgs(self): + global name + sender = Obj() + sender.dummySignalArgs.connect(Obj.static_method_args) + sender.dummySignalArgs[str].emit("New") + self.assertEqual(name, "New") + + def testLambdaSlot(self): + sender = Obj() + sender.numberSignal[int].connect(lambda x: 42) + with self.assertRaises(IndexError): + sender.numberSignal[str].emit("test") + + if __name__ == '__main__': unittest.main() |