Commit 2ca3f569 authored by da-woods's avatar da-woods Committed by GitHub

Relaxed some of the checks on calling fused functions (GH-3381)

Relaxed some of the checks of fused functions to be consistent with general CyFunctions.

```
 # cython: binding=True
def f(arg):
  pass

class C:  # or cdef class...
  f = f
  def g(self, ...):
    pass

C.f(something) or C().f() # doesn't enforce any checks on the type of arg -
 # with a fused function it does.

C.g(something) # assumes that self is "C" (at least for a cdef class)
 # but doesn't check it. A fused function enforces that it is C.

C.f() # fails with a fused function claiming too few arguments, even though
 # default arguments may make it a valid call
```

Obviously removing checks does make things a little less safe, but it
is consistent with the more general function behaviour. (I'm doing
this as part of a broad plan to abuse fused functions to be a bit
cleverer about decorators, but I don't think the motivation hugely
matters for this change)
parent 830bdfac
......@@ -1001,7 +1001,6 @@ static int __Pyx_CyFunction_InitClassCell(PyObject *cyfunctions, PyObject *class
typedef struct {
__pyx_CyFunctionObject func;
PyObject *__signatures__;
PyObject *type;
PyObject *self;
} __pyx_FusedFunctionObject;
......@@ -1037,7 +1036,6 @@ __pyx_FusedFunction_New(PyTypeObject *type, PyMethodDef *ml, int flags,
return NULL;
fusedfunc->__signatures__ = NULL;
fusedfunc->type = NULL;
fusedfunc->self = NULL;
return (PyObject *) fusedfunc;
}
......@@ -1047,7 +1045,6 @@ __pyx_FusedFunction_dealloc(__pyx_FusedFunctionObject *self)
{
PyObject_GC_UnTrack(self);
Py_CLEAR(self->self);
Py_CLEAR(self->type);
Py_CLEAR(self->__signatures__);
__Pyx__CyFunction_dealloc((__pyx_CyFunctionObject *) self);
}
......@@ -1058,7 +1055,6 @@ __pyx_FusedFunction_traverse(__pyx_FusedFunctionObject *self,
void *arg)
{
Py_VISIT(self->self);
Py_VISIT(self->type);
Py_VISIT(self->__signatures__);
return __Pyx_CyFunction_traverse((__pyx_CyFunctionObject *) self, visit, arg);
}
......@@ -1067,7 +1063,6 @@ static int
__pyx_FusedFunction_clear(__pyx_FusedFunctionObject *self)
{
Py_CLEAR(self->self);
Py_CLEAR(self->type);
Py_CLEAR(self->__signatures__);
return __Pyx_CyFunction_clear((__pyx_CyFunctionObject *) self);
}
......@@ -1126,9 +1121,6 @@ __pyx_FusedFunction_descr_get(PyObject *self, PyObject *obj, PyObject *type)
Py_XINCREF(func->__signatures__);
meth->__signatures__ = func->__signatures__;
Py_XINCREF(type);
meth->type = type;
Py_XINCREF(func->func.defaults_tuple);
meth->func.defaults_tuple = func->func.defaults_tuple;
......@@ -1205,7 +1197,7 @@ __pyx_err:
unbound_result_func = PyObject_GetItem(self->__signatures__, signature);
if (unbound_result_func) {
if (self->self || self->type) {
if (self->self) {
__pyx_FusedFunctionObject *unbound = (__pyx_FusedFunctionObject *) unbound_result_func;
// TODO: move this to InitClassCell
......@@ -1214,7 +1206,7 @@ __pyx_err:
unbound->func.func_classobj = self->func.func_classobj;
result_func = __pyx_FusedFunction_descr_get(unbound_result_func,
self->self, self->type);
self->self, self->self);
} else {
result_func = unbound_result_func;
Py_INCREF(result_func);
......@@ -1255,23 +1247,21 @@ __pyx_FusedFunction_call(PyObject *func, PyObject *args, PyObject *kw)
PyObject *new_args = NULL;
__pyx_FusedFunctionObject *new_func = NULL;
PyObject *result = NULL;
PyObject *self = NULL;
int is_staticmethod = binding_func->func.flags & __Pyx_CYFUNCTION_STATICMETHOD;
int is_classmethod = binding_func->func.flags & __Pyx_CYFUNCTION_CLASSMETHOD;
if (binding_func->self) {
// Bound method call, put 'self' in the args tuple
PyObject *self;
Py_ssize_t i;
new_args = PyTuple_New(argc + 1);
if (!new_args)
return NULL;
self = binding_func->self;
#if !(CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS)
Py_INCREF(self);
#endif
Py_INCREF(self);
PyTuple_SET_ITEM(new_args, 0, self);
self = NULL;
for (i = 0; i < argc; i++) {
#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
......@@ -1284,36 +1274,8 @@ __pyx_FusedFunction_call(PyObject *func, PyObject *args, PyObject *kw)
}
args = new_args;
} else if (binding_func->type) {
// Unbound method call
if (argc < 1) {
PyErr_SetString(PyExc_TypeError, "Need at least one argument, 0 given.");
return NULL;
}
#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
self = PyTuple_GET_ITEM(args, 0);
#else
self = PySequence_ITEM(args, 0); if (unlikely(!self)) return NULL;
#endif
}
if (self && !is_classmethod && !is_staticmethod) {
int is_instance = PyObject_IsInstance(self, binding_func->type);
if (unlikely(!is_instance)) {
PyErr_Format(PyExc_TypeError,
"First argument should be of type %.200s, got %.200s.",
((PyTypeObject *) binding_func->type)->tp_name,
self->ob_type->tp_name);
goto bad;
} else if (unlikely(is_instance == -1)) {
goto bad;
}
}
#if !(CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS)
Py_XDECREF(self);
self = NULL;
#endif
if (binding_func->__signatures__) {
PyObject *tup;
if (is_staticmethod && binding_func->func.flags & __Pyx_CYFUNCTION_CCLASS) {
......@@ -1345,9 +1307,6 @@ __pyx_FusedFunction_call(PyObject *func, PyObject *args, PyObject *kw)
result = __pyx_FusedFunction_callfunction(func, args, kw);
bad:
#if !(CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS)
Py_XDECREF(self);
#endif
Py_XDECREF(new_args);
Py_XDECREF((PyObject *) new_func);
return result;
......
# mode: run
# tag: pure3.0
# cython: binding=True
"""
Test that fused functions can be used in the same way as CyFunctions with respect to
assigning them to class attributes. Previously they enforced extra type/argument checks
beyond those which CyFunctions did.
"""
import cython
MyFusedClass = cython.fused_type(
float,
'Cdef',
object)
def fused_func(x: MyFusedClass):
return (type(x).__name__, cython.typeof(x))
IntOrFloat = cython.fused_type(int, float)
def fused_func_0(x: IntOrFloat = 0):
"""
Fused functions can legitimately take 0 arguments
>>> fused_func_0()
('int', 'int')
# subscripted in module __doc__ conditionally
"""
return (type(x).__name__, cython.typeof(x))
def regular_func(x):
return (type(x).__name__, cython.typeof(x))
def regular_func_0():
return
@cython.cclass
class Cdef:
__doc__ = """
>>> c = Cdef()
# functions are callable with an instance of c
>>> c.fused_func()
('Cdef', 'Cdef')
>>> c.regular_func()
('Cdef', '{typeofCdef}')
>>> c.fused_in_class(1.5)
('float', 'float')
# Fused functions are callable without an instance
# (This applies to everything in Py3 - see __doc__ below)
>>> Cdef.fused_func(1.5)
('float', 'float')
>>> Cdef.fused_in_class(c, 1.5)
('float', 'float')
>>> Cdef.fused_func_0()
('int', 'int')
# Functions not expecting an argument don't work with an instance
>>> c.regular_func_0() # doctest: +ELLIPSIS
Traceback (most recent call last):
TypeError: regular_func_0() takes ... arguments ...1... given...
""".format(typeofCdef = 'Python object' if cython.compiled else 'Cdef')
if cython.compiled:
__doc__ += """
# fused_func_0 does not accept a "Cdef" instance
>>> c.fused_func_0()
Traceback (most recent call last):
TypeError: No matching signature found
# subscripting requires fused methods (so not pure Python)
>>> Cdef.fused_func_0['float']()
('float', 'float')
>>> c.fused_func_0['float']() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
TypeError: (Exception looks quite different in Python2 and 3 so no way to match both)
"""
fused_func = fused_func
fused_func_0 = fused_func_0
regular_func = regular_func
regular_func_0 = regular_func_0
def fused_in_class(self, x: MyFusedClass):
return (type(x).__name__, cython.typeof(x))
def regular_in_class(self):
return type(self).__name__
class Regular(object):
__doc__ = """
>>> c = Regular()
# Functions are callable with an instance of C
>>> c.fused_func()
('Regular', '{typeofRegular}')
>>> c.regular_func()
('Regular', '{typeofRegular}')
# Fused functions are callable without an instance
# (This applies to everything in Py3 - see __doc__ below)
>>> Regular.fused_func(1.5)
('float', 'float')
>>> Regular.fused_func_0()
('int', 'int')
# Functions not expecting an argument don't work with an instance
>>> c.regular_func_0() # doctest: +ELLIPSIS
Traceback (most recent call last):
TypeError: regular_func_0() takes ... arguments ...1... given...
""".format(typeofRegular = "Python object" if cython.compiled else 'Regular')
if cython.compiled:
__doc__ += """
# fused_func_0 does not accept a "Regular" instance
>>> c.fused_func_0()
Traceback (most recent call last):
TypeError: No matching signature found
# subscripting requires fused methods (so not pure Python)
>>> c.fused_func_0['float']() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
TypeError: (Exception looks quite different in Python2 and 3 so no way to match both)
>>> Regular.fused_func_0['float']()
('float', 'float')
"""
fused_func = fused_func
fused_func_0 = fused_func_0
regular_func = regular_func
regular_func_0 = regular_func_0
import sys
if sys.version_info[0] > 2:
# extra Py3 only tests - shows that functions added to a class can be called
# with an type as the first argument
__doc__ = """
>>> Cdef.regular_func(1.5)
('float', '{typeoffloat}')
>>> Regular.regular_func(1.5)
('float', '{typeoffloat}')
>>> Cdef.regular_func_0()
>>> Regular.regular_func_0()
""".format(typeoffloat='Python object' if cython.compiled else 'float')
if cython.compiled:
__doc__ += """
>>> fused_func_0['float']()
('float', 'float')
"""
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment