Commits

Ronald Oussoren committed ad97cf6

Issue #76: It is now possible to pass ``None`` to a method expecting a block
argument, as with normal object arguments the Objective-C method receives
a ``nil`` value.

Closes #76

Comments (0)

Files changed (4)

pyobjc-core/Modules/objc/libffi_support.m

             case _C_ID:
                 if (argtype[1] == '?') {
                     /* Argument is a block */
-                    if (methinfo->argtype[i]->callable == NULL) {
-                        PyErr_Format(PyExc_TypeError, "Argument %"PY_FORMAT_SIZE_T"d is a block, but no signature available", i);
-                        return -1;
+                    if (argument == Py_None) {
+                        argbuf_cur = align(argbuf_cur, PyObjCRT_AlignOfType(argtype));
+                        arg = argbuf + argbuf_cur;
+                        argbuf_cur += PyObjCRT_SizeOfType(argtype);
+                        PyObjC_Assert(argbuf_cur <= argbuf_len, -1);
+                        *(void**)arg = NULL;
+                    } else {
+                        if (methinfo->argtype[i]->callable == NULL) {
+                            PyErr_Format(PyExc_TypeError, "Argument %"PY_FORMAT_SIZE_T"d is a block, but no signature available", i);
+                            return -1;
+                        }
+                        argbuf_cur = align(argbuf_cur, PyObjCRT_AlignOfType(argtype));
+                        arg = argbuf + argbuf_cur;
+                        argbuf_cur += PyObjCRT_SizeOfType(argtype);
+                        PyObjC_Assert(argbuf_cur <= argbuf_len, -1);
+                        *(void**)arg = PyObjCBlock_Create(
+                            methinfo->argtype[i]->callable, argument);
+                        if (*(void**)arg == NULL) {
+                            return -1;
+                        }
+                        byref_attr[i].buffer = PyCapsule_New(
+                            *(void**)arg,
+                            "objc.__block__",
+                            block_capsule_cleanup);
                     }
-                    argbuf_cur = align(argbuf_cur, PyObjCRT_AlignOfType(argtype));
-                    arg = argbuf + argbuf_cur;
-                    argbuf_cur += PyObjCRT_SizeOfType(argtype);
-                    PyObjC_Assert(argbuf_cur <= argbuf_len, -1);
-                    *(void**)arg = PyObjCBlock_Create(
-                        methinfo->argtype[i]->callable, argument);
-                    if (*(void**)arg == NULL) {
-                        return -1;
-                    }
-                    byref_attr[i].buffer = PyCapsule_New(
-                        *(void**)arg,
-                        "objc.__block__",
-                        block_capsule_cleanup);
                     arglist[i] = signature_to_ffi_type(argtype);
                     values[i] = arg;
 

pyobjc-core/Modules/objc/test/block.m

 #if (PyObjC_BUILD_RELEASE >= 1006) && (__GNUC__ >= 4 && __GNUC_MINOR__ >= 2)
 @interface NSObject (IndirectBlockTest)
 -(double)processBlock:(double(^)(double, double))aBlock;
+-(id)optionalBlock:(id(^)(id))aBlock;
 @end
 #endif
 
 -(NSRect(^)(double, double, double, double))getStructBlock;
 -(void)callIntBlock:(void(^)(int))block withValue:(int)value;
 -(double)callDoubleBlock:(double(^)(double, double))block withValue:(double)v1 andValue:(double)v2;
+-(id)callOptionalBlock:(id(^)(id))block withValue:(id)value;
 #endif
 
 @end
     return [testObject processBlock:^(double a, double b) { return a*b; }];
 }
 
+-(id)callOptionalBlockOn:(NSObject*)testObject
+{
+    return [testObject optionalBlock:nil];
+}
+
+-(id)callOptionalBlock:(id(^)(id))block withValue:(id)value
+{
+    if (!block) {
+        return @"NOBLOCK";
+    } else {
+        return block(value);
+    }
+}
+
 #endif
 
 @end
 Version 3.0
 -----------
 
+* Issue #76: It is now possible to pass ``None`` to a method expecting a block
+  argument, as with normal object arguments the Objective-C method receives
+  a ``nil`` value.
+
 * Python integer values with values between 2 ** 63 and 2**64 are now proxied
   as plain NSNumber objects, not as using PyObjC specific subclass of NSNumber,
   to avoid a problem with writing them to binary plist files.

pyobjc-core/PyObjCTest/test_blocks.py

               <arg type='d' />
           </retval>
         </method>
+        <method selector='callOptionalBlock:withValue:'>
+          <arg index='0' block='true'>
+            <retval type='@'/>
+            <arg type='@' />
+          </arg>
+        </method>
         <method selector='callIntBlock:withValue:'>
           <arg index='0' block='true' >
               <retval type='v' />
             <arg type='d' />
           </arg>
         </method>
+        <method selector='optionalBlock:'>
+          <retval type='@' />
+          <arg index='0' block='true' type='@?'>
+            <retval type='@'/>
+            <arg type='@' />
+          </arg>
+        </method>
       </class>
     </signatures>
     ''' % dict(NSRect_tp=NSRect_tp if sys.version_info[0] == 2 else NSRect_tp.decode('ascii')),
     def processBlock_(self, block):
         return -block(2.5, 4.0)
 
+    def optionalBlock_(self, block):
+        if block is None:
+            return "no block"
+
+        else:
+            return block("x")
 
 
 class TestBlocks (TestCase):
     @min_os_level('10.6')
     @onlyIf(blocksEnabled, "no blocks")
+    def testOptionalBlock(self):
+        obj = OCTestBlock.alloc().init()
+
+        self.assertEqual(obj.callOptionalBlock_withValue_(None, "hello"), "NOBLOCK")
+        self.assertEqual(obj.callOptionalBlock_withValue_(lambda x: x+x, "hello"), "hellohello")
+
+    @min_os_level('10.6')
+    @onlyIf(blocksEnabled, "no blocks")
     def testBlockToObjC(self):
         obj = OCTestBlock.alloc().init()
 
         value = obj.callProcessBlockOn_(helper)
         self.assertEqual(value, -(2.5 * 4.0))
 
+        value = obj.callOptionalBlockOn_(helper)
+        self.assertEqual(value, "no block")
+
 if __name__ == "__main__":
     main()