1 // This is the implementation of the pyqtSlot decorator.
2 //
3 // Copyright (c) 2021 Riverbank Computing Limited <info@riverbankcomputing.com>
4 //
5 // This file is part of PyQt5.
6 //
7 // This file may be used under the terms of the GNU General Public License
8 // version 3.0 as published by the Free Software Foundation and appearing in
9 // the file LICENSE included in the packaging of this file.  Please review the
10 // following information to ensure the GNU General Public License version 3.0
11 // requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 //
13 // If you do not wish to use this file under the terms of the GPL version 3.0
14 // then you may purchase a commercial license.  For more information contact
15 // info@riverbankcomputing.com.
16 //
17 // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
18 // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19 
20 
21 #include <Python.h>
22 
23 #include <QByteArray>
24 
25 #include "qpycore_api.h"
26 #include "qpycore_chimera.h"
27 #include "qpycore_misc.h"
28 #include "qpycore_objectified_strings.h"
29 
30 
31 // Forward declarations.
32 extern "C" {static PyObject *decorator(PyObject *self, PyObject *f);}
33 
34 
35 // This implements the pyqtSlot decorator.
qpycore_pyqtslot(PyObject * args,PyObject * kwds)36 PyObject *qpycore_pyqtslot(PyObject *args, PyObject *kwds)
37 {
38     const char *name_str = 0;
39     PyObject *res_obj = 0;
40     int revision = 0;
41     static const char *kwlist[] = {"name", "result", "revision", 0};
42 
43     static PyObject *no_args = 0;
44 
45     if (!no_args)
46     {
47         no_args = PyTuple_New(0);
48 
49         if (!no_args)
50             return 0;
51     }
52 
53     if (!PyArg_ParseTupleAndKeywords(no_args, kwds, "|sOi:pyqtSlot",
54             const_cast<char **>(kwlist), &name_str, &res_obj, &revision))
55         return 0;
56 
57     Chimera::Signature *parsed_sig = Chimera::parse(args, name_str,
58             "a pyqtSlot type argument");
59 
60     if (!parsed_sig)
61         return 0;
62 
63     // Sticking the revision here is an awful hack, but it saves creating
64     // another data structure wrapped in a capsule.
65     parsed_sig->revision = revision;
66 
67     // Parse any result type.
68     if (res_obj)
69     {
70         parsed_sig->result = Chimera::parse(res_obj);
71 
72         if (!parsed_sig->result)
73         {
74             Chimera::raiseParseException(res_obj, "a pyqtSlot result");
75             delete parsed_sig;
76             return 0;
77         }
78     }
79 
80     // Wrap the parsed signature in a Python object.
81     PyObject *sig_obj = Chimera::Signature::toPyObject(parsed_sig);
82 
83     if (!sig_obj)
84         return 0;
85 
86     // Create the decorator function itself.  We stash the arguments in "self".
87     // This may be an abuse, but it seems to be Ok.
88     static PyMethodDef deco_method = {
89 #if PY_VERSION_HEX >= 0x02050000
90         "_deco", decorator, METH_O, 0
91 #else
92         const_cast<char *>("_deco"), decorator, METH_O, 0
93 #endif
94     };
95 
96     PyObject *obj = PyCFunction_New(&deco_method, sig_obj);
97     Py_DECREF(sig_obj);
98 
99     return obj;
100 }
101 
102 
103 // This is the decorator function that saves the C++ signature as a function
104 // attribute.
decorator(PyObject * self,PyObject * f)105 static PyObject *decorator(PyObject *self, PyObject *f)
106 {
107     Chimera::Signature *parsed_sig = Chimera::Signature::fromPyObject(self);
108     const QByteArray &sig = parsed_sig->signature;
109 
110     // Use the function name if there isn't already one.
111     if (sig.startsWith('('))
112     {
113         // Get the function's name.
114         PyObject *nobj = PyObject_GetAttr(f, qpycore_dunder_name);
115 
116         if (!nobj)
117             return 0;
118 
119         PyObject *ascii_obj = nobj;
120         const char *ascii = sipString_AsASCIIString(&ascii_obj);
121         Py_DECREF(nobj);
122 
123         if (!ascii)
124             return 0;
125 
126         parsed_sig->signature.prepend(ascii);
127         parsed_sig->py_signature.prepend(ascii);
128         Py_DECREF(ascii_obj);
129     }
130 
131     // See if the function has already been decorated.
132     PyObject *decorations = PyObject_GetAttr(f, qpycore_dunder_pyqtsignature);
133     int rc;
134 
135     if (decorations)
136     {
137         // Insert the new decoration at the head of the existing ones so that
138         // the list order matches the order they appear in the script.
139         rc = PyList_Insert(decorations, 0, self);
140     }
141     else
142     {
143         PyErr_Clear();
144 
145         decorations = PyList_New(1);
146 
147         if (!decorations)
148             return 0;
149 
150         Py_INCREF(self);
151         PyList_SetItem(decorations, 0, self);
152 
153         // Save the new decoration.
154         rc = PyObject_SetAttr(f, qpycore_dunder_pyqtsignature, decorations);
155     }
156 
157     Py_DECREF(decorations);
158 
159     if (rc < 0)
160         return 0;
161 
162     // Return the function.
163     Py_INCREF(f);
164     return f;
165 }
166