Ronald Oussoren avatar Ronald Oussoren committed 6d95d29

Add workaround for issue 17

This workaround is fishy at best, there is probably a bug
in formal-protocol.m w.r.t. creating new protocols, but that
eluded me for now.

Comments (0)

Files changed (4)

pyobjc-core/Modules/objc/formal-protocol.m

 	for (i = 0; i < len; i++) {
 		PyObject* sel = PySequence_Fast_GET_ITEM(selectors, i);
 		SEL theSel = PyObjCSelector_GetSelector(sel);
-		const char* theSignature = strdup(PyObjCSelector_Signature(sel));
+		const char* theSignature = PyObjCSelector_Signature(sel);
 		if (theSignature == NULL) {
 			goto error;
 		}
 			theSel, 
 			theSignature, 
 			(BOOL)PyObjCSelector_Required(sel), 
-			(BOOL)!PyObjCSelector_IsClassMethod(sel));
+			PyObjCSelector_IsClassMethod(sel)?NO:YES);
 	}
 
 	objc_registerProtocol(theProtocol);
 	}
 }
 
+static int
+append_method_list(PyObject* lst, Protocol* protocol, BOOL isRequired, BOOL isInstance)
+{
+	struct objc_method_description * methods;
+	unsigned int method_count, i;
+
+	methods = protocol_copyMethodDescriptionList(protocol, isRequired, isInstance, &method_count);
+	if (!methods) {
+		return 0;
+	}
+
+	for (i = 0; i < method_count; i++) {
+		PyObject* item = Py_BuildValue(
+#if PY_MAJOR_VERSION == 2
+			"{sssssO}",
+#else
+			"{sysysO}",
+#endif
+			"selector", sel_getName(methods[i].name),
+			"typestr",  methods[i].types,
+			"required", isRequired?Py_True:Py_False);
+		if (item == NULL) {
+			free(methods);
+			return -1;
+		}
+		if (PyList_Append(lst, item) < 0) {
+			Py_DECREF(item);
+			free(methods);
+			return -1;
+		}
+		Py_DECREF(item);
+	}
+
+	free(methods);
+	return 0;
+}
+
+
+static PyObject* 
+instanceMethods(PyObject* object)
+{
+	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;	
+	int r;
+
+	PyObject* result = PyList_New(0);
+	if (result == NULL) {
+		return NULL;
+	}
+
+	r = append_method_list(result, self->objc, YES, YES);
+	if (r == -1) {
+		Py_DECREF(result);
+		return NULL;
+	}
+
+	r = append_method_list(result, self->objc, NO, YES);
+	if (r == -1) {
+		Py_DECREF(result);
+		return NULL;
+	}
+
+	return result;
+
+}
+
+static PyObject* 
+classMethods(PyObject* object)
+{
+	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;	
+	int r;
+
+	PyObject* result = PyList_New(0);
+	if (result == NULL) {
+		return NULL;
+	}
+
+	r = append_method_list(result, self->objc, YES, NO);
+	if (r == -1) {
+		Py_DECREF(result);
+		return NULL;
+	}
+
+	r = append_method_list(result, self->objc, NO, NO);
+	if (r == -1) {
+		Py_DECREF(result);
+		return NULL;
+	}
+
+	return result;
+
+}
+
 static PyObject*
 descriptionForInstanceMethod_(PyObject* object, PyObject* sel)
 {
 
 	if (PyObjCSelector_Check(sel)) {
 		aSelector = PyObjCSelector_GetSelector(sel);
+#if PY_MAJOR_VERSION == 2
 	} else if (PyUnicode_Check(sel)) {
 		PyObject* bytes = PyUnicode_AsEncodedString(sel, NULL, NULL);
 		if (bytes == NULL) {
 		aSelector = sel_getUid(s);
 		Py_DECREF(bytes);
 
-#if PY_MAJOR_VERSION == 2
+#endif
 	} else if (PyString_Check(sel)) {
 		char* s = PyString_AsString(sel);
 		if (*s == '\0') {
 		}
 
 		aSelector = sel_getUid(s);
-#endif
 	} else {
 		PyErr_Format(PyExc_TypeError, "expecting a SEL, got instance of '%s'",
 				Py_TYPE(sel)->tp_name);
 
 	if (PyObjCSelector_Check(sel)) {
 		aSelector = PyObjCSelector_GetSelector(sel);
+#if PY_MAJOR_VERSION == 2
 	} else if (PyUnicode_Check(sel)) {
 		PyObject* bytes = PyUnicode_AsEncodedString(sel, NULL, NULL);
 		if (bytes == NULL) {
 		aSelector = sel_getUid(s);
 		Py_DECREF(bytes);
 
-#if PY_MAJOR_VERSION == 2
+#endif
 	} else if (PyString_Check(sel)) {
 		char* s = PyString_AsString(sel);
 		if (*s == '\0') {
 		}
 
 		aSelector = sel_getUid(s);
-#endif
 	} else {
 		PyErr_Format(PyExc_TypeError, "expecting a SEL, got instance of '%s'",
 				Py_TYPE(sel)->tp_name);
 		METH_O,
 		"Description for a class method in the protocol"
 	},
+	{
+		"instanceMethods",
+		(PyCFunction)instanceMethods,
+		METH_NOARGS,
+		"List of instance methods in this protocol"
+	},
+	{
+		"classMethods",
+		(PyCFunction)classMethods,
+		METH_NOARGS,
+		"List of class methods in this protocol"
+	},
 	{ 0, 0, 0, 0 }
 };
 

pyobjc-core/Modules/objc/objc-runtime-compat.h

 
 #endif
 
+/* 
+ * XXX: Override protocol_getMethodDescription. This is a crude hack that's added because
+ * protocol_getMethodDescription sometimes gives the wrong answer (test_protocols.py).
+ * I haven't found the root cause for this yet, it may or may not be a problem with PyObjC.
+ */
+extern struct objc_method_description PyObjC_protocol_getMethodDescription(Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod);
+#define protocol_getMethodDescription PyObjC_protocol_getMethodDescription
+
+
 extern void PyObjC_SetupRuntimeCompat(void);
 
 #endif /* PyObjC_RUNTIME_COMPAT */

pyobjc-core/Modules/objc/objc-runtime-compat.m

 #endif
 #endif
 
+#undef protocol_getMethodDescription
+struct objc_method_description 
+PyObjC_protocol_getMethodDescription(Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod)
+{
+	struct objc_method_description  result = protocol_getMethodDescription(p, aSel, isRequiredMethod, isInstanceMethod);
+	if (result.name != NULL) {
+		return result;
+	}
+
+	{
+		/* This code should not be necessary.... 
+		 * 
+		 * for some reason the protocols created by PyObjC don't always work, without this
+		 * function test_protocol.py sometimes (but not always!) fails.
+		 */
+
+		struct objc_method_description* methods;
+		unsigned int count, i;
+		methods = protocol_copyMethodDescriptionList(p, isRequiredMethod, isInstanceMethod, &count);
+		if (methods == NULL) {
+			return result;
+		}
+
+		for (i = 0; i < count; i++) {
+			if (sel_isEqual(methods[i].name, aSel)) {
+				result = methods[i];
+				free(methods);
+				return result;
+			}
+		}
+		free(methods);
+		return result;
+
+	}
+}
+
+
 
 void PyObjC_SetupRuntimeCompat(void)
 {

pyobjc-core/PyObjCTest/test_protocol.py

     if OC_TestProtocol is not None:
 
         class TestFormalOCProtocols(TestCase):
+            def testMethodInfo(self):
+                actual = OC_TestProtocol.instanceMethods()
+                actual.sort(key=lambda item: item['selector'])
+                expected = [
+                    {'required': True, 'selector': b'method1', 'typestr': b'i16@0:8'},
+                    {'required': True, 'selector': b'method2:', 'typestr': b'v20@0:8i16'}
+                ]
+                self.assertEqual(actual, expected)
+                self.assertEqual(OC_TestProtocol.classMethods(), [])
+
+                self.assertEqual(OC_TestProtocol.descriptionForInstanceMethod_(b"method1"), (b"method1", b"i16@0:8"))
+                self.assertEqual(OC_TestProtocol.descriptionForInstanceMethod_(b"method2:"), (b"method2:", b"v20@0:8i16"))
             
             def testImplementFormalProtocol(self):
 
                 ])
 
         def testMethodInfo(self):
+            self.assertEqual(MyProtocol.instanceMethods(), [
+                {'typestr': b'I@:', 'required': True, 'selector': b'protoMethod'}, 
+                {'typestr': b'v@:ii', 'required': True, 'selector': b'anotherProto:with:'},
+            ])
+            self.assertEqual(MyProtocol.classMethods(), [
+            ])
             self.assertEqual(
-                    MyProtocol.descriptionForInstanceMethod_("protoMethod"),
+                    MyProtocol.descriptionForInstanceMethod_(b"protoMethod"),
                         ("protoMethod", "I@:"))
 
             self.assertEqual(
-                    MyProtocol.descriptionForInstanceMethod_("nosuchmethod"),
+                    MyProtocol.descriptionForInstanceMethod_(b"nosuchmethod"),
                         None)
 
+            self.assertEqual(MyClassProtocol.classMethods(), [
+                {'required': True, 'selector': 'aClassOne:', 'typestr': '@@:i'}
+            ])
+            self.assertEqual(MyProtocol.classMethods(), [
+            ])
             self.assertEqual(
-                    MyClassProtocol.descriptionForClassMethod_("aClassOne:"),
+                    MyClassProtocol.descriptionForClassMethod_(b"aClassOne:"),
                         ("aClassOne:", "@@:i"))
 
             self.assertEqual(
-                    MyClassProtocol.descriptionForClassMethod_("nosuchmethod"),
+                    MyClassProtocol.descriptionForClassMethod_(b"nosuchmethod"),
                         None)
 
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.