Commit 8141a942 authored by Mark's avatar Mark

Merge pull request #66 from markflorisson88/fusedmerge

Fused Types
parents 5008e863 751dd58f
from Cython.Compiler.Visitor import VisitorTransform, ScopeTrackingTransform, TreeVisitor
from Nodes import StatListNode, SingleAssignmentNode, CFuncDefNode
from Nodes import StatListNode, SingleAssignmentNode, CFuncDefNode, DefNode
from ExprNodes import DictNode, DictItemNode, NameNode, UnicodeNode, NoneNode, \
ExprNode, AttributeNode, ModuleRefNode, DocstringRefNode
from PyrexTypes import py_object_type
......@@ -63,7 +63,7 @@ class AutoTestDictTransform(ScopeTrackingTransform):
return node
def visit_FuncDefNode(self, node):
if not node.doc:
if not node.doc or (isinstance(node, DefNode) and node.fused_py_func):
return node
if not self.cdef_docstrings:
if isinstance(node, CFuncDefNode) and not node.py_func:
......
......@@ -263,7 +263,6 @@ class UtilityCode(UtilityCodeBase):
def get_tree(self):
pass
def specialize(self, pyrex_type=None, tempita=False, **data):
# Dicts aren't hashable...
if pyrex_type is not None:
......
......@@ -18,6 +18,13 @@ class CythonScope(ModuleScope):
# The Main.Context object
self.context = context
for fused_type in (cy_integral_type, cy_floating_type, cy_numeric_type):
entry = self.declare_typedef(fused_type.name,
fused_type,
None,
cname='<error>')
entry.in_cinclude = True
def lookup_type(self, name):
# This function should go away when types are all first-level objects.
type = parse_basic_type(name)
......@@ -114,6 +121,7 @@ class CythonScope(ModuleScope):
view_utility_scope = MemoryView.view_utility_code.declare_in_scope(
viewscope, cython_scope=self)
# MemoryView.memview_fromslice_utility_code.from_scope = view_utility_scope
# MemoryView.memview_fromslice_utility_code.declare_in_scope(viewscope)
......@@ -124,7 +132,6 @@ def create_cython_scope(context):
# it across different contexts)
return CythonScope(context)
# Load test utilities for the cython scope
def load_testscope_utility(cy_util_name, **kwargs):
......
......@@ -42,6 +42,14 @@ except ImportError:
basestring = str # Python 3
class NotConstant(object):
_obj = None
def __new__(cls):
if NotConstant._obj is None:
NotConstant._obj = super(NotConstant, cls).__new__(cls)
return NotConstant._obj
def __repr__(self):
return "<NOT CONSTANT>"
......@@ -591,6 +599,25 @@ class ExprNode(Node):
if dst_type.is_reference:
dst_type = dst_type.ref_base_type
if src_type.is_fused or dst_type.is_fused:
# See if we are coercing a fused function to a pointer to a
# specialized function
if (src_type.is_cfunction and not dst_type.is_fused and
dst_type.is_ptr and dst_type.base_type.is_cfunction):
dst_type = dst_type.base_type
for signature in src_type.get_all_specific_function_types():
if signature.same_as(dst_type):
src.type = signature
src.entry = src.type.entry
src.entry.used = True
return self
error(self.pos, "Type is not specific")
self.type = error_type
return self
if self.coercion_type is not None:
# This is purely for error checking purposes!
node = NameNode(self.pos, name='', type=self.coercion_type)
......@@ -1459,6 +1486,13 @@ class NameNode(AtomicExprNode):
def analyse_target_types(self, env):
self.analyse_entry(env)
if (not self.is_lvalue() and self.entry.is_cfunction and
self.entry.fused_cfunction and self.entry.as_variable):
# We need this for the fused 'def' TreeFragment
self.entry = self.entry.as_variable
self.type = self.entry.type
if not self.is_lvalue():
error(self.pos, "Assignment to non-lvalue '%s'"
% self.name)
......@@ -2275,10 +2309,15 @@ class IndexNode(ExprNode):
# indices is used on buffer access, index on non-buffer access.
# The former contains a clean list of index parameters, the
# latter whatever Python object is needed for index access.
#
# is_fused_index boolean Whether the index is used to specialize a
# c(p)def function
subexprs = ['base', 'index', 'indices']
indices = None
is_fused_index = False
# Whether we're assigning to a buffer (in that case it needs to be
# writable)
writable_needed = False
......@@ -2568,11 +2607,15 @@ class IndexNode(ExprNode):
else:
base_type = self.base.type
if isinstance(self.index, TupleNode):
self.index.analyse_types(env, skip_children=skip_child_analysis)
elif not skip_child_analysis:
self.index.analyse_types(env)
self.original_index_type = self.index.type
fused_index_operation = base_type.is_cfunction and base_type.is_fused
if not fused_index_operation:
if isinstance(self.index, TupleNode):
self.index.analyse_types(env, skip_children=skip_child_analysis)
elif not skip_child_analysis:
self.index.analyse_types(env)
self.original_index_type = self.index.type
if base_type.is_unicode_char:
# we infer Py_UNICODE/Py_UCS4 for unicode strings in some
# cases, but indexing must still work for them
......@@ -2631,12 +2674,108 @@ class IndexNode(ExprNode):
self.type = func_type.return_type
if setting and not func_type.return_type.is_reference:
error(self.pos, "Can't set non-reference result '%s'" % self.type)
elif fused_index_operation:
self.parse_indexed_fused_cdef(env)
else:
error(self.pos,
"Attempting to index non-array type '%s'" %
base_type)
self.type = PyrexTypes.error_type
def parse_indexed_fused_cdef(self, env):
"""
Interpret fused_cdef_func[specific_type1, ...]
Note that if this method is called, we are an indexed cdef function
with fused argument types, and this IndexNode will be replaced by the
NameNode with specific entry just after analysis of expressions by
AnalyseExpressionsTransform.
"""
self.type = PyrexTypes.error_type
self.is_fused_index = True
base_type = self.base.type
specific_types = []
positions = []
if self.index.is_name:
positions.append(self.index.pos)
specific_types.append(self.index.analyse_as_type(env))
elif isinstance(self.index, TupleNode):
for arg in self.index.args:
positions.append(arg.pos)
specific_type = arg.analyse_as_type(env)
specific_types.append(specific_type)
else:
specific_types = [False]
if not Utils.all(specific_types):
self.index.analyse_types(env)
if not self.base.entry.as_variable:
error(self.pos, "Can only index fused functions with types")
else:
# A cpdef function indexed with Python objects
self.base.entry = self.entry = self.base.entry.as_variable
self.base.type = self.type = self.entry.type
self.base.is_temp = True
self.is_temp = True
self.entry.used = True
self.is_fused_index = False
return
fused_types = base_type.get_fused_types()
if len(specific_types) > len(fused_types):
return error(self.pos, "Too many types specified")
elif len(specific_types) < len(fused_types):
t = fused_types[len(specific_types)]
return error(self.pos, "Not enough types specified to specialize "
"the function, %s is still fused" % t)
# See if our index types form valid specializations
for pos, specific_type, fused_type in zip(positions,
specific_types,
fused_types):
if not Utils.any([specific_type.same_as(t)
for t in fused_type.types]):
return error(pos, "Type not in fused type")
if specific_type is None or specific_type.is_error:
return
fused_to_specific = dict(zip(fused_types, specific_types))
type = base_type.specialize(fused_to_specific)
if type.is_fused:
# Only partially specific, this is invalid
error(self.pos,
"Index operation makes function only partially specific")
else:
# Fully specific, find the signature with the specialized entry
for signature in self.base.type.get_all_specific_function_types():
if type.same_as(signature):
self.type = signature
if self.base.is_attribute:
# Pretend to be a normal attribute, for cdef extension
# methods
self.entry = signature.entry
self.is_attribute = True
self.obj = self.base.obj
self.type.entry.used = True
self.base.type = signature
self.base.entry = signature.entry
break
else:
# This is a bug
raise InternalError("Couldn't find the right signature")
gil_message = "Indexing Python object"
def nogil_check(self, env):
......@@ -3358,11 +3497,13 @@ class SimpleCallNode(CallNode):
function = self.function
function.is_called = 1
self.function.analyse_types(env)
if function.is_attribute and function.entry and function.entry.is_cmethod:
# Take ownership of the object from which the attribute
# was obtained, because we need to pass it as 'self'.
self.self = function.obj
function.obj = CloneNode(self.self)
func_type = self.function_type()
if func_type.is_pyobject:
self.arg_tuple = TupleNode(self.pos, args = self.args)
......@@ -3394,6 +3535,7 @@ class SimpleCallNode(CallNode):
else:
for arg in self.args:
arg.analyse_types(env)
if self.self and func_type.args:
# Coerce 'self' to the type expected by the method.
self_arg = func_type.args[0]
......@@ -3414,10 +3556,13 @@ class SimpleCallNode(CallNode):
def function_type(self):
# Return the type of the function being called, coercing a function
# pointer to a function if necessary.
# pointer to a function if necessary. If the function has fused
# arguments, return the specific type.
func_type = self.function.type
if func_type.is_ptr:
func_type = func_type.base_type
return func_type
def is_simple(self):
......@@ -3431,6 +3576,7 @@ class SimpleCallNode(CallNode):
if self.function.type is error_type:
self.type = error_type
return
if self.function.type.is_cpp_class:
overloaded_entry = self.function.type.scope.lookup("operator()")
if overloaded_entry is None:
......@@ -3439,14 +3585,27 @@ class SimpleCallNode(CallNode):
return
elif hasattr(self.function, 'entry'):
overloaded_entry = self.function.entry
elif (isinstance(self.function, IndexNode) and
self.function.is_fused_index):
overloaded_entry = self.function.type.entry
else:
overloaded_entry = None
if overloaded_entry:
entry = PyrexTypes.best_match(self.args, overloaded_entry.all_alternatives(), self.pos)
if self.function.type.is_fused:
functypes = self.function.type.get_all_specific_function_types()
alternatives = [f.entry for f in functypes]
else:
alternatives = overloaded_entry.all_alternatives()
entry = PyrexTypes.best_match(self.args, alternatives, self.pos, env)
if not entry:
self.type = PyrexTypes.error_type
self.result_code = "<error>"
return
entry.used = True
self.function.entry = entry
self.function.type = entry.type
func_type = self.function_type()
......@@ -3589,8 +3748,8 @@ class SimpleCallNode(CallNode):
for actual_arg in self.args[len(formal_args):]:
arg_list_code.append(actual_arg.result())
result = "%s(%s)" % (self.function.result(),
', '.join(arg_list_code))
result = "%s(%s)" % (self.function.result(), ', '.join(arg_list_code))
return result
def generate_result_code(self, code):
......@@ -4062,7 +4221,7 @@ class AttributeNode(ExprNode):
self.type = self.obj.type
return
else:
obj_type.declare_attribute(self.attribute)
obj_type.declare_attribute(self.attribute, env)
entry = obj_type.scope.lookup_here(self.attribute)
if entry and entry.is_member:
entry = None
......@@ -4147,6 +4306,14 @@ class AttributeNode(ExprNode):
if obj.type.is_extension_type and not self.entry.is_builtin_cmethod:
if self.entry.final_func_cname:
return self.entry.final_func_cname
if self.type.from_fused:
# If the attribute was specialized through indexing, make
# sure to get the right fused name, as our entry was
# replaced by our parent index node
# (AnalyseExpressionsTransform)
self.member = self.entry.cname
return "((struct %s *)%s%s%s)->%s" % (
obj.type.vtabstruct_cname, obj_code, self.op,
obj.type.vtabslot_cname, self.member)
......@@ -5677,9 +5844,17 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
type = py_object_type
is_temp = 1
specialized_cpdefs = None
def analyse_types(self, env):
if self.specialized_cpdefs:
self.binding = True
if self.binding:
env.use_utility_code(binding_cfunc_utility_code)
if self.specialized_cpdefs:
env.use_utility_code(fused_function_utility_code)
else:
env.use_utility_code(binding_cfunc_utility_code)
#TODO(craig,haoyu) This should be moved to a better place
self.set_mod_name(env)
......@@ -5698,7 +5873,11 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
def generate_result_code(self, code):
if self.binding:
constructor = "__Pyx_CyFunction_NewEx"
if self.specialized_cpdefs:
constructor = "__pyx_FusedFunction_NewEx"
else:
constructor = "__Pyx_CyFunction_NewEx"
if self.code_object:
code_object_result = ', ' + self.code_object.py_result()
else:
......@@ -5706,6 +5885,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
else:
constructor = "PyCFunction_NewEx"
code_object_result = ''
py_mod_name = self.get_py_mod_name(code)
code.putln(
'%s = %s(&%s, %s, %s%s); %s' % (
......@@ -5716,8 +5896,67 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
py_mod_name,
code_object_result,
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
if self.specialized_cpdefs:
self.generate_fused_cpdef(code, code_object_result)
def generate_fused_cpdef(self, code, code_object_result):
"""
Generate binding function objects for all specialized cpdefs, and the
original fused one. The fused function gets a dict __signatures__
mapping the specialized signature to the specialized binding function.
In Python space, the specialized versions can be obtained by indexing
the fused function.
For unsubscripted dispatch, we also need to remember the positions of
the arguments with fused types.
"""
def goto_err(string):
string = "(%s)" % string
code.putln(code.error_goto_if_null(string % fmt_dict, self.pos))
# Set up an interpolation dict
fmt_dict = dict(
vars(Naming),
result=self.result(),
py_mod_name=self.get_py_mod_name(code),
self=self.self_result_code(),
code=code_object_result,
func=code.funcstate.allocate_temp(py_object_type,
manage_ref=True),
signature=code.funcstate.allocate_temp(py_object_type,
manage_ref=True),
)
fmt_dict['sigdict'] = \
"((__pyx_FusedFunctionObject *) %(result)s)->__signatures__" % fmt_dict
# Initialize __signatures__
goto_err("%(sigdict)s = PyDict_New()")
# Now put all specialized cpdefs in __signatures__
for cpdef in self.specialized_cpdefs:
fmt_dict['signature_string'] = cpdef.specialized_signature_string
fmt_dict['pymethdef_cname'] = cpdef.entry.pymethdef_cname
goto_err('%(signature)s = PyUnicode_FromString('
'"%(signature_string)s")')
goto_err("%(func)s = __pyx_FusedFunction_NewEx("
"&%(pymethdef_cname)s, %(self)s, %(py_mod_name)s %(code)s)")
s = "PyDict_SetItem(%(sigdict)s, %(signature)s, %(func)s)"
code.put_error_if_neg(self.pos, s % fmt_dict)
code.putln("Py_DECREF(%(signature)s); %(signature)s = NULL;" % fmt_dict)
code.putln("Py_DECREF(%(func)s); %(func)s = NULL;" % fmt_dict)
code.funcstate.release_temp(fmt_dict['func'])
code.funcstate.release_temp(fmt_dict['signature'])
class InnerFunctionNode(PyCFunctionNode):
# Special PyCFunctionNode that depends on a closure class
#
......@@ -6341,6 +6580,9 @@ class TypecastNode(ExprNode):
self.operand = PyTypeTestNode(self.operand, self.type, env, notnone=True)
elif self.type.is_complex and self.operand.type.is_complex:
self.operand = self.operand.coerce_to_simple(env)
elif self.operand.type.is_fused:
self.operand = self.operand.coerce_to(self.type, env)
#self.type = self.operand.type
def is_simple(self):
# either temp or a C cast => no side effects other than the operand's
......@@ -6492,7 +6734,7 @@ class CythonArrayNode(ExprNode):
self.type = self.get_cython_array_type(env)
assert self.type
env.use_utility_code(MemoryView.cython_array_utility_code)
MemoryView.use_cython_array_utility_code(env)
env.use_utility_code(MemoryView.typeinfo_to_format_code)
def allocate_temp_result(self, code):
......@@ -6652,8 +6894,8 @@ class TypeofNode(ExprNode):
def analyse_types(self, env):
self.operand.analyse_types(env)
self.literal = StringNode(
self.pos, value=StringEncoding.EncodedString(str(self.operand.type)))
value = StringEncoding.EncodedString(str(self.operand.type)) #self.operand.type.typeof_name())
self.literal = StringNode(self.pos, value=value)
self.literal.analyse_types(env)
self.literal = self.literal.coerce_to_pyobject(env)
......@@ -9609,342 +9851,14 @@ proto="""
(((x) < 0) & ((unsigned long)(x) == 0-(unsigned long)(x)))
""")
binding_cfunc_utility_code = UtilityCode.load("CythonFunction",
context=vars(Naming))
fused_function_utility_code = UtilityCode.load(
"FusedFunction",
"CythonFunction.c",
context=vars(Naming),
requires=[binding_cfunc_utility_code])
binding_cfunc_utility_code = UtilityCode(
proto="""
#define __Pyx_CyFunction_USED 1
#include <structmember.h>
typedef struct {
PyCFunctionObject func;
PyObject *func_dict;
PyObject *func_weakreflist;
PyObject *func_name;
PyObject *func_doc;
PyObject *func_code;
} __pyx_CyFunctionObject;
static PyTypeObject *__pyx_CyFunctionType = 0;
static PyObject *__Pyx_CyFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module, PyObject* code);
static int __Pyx_CyFunction_init(void);
""" % Naming.__dict__,
impl="""
static PyObject *
__Pyx_CyFunction_get_doc(__pyx_CyFunctionObject *op, CYTHON_UNUSED void *closure)
{
if (op->func_doc == NULL && op->func.m_ml->ml_doc) {
#if PY_MAJOR_VERSION >= 3
op->func_doc = PyUnicode_FromString(op->func.m_ml->ml_doc);
#else
op->func_doc = PyString_FromString(op->func.m_ml->ml_doc);
#endif
}
if (op->func_doc == 0) {
Py_INCREF(Py_None);
return Py_None;
}
Py_INCREF(op->func_doc);
return op->func_doc;
}
static int
__Pyx_CyFunction_set_doc(__pyx_CyFunctionObject *op, PyObject *value)
{
PyObject *tmp = op->func_doc;
if (value == NULL)
op->func_doc = Py_None; /* Mark as deleted */
else
op->func_doc = value;
Py_INCREF(op->func_doc);
Py_XDECREF(tmp);
return 0;
}
static PyObject *
__Pyx_CyFunction_get_name(__pyx_CyFunctionObject *op)
{
if (op->func_name == NULL) {
#if PY_MAJOR_VERSION >= 3
op->func_name = PyUnicode_InternFromString(op->func.m_ml->ml_name);
#else
op->func_name = PyString_InternFromString(op->func.m_ml->ml_name);
#endif
}
Py_INCREF(op->func_name);
return op->func_name;
}
static int
__Pyx_CyFunction_set_name(__pyx_CyFunctionObject *op, PyObject *value)
{
PyObject *tmp;
#if PY_MAJOR_VERSION >= 3
if (value == NULL || !PyUnicode_Check(value)) {
#else
if (value == NULL || !PyString_Check(value)) {
#endif
PyErr_SetString(PyExc_TypeError,
"__name__ must be set to a string object");
return -1;
}
tmp = op->func_name;
Py_INCREF(value);
op->func_name = value;
Py_XDECREF(tmp);
return 0;
}
static PyObject *
__Pyx_CyFunction_get_self(__pyx_CyFunctionObject *m, CYTHON_UNUSED void *closure)
{
PyObject *self;
self = m->func.m_self;
if (self == NULL)
self = Py_None;
Py_INCREF(self);
return self;
}
static PyObject *
__Pyx_CyFunction_get_dict(__pyx_CyFunctionObject *op)
{
if (op->func_dict == NULL) {
op->func_dict = PyDict_New();
if (op->func_dict == NULL)
return NULL;
}
Py_INCREF(op->func_dict);
return op->func_dict;
}
static int
__Pyx_CyFunction_set_dict(__pyx_CyFunctionObject *op, PyObject *value)
{
PyObject *tmp;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError,
"function's dictionary may not be deleted");
return -1;
}
if (!PyDict_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"setting function's dictionary to a non-dict");
return -1;
}
tmp = op->func_dict;
Py_INCREF(value);
op->func_dict = value;
Py_XDECREF(tmp);
return 0;
}
""" + (
# TODO: we implicitly use the global module to get func_globals. This
# will need to be passed into __Pyx_CyFunction_NewEx() if we share
# this type accross modules. We currently avoid doing this to reduce
# the overhead of creating a function object, and to avoid keeping a
# reference to the module dict as long as we don't need to.
"""
static PyObject *
__Pyx_CyFunction_get_globals(CYTHON_UNUSED __pyx_CyFunctionObject *op)
{
PyObject* dict = PyModule_GetDict(%(module_cname)s);
Py_XINCREF(dict);
return dict;
}
""" % Naming.__dict__ ) +
"""
static PyObject *
__Pyx_CyFunction_get_closure(CYTHON_UNUSED __pyx_CyFunctionObject *op)
{
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
__Pyx_CyFunction_get_code(__pyx_CyFunctionObject *op)
{
PyObject* result = (op->func_code) ? op->func_code : Py_None;
Py_INCREF(result);
return result;
}
static PyGetSetDef __pyx_CyFunction_getsets[] = {
{(char *) "func_doc", (getter)__Pyx_CyFunction_get_doc, (setter)__Pyx_CyFunction_set_doc, 0, 0},
{(char *) "__doc__", (getter)__Pyx_CyFunction_get_doc, (setter)__Pyx_CyFunction_set_doc, 0, 0},
{(char *) "func_name", (getter)__Pyx_CyFunction_get_name, (setter)__Pyx_CyFunction_set_name, 0, 0},
{(char *) "__name__", (getter)__Pyx_CyFunction_get_name, (setter)__Pyx_CyFunction_set_name, 0, 0},
{(char *) "__self__", (getter)__Pyx_CyFunction_get_self, 0, 0, 0},
{(char *) "func_dict", (getter)__Pyx_CyFunction_get_dict, (setter)__Pyx_CyFunction_set_dict, 0, 0},
{(char *) "__dict__", (getter)__Pyx_CyFunction_get_dict, (setter)__Pyx_CyFunction_set_dict, 0, 0},
{(char *) "func_globals", (getter)__Pyx_CyFunction_get_globals, 0, 0, 0},
{(char *) "__globals__", (getter)__Pyx_CyFunction_get_globals, 0, 0, 0},
{(char *) "func_closure", (getter)__Pyx_CyFunction_get_closure, 0, 0, 0},
{(char *) "__closure__", (getter)__Pyx_CyFunction_get_closure, 0, 0, 0},
{(char *) "func_code", (getter)__Pyx_CyFunction_get_code, 0, 0, 0},
{(char *) "__code__", (getter)__Pyx_CyFunction_get_code, 0, 0, 0},
{0, 0, 0, 0, 0}
};
#ifndef PY_WRITE_RESTRICTED /* < Py2.5 */
#define PY_WRITE_RESTRICTED WRITE_RESTRICTED
#endif
static PyMemberDef __pyx_CyFunction_members[] = {
{(char *) "__module__", T_OBJECT, offsetof(__pyx_CyFunctionObject, func.m_module), PY_WRITE_RESTRICTED, 0},
{0, 0, 0, 0, 0}
};
static PyObject *
__Pyx_CyFunction_reduce(__pyx_CyFunctionObject *m, CYTHON_UNUSED PyObject *args)
{
#if PY_MAJOR_VERSION >= 3
return PyUnicode_FromString(m->func.m_ml->ml_name);
#else
return PyString_FromString(m->func.m_ml->ml_name);
#endif
}
static PyMethodDef __pyx_CyFunction_methods[] = {
{__Pyx_NAMESTR("__reduce__"), (PyCFunction)__Pyx_CyFunction_reduce, METH_VARARGS, 0},
{0, 0, 0, 0}
};
static PyObject *__Pyx_CyFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module, PyObject* code) {
__pyx_CyFunctionObject *op = PyObject_GC_New(__pyx_CyFunctionObject, __pyx_CyFunctionType);
if (op == NULL)
return NULL;
op->func_weakreflist = NULL;
op->func.m_ml = ml;
Py_XINCREF(self);
op->func.m_self = self;
Py_XINCREF(module);
op->func.m_module = module;
op->func_dict = NULL;
op->func_name = NULL;
op->func_doc = NULL;
Py_XINCREF(code);
op->func_code = code;
PyObject_GC_Track(op);
return (PyObject *)op;
}
static void __Pyx_CyFunction_dealloc(__pyx_CyFunctionObject *m)
{
PyObject_GC_UnTrack(m);
if (m->func_weakreflist != NULL)
PyObject_ClearWeakRefs((PyObject *) m);
Py_XDECREF(m->func.m_self);
Py_XDECREF(m->func.m_module);
Py_XDECREF(m->func_dict);
Py_XDECREF(m->func_name);
Py_XDECREF(m->func_doc);
Py_XDECREF(m->func_code);
PyObject_GC_Del(m);
}
static int __Pyx_CyFunction_traverse(__pyx_CyFunctionObject *m, visitproc visit, void *arg)
{
Py_VISIT(m->func.m_self);
Py_VISIT(m->func.m_module);
Py_VISIT(m->func_dict);
Py_VISIT(m->func_name);
Py_VISIT(m->func_doc);
Py_VISIT(m->func_code);
return 0;
}
static PyObject *__Pyx_CyFunction_descr_get(PyObject *func, PyObject *obj, PyObject *type)
{
if (obj == Py_None)
obj = NULL;
return PyMethod_New(func, obj, type);
}
static PyObject*
__Pyx_CyFunction_repr(__pyx_CyFunctionObject *op)
{
PyObject *func_name = __Pyx_CyFunction_get_name(op);
#if PY_MAJOR_VERSION >= 3
return PyUnicode_FromFormat("<cyfunction %U at %p>",
func_name, op);
#else
return PyString_FromFormat("<cyfunction %s at %p>",
PyString_AsString(func_name), op);
#endif
}
static PyTypeObject __pyx_CyFunctionType_type = {
PyVarObject_HEAD_INIT(0, 0)
__Pyx_NAMESTR("cython_function_or_method"), /*tp_name*/
sizeof(__pyx_CyFunctionObject), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor) __Pyx_CyFunction_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
#if PY_MAJOR_VERSION < 3
0, /*tp_compare*/
#else
0, /*reserved*/
#endif
(reprfunc) __Pyx_CyFunction_repr, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
PyCFunction_Call, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags*/
0, /*tp_doc*/
(traverseproc) __Pyx_CyFunction_traverse, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
offsetof(__pyx_CyFunctionObject, func_weakreflist), /* tp_weaklistoffse */
0, /*tp_iter*/
0, /*tp_iternext*/
__pyx_CyFunction_methods, /*tp_methods*/
__pyx_CyFunction_members, /*tp_members*/
__pyx_CyFunction_getsets, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
__Pyx_CyFunction_descr_get, /*tp_descr_get*/
0, /*tp_descr_set*/
offsetof(__pyx_CyFunctionObject, func_dict),/*tp_dictoffset*/
0, /*tp_init*/
0, /*tp_alloc*/
0, /*tp_new*/
0, /*tp_free*/
0, /*tp_is_gc*/
0, /*tp_bases*/
0, /*tp_mro*/
0, /*tp_cache*/
0, /*tp_subclasses*/
0, /*tp_weaklist*/
0, /*tp_del*/
#if PY_VERSION_HEX >= 0x02060000
0, /*tp_version_tag*/
#endif
};
static int __Pyx_CyFunction_init(void)
{
if (PyType_Ready(&__pyx_CyFunctionType_type) < 0)
return -1;
__pyx_CyFunctionType = &__pyx_CyFunctionType_type;
return 0;
}
""")
generator_utility_code = UtilityCode(
proto="""
......
......@@ -130,9 +130,6 @@ def get_buf_flags(specs):
return memview_strided_access
def use_cython_array(env):
env.use_utility_code(cython_array_utility_code)
def src_conforms_to_dst(src, dst):
'''
returns True if src conforms to dst, False otherwise.
......@@ -171,15 +168,18 @@ def valid_memslice_dtype(dtype):
return (
dtype.is_error or
# Pointers are not valid (yet)
# (dtype.is_ptr and valid_memslice_dtype(dtype.base_type)) or
dtype.is_numeric or
dtype.is_struct or
dtype.is_pyobject or
dtype.is_fused or # accept this as it will be replaced by specializations later
(dtype.is_typedef and valid_memslice_dtype(dtype.typedef_base_type))
)
def validate_memslice_dtype(pos, dtype):
if not valid_memslice_dtype(dtype):
error(pos, "Invalid base type for memoryview slice")
error(pos, "Invalid base type for memoryview slice: %s" % dtype)
class MemoryViewSliceBufferEntry(Buffer.BufferEntry):
......@@ -936,6 +936,10 @@ def load_memview_c_utility(util_code_name, context=None, **kwargs):
return UtilityCode.load(util_code_name, "MemoryView_C.c",
context=context, **kwargs)
def use_cython_array_utility_code(env):
env.global_scope().context.cython_scope.lookup('array_cwrapper').used = True
env.use_utility_code(cython_array_utility_code)
context = {
'memview_struct_name': memview_objstruct_cname,
'max_dims': Options.buffer_max_dims,
......
......@@ -591,6 +591,10 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
PyCode_New(a, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)
#endif
#if PY_MAJOR_VERSION < 3 && PY_MINOR_VERSION < 6
#define PyUnicode_FromString(s) PyUnicode_Decode(s, strlen(s), "UTF-8", "strict")
#endif
#if PY_MAJOR_VERSION >= 3
#define Py_TPFLAGS_CHECKTYPES 0
#define Py_TPFLAGS_HAVE_INDEX 0
......@@ -985,6 +989,9 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
# Generate struct declaration for an extension type's vtable.
type = entry.type
scope = type.scope
self.specialize_fused_types(scope)
if type.vtabstruct_cname:
code.putln("")
code.putln(
......@@ -1128,7 +1135,8 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
def generate_cfunction_declarations(self, env, code, definition):
for entry in env.cfunc_entries:
generate_cfunction_declaration(entry, env, code, definition)
if entry.used:
generate_cfunction_declaration(entry, env, code, definition)
def generate_variable_definitions(self, env, code):
for entry in env.var_entries:
......@@ -1800,7 +1808,8 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
"static PyMethodDef %s[] = {" %
env.method_table_cname)
for entry in env.pyfunc_entries:
code.put_pymethoddef(entry, ",")
if not entry.fused_cfunction:
code.put_pymethoddef(entry, ",")
code.putln(
"{0, 0, 0, 0}")
code.putln(
......@@ -1928,6 +1937,10 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
code.putln("if (__Pyx_CyFunction_init() < 0) %s" % code.error_goto(self.pos))
code.putln("#endif")
code.putln("#ifdef __Pyx_FusedFunction_USED")
code.putln("if (__pyx_FusedFunction_init() < 0) %s" % code.error_goto(self.pos))
code.putln("#endif")
code.putln("/*--- Library function declarations ---*/")
env.generate_library_function_declarations(code)
......@@ -1983,6 +1996,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
code.putln("/*--- Function import code ---*/")
for module in imported_modules:
self.specialize_fused_types(module)
self.generate_c_function_import_code_for_module(module, env, code)
code.putln("/*--- Execution code ---*/")
......@@ -2200,6 +2214,18 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
if entry.defined_in_pxd:
self.generate_type_import_code(env, entry.type, entry.pos, code)
def specialize_fused_types(self, pxd_env):
"""
If fused c(p)def functions are defined in an imported pxd, but not
used in this implementation file, we still have fused entries and
not specialized ones. This method replaces any fused entries with their
specialized ones.
"""
for entry in pxd_env.cfunc_entries[:]:
if entry.type.is_fused:
# This call modifies the cfunc_entries in-place
entry.type.get_all_specific_function_types()
def generate_c_variable_import_code_for_module(self, module, env, code):
# Generate import code for all exported C functions in a cimported module.
entries = []
......@@ -2232,7 +2258,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
# Generate import code for all exported C functions in a cimported module.
entries = []
for entry in module.cfunc_entries:
if entry.defined_in_pxd:
if entry.defined_in_pxd and entry.used:
entries.append(entry)
if entries:
env.use_utility_code(import_module_utility_code)
......@@ -2441,7 +2467,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
def generate_cfunction_declaration(entry, env, code, definition):
from_cy_utility = entry.used and entry.utility_code_definition
if entry.inline_func_in_pxd or (not entry.in_cinclude and (definition
if entry.used and entry.inline_func_in_pxd or (not entry.in_cinclude and (definition
or entry.defined_in_pxd or entry.visibility == 'extern' or from_cy_utility)):
if entry.visibility == 'extern':
storage_class = "%s " % Naming.extern_c_macro
......
......@@ -92,6 +92,7 @@ enc_scope_cname = pyrex_prefix + "enc_scope"
frame_cname = pyrex_prefix + "frame"
frame_code_cname = pyrex_prefix + "frame_code"
binding_cfunc = pyrex_prefix + "binding_PyCFunctionType"
fused_func_prefix = pyrex_prefix + 'fuse_'
quick_temp_cname = pyrex_prefix + "temp" # temp variable for quick'n'dirty temping
genexpr_id_ref = 'genexpr'
......
......@@ -20,11 +20,13 @@ import TypeSlots
from PyrexTypes import py_object_type, error_type, CTypedefType, CFuncType, cython_memoryview_ptr_type
from Symtab import ModuleScope, LocalScope, ClosureScope, \
StructOrUnionScope, PyClassScope, CClassScope, CppClassScope
from Cython.Compiler import Symtab
from Cython.Utils import open_new_file, replace_suffix
from Code import UtilityCode, ClosureTempAllocator
from StringEncoding import EncodedString, escape_byte_string, split_string_literal
import Options
import DebugFlags
from Cython.Compiler import Errors
from itertools import chain
absolute_path_length = 0
......@@ -564,22 +566,6 @@ class CFuncDeclaratorNode(CDeclaratorNode):
elif self.optional_arg_count:
error(self.pos, "Non-default argument follows default argument")
if self.optional_arg_count:
scope = StructOrUnionScope()
arg_count_member = '%sn' % Naming.pyrex_prefix
scope.declare_var(arg_count_member, PyrexTypes.c_int_type, self.pos)
for arg in func_type_args[len(func_type_args)-self.optional_arg_count:]:
scope.declare_var(arg.name, arg.type, arg.pos, allow_pyobject = 1)
struct_cname = env.mangle(Naming.opt_arg_prefix, self.base.name)
self.op_args_struct = env.global_scope().declare_struct_or_union(name = struct_cname,
kind = 'struct',
scope = scope,
typedef_flag = 0,
pos = self.pos,
cname = struct_cname)
self.op_args_struct.defined_in_pxd = 1
self.op_args_struct.used = 1
exc_val = None
exc_check = 0
if self.exception_check == '+':
......@@ -624,8 +610,19 @@ class CFuncDeclaratorNode(CDeclaratorNode):
exception_value = exc_val, exception_check = exc_check,
calling_convention = self.base.calling_convention,
nogil = self.nogil, with_gil = self.with_gil, is_overridable = self.overridable)
if self.optional_arg_count:
func_type.op_arg_struct = PyrexTypes.c_ptr_type(self.op_args_struct.type)
if func_type.is_fused:
# This is a bit of a hack... When we need to create specialized CFuncTypes
# on the fly because the cdef is defined in a pxd, we need to declare the specialized optional arg
# struct
def declare_opt_arg_struct(func_type, fused_cname):
self.declare_optional_arg_struct(func_type, env, fused_cname)
func_type.declare_opt_arg_struct = declare_opt_arg_struct
else:
self.declare_optional_arg_struct(func_type, env)
callspec = env.directives['callspec']
if callspec:
current = func_type.calling_convention
......@@ -635,6 +632,38 @@ class CFuncDeclaratorNode(CDeclaratorNode):
func_type.calling_convention = callspec
return self.base.analyse(func_type, env)
def declare_optional_arg_struct(self, func_type, env, fused_cname=None):
"""
Declares the optional argument struct (the struct used to hold the
values for optional arguments). For fused cdef functions, this is
deferred as analyse_declarations is called only once (on the fused
cdef function).
"""
scope = StructOrUnionScope()
arg_count_member = '%sn' % Naming.pyrex_prefix
scope.declare_var(arg_count_member, PyrexTypes.c_int_type, self.pos)
for arg in func_type.args[len(func_type.args)-self.optional_arg_count:]:
scope.declare_var(arg.name, arg.type, arg.pos, allow_pyobject = 1)
struct_cname = env.mangle(Naming.opt_arg_prefix, self.base.name)
if fused_cname is not None:
struct_cname = PyrexTypes.get_fused_cname(fused_cname, struct_cname)
op_args_struct = env.global_scope().declare_struct_or_union(
name = struct_cname,
kind = 'struct',
scope = scope,
typedef_flag = 0,
pos = self.pos,
cname = struct_cname)
op_args_struct.defined_in_pxd = 1
op_args_struct.used = 1
func_type.op_arg_struct = PyrexTypes.c_ptr_type(op_args_struct.type)
class CArgDeclNode(Node):
# Item in a function declaration argument list.
......@@ -678,6 +707,7 @@ class CArgDeclNode(Node):
could_be_name = True
else:
could_be_name = False
self.base_type.is_arg = True
base_type = self.base_type.analyse(env, could_be_name = could_be_name)
if hasattr(self.base_type, 'arg_name') and self.base_type.arg_name:
self.declarator.name = self.base_type.arg_name
......@@ -774,9 +804,10 @@ class CSimpleBaseTypeNode(CBaseTypeNode):
if scope:
if scope.is_c_class_scope:
scope = scope.global_scope()
entry = scope.lookup(self.name)
if entry and entry.is_type:
type = entry.type
type = scope.lookup_type(self.name)
if type is not None:
pass
elif could_be_name:
if self.is_self_arg and env.is_c_class_scope:
type = env.parent_type
......@@ -812,6 +843,7 @@ class CSimpleBaseTypeNode(CBaseTypeNode):
class MemoryViewSliceTypeNode(CBaseTypeNode):
name = 'memoryview'
child_attrs = ['base_type_node', 'axes']
def analyse(self, env, could_be_name = False):
......@@ -946,6 +978,47 @@ class CComplexBaseTypeNode(CBaseTypeNode):
return type
class FusedTypeNode(CBaseTypeNode):
"""
Represents a fused type in a ctypedef statement:
ctypedef cython.fused_type(int, long, long long) integral
name str name of this fused type
types [CSimpleBaseTypeNode] is the list of types to be fused
"""
child_attrs = []
def analyse_declarations(self, env):
type = self.analyse(env)
entry = env.declare_typedef(self.name, type, self.pos)
# Omit the typedef declaration that self.declarator would produce
entry.in_cinclude = True
def analyse(self, env):
types = []
for type_node in self.types:
type = type_node.analyse_as_type(env)
if not type:
error(type_node.pos, "Not a type")
continue
if type in types:
error(type_node.pos, "Type specified multiple times")
elif type.is_fused:
error(type_node.pos, "Cannot fuse a fused type")
else:
types.append(type)
if len(self.types) == 1:
return types[0]
return PyrexTypes.FusedType(types, name=self.name)
class CVarDefNode(StatNode):
# C variable definition or forward/extern function declaration.
#
......@@ -954,6 +1027,7 @@ class CVarDefNode(StatNode):
# declarators [CDeclaratorNode]
# in_pxd boolean
# api boolean
# overridable boolean whether it is a cpdef
# decorators [cython.locals(...)] or None
# directive_locals { string : NameNode } locals defined by cython.locals(...)
......@@ -970,6 +1044,8 @@ class CVarDefNode(StatNode):
dest_scope = env
self.dest_scope = dest_scope
base_type = self.base_type.analyse(env)
self.entry = None
visibility = self.visibility
for declarator in self.declarators:
......@@ -990,17 +1066,18 @@ class CVarDefNode(StatNode):
error(declarator.pos, "Missing name in declaration.")
return
if type.is_cfunction:
entry = dest_scope.declare_cfunction(name, type, declarator.pos,
cname = cname, visibility = self.visibility,
in_pxd = self.in_pxd, api = self.api)
if entry is not None:
entry.directive_locals = copy.copy(self.directive_locals)
self.entry = dest_scope.declare_cfunction(name, type, declarator.pos,
cname = cname, visibility = self.visibility, in_pxd = self.in_pxd,
api = self.api)
if self.entry is not None:
self.entry.is_overridable = self.overridable
self.entry.directive_locals = copy.copy(self.directive_locals)
else:
if self.directive_locals:
error(self.pos, "Decorators can only be followed by functions")
entry = dest_scope.declare_var(name, type, declarator.pos,
cname = cname, visibility = visibility,
in_pxd = self.in_pxd, api = self.api, is_cdef = 1)
self.entry = dest_scope.declare_var(name, type, declarator.pos,
cname=cname, visibility=visibility, in_pxd=self.in_pxd,
api=self.api, is_cdef=1)
class CStructOrUnionDefNode(StatNode):
......@@ -1177,8 +1254,13 @@ class CTypeDefNode(StatNode):
name_declarator, type = self.declarator.analyse(base, env)
name = name_declarator.name
cname = name_declarator.cname
entry = env.declare_typedef(name, type, self.pos,
cname = cname, visibility = self.visibility, api = self.api)
if type.is_fused:
entry.in_cinclude = True
if self.in_pxd and not env.in_cinclude:
entry.defined_in_pxd = 1
......@@ -1201,6 +1283,11 @@ class FuncDefNode(StatNode, BlockNode):
# star_arg PyArgDeclNode or None * argument
# starstar_arg PyArgDeclNode or None ** argument
# has_fused_arguments boolean
# Whether this cdef function has fused parameters. This is needed
# by AnalyseDeclarationsTransform, so it can replace CFuncDefNodes
# with fused argument types with a FusedCFuncDefNode
py_func = None
assmt = None
needs_closure = False
......@@ -1209,6 +1296,7 @@ class FuncDefNode(StatNode, BlockNode):
is_generator = False
is_generator_body = False
modifiers = []
has_fused_arguments = False
star_arg = None
starstar_arg = None
......@@ -1757,6 +1845,9 @@ class CFuncDefNode(FuncDefNode):
# visibility 'private' or 'public' or 'extern'
# base_type CBaseTypeNode
# declarator CDeclaratorNode
# cfunc_declarator the CFuncDeclarator of this function
# (this is also available through declarator or a
# base thereof)
# body StatListNode
# api boolean
# decorators [DecoratorNode] list of decorators
......@@ -1801,12 +1892,30 @@ class CFuncDefNode(FuncDefNode):
declarator = self.declarator
while not hasattr(declarator, 'args'):
declarator = declarator.base
self.cfunc_declarator = declarator
self.args = declarator.args
opt_arg_count = self.cfunc_declarator.optional_arg_count
if (self.visibility == 'public' or self.api) and opt_arg_count:
error(self.cfunc_declarator.pos,
"Function with optional arguments may not be declared "
"public or api")
for formal_arg, type_arg in zip(self.args, type.args):
self.align_argument_type(env, type_arg)
formal_arg.type = type_arg.type
formal_arg.name = type_arg.name
formal_arg.cname = type_arg.cname
self._validate_type_visibility(type_arg.type, type_arg.pos, env)
if type_arg.type.is_fused:
self.has_fused_arguments = True
if type_arg.type.is_buffer and 'inline' in self.modifiers:
warning(formal_arg.pos, "Buffer unpacking not optimized away.", 1)
if type_arg.type.is_buffer:
if self.type.nogil:
error(formal_arg.pos,
......@@ -1815,8 +1924,11 @@ class CFuncDefNode(FuncDefNode):
elif 'inline' in self.modifiers:
warning(formal_arg.pos, "Buffer unpacking not optimized away.", 1)
self._validate_type_visibility(type.return_type, self.pos, env)
name = name_declarator.name
cname = name_declarator.cname
self.entry = env.declare_cfunction(
name, type, self.pos,
cname = cname, visibility = self.visibility, api = self.api,
......@@ -1832,8 +1944,12 @@ class CFuncDefNode(FuncDefNode):
# An error will be produced in the cdef function
self.overridable = False
self.declare_cpdef_wrapper(env)
self.create_local_scope(env)
def declare_cpdef_wrapper(self, env):
if self.overridable:
import ExprNodes
name = self.entry.name
py_func_body = self.call_self_node(is_module_scope = env.is_module_scope)
self.py_func = DefNode(pos = self.pos,
name = self.entry.name,
......@@ -1846,13 +1962,26 @@ class CFuncDefNode(FuncDefNode):
self.py_func.is_module_scope = env.is_module_scope
self.py_func.analyse_declarations(env)
self.entry.as_variable = self.py_func.entry
self.entry.used = self.entry.as_variable.used = True
# Reset scope entry the above cfunction
env.entries[name] = self.entry
if (not self.entry.is_final_cmethod and
(not env.is_module_scope or Options.lookup_module_cpdef)):
self.override = OverrideCheckNode(self.pos, py_func = self.py_func)
self.body = StatListNode(self.pos, stats=[self.override, self.body])
self.create_local_scope(env)
def _validate_type_visibility(self, type, pos, env):
"""
Ensure that types used in cdef functions are public or api, or
defined in a C header.
"""
public_or_api = (self.visibility == 'public' or self.api)
entry = getattr(type, 'entry', None)
if public_or_api and entry and env.is_module_scope:
if not (entry.visibility in ('public', 'extern') or
entry.api or entry.in_cinclude):
error(pos, "Function declared public or api may not have "
"private types")
def call_self_node(self, omit_optional_args=0, is_module_scope=0):
import ExprNodes
......@@ -2034,6 +2163,420 @@ class CFuncDefNode(FuncDefNode):
code.putln('}')
class FusedCFuncDefNode(StatListNode):
"""
This node replaces a function with fused arguments. It deep-copies the
function for every permutation of fused types, and allocates a new local
scope for it. It keeps track of the original function in self.node, and
the entry of the original function in the symbol table is given the
'fused_cfunction' attribute which points back to us.
Then when a function lookup occurs (to e.g. call it), the call can be
dispatched to the right function.
node FuncDefNode the original function
nodes [FuncDefNode] list of copies of node with different specific types
py_func DefNode the fused python function subscriptable from
Python space
"""
def __init__(self, node, env):
super(FusedCFuncDefNode, self).__init__(node.pos)
self.nodes = []
self.node = node
is_def = isinstance(self.node, DefNode)
if is_def:
self.copy_def(env)
else:
self.copy_cdef(env)
# Perform some sanity checks. If anything fails, it's a bug
for n in self.nodes:
assert not n.entry.type.is_fused
assert not n.local_scope.return_type.is_fused
if node.return_type.is_fused:
assert not n.return_type.is_fused
if not is_def and n.cfunc_declarator.optional_arg_count:
assert n.type.op_arg_struct
node.entry.fused_cfunction = self
if self.py_func:
self.py_func.entry.fused_cfunction = self
for node in self.nodes:
if is_def:
node.fused_py_func = self.py_func
else:
node.py_func.fused_py_func = self.py_func
node.entry.as_variable = self.py_func.entry
# Copy the nodes as AnalyseDeclarationsTransform will prepend
# self.py_func to self.stats, as we only want specialized
# CFuncDefNodes in self.nodes
self.stats = self.nodes[:]
def copy_def(self, env):
"""
Create a copy of the original def or lambda function for specialized
versions.
"""
fused_types = [arg.type for arg in self.node.args if arg.type.is_fused]
permutations = PyrexTypes.get_all_specific_permutations(fused_types)
if self.node.entry in env.pyfunc_entries:
env.pyfunc_entries.remove(self.node.entry)
for cname, fused_to_specific in permutations:
copied_node = copy.deepcopy(self.node)
self._specialize_function_args(copied_node.args, fused_to_specific)
copied_node.return_type = self.node.return_type.specialize(
fused_to_specific)
copied_node.analyse_declarations(env)
self.create_new_local_scope(copied_node, env, fused_to_specific)
self.specialize_copied_def(copied_node, cname, self.node.entry,
fused_to_specific, fused_types)
PyrexTypes.specialize_entry(copied_node.entry, cname)
copied_node.entry.used = True
env.entries[copied_node.entry.name] = copied_node.entry
if not self.replace_fused_typechecks(copied_node):
break
self.py_func = self.make_fused_cpdef(self.node, env, is_def=True)
def copy_cdef(self, env):
"""
Create a copy of the original c(p)def function for all specialized
versions.
"""
permutations = self.node.type.get_all_specific_permutations()
# print 'Node %s has %d specializations:' % (self.node.entry.name,
# len(permutations))
# import pprint; pprint.pprint([d for cname, d in permutations])
if self.node.entry in env.cfunc_entries:
env.cfunc_entries.remove(self.node.entry)
# Prevent copying of the python function
orig_py_func = self.node.py_func
self.node.py_func = None
if orig_py_func:
env.pyfunc_entries.remove(orig_py_func.entry)
fused_types = self.node.type.get_fused_types()
for cname, fused_to_specific in permutations:
copied_node = copy.deepcopy(self.node)
# Make the types in our CFuncType specific
type = copied_node.type.specialize(fused_to_specific)
entry = copied_node.entry
copied_node.type = type
entry.type, type.entry = type, entry
entry.used = (entry.used or
self.node.entry.defined_in_pxd or
env.is_c_class_scope or
entry.is_cmethod)
if self.node.cfunc_declarator.optional_arg_count:
self.node.cfunc_declarator.declare_optional_arg_struct(
type, env, fused_cname=cname)
copied_node.return_type = type.return_type
self.create_new_local_scope(copied_node, env, fused_to_specific)
# Make the argument types in the CFuncDeclarator specific
self._specialize_function_args(copied_node.cfunc_declarator.args,
fused_to_specific)
type.specialize_entry(entry, cname)
env.cfunc_entries.append(entry)
# If a cpdef, declare all specialized cpdefs (this
# also calls analyse_declarations)
copied_node.declare_cpdef_wrapper(env)
if copied_node.py_func:
env.pyfunc_entries.remove(copied_node.py_func.entry)
self.specialize_copied_def(
copied_node.py_func, cname, self.node.entry.as_variable,
fused_to_specific, fused_types)
if not self.replace_fused_typechecks(copied_node):
break
if orig_py_func:
self.py_func = self.make_fused_cpdef(orig_py_func, env,
is_def=False)
else:
self.py_func = orig_py_func
def _specialize_function_args(self, args, fused_to_specific):
import MemoryView
for arg in args:
if arg.type.is_fused:
arg.type = arg.type.specialize(fused_to_specific)
if arg.type.is_memoryviewslice:
MemoryView.validate_memslice_dtype(arg.pos, arg.type.dtype)
def create_new_local_scope(self, node, env, f2s):
"""
Create a new local scope for the copied node and append it to
self.nodes. A new local scope is needed because the arguments with the
fused types are aready in the local scope, and we need the specialized
entries created after analyse_declarations on each specialized version
of the (CFunc)DefNode.
f2s is a dict mapping each fused type to its specialized version
"""
node.create_local_scope(env)
node.local_scope.fused_to_specific = f2s
# This is copied from the original function, set it to false to
# stop recursion
node.has_fused_arguments = False
self.nodes.append(node)
def specialize_copied_def(self, node, cname, py_entry, f2s, fused_types):
"""Specialize the copy of a DefNode given the copied node,
the specialization cname and the original DefNode entry"""
type_strings = [
fused_type.specialize(f2s).typeof_name()
for fused_type in fused_types
]
#type_strings = [f2s[fused_type].typeof_name()
# for fused_type in fused_types]
node.specialized_signature_string = ', '.join(type_strings)
node.entry.pymethdef_cname = PyrexTypes.get_fused_cname(
cname, node.entry.pymethdef_cname)
node.entry.doc = py_entry.doc
node.entry.doc_cname = py_entry.doc_cname
def replace_fused_typechecks(self, copied_node):
"""
Branch-prune fused type checks like
if fused_t is int:
...
Returns whether an error was issued and whether we should stop in
in order to prevent a flood of errors.
"""
from Cython.Compiler import ParseTreeTransforms
num_errors = Errors.num_errors
transform = ParseTreeTransforms.ReplaceFusedTypeChecks(
copied_node.local_scope)
transform(copied_node)
if Errors.num_errors > num_errors:
return False
return True
def make_fused_cpdef(self, orig_py_func, env, is_def):
"""
This creates the function that is indexable from Python and does
runtime dispatch based on the argument types. The function gets the
arg tuple and kwargs dict (or None) as arugments from the Binding
Fused Function's tp_call.
"""
from Cython.Compiler import TreeFragment
from Cython.Compiler import ParseTreeTransforms
# { (arg_pos, FusedType) : specialized_type }
seen_fused_types = set()
# list of statements that do the instance checks
body_stmts = []
args = self.node.args
for i, arg in enumerate(args):
arg_type = arg.type
if arg_type.is_fused and arg_type not in seen_fused_types:
seen_fused_types.add(arg_type)
specialized_types = PyrexTypes.get_specialized_types(arg_type)
# Prefer long over int, etc
# specialized_types.sort()
seen_py_type_names = set()
first_check = True
body_stmts.append(u"""
if nargs >= %(nextidx)d or '%(argname)s' in kwargs:
if nargs >= %(nextidx)d:
arg = args[%(idx)d]
else:
arg = kwargs['%(argname)s']
""" % {'idx': i, 'nextidx': i + 1, 'argname': arg.name})
all_numeric = True
for specialized_type in specialized_types:
py_type_name = specialized_type.py_type_name()
if not py_type_name or py_type_name in seen_py_type_names:
continue
seen_py_type_names.add(py_type_name)
all_numeric = all_numeric and specialized_type.is_numeric
if first_check:
if_ = 'if'
first_check = False
else:
if_ = 'elif'
# in the case of long, unicode or bytes we need to instance
# check for long_, unicode_, bytes_ (long = long is no longer
# valid code with control flow analysis)
instance_check_py_type_name = py_type_name
if py_type_name in ('long', 'unicode', 'bytes'):
instance_check_py_type_name += '_'
tup = (if_, instance_check_py_type_name,
len(seen_fused_types) - 1,
specialized_type.typeof_name())
body_stmts.append(
" %s isinstance(arg, %s): "
"dest_sig[%d] = '%s'" % tup)
if arg.default and all_numeric:
arg.default.analyse_types(env)
ts = specialized_types
if arg.default.type.is_complex:
typelist = [t for t in ts if t.is_complex]
elif arg.default.type.is_float:
typelist = [t for t in ts if t.is_float]
else:
typelist = [t for t in ts if t.is_int]
if typelist:
body_stmts.append(u"""\
else:
dest_sig[%d] = '%s'
""" % (i, typelist[0].typeof_name()))
fmt_dict = {
'body': '\n'.join(body_stmts),
'nargs': len(args),
'name': orig_py_func.entry.name,
}
fragment_code = u"""
def __pyx_fused_cpdef(signatures, args, kwargs):
#if len(args) < %(nargs)d:
# raise TypeError("Invalid number of arguments, expected %(nargs)d, "
# "got %%d" %% len(args))
cdef int nargs
nargs = len(args)
import sys
if sys.version_info >= (3, 0):
long_ = int
unicode_ = str
bytes_ = bytes
else:
long_ = long
unicode_ = unicode
bytes_ = str
dest_sig = [None] * %(nargs)d
if kwargs is None:
kwargs = {}
# instance check body
%(body)s
candidates = []
for sig in signatures:
match_found = True
for src_type, dst_type in zip(sig.strip('()').split(', '), dest_sig):
if dst_type is not None and match_found:
match_found = src_type == dst_type
if match_found:
candidates.append(sig)
if not candidates:
raise TypeError("No matching signature found")
elif len(candidates) > 1:
raise TypeError("Function call with ambiguous argument types")
else:
return signatures[candidates[0]]
""" % fmt_dict
fragment = TreeFragment.TreeFragment(fragment_code, level='module')
# analyse the declarations of our fragment ...
py_func, = fragment.substitute(pos=self.node.pos).stats
# Analyse the function object ...
py_func.analyse_declarations(env)
# ... and its body
py_func.scope = env
ParseTreeTransforms.AnalyseDeclarationsTransform(None)(py_func)
e, orig_e = py_func.entry, orig_py_func.entry
# Update the new entry ...
py_func.name = e.name = orig_e.name
e.cname, e.func_cname = orig_e.cname, orig_e.func_cname
e.pymethdef_cname = orig_e.pymethdef_cname
e.doc, e.doc_cname = orig_e.doc, orig_e.doc_cname
# e.signature = TypeSlots.binaryfunc
py_func.doc = orig_py_func.doc
# ... and the symbol table
del env.entries['__pyx_fused_cpdef']
if is_def:
env.entries[e.name] = e
else:
env.entries[e.name].as_variable = e
env.pyfunc_entries.append(e)
if is_def:
py_func.specialized_cpdefs = self.nodes[:]
else:
py_func.specialized_cpdefs = [n.py_func for n in self.nodes]
return py_func
def generate_function_definitions(self, env, code):
# Ensure the indexable fused function is generated first, so we can
# use its docstring
# self.stats.insert(0, self.stats.pop())
for stat in self.stats:
# print stat.entry, stat.entry.used
if stat.entry.used:
code.mark_pos(stat.pos)
stat.generate_function_definitions(env, code)
def generate_execution_code(self, code):
for stat in self.stats:
if stat.entry.used:
code.mark_pos(stat.pos)
stat.generate_execution_code(code)
def annotate(self, code):
for stat in self.stats:
if stat.entry.used:
stat.annotate(code)
class PyArgDeclNode(Node):
# Argument which must be a Python object (used
# for * and ** arguments).
......@@ -2069,6 +2612,10 @@ class DefNode(FuncDefNode):
# when the def statement is inside a Python class definition.
#
# assmt AssignmentNode Function construction/assignment
#
# fused_py_func DefNode The original fused cpdef DefNode
# (in case this is a specialization)
# specialized_cpdefs [DefNode] list of specialized cpdef DefNodes
# py_cfunc_node PyCFunctionNode/InnerFunctionNode The PyCFunction to create and assign
child_attrs = ["args", "star_arg", "starstar_arg", "body", "decorators"]
......@@ -2088,6 +2635,9 @@ class DefNode(FuncDefNode):
py_cfunc_node = None
doc = None
fused_py_func = False
specialized_cpdefs = None
def __init__(self, pos, **kwds):
FuncDefNode.__init__(self, pos, **kwds)
k = rk = r = 0
......@@ -2199,6 +2749,7 @@ class DefNode(FuncDefNode):
self.declare_lambda_function(env)
else:
self.declare_pyfunction(env)
self.analyse_signature(env)
self.return_type = self.entry.signature.return_type()
self.create_local_scope(env)
......@@ -2215,6 +2766,10 @@ class DefNode(FuncDefNode):
arg.declarator.analyse(base_type, env)
arg.name = name_declarator.name
arg.type = type
if type.is_fused:
self.has_fused_arguments = True
self.align_argument_type(env, arg)
if name_declarator and name_declarator.cname:
error(self.pos,
......@@ -2404,6 +2959,7 @@ class DefNode(FuncDefNode):
def analyse_expressions(self, env):
self.local_scope.directives = env.directives
self.analyse_default_values(env)
if self.needs_assignment_synthesis(env):
# Shouldn't we be doing this at the module level too?
self.synthesize_assignment_node(env)
......@@ -2412,6 +2968,8 @@ class DefNode(FuncDefNode):
decorator.decorator.analyse_expressions(env)
def needs_assignment_synthesis(self, env, code=None):
if self.specialized_cpdefs:
return True
if self.no_assignment_synthesis:
return False
# Should enable for module level as well, that will require more testing...
......@@ -2426,6 +2984,10 @@ class DefNode(FuncDefNode):
def synthesize_assignment_node(self, env):
import ExprNodes
if self.fused_py_func:
return
genv = env
while genv.is_py_class_scope or genv.is_c_class_scope:
genv = genv.outer_scope
......@@ -2435,10 +2997,12 @@ class DefNode(FuncDefNode):
self.pos, pymethdef_cname = self.entry.pymethdef_cname,
code_object = ExprNodes.CodeObjectNode(self))
else:
rhs = self.py_cfunc_node = ExprNodes.PyCFunctionNode(
self.pos, pymethdef_cname = self.entry.pymethdef_cname,
binding = env.directives['binding'],
code_object = ExprNodes.CodeObjectNode(self))
rhs = ExprNodes.PyCFunctionNode(
self.pos,
pymethdef_cname=self.entry.pymethdef_cname,
binding=env.directives['binding'],
specialized_cpdefs=self.specialized_cpdefs,
code_object=ExprNodes.CodeObjectNode(self))
if env.is_py_class_scope:
if not self.is_staticmethod and not self.is_classmethod:
......@@ -2490,22 +3054,36 @@ class DefNode(FuncDefNode):
if mf: mf += " "
header = "static %s%s(%s)" % (mf, dc, arg_code)
code.putln("%s; /*proto*/" % header)
if proto_only:
if self.fused_py_func:
# If we are the specialized version of the cpdef, we still
# want the prototype for the "fused cpdef", in case we're
# checking to see if our method was overridden in Python
self.fused_py_func.generate_function_header(
code, with_pymethdef, proto_only=True)
return
if (Options.docstrings and self.entry.doc and
not self.fused_py_func and
not self.entry.scope.is_property_scope and
(not self.entry.is_special or self.entry.wrapperbase_cname)):
# h_code = code.globalstate['h_code']
docstr = self.entry.doc
if docstr.is_unicode:
docstr = docstr.utf8encode()
code.putln(
'static char %s[] = "%s";' % (
self.entry.doc_cname,
split_string_literal(escape_byte_string(docstr))))
if self.entry.is_special:
code.putln(
"struct wrapperbase %s;" % self.entry.wrapperbase_cname)
if with_pymethdef:
if with_pymethdef or self.fused_py_func:
code.put(
"static PyMethodDef %s = " %
self.entry.pymethdef_cname)
......@@ -2635,10 +3213,12 @@ class DefNode(FuncDefNode):
else:
func = arg.type.from_py_function
if func:
code.putln("%s = %s(%s); %s" % (
rhs = "%s(%s)" % (func, item)
if arg.type.is_enum:
rhs = arg.type.cast_code(rhs)
code.putln("%s = %s; %s" % (
arg.entry.cname,
func,
item,
rhs,
code.error_goto_if(arg.type.error_condition(arg.entry.cname), arg.pos)))
else:
error(arg.pos, "Cannot convert Python object argument to type '%s'" % arg.type)
......@@ -3877,6 +4457,7 @@ class SingleAssignmentNode(AssignmentNode):
if len(args) > 2 or kwds is not None:
error(self.rhs.pos, "Can only declare one type at a time.")
return
type = args[0].analyse_as_type(env)
if type is None:
error(args[0].pos, "Unknown type")
......@@ -3925,6 +4506,17 @@ class SingleAssignmentNode(AssignmentNode):
for member, type, pos in members:
scope.declare_var(member, type, pos)
elif func_name == 'fused_type':
# dtype = cython.fused_type(...)
self.declaration_only = True
if kwds:
error(self.rhs.function.pos,
"fused_type does not take keyword arguments")
fusednode = FusedTypeNode(self.rhs.pos,
name = self.lhs.name, types=args)
fusednode.analyse_declarations(env)
if self.declaration_only:
return
else:
......
......@@ -2992,8 +2992,18 @@ class ConstantFolding(Visitor.VisitorTransform, SkipDeclarations):
literal nodes at each step. Non-literal nodes are never merged
into a single node.
"""
def __init__(self, reevaluate=False):
"""
The reevaluate argument specifies whether constant values that were
previously computed should be recomputed.
"""
super(ConstantFolding, self).__init__()
self.reevaluate = reevaluate
def _calculate_const(self, node):
if node.constant_result is not ExprNodes.constant_value_not_set:
if (not self.reevaluate and
node.constant_result is not ExprNodes.constant_value_not_set):
return
# make sure we always set the value
......
......@@ -629,8 +629,9 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
'operator.comma' : ExprNodes.c_binop_constructor(','),
}
special_methods = set(['declare', 'union', 'struct', 'typedef', 'sizeof',
'cast', 'pointer', 'compiled', 'NULL', 'parallel'])
special_methods = set(['declare', 'union', 'struct', 'typedef',
'sizeof', 'cast', 'pointer', 'compiled',
'NULL', 'fused_type', 'parallel'])
special_methods.update(unop_method_nodes.keys())
valid_parallel_directives = set([
......@@ -1381,10 +1382,13 @@ if VALUE is not None:
count += 1
""")
fused_function = None
def __call__(self, root):
self.env_stack = [root.scope]
# needed to determine if a cdef var is declared after it's used.
self.seen_vars_stack = []
self.fused_error_funcs = set()
return super(AnalyseDeclarationsTransform, self).__call__(root)
def visit_NameNode(self, node):
......@@ -1424,9 +1428,20 @@ if VALUE is not None:
return node
def visit_FuncDefNode(self, node):
"""
Analyse a function and its body, as that hasn't happend yet. Also
analyse the directive_locals set by @cython.locals(). Then, if we are
a function with fused arguments, replace the function (after it has
declared itself in the symbol table!) with a FusedCFuncDefNode, and
analyse its children (which are in turn normal functions). If we're a
normal function, just analyse the body of the function.
"""
env = self.env_stack[-1]
self.seen_vars_stack.append(set())
lenv = node.local_scope
node.declare_arguments(lenv)
for var, type_node in node.directive_locals.items():
if not lenv.lookup_here(var): # don't redeclare args
type = type_node.analyse_as_type(lenv)
......@@ -1434,22 +1449,54 @@ if VALUE is not None:
lenv.declare_var(var, type, type_node.pos)
else:
error(type_node.pos, "Not a type")
node.body.analyse_declarations(lenv)
if lenv.nogil and lenv.has_with_gil_block:
# Acquire the GIL for cleanup in 'nogil' functions, by wrapping
# the entire function body in try/finally.
# The corresponding release will be taken care of by
# Nodes.FuncDefNode.generate_function_definitions()
node.body = Nodes.NogilTryFinallyStatNode(
node.body.pos,
body = node.body,
finally_clause = Nodes.EnsureGILNode(node.body.pos),
)
self.env_stack.append(lenv)
self.visitchildren(node)
self.env_stack.pop()
if node.is_generator and node.has_fused_arguments:
node.has_fused_arguments = False
error(node.pos, "Fused generators not supported")
node.gbody = Nodes.StatListNode(node.pos,
stats=[],
body=Nodes.PassStatNode(node.pos))
if node.has_fused_arguments:
if self.fused_function:
if self.fused_function not in self.fused_error_funcs:
error(node.pos, "Cannot nest fused functions")
self.fused_error_funcs.add(self.fused_function)
# env.declare_var(node.name, PyrexTypes.py_object_type, node.pos)
node = Nodes.SingleAssignmentNode(
node.pos,
lhs=ExprNodes.NameNode(node.pos, name=node.name),
rhs=ExprNodes.NoneNode(node.pos))
node.analyse_declarations(env)
return node
node = Nodes.FusedCFuncDefNode(node, env)
self.fused_function = node
self.visitchildren(node)
self.fused_function = None
if node.py_func:
node.stats.insert(0, node.py_func)
else:
node.body.analyse_declarations(lenv)
if lenv.nogil and lenv.has_with_gil_block:
# Acquire the GIL for cleanup in 'nogil' functions, by wrapping
# the entire function body in try/finally.
# The corresponding release will be taken care of by
# Nodes.FuncDefNode.generate_function_definitions()
node.body = Nodes.NogilTryFinallyStatNode(
node.body.pos,
body = node.body,
finally_clause = Nodes.EnsureGILNode(node.body.pos),
)
self.env_stack.append(lenv)
self.visitchildren(node)
self.env_stack.pop()
self.seen_vars_stack.pop()
return node
......@@ -1628,15 +1675,18 @@ if VALUE is not None:
class AnalyseExpressionsTransform(CythonTransform):
def visit_ModuleNode(self, node):
self.env_stack = [node.scope]
node.scope.infer_types()
node.body.analyse_expressions(node.scope)
self.visitchildren(node)
return node
def visit_FuncDefNode(self, node):
self.env_stack.append(node.local_scope)
node.local_scope.infer_types()
node.body.analyse_expressions(node.local_scope)
self.visitchildren(node)
self.env_stack.pop()
return node
def visit_ScopedExprNode(self, node):
......@@ -1646,6 +1696,24 @@ class AnalyseExpressionsTransform(CythonTransform):
self.visitchildren(node)
return node
def visit_IndexNode(self, node):
"""
Replace index nodes used to specialize cdef functions with fused
argument types with the Attribute- or NameNode referring to the
function. We then need to copy over the specialization properties to
the attribute or name node.
Because the indexing might be a Python indexing operation on a fused
function, or (usually) a Cython indexing operation, we need to
re-analyse the types.
"""
self.visit_Node(node)
if node.is_fused_index and node.type is not PyrexTypes.error_type:
node = node.base
return node
class ExpandInplaceOperators(EnvTransform):
......@@ -2084,6 +2152,10 @@ class CreateClosureClasses(CythonTransform):
target_module_scope.check_c_class(func_scope.scope_class)
def visit_LambdaNode(self, node):
if not isinstance(node.def_node, Nodes.DefNode):
# fused function, an error has been previously issued
return node
was_in_lambda = self.in_lambda
self.in_lambda = True
self.create_class_from_scope(node.def_node, self.module_scope, node)
......@@ -2396,6 +2468,95 @@ class TransformBuiltinMethods(EnvTransform):
return node
class ReplaceFusedTypeChecks(VisitorTransform):
"""
This is not a transform in the pipeline. It is invoked on the specific
versions of a cdef function with fused argument types. It filters out any
type branches that don't match. e.g.
if fused_t is mytype:
...
elif fused_t in other_fused_type:
...
"""
# Defer the import until now to avoid circularity...
from Cython.Compiler import Optimize
transform = Optimize.ConstantFolding(reevaluate=True)
def __init__(self, local_scope):
super(ReplaceFusedTypeChecks, self).__init__()
self.local_scope = local_scope
def visit_IfStatNode(self, node):
"""
Filters out any if clauses with false compile time type check
expression.
"""
self.visitchildren(node)
return self.transform(node)
def visit_PrimaryCmpNode(self, node):
type1 = node.operand1.analyse_as_type(self.local_scope)
type2 = node.operand2.analyse_as_type(self.local_scope)
if type1 and type2:
false_node = ExprNodes.BoolNode(node.pos, value=False)
true_node = ExprNodes.BoolNode(node.pos, value=True)
type1 = self.specialize_type(type1, node.operand1.pos)
op = node.operator
if op in ('is', 'is_not', '==', '!='):
type2 = self.specialize_type(type2, node.operand2.pos)
is_same = type1.same_as(type2)
eq = op in ('is', '==')
if (is_same and eq) or (not is_same and not eq):
return true_node
elif op in ('in', 'not_in'):
# We have to do an instance check directly, as operand2
# needs to be a fused type and not a type with a subtype
# that is fused. First unpack the typedef
if isinstance(type2, PyrexTypes.CTypedefType):
type2 = type2.typedef_base_type
if type1.is_fused:
error(node.operand1.pos, "Type is fused")
elif not type2.is_fused:
error(node.operand2.pos,
"Can only use 'in' or 'not in' on a fused type")
else:
types = PyrexTypes.get_specialized_types(type2)
for specific_type in types:
if type1.same_as(specific_type):
if op == 'in':
return true_node
else:
return false_node
if op == 'not_in':
return true_node
return false_node
return node
def specialize_type(self, type, pos):
try:
return type.specialize(self.local_scope.fused_to_specific)
except KeyError:
error(pos, "Type is not specific")
return type
def visit_Node(self, node):
self.visitchildren(node)
return node
class DebugTransform(CythonTransform):
"""
Write debug information for this Cython module.
......
......@@ -134,9 +134,10 @@ cdef p_buffer_or_template(PyrexScanner s, base_type_node, templates)
cdef is_memoryviewslice_access(PyrexScanner s)
cdef p_memoryviewslice_access(PyrexScanner s, base_type_node)
cdef bint looking_at_name(PyrexScanner s) except -2
cdef bint looking_at_expr(PyrexScanner s) except -2
cdef object looking_at_expr(PyrexScanner s)# except -2
cdef bint looking_at_base_type(PyrexScanner s) except -2
cdef bint looking_at_dotted_name(PyrexScanner s) except -2
cdef bint looking_at_call(PyrexScanner s) except -2
cdef p_sign_and_longness(PyrexScanner s)
cdef p_opt_cname(PyrexScanner s)
cpdef p_c_declarator(PyrexScanner s, ctx = *, bint empty = *, bint is_type = *, bint cmethod_flag = *,
......@@ -161,6 +162,7 @@ cdef p_c_enum_definition(PyrexScanner s, pos, ctx)
cdef p_c_enum_line(PyrexScanner s, ctx, list items)
cdef p_c_enum_item(PyrexScanner s, ctx, list items)
cdef p_c_struct_or_union_definition(PyrexScanner s, pos, ctx)
cdef p_fused_definition(PyrexScanner s, pos, ctx)
cdef p_visibility(PyrexScanner s, prev_visibility)
cdef p_c_modifiers(PyrexScanner s)
cdef p_c_func_or_var_declaration(PyrexScanner s, pos, ctx)
......
......@@ -2121,10 +2121,12 @@ def looking_at_expr(s):
name = s.systring
dotted_path = []
s.next()
while s.sy == '.':
s.next()
dotted_path.append(s.systring)
s.expect('IDENT')
saved = s.sy, s.systring
if s.sy == 'IDENT':
is_type = True
......@@ -2140,12 +2142,14 @@ def looking_at_expr(s):
s.next()
is_type = s.sy == ']'
s.put_back(*saved)
dotted_path.reverse()
for p in dotted_path:
s.put_back('IDENT', p)
s.put_back('.', '.')
s.put_back('IDENT', name)
return not is_type
return not is_type and saved[0]
else:
return True
......@@ -2163,6 +2167,17 @@ def looking_at_dotted_name(s):
else:
return 0
def looking_at_call(s):
"See if we're looking at a.b.c("
# Don't mess up the original position, so save and restore it.
# Unfortunately there's no good way to handle this, as a subsequent call
# to next() will not advance the position until it reads a new token.
position = s.start_line, s.start_col
result = looking_at_expr(s) == u'('
if not result:
s.start_line, s.start_col = position
return result
basic_c_type_names = ("void", "char", "int", "float", "double", "bint")
special_basic_c_types = {
......@@ -2179,6 +2194,8 @@ sign_and_longness_words = ("short", "long", "signed", "unsigned")
base_type_start_words = \
basic_c_type_names + sign_and_longness_words + tuple(special_basic_c_types)
struct_enum_union = ("struct", "union", "enum", "packed")
def p_sign_and_longness(s):
signed = 1
longness = 0
......@@ -2485,15 +2502,14 @@ def p_cdef_statement(s, ctx):
if ctx.visibility != 'extern':
error(pos, "C++ classes need to be declared extern")
return p_cpp_class_definition(s, pos, ctx)
elif s.sy == 'IDENT' and s.systring in ("struct", "union", "enum", "packed"):
elif s.sy == 'IDENT' and s.systring in struct_enum_union:
if ctx.level not in ('module', 'module_pxd'):
error(pos, "C struct/union/enum definition not allowed here")
if ctx.overridable:
error(pos, "C struct/union/enum cannot be declared cpdef")
if s.systring == "enum":
return p_c_enum_definition(s, pos, ctx)
else:
return p_c_struct_or_union_definition(s, pos, ctx)
return p_struct_enum(s, pos, ctx)
elif s.sy == 'IDENT' and s.systring == 'fused':
return p_fused_definition(s, pos, ctx)
else:
return p_c_func_or_var_declaration(s, pos, ctx)
......@@ -2610,6 +2626,46 @@ def p_c_struct_or_union_definition(s, pos, ctx):
typedef_flag = ctx.typedef_flag, visibility = ctx.visibility,
api = ctx.api, in_pxd = ctx.level == 'module_pxd', packed = packed)
def p_fused_definition(s, pos, ctx):
"""
c(type)def fused my_fused_type:
...
"""
# s.systring == 'fused'
if ctx.level not in ('module', 'module_pxd'):
error(pos, "Fused type definition not allowed here")
s.next()
name = p_ident(s)
s.expect(":")
s.expect_newline()
s.expect_indent()
types = []
while s.sy != 'DEDENT':
if s.sy != 'pass':
#types.append(p_c_declarator(s))
types.append(p_c_base_type(s)) #, nonempty=1))
else:
s.next()
s.expect_newline()
s.expect_dedent()
if not types:
error(pos, "Need at least one type")
return Nodes.FusedTypeNode(pos, name=name, types=types)
def p_struct_enum(s, pos, ctx):
if s.systring == 'enum':
return p_c_enum_definition(s, pos, ctx)
else:
return p_c_struct_or_union_definition(s, pos, ctx)
def p_visibility(s, prev_visibility):
pos = s.position()
visibility = prev_visibility
......@@ -2680,11 +2736,10 @@ def p_ctypedef_statement(s, ctx):
ctx.api = 1
if s.sy == 'class':
return p_c_class_definition(s, pos, ctx)
elif s.sy == 'IDENT' and s.systring in ('packed', 'struct', 'union', 'enum'):
if s.systring == 'enum':
return p_c_enum_definition(s, pos, ctx)
else:
return p_c_struct_or_union_definition(s, pos, ctx)
elif s.sy == 'IDENT' and s.systring in struct_enum_union:
return p_struct_enum(s, pos, ctx)
elif s.sy == 'IDENT' and s.systring == 'fused':
return p_fused_definition(s, pos, ctx)
else:
base_type = p_c_base_type(s, nonempty = 1)
declarator = p_c_declarator(s, ctx, is_type = 1, nonempty = 1)
......
......@@ -62,17 +62,25 @@ def inject_pxd_code_stage_factory(context):
return module_node
return inject_pxd_code_stage
def use_utility_code_definitions(scope, target):
def use_utility_code_definitions(scope, target, seen=None):
if seen is None:
seen = set()
for entry in scope.entries.itervalues():
if entry in seen:
continue
seen.add(entry)
if entry.used and entry.utility_code_definition:
target.use_utility_code(entry.utility_code_definition)
for required_utility in entry.utility_code_definition.requires:
target.use_utility_code(required_utility)
elif entry.as_module:
use_utility_code_definitions(entry.as_module, target)
use_utility_code_definitions(entry.as_module, target, seen)
def inject_utility_code_stage_factory(context):
def inject_utility_code_stage(module_node):
use_utility_code_definitions(context.cython_scope, module_node.scope)
added = []
# Note: the list might be extended inside the loop (if some utility code
# pulls in other utility code, explicitly or implicitly)
......
......@@ -2,6 +2,8 @@
# Cython/Python language types
#
import cython
from Code import UtilityCode, LazyUtilityCode, ContentHashingUtilityCode
import StringEncoding
import Naming
......@@ -14,6 +16,9 @@ class BaseType(object):
#
# Base class for all Cython types including pseudo-types.
# List of attribute names of any subtypes
subtypes = []
def can_coerce_to_pyobject(self, env):
return False
......@@ -29,6 +34,62 @@ class BaseType(object):
else:
return base_code
def __deepcopy__(self, memo):
"""
Types never need to be copied, if we do copy, Unfortunate Things
Will Happen!
"""
return self
def get_fused_types(self, result=None, seen=None, subtypes=None):
subtypes = subtypes or self.subtypes
if subtypes:
if result is None:
result = []
seen = set()
for attr in subtypes:
list_or_subtype = getattr(self, attr)
if isinstance(list_or_subtype, BaseType):
list_or_subtype.get_fused_types(result, seen)
else:
for subtype in list_or_subtype:
subtype.get_fused_types(result, seen)
return result
return None
is_fused = property(get_fused_types, doc="Whether this type or any of its "
"subtypes is a fused type")
def __lt__(self, other):
"""
For sorting. The sorting order should correspond to the preference of
conversion from Python types.
Override to provide something sensible. This is only implemented so that
python 3 doesn't trip
"""
return id(type(self)) < id(type(other))
def py_type_name(self):
"""
Return the name of the Python type that can coerce to this type.
"""
def typeof_name(self):
"""
Return the string with which fused python functions can be indexed.
"""
if self.is_builtin_type or self.py_type_name() == 'object':
index_name = self.py_type_name()
else:
index_name = str(self)
return index_name
def check_for_null_code(self, cname):
"""
Return the code for a NULL-check in case an UnboundLocalError should
......@@ -43,6 +104,7 @@ class BaseType(object):
C expression string. Returns None if no such value exists.
"""
class PyrexType(BaseType):
#
# Base class for all Cython types
......@@ -72,6 +134,7 @@ class PyrexType(BaseType):
# is_buffer boolean Is buffer access type
# has_attributes boolean Has C dot-selectable attributes
# default_value string Initial value
# entry Entry The Entry for this type
#
# declaration_code(entity_code,
# for_display = 0, dll_linkage = None, pyrex = 0)
......@@ -195,9 +258,16 @@ def public_decl(base_code, dll_linkage):
return base_code
def create_typedef_type(name, base_type, cname, is_external=0):
if base_type.is_complex:
is_fused = base_type.is_fused
if base_type.is_complex or is_fused:
if is_external:
raise ValueError("Complex external typedefs not supported")
if is_fused:
msg = "Fused"
else:
msg = "Complex"
raise ValueError("%s external typedefs not supported" % msg)
return base_type
else:
return CTypedefType(name, base_type, cname, is_external)
......@@ -222,6 +292,7 @@ class CTypedefType(BaseType):
to_py_utility_code = None
from_py_utility_code = None
subtypes = ['typedef_base_type']
def __init__(self, name, base_type, cname, is_external=0):
assert not base_type.is_complex
......@@ -326,6 +397,10 @@ class CTypedefType(BaseType):
def __getattr__(self, name):
return getattr(self.typedef_base_type, name)
def py_type_name(self):
return self.typedef_base_type.py_type_name()
class MemoryViewSliceType(PyrexType):
is_memoryviewslice = 1
......@@ -340,6 +415,8 @@ class MemoryViewSliceType(PyrexType):
exception_value = None
exception_check = True
subtypes = ['dtype']
def __init__(self, base_dtype, axes):
'''
MemoryViewSliceType(base, axes)
......@@ -387,7 +464,8 @@ class MemoryViewSliceType(PyrexType):
self.mode = MemoryView.get_mode(axes)
self.writable_needed = False
self.dtype_name = MemoryView.mangle_dtype_name(self.dtype)
if not self.dtype.is_fused:
self.dtype_name = MemoryView.mangle_dtype_name(self.dtype)
def same_as_resolved_type(self, other_type):
return ((other_type.is_memoryviewslice and
......@@ -429,7 +507,7 @@ class MemoryViewSliceType(PyrexType):
return True
def declare_attribute(self, attribute):
def declare_attribute(self, attribute, env):
import MemoryView, Options
scope = self.scope
......@@ -489,6 +567,8 @@ class MemoryViewSliceType(PyrexType):
entry.utility_code_definition = \
MemoryView.CopyFuncUtilCode(self, to_memview)
MemoryView.use_cython_array_utility_code(env)
elif attribute in ("is_c_contig", "is_f_contig"):
# is_c_contig and is_f_contig functions
for (c_or_f, cython_name) in (('c', 'is_c_contig'), ('fortran', 'is_f_contig')):
......@@ -634,11 +714,17 @@ class MemoryViewSliceType(PyrexType):
import MemoryView
axes_code_list = []
for access, packing in self.axes:
for idx, (access, packing) in enumerate(self.axes):
flag = MemoryView.get_memoryview_flag(access, packing)
if flag == "strided":
axes_code_list.append(":")
else:
if flag == 'contiguous':
have_follow = [p for a, p in self.axes[idx - 1:idx + 2]
if p == 'follow']
if have_follow or self.ndim == 1:
flag = '1'
axes_code_list.append("::" + flag)
if self.dtype.is_pyobject:
......@@ -648,6 +734,13 @@ class MemoryViewSliceType(PyrexType):
return "%s[%s]" % (dtype_name, ", ".join(axes_code_list))
def specialize(self, values):
"This does not validate the base type!!"
dtype = self.dtype.specialize(values)
if dtype is not self.dtype:
return MemoryViewSliceType(dtype, self.axes)
class BufferType(BaseType):
#
......@@ -665,6 +758,9 @@ class BufferType(BaseType):
is_buffer = 1
writable = True
subtypes = ['dtype']
def __init__(self, base, dtype, ndim, mode, negative_indices, cast):
self.base = base
self.dtype = dtype
......@@ -729,6 +825,16 @@ class PyObjectType(PyrexType):
else:
return cname
def py_type_name(self):
return "object"
def __lt__(self, other):
"""
Make sure we sort highest, as instance checking on py_type_name
('object') is always true
"""
return False
def global_init_code(self, entry, code):
code.put_init_var_to_py_none(entry, nanny=False)
......@@ -837,6 +943,10 @@ class BuiltinObjectType(PyObjectType):
to_object_struct and self.objstruct_cname or "PyObject", # self.objstruct_cname may be None
expr_code)
def py_type_name(self):
return self.name
class PyExtensionType(PyObjectType):
#
......@@ -955,6 +1065,12 @@ class PyExtensionType(PyObjectType):
return "<PyExtensionType %s%s>" % (self.scope.class_name,
("", " typedef")[self.typedef_flag])
def py_type_name(self):
if not self.module_name:
return self.name
return "__import__(%r, None, None, ['']).%s" % (self.module_name,
self.name)
class CType(PyrexType):
#
......@@ -992,6 +1108,46 @@ class CType(PyrexType):
return 0
class FusedType(PyrexType):
"""
Represents a Fused Type. All it needs to do is keep track of the types
it aggregates, as it will be replaced with its specific version wherever
needed.
See http://wiki.cython.org/enhancements/fusedtypes
types [PyrexType] is the list of types to be fused
name str the name of the ctypedef
"""
is_fused = 1
def __init__(self, types, name=None):
self.types = types
self.name = name
def declaration_code(self, entity_code, for_display = 0,
dll_linkage = None, pyrex = 0):
if pyrex or for_display:
return self.name
raise Exception("This may never happen, please report a bug")
def __repr__(self):
return 'FusedType(name=%r)' % self.name
def specialize(self, values):
return values[self]
def get_fused_types(self, result=None, seen=None):
if result is None:
return [self]
if self not in seen:
result.append(self)
seen.add(self)
class CVoidType(CType):
#
# C "void" type
......@@ -1067,6 +1223,18 @@ class CNumericType(CType):
cname=" ")
return True
def __lt__(self, other):
"Sort based on rank, preferring signed over unsigned"
if other.is_numeric:
return self.rank > other.rank and self.signed >= other.signed
# Prefer numeric types over others
return True
def py_type_name(self):
if self.rank <= 4:
return "(int, long)"
return "float"
type_conversion_predeclarations = ""
type_conversion_functions = ""
......@@ -1312,6 +1480,9 @@ class CBIntType(CIntType):
def __str__(self):
return 'bint'
def py_type_name(self):
return "bool"
class CPyUCS4IntType(CIntType):
# Py_UCS4
......@@ -1653,6 +1824,9 @@ class CComplexType(CNumericType):
def binary_op(self, op):
return self.lookup_op(2, op)
def py_type_name(self):
return "complex"
complex_ops = {
(1, '-'): 'neg',
(1, 'zero'): 'is_zero',
......@@ -1926,6 +2100,8 @@ class CArrayType(CType):
is_array = 1
subtypes = ['base_type']
def __init__(self, base_type, size):
self.base_type = base_type
self.size = size
......@@ -1972,6 +2148,8 @@ class CPtrType(CType):
is_ptr = 1
default_value = "0"
subtypes = ['base_type']
def __init__(self, base_type):
self.base_type = base_type
......@@ -2068,11 +2246,18 @@ class CFuncType(CType):
# nogil boolean Can be called without gil
# with_gil boolean Acquire gil around function body
# templates [string] or None
# cached_specialized_types [CFuncType] cached specialized versions of the CFuncType if defined in a pxd
# from_fused boolean Indicates whether this is a specialized
# C function
# is_strict_signature boolean function refuses to accept coerced arguments
# (used for optimisation overrides)
is_cfunction = 1
original_sig = None
cached_specialized_types = None
from_fused = False
subtypes = ['return_type', 'args']
def __init__(self, return_type, args, has_varargs = 0,
exception_value = None, exception_check = 0, calling_convention = "",
......@@ -2298,23 +2483,157 @@ class CFuncType(CType):
new_templates = None
else:
new_templates = [v.specialize(values) for v in self.templates]
return CFuncType(self.return_type.specialize(values),
[arg.specialize(values) for arg in self.args],
has_varargs = 0,
exception_value = self.exception_value,
exception_check = self.exception_check,
calling_convention = self.calling_convention,
nogil = self.nogil,
with_gil = self.with_gil,
is_overridable = self.is_overridable,
optional_arg_count = self.optional_arg_count,
templates = new_templates)
result = CFuncType(self.return_type.specialize(values),
[arg.specialize(values) for arg in self.args],
has_varargs = 0,
exception_value = self.exception_value,
exception_check = self.exception_check,
calling_convention = self.calling_convention,
nogil = self.nogil,
with_gil = self.with_gil,
is_overridable = self.is_overridable,
optional_arg_count = self.optional_arg_count,
templates = new_templates)
result.from_fused = self.is_fused
return result
def opt_arg_cname(self, arg_name):
return self.op_arg_struct.base_type.scope.lookup(arg_name).cname
# Methods that deal with Fused Types
# All but map_with_specific_entries should be called only on functions
# with fused types (and not on their corresponding specific versions).
def get_all_specific_permutations(self, fused_types=None):
"""
Permute all the types. For every specific instance of a fused type, we
want all other specific instances of all other fused types.
It returns an iterable of two-tuples of the cname that should prefix
the cname of the function, and a dict mapping any fused types to their
respective specific types.
"""
assert self.is_fused
if fused_types is None:
fused_types = self.get_fused_types()
return get_all_specific_permutations(fused_types)
def get_all_specific_function_types(self):
"""
Get all the specific function types of this one.
"""
assert self.is_fused
if self.entry.fused_cfunction:
return [n.type for n in self.entry.fused_cfunction.nodes]
elif self.cached_specialized_types is not None:
return self.cached_specialized_types
cfunc_entries = self.entry.scope.cfunc_entries
cfunc_entries.remove(self.entry)
result = []
permutations = self.get_all_specific_permutations()
for cname, fused_to_specific in permutations:
new_func_type = self.entry.type.specialize(fused_to_specific)
if self.optional_arg_count:
# Remember, this method is set by CFuncDeclaratorNode
self.declare_opt_arg_struct(new_func_type, cname)
new_entry = copy.deepcopy(self.entry)
new_func_type.specialize_entry(new_entry, cname)
new_entry.type = new_func_type
new_func_type.entry = new_entry
result.append(new_func_type)
cfunc_entries.append(new_entry)
self.cached_specialized_types = result
return result
def get_fused_types(self, result=None, seen=None, subtypes=None):
"Return fused types in the order they appear as parameter types"
return super(CFuncType, self).get_fused_types(result, seen,
subtypes=['args'])
def specialize_entry(self, entry, cname):
assert not self.is_fused
specialize_entry(entry, cname)
def specialize_entry(entry, cname):
"""
Specialize an entry of a copied fused function or method
"""
entry.name = get_fused_cname(cname, entry.name)
class CFuncTypeArg(object):
if entry.is_cmethod:
entry.cname = entry.name
if entry.is_inherited:
entry.cname = StringEncoding.EncodedString(
"%s.%s" % (Naming.obj_base_cname, entry.cname))
else:
entry.cname = get_fused_cname(cname, entry.cname)
if entry.func_cname:
entry.func_cname = get_fused_cname(cname, entry.func_cname)
def get_fused_cname(fused_cname, orig_cname):
"""
Given the fused cname id and an original cname, return a specialized cname
"""
assert fused_cname and orig_cname
return StringEncoding.EncodedString('%s%s%s' % (Naming.fused_func_prefix,
fused_cname, orig_cname))
def get_all_specific_permutations(fused_types, id="", f2s=()):
fused_type, = fused_types[0].get_fused_types()
result = []
for newid, specific_type in enumerate(fused_type.types):
# f2s = dict(f2s, **{ fused_type: specific_type })
f2s = dict(f2s)
f2s.update({ fused_type: specific_type })
if id:
cname = '%s_%s' % (id, newid)
else:
cname = str(newid)
if len(fused_types) > 1:
result.extend(get_all_specific_permutations(
fused_types[1:], cname, f2s))
else:
result.append((cname, f2s))
return result
def get_specialized_types(type):
"""
Return a list of specialized types sorted in reverse order in accordance
with their preference in runtime fused-type dispatch
"""
assert type.is_fused
if isinstance(type, FusedType):
result = type.types
else:
result = []
for cname, f2s in get_all_specific_permutations(type.get_fused_types()):
result.append(type.specialize(f2s))
return sorted(result)
class CFuncTypeArg(BaseType):
# name string
# cname string
# type PyrexType
......@@ -2326,6 +2645,8 @@ class CFuncTypeArg(object):
accept_none = True
accept_builtin_subtypes = False
subtypes = ['type']
def __init__(self, name, type, pos, cname=None):
self.name = name
if cname is not None:
......@@ -2722,6 +3043,10 @@ class CStringType(object):
assert isinstance(value, str)
return '"%s"' % StringEncoding.escape_byte_string(value)
def py_type_name(self):
if self.is_unicode:
return "unicode"
return "bytes"
class CUTF8CharArrayType(CStringType, CArrayType):
# C 'char []' type.
......@@ -2873,6 +3198,16 @@ c_size_t_ptr_type = CPtrType(c_size_t_type)
c_py_buffer_type = CStructOrUnionType("Py_buffer", "struct", None, 1, "Py_buffer")
c_py_buffer_ptr_type = CPtrType(c_py_buffer_type)
# Not sure whether the unsigned versions and 'long long' should be in there
# long long requires C99 and might be slow, and would always get preferred
# when specialization happens through calling and not indexing
cy_integral_type = FusedType([c_int_type, c_long_type], name="integral")
# Omitting long double as it might be slow
cy_floating_type = FusedType([c_float_type, c_double_type], name="floating")
cy_numeric_type = FusedType([c_long_type,
c_double_type,
c_double_complex_type], name="numeric")
# buffer-related structs
c_buf_diminfo_type = CStructOrUnionType("__Pyx_Buf_DimInfo", "struct",
None, 1, "__Pyx_Buf_DimInfo")
......@@ -2949,7 +3284,7 @@ def is_promotion(src_type, dst_type):
return src_type.is_float and src_type.rank <= dst_type.rank
return False
def best_match(args, functions, pos=None):
def best_match(args, functions, pos=None, env=None):
"""
Given a list args of arguments and a list of functions, choose one
to call which seems to be the "best" fit for this list of arguments.
......@@ -2971,6 +3306,8 @@ def best_match(args, functions, pos=None):
the same weight, we return None (as there is no best match). If pos
is not None, we also generate an error.
"""
from Cython import Utils
# TODO: args should be a list of types, not a list of Nodes.
actual_nargs = len(args)
......@@ -3009,26 +3346,53 @@ def best_match(args, functions, pos=None):
return candidates[0][0]
elif len(candidates) == 0:
if pos is not None:
if len(errors) == 1:
error(pos, errors[0][1])
func, errmsg = errors[0]
if len(errors) == 1 or [1 for func, e in errors if e == errmsg]:
error(pos, errmsg)
else:
error(pos, "no suitable method found")
return None
possibilities = []
bad_types = []
needed_coercions = {}
for index, (func, func_type) in enumerate(candidates):
score = [0,0,0]
score = [0,0,0,0]
for i in range(min(len(args), len(func_type.args))):
src_type = args[i].type
dst_type = func_type.args[i].type
if dst_type.assignable_from(src_type):
assignable = dst_type.assignable_from(src_type)
# Now take care of normal string literals. So when you call a cdef
# function that takes a char *, the coercion will mean that the
# type will simply become bytes. We need to do this coercion
# manually for overloaded and fused functions
if not assignable and src_type.is_pyobject:
if (src_type.is_builtin_type and src_type.name == 'str' and
dst_type.resolve() is c_char_ptr_type):
c_src_type = c_char_ptr_type
else:
c_src_type = src_type.default_coerced_ctype()
if c_src_type:
assignable = dst_type.assignable_from(c_src_type)
if assignable:
src_type = c_src_type
needed_coercions[func] = i, dst_type
if assignable:
if src_type == dst_type or dst_type.same_as(src_type):
pass # score 0
elif func_type.is_strict_signature:
break # exact match requested but not found
elif is_promotion(src_type, dst_type):
score[2] += 1
elif ((src_type.is_int and dst_type.is_int) or
(src_type.is_float and dst_type.is_float)):
score[2] += abs(dst_type.rank + (not dst_type.signed) -
(src_type.rank + (not src_type.signed)))
elif not src_type.is_pyobject:
score[1] += 1
else:
......@@ -3040,18 +3404,28 @@ def best_match(args, functions, pos=None):
break
else:
possibilities.append((score, index, func)) # so we can sort it
if possibilities:
possibilities.sort()
if len(possibilities) > 1 and possibilities[0][0] == possibilities[1][0]:
if pos is not None:
error(pos, "ambiguous overloaded method")
return None
return possibilities[0][-1]
function = possibilities[0][-1]
if function in needed_coercions and env:
arg_i, coerce_to_type = needed_coercions[function]
args[arg_i] = args[arg_i].coerce_to(coerce_to_type, env)
return function
if pos is not None:
if len(bad_types) == 1:
error(pos, bad_types[0][1])
else:
error(pos, "no suitable method found")
return None
def widest_numeric_type(type1, type2):
......
......@@ -115,6 +115,9 @@ class EncodedString(_unicode):
# otherwise
encoding = None
def __deepcopy__(self, memo):
return self
def byteencode(self):
assert self.encoding is not None
return self.encode(self.encoding)
......@@ -131,6 +134,9 @@ class BytesLiteral(_bytes):
# bytes subclass that is compatible with EncodedString
encoding = None
def __deepcopy__(self, memo):
return self
def byteencode(self):
if IS_PYTHON3:
return _bytes(self)
......
......@@ -180,6 +180,7 @@ class Entry(object):
buffer_aux = None
prev_entry = None
might_overflow = 0
fused_cfunction = None
utility_code_definition = None
in_with_gil_block = 0
from_cython_utility_code = None
......@@ -250,6 +251,7 @@ class Scope(object):
scope_prefix = ""
in_cinclude = 0
nogil = 0
fused_to_specific = None
def __init__(self, name, outer_scope, parent_scope):
# The outer_scope is the next scope in the lookup chain.
......@@ -286,6 +288,9 @@ class Scope(object):
self.return_type = None
self.id_counters = {}
def __deepcopy__(self, memo):
return self
def merge_in(self, other, merge_unused=True):
# Use with care...
entries = [(name, entry)
......@@ -415,6 +420,9 @@ class Scope(object):
entry.api = api
if defining:
self.type_entries.append(entry)
type.entry = entry
# here we would set as_variable to an object representing this type
return entry
......@@ -670,6 +678,7 @@ class Scope(object):
if modifiers:
entry.func_modifiers = modifiers
entry.utility_code = utility_code
type.entry = entry
return entry
def add_cfunction(self, name, type, pos, cname, visibility, modifiers):
......@@ -726,6 +735,8 @@ class Scope(object):
def lookup_type(self, name):
entry = self.lookup(name)
if entry and entry.is_type:
if entry.type.is_fused and self.fused_to_specific:
return entry.type.specialize(self.fused_to_specific)
return entry.type
def lookup_operator(self, operator, operands):
......@@ -770,6 +781,7 @@ class Scope(object):
def add_include_file(self, filename):
self.outer_scope.add_include_file(filename)
class PreImportScope(Scope):
namespace_cname = Naming.preimport_cname
......@@ -1562,6 +1574,7 @@ class ClosureScope(LocalScope):
def declare_pyfunction(self, name, pos, allow_redefine=False):
return LocalScope.declare_pyfunction(self, name, pos, allow_redefine, visibility='private')
class StructOrUnionScope(Scope):
# Namespace of a C struct or union.
......@@ -1850,12 +1863,16 @@ class CClassScope(ClassScope):
if defining:
entry.func_cname = self.mangle(Naming.func_prefix, name)
entry.utility_code = utility_code
type.entry = entry
if u'inline' in modifiers:
entry.is_inline_cmethod = True
if (self.parent_type.is_final_type or entry.is_inline_cmethod or
self.directives.get('final')):
entry.is_final_cmethod = True
entry.final_func_cname = entry.func_cname
return entry
def add_cfunction(self, name, type, pos, cname, visibility, modifiers):
......@@ -1898,12 +1915,21 @@ class CClassScope(ClassScope):
# to work with this type.
def adapt(cname):
return "%s.%s" % (Naming.obj_base_cname, base_entry.cname)
for base_entry in \
base_scope.inherited_var_entries + base_scope.var_entries:
entry = self.declare(base_entry.name, adapt(base_entry.cname),
base_entry.type, None, 'private')
entry.is_variable = 1
self.inherited_var_entries.append(entry)
entries = base_scope.inherited_var_entries + base_scope.var_entries
for base_entry in entries:
entry = self.declare(base_entry.name, adapt(base_entry.cname),
base_entry.type, None, 'private')
entry.is_variable = 1
self.inherited_var_entries.append(entry)
# If the class defined in a pxd, specific entries have not been added.
# Ensure now that the parent (base) scope has specific entries
# Iterate over a copy as get_all_specific_function_types() will mutate
for base_entry in base_scope.cfunc_entries[:]:
if base_entry.type.is_fused:
base_entry.type.get_all_specific_function_types()
for base_entry in base_scope.cfunc_entries:
cname = base_entry.cname
var_entry = base_entry.as_variable
......@@ -1993,6 +2019,7 @@ class CppClassScope(Scope):
if prev_entry:
entry.overloaded_alternatives = prev_entry.all_alternatives()
entry.utility_code = utility_code
type.entry = entry
return entry
def declare_inherited_cpp_attributes(self, base_scope):
......
......@@ -372,7 +372,7 @@ class SimpleAssignmentTypeInferer(object):
while ready_to_infer:
entry = ready_to_infer.pop()
types = [expr.infer_type(scope) for expr in entry.assignments]
if types:
if types and Utils.all(types):
entry.type = spanning_type(types, entry.might_overflow)
else:
# FIXME: raise a warning?
......
......@@ -55,16 +55,6 @@ class Optimization(object):
optimization = Optimization()
try:
any
except NameError:
def any(it):
for x in it:
if x:
return True
return False
class build_ext(_build_ext.build_ext):
......@@ -128,8 +118,8 @@ class build_ext(_build_ext.build_ext):
# If --pyrex-gdb is in effect as a command line option or as option
# of any Extension module, disable optimization for the C or C++
# compiler.
if (self.pyrex_gdb or any([getattr(ext, 'pyrex_gdb', False)
for ext in self.extensions])):
if self.pyrex_gdb or [1 for ext in self.extensions
if getattr(ext, 'pyrex_gdb', False)]:
optimization.disable_optimization()
_build_ext.build_ext.run(self)
......
......@@ -66,7 +66,8 @@ def sizeof(arg):
return 1
def typeof(arg):
return type(arg)
return arg.__class__.__name__
# return type(arg)
def address(arg):
return pointer(type(arg))([arg])
......@@ -138,6 +139,9 @@ class PointerType(CythonType):
else:
return not self._items and not value._items
def __repr__(self):
return "%s *" % (self._basetype,)
class ArrayType(PointerType):
def __init__(self):
......@@ -221,22 +225,54 @@ def union(**members):
class typedef(CythonType):
def __init__(self, type):
def __init__(self, type, name=None):
self._basetype = type
self.name = name
def __call__(self, *arg):
value = cast(self._basetype, *arg)
return value
def __repr__(self):
return self.name or str(self._basetype)
class _FusedType(CythonType):
pass
def fused_type(*args):
if not args:
raise TypeError("Expected at least one type as argument")
# Find the numeric type with biggest rank if all types are numeric
rank = -1
for type in args:
if type not in (py_int, py_long, py_float, py_complex):
break
if type_ordering.index(type) > rank:
result_type = type
else:
return result_type
# Not a simple numeric type, return a fused type instance. The result
# isn't really meant to be used, as we can't keep track of the context in
# pure-mode. Casting won't do anything in this case.
return _FusedType()
py_int = int
def _specialized_from_args(signatures, args, kwargs):
"Perhaps this should be implemented in a TreeFragment in Cython code"
raise Exception("yet to be implemented")
py_int = typedef(int, "int")
try:
py_long = long
py_long = typedef(long, "long")
except NameError: # Py3
py_long = int
py_float = float
py_complex = complex
py_long = typedef(int, "long")
py_float = typedef(float, "float")
py_complex = typedef(complex, "double complex")
# Predefined types
......@@ -246,30 +282,43 @@ float_types = ['longdouble', 'double', 'float']
complex_types = ['longdoublecomplex', 'doublecomplex', 'floatcomplex', 'complex']
other_types = ['bint', 'void']
to_repr = {
'longlong': 'long long',
'longdouble': 'long double',
'longdoublecomplex': 'long double complex',
'doublecomplex': 'double complex',
'floatcomplex': 'float complex',
}.get
gs = globals()
for name in int_types:
gs[name] = typedef(py_int)
reprname = to_repr(name, name)
gs[name] = typedef(py_int, reprname)
if name != 'Py_UNICODE' and not name.endswith('size_t'):
gs['u'+name] = typedef(py_int)
gs['s'+name] = typedef(py_int)
gs['u'+name] = typedef(py_int, "unsigned " + reprname)
gs['s'+name] = typedef(py_int, "signed " + reprname)
for name in float_types:
gs[name] = typedef(py_float)
gs[name] = typedef(py_float, to_repr(name, name))
for name in complex_types:
gs[name] = typedef(py_complex)
gs[name] = typedef(py_complex, to_repr(name, name))
bint = typedef(bool)
void = typedef(int)
bint = typedef(bool, "bint")
void = typedef(int, "void")
for t in int_types + float_types + complex_types + other_types:
for i in range(1, 4):
gs["%s_%s" % ('p'*i, t)] = globals()[t]._pointer(i)
void = typedef(None)
void = typedef(None, "void")
NULL = p_void(0)
integral = floating = numeric = _FusedType()
type_ordering = [py_int, py_long, py_float, py_complex]
class CythonDotParallel(object):
"""
The cython.parallel module.
......
//////////////////// CythonFunction.proto ////////////////////
#define __Pyx_CyFunction_USED 1
#include <structmember.h>
typedef struct {
PyCFunctionObject func;
PyObject *func_dict;
PyObject *func_weakreflist;
PyObject *func_name;
PyObject *func_doc;
PyObject *func_code;
} __pyx_CyFunctionObject;
static PyTypeObject *__pyx_CyFunctionType = 0;
#define __Pyx_CyFunction_NewEx(ml, self, module, code) __Pyx_CyFunction_New(__pyx_CyFunctionType, ml, self, module, code)
static PyObject *__Pyx_CyFunction_New(PyTypeObject *, PyMethodDef *ml, PyObject *self, PyObject *module, PyObject* code);
static int __Pyx_CyFunction_init(void);
//////////////////// CythonFunction ////////////////////
static PyObject *
__Pyx_CyFunction_get_doc(__pyx_CyFunctionObject *op, CYTHON_UNUSED void *closure)
{
if (op->func_doc == NULL && op->func.m_ml->ml_doc) {
#if PY_MAJOR_VERSION >= 3
op->func_doc = PyUnicode_FromString(op->func.m_ml->ml_doc);
#else
op->func_doc = PyString_FromString(op->func.m_ml->ml_doc);
#endif
}
if (op->func_doc == 0) {
Py_INCREF(Py_None);
return Py_None;
}
Py_INCREF(op->func_doc);
return op->func_doc;
}
static int
__Pyx_CyFunction_set_doc(__pyx_CyFunctionObject *op, PyObject *value)
{
PyObject *tmp = op->func_doc;
if (value == NULL)
op->func_doc = Py_None; /* Mark as deleted */
else
op->func_doc = value;
Py_INCREF(op->func_doc);
Py_XDECREF(tmp);
return 0;
}
static PyObject *
__Pyx_CyFunction_get_name(__pyx_CyFunctionObject *op)
{
if (op->func_name == NULL) {
#if PY_MAJOR_VERSION >= 3
op->func_name = PyUnicode_InternFromString(op->func.m_ml->ml_name);
#else
op->func_name = PyString_InternFromString(op->func.m_ml->ml_name);
#endif
}
Py_INCREF(op->func_name);
return op->func_name;
}
static int
__Pyx_CyFunction_set_name(__pyx_CyFunctionObject *op, PyObject *value)
{
PyObject *tmp;
#if PY_MAJOR_VERSION >= 3
if (value == NULL || !PyUnicode_Check(value)) {
#else
if (value == NULL || !PyString_Check(value)) {
#endif
PyErr_SetString(PyExc_TypeError,
"__name__ must be set to a string object");
return -1;
}
tmp = op->func_name;
Py_INCREF(value);
op->func_name = value;
Py_XDECREF(tmp);
return 0;
}
static PyObject *
__Pyx_CyFunction_get_self(__pyx_CyFunctionObject *m, CYTHON_UNUSED void *closure)
{
PyObject *self;
self = m->func.m_self;
if (self == NULL)
self = Py_None;
Py_INCREF(self);
return self;
}
static PyObject *
__Pyx_CyFunction_get_dict(__pyx_CyFunctionObject *op)
{
if (op->func_dict == NULL) {
op->func_dict = PyDict_New();
if (op->func_dict == NULL)
return NULL;
}
Py_INCREF(op->func_dict);
return op->func_dict;
}
static int
__Pyx_CyFunction_set_dict(__pyx_CyFunctionObject *op, PyObject *value)
{
PyObject *tmp;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError,
"function's dictionary may not be deleted");
return -1;
}
if (!PyDict_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"setting function's dictionary to a non-dict");
return -1;
}
tmp = op->func_dict;
Py_INCREF(value);
op->func_dict = value;
Py_XDECREF(tmp);
return 0;
}
{{# """
/*
TODO: we implicitly use the global module to get func_globals. This
will need to be passed into __Pyx_CyFunction_NewEx() if we share
this type across modules. We currently avoid doing this to reduce
the overhead of creating a function object, and to avoid keeping a
reference to the module dict as long as we don't need to.
*/
""" }}
static PyObject *
__Pyx_CyFunction_get_globals(CYTHON_UNUSED __pyx_CyFunctionObject *op)
{
PyObject* dict = PyModule_GetDict({{module_cname}});
Py_XINCREF(dict);
return dict;
}
static PyObject *
__Pyx_CyFunction_get_closure(CYTHON_UNUSED __pyx_CyFunctionObject *op)
{
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
__Pyx_CyFunction_get_code(__pyx_CyFunctionObject *op)
{
PyObject* result = (op->func_code) ? op->func_code : Py_None;
Py_INCREF(result);
return result;
}
static PyGetSetDef __pyx_CyFunction_getsets[] = {
{(char *) "func_doc", (getter)__Pyx_CyFunction_get_doc, (setter)__Pyx_CyFunction_set_doc, 0, 0},
{(char *) "__doc__", (getter)__Pyx_CyFunction_get_doc, (setter)__Pyx_CyFunction_set_doc, 0, 0},
{(char *) "func_name", (getter)__Pyx_CyFunction_get_name, (setter)__Pyx_CyFunction_set_name, 0, 0},
{(char *) "__name__", (getter)__Pyx_CyFunction_get_name, (setter)__Pyx_CyFunction_set_name, 0, 0},
{(char *) "__self__", (getter)__Pyx_CyFunction_get_self, 0, 0, 0},
{(char *) "func_dict", (getter)__Pyx_CyFunction_get_dict, (setter)__Pyx_CyFunction_set_dict, 0, 0},
{(char *) "__dict__", (getter)__Pyx_CyFunction_get_dict, (setter)__Pyx_CyFunction_set_dict, 0, 0},
{(char *) "func_globals", (getter)__Pyx_CyFunction_get_globals, 0, 0, 0},
{(char *) "__globals__", (getter)__Pyx_CyFunction_get_globals, 0, 0, 0},
{(char *) "func_closure", (getter)__Pyx_CyFunction_get_closure, 0, 0, 0},
{(char *) "__closure__", (getter)__Pyx_CyFunction_get_closure, 0, 0, 0},
{(char *) "func_code", (getter)__Pyx_CyFunction_get_code, 0, 0, 0},
{(char *) "__code__", (getter)__Pyx_CyFunction_get_code, 0, 0, 0},
{0, 0, 0, 0, 0}
};
#ifndef PY_WRITE_RESTRICTED /* < Py2.5 */
#define PY_WRITE_RESTRICTED WRITE_RESTRICTED
#endif
static PyMemberDef __pyx_CyFunction_members[] = {
{(char *) "__module__", T_OBJECT, offsetof(__pyx_CyFunctionObject, func.m_module), PY_WRITE_RESTRICTED, 0},
{0, 0, 0, 0, 0}
};
static PyObject *
__Pyx_CyFunction_reduce(__pyx_CyFunctionObject *m, CYTHON_UNUSED PyObject *args)
{
#if PY_MAJOR_VERSION >= 3
return PyUnicode_FromString(m->func.m_ml->ml_name);
#else
return PyString_FromString(m->func.m_ml->ml_name);
#endif
}
static PyMethodDef __pyx_CyFunction_methods[] = {
{__Pyx_NAMESTR("__reduce__"), (PyCFunction)__Pyx_CyFunction_reduce, METH_VARARGS, 0},
{0, 0, 0, 0}
};
static PyObject *__Pyx_CyFunction_New(PyTypeObject *type, PyMethodDef *ml, PyObject *self, PyObject *module, PyObject* code) {
__pyx_CyFunctionObject *op = PyObject_GC_New(__pyx_CyFunctionObject, type);
if (op == NULL)
return NULL;
op->func_weakreflist = NULL;
op->func.m_ml = ml;
Py_XINCREF(self);
op->func.m_self = self;
Py_XINCREF(module);
op->func.m_module = module;
op->func_dict = NULL;
op->func_name = NULL;
op->func_doc = NULL;
Py_XINCREF(code);
op->func_code = code;
PyObject_GC_Track(op);
return (PyObject *) op;
}
static int
__Pyx_CyFunction_clear(__pyx_CyFunctionObject *m)
{
Py_CLEAR(m->func.m_self);
Py_CLEAR(m->func.m_module);
Py_CLEAR(m->func_dict);
Py_CLEAR(m->func_name);
Py_CLEAR(m->func_doc);
Py_CLEAR(m->func_code);
return 0;
}
static void __Pyx_CyFunction_dealloc(__pyx_CyFunctionObject *m)
{
PyObject_GC_UnTrack(m);
if (m->func_weakreflist != NULL)
PyObject_ClearWeakRefs((PyObject *) m);
__Pyx_CyFunction_clear(m);
PyObject_GC_Del(m);
}
static int __Pyx_CyFunction_traverse(__pyx_CyFunctionObject *m, visitproc visit, void *arg)
{
Py_VISIT(m->func.m_self);
Py_VISIT(m->func.m_module);
Py_VISIT(m->func_dict);
Py_VISIT(m->func_name);
Py_VISIT(m->func_doc);
Py_VISIT(m->func_code);
return 0;
}
static PyObject *__Pyx_CyFunction_descr_get(PyObject *func, PyObject *obj, PyObject *type)
{
if (obj == Py_None)
obj = NULL;
return PyMethod_New(func, obj, type);
}
static PyObject*
__Pyx_CyFunction_repr(__pyx_CyFunctionObject *op)
{
PyObject *func_name = __Pyx_CyFunction_get_name(op);
#if PY_MAJOR_VERSION >= 3
return PyUnicode_FromFormat("<cyfunction %U at %p>",
func_name, op);
#else
return PyString_FromFormat("<cyfunction %s at %p>",
PyString_AsString(func_name), op);
#endif
}
static PyTypeObject __pyx_CyFunctionType_type = {
PyVarObject_HEAD_INIT(0, 0)
__Pyx_NAMESTR("cython_function_or_method"), /*tp_name*/
sizeof(__pyx_CyFunctionObject), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor) __Pyx_CyFunction_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
#if PY_MAJOR_VERSION < 3
0, /*tp_compare*/
#else
0, /*reserved*/
#endif
(reprfunc) __Pyx_CyFunction_repr, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
PyCFunction_Call, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags*/
0, /*tp_doc*/
(traverseproc) __Pyx_CyFunction_traverse, /*tp_traverse*/
(inquiry) __Pyx_CyFunction_clear, /*tp_clear*/
0, /*tp_richcompare*/
offsetof(__pyx_CyFunctionObject, func_weakreflist), /* tp_weaklistoffse */
0, /*tp_iter*/
0, /*tp_iternext*/
__pyx_CyFunction_methods, /*tp_methods*/
__pyx_CyFunction_members, /*tp_members*/
__pyx_CyFunction_getsets, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
__Pyx_CyFunction_descr_get, /*tp_descr_get*/
0, /*tp_descr_set*/
offsetof(__pyx_CyFunctionObject, func_dict),/*tp_dictoffset*/
0, /*tp_init*/
0, /*tp_alloc*/
0, /*tp_new*/
0, /*tp_free*/
0, /*tp_is_gc*/
0, /*tp_bases*/
0, /*tp_mro*/
0, /*tp_cache*/
0, /*tp_subclasses*/
0, /*tp_weaklist*/
0, /*tp_del*/
#if PY_VERSION_HEX >= 0x02060000
0, /*tp_version_tag*/
#endif
};
static int __Pyx_CyFunction_init(void)
{
if (PyType_Ready(&__pyx_CyFunctionType_type) < 0)
return -1;
__pyx_CyFunctionType = &__pyx_CyFunctionType_type;
return 0;
}
//////////////////// FusedFunction.proto ////////////////////
typedef struct {
__pyx_CyFunctionObject func;
PyObject *__signatures__;
PyObject *type;
PyObject *self;
} __pyx_FusedFunctionObject;
#define __pyx_FusedFunction_NewEx(ml, self, module, code) \
__pyx_FusedFunction_New(__pyx_FusedFunctionType, ml, self, module, code)
static PyObject *__pyx_FusedFunction_New(PyTypeObject *type, PyMethodDef *ml,
PyObject *self, PyObject *module,
PyObject *code);
static PyTypeObject *__pyx_FusedFunctionType = NULL;
static int __pyx_FusedFunction_init(void);
#define __Pyx_FusedFunction_USED
//////////////////// FusedFunction ////////////////////
static PyObject *
__pyx_FusedFunction_New(PyTypeObject *type, PyMethodDef *ml, PyObject *self,
PyObject *module, PyObject *code)
{
__pyx_FusedFunctionObject *fusedfunc = \
(__pyx_FusedFunctionObject *) __Pyx_CyFunction_New(type, ml, self,
module, code);
if (!fusedfunc)
return NULL;
fusedfunc->__signatures__ = NULL;
fusedfunc->type = NULL;
fusedfunc->self = NULL;
return (PyObject *) fusedfunc;
}
static void __pyx_FusedFunction_dealloc(__pyx_FusedFunctionObject *self) {
Py_XDECREF(self->__signatures__);
/* __pyx_CyFunction_dealloc((__pyx_CyFunctionObject *) m); */
__pyx_FusedFunctionType->tp_free((PyObject *) self);
}
static int
__pyx_FusedFunction_traverse(__pyx_FusedFunctionObject *self,
visitproc visit,
void *arg)
{
Py_VISIT(self->self);
Py_VISIT(self->type);
Py_VISIT(self->__signatures__);
return __Pyx_CyFunction_traverse((__pyx_CyFunctionObject *) self, visit, arg);
}
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);
}
static PyObject *
__pyx_FusedFunction_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{
__pyx_FusedFunctionObject *func, *meth;
func = (__pyx_FusedFunctionObject *) self;
if (func->self) {
/* Do not allow rebinding */
Py_INCREF(self);
return self;
}
if (obj == Py_None)
obj = NULL;
meth = (__pyx_FusedFunctionObject *) __pyx_FusedFunction_NewEx(
((PyCFunctionObject *) func)->m_ml,
((PyCFunctionObject *) func)->m_self,
((PyCFunctionObject *) func)->m_module,
((__pyx_CyFunctionObject *) func)->func_code);
if (!meth)
return NULL;
Py_XINCREF(func->__signatures__);
meth->__signatures__ = func->__signatures__;
Py_XINCREF(type);
meth->type = type;
Py_XINCREF(obj);
meth->self = obj;
return (PyObject *) meth;
}
static PyObject *
__pyx_FusedFunction_getitem(__pyx_FusedFunctionObject *self, PyObject *idx)
{
PyObject *signature = NULL;
PyObject *unbound_result_func;
PyObject *result_func = NULL;
if (self->__signatures__ == NULL) {
PyErr_SetString(PyExc_TypeError, "Function is not fused");
return NULL;
}
if (PyTuple_Check(idx)) {
PyObject *list = PyList_New(0);
Py_ssize_t n = PyTuple_GET_SIZE(idx);
PyObject *string = NULL;
PyObject *sep = NULL;
int i;
if (!list)
return NULL;
for (i = 0; i < n; i++) {
PyObject *item = PyTuple_GET_ITEM(idx, i);
if (PyType_Check(item))
string = PyObject_GetAttrString(item, "__name__");
else
string = PyObject_Str(item);
if (!string || PyList_Append(list, string) < 0)
goto __pyx_err;
Py_DECREF(string);
}
sep = PyUnicode_FromString(", ");
if (sep)
signature = PyUnicode_Join(sep, list);
__pyx_err:
;
Py_DECREF(list);
Py_XDECREF(sep);
} else {
signature = PyObject_Str(idx);
}
if (!signature)
return NULL;
unbound_result_func = PyObject_GetItem(self->__signatures__, signature);
if (unbound_result_func)
result_func = __pyx_FusedFunction_descr_get(unbound_result_func,
self->self, self->type);
Py_DECREF(signature);
Py_XDECREF(unbound_result_func);
return result_func;
}
/* Note: the 'self' from method binding is passed in in the args tuple,
whereas PyCFunctionObject's m_self is passed in as the first
argument to the C function. For extension methods we also need
to pass 'self' as 'm_self' and not as the first element of the
args tuple.
*/
static PyObject *
__pyx_FusedFunction_call(PyObject *func, PyObject *args, PyObject *kw)
{
__pyx_FusedFunctionObject *binding_func = (__pyx_FusedFunctionObject *) func;
Py_ssize_t argc = PyTuple_GET_SIZE(args);
PyObject *new_args = NULL;
PyObject *new_func = NULL;
PyObject *result = NULL;
PyObject *self = NULL;
if (binding_func->self) {
/* Bound method call, put 'self' in the args tuple */
Py_ssize_t i;
new_args = PyTuple_New(argc + 1);
if (!new_args)
return NULL;
self = binding_func->self;
Py_INCREF(self);
PyTuple_SET_ITEM(new_args, 0, self);
for (i = 0; i < argc; i++) {
PyObject *item = PyTuple_GET_ITEM(args, i);
Py_INCREF(item);
PyTuple_SET_ITEM(new_args, i + 1, item);
}
args = new_args;
} else if (binding_func->type) {
/* Unbound method call */
if (argc < 1) {
PyErr_Format(PyExc_TypeError, "Need at least one argument, 0 given.");
return NULL;
}
self = PyTuple_GET_ITEM(args, 0);
}
if (self && !PyObject_IsInstance(self, binding_func->type)) {
PyErr_Format(PyExc_TypeError,
"First argument should be of type %s, got %s.",
((PyTypeObject *) binding_func->type)->tp_name,
self->ob_type->tp_name);
goto __pyx_err;
}
if (binding_func->__signatures__) {
/*
binaryfunc meth = (binaryfunc) binding_func->func.m_ml->ml_meth;
func = new_func = meth(binding_func->__signatures__, args);
*/
PyObject *tup = PyTuple_Pack(3, binding_func->__signatures__, args,
kw == NULL ? Py_None : kw);
if (!tup)
goto __pyx_err;
func = new_func = PyCFunction_Call(func, tup, NULL);
Py_DECREF(tup);
if (!new_func)
goto __pyx_err;
}
result = PyCFunction_Call(func, args, kw);
__pyx_err:
Py_XDECREF(new_args);
Py_XDECREF(new_func);
return result;
}
static PyMemberDef __pyx_FusedFunction_members[] = {
{(char *) "__signatures__",
T_OBJECT,
offsetof(__pyx_FusedFunctionObject, __signatures__),
READONLY,
__Pyx_DOCSTR(0)},
};
static PyMappingMethods __pyx_FusedFunction_mapping_methods = {
0,
(binaryfunc) __pyx_FusedFunction_getitem,
0,
};
static PyTypeObject __pyx_FusedFunctionType_type = {
PyVarObject_HEAD_INIT(0, 0)
__Pyx_NAMESTR("fused_cython_function"), /*tp_name*/
sizeof(__pyx_FusedFunctionObject), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor) __pyx_FusedFunction_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
#if PY_MAJOR_VERSION < 3
0, /*tp_compare*/
#else
0, /*reserved*/
#endif
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
&__pyx_FusedFunction_mapping_methods, /*tp_as_mapping*/
0, /*tp_hash*/
(ternaryfunc) __pyx_FusedFunction_call, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags*/
0, /*tp_doc*/
(traverseproc) __pyx_FusedFunction_traverse, /*tp_traverse*/
(inquiry) __pyx_FusedFunction_clear,/*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
0, /*tp_methods*/
__pyx_FusedFunction_members, /*tp_members*/
0, /*tp_getset*/
&__pyx_CyFunctionType_type, /*tp_base*/
0, /*tp_dict*/
__pyx_FusedFunction_descr_get, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
0, /*tp_init*/
0, /*tp_alloc*/
0, /*tp_new*/
0, /*tp_free*/
0, /*tp_is_gc*/
0, /*tp_bases*/
0, /*tp_mro*/
0, /*tp_cache*/
0, /*tp_subclasses*/
0, /*tp_weaklist*/
0, /*tp_del*/
#if PY_VERSION_HEX >= 0x02060000
0, /*tp_version_tag*/
#endif
};
static int __pyx_FusedFunction_init(void) {
if (PyType_Ready(&__pyx_FusedFunctionType_type) < 0) {
return -1;
}
__pyx_FusedFunctionType = &__pyx_FusedFunctionType_type;
return 0;
}
......@@ -216,3 +216,22 @@ def long_literal(value):
if isinstance(value, basestring):
value = str_to_number(value)
return not -2**31 <= value < 2**31
# all() and any() are new in 2.5
try:
# Make sure to bind them on the module, as they will be accessed as
# attributes
all = all
any = any
except NameError:
def all(items):
for item in items:
if not item:
return False
return True
def any(items):
for item in items:
if item:
return True
return False
.. highlight:: cython
.. _fusedtypes:
**************************
Fused Types (Templates)
**************************
Fused types can be used to fuse multiple types into a single type, to allow a single
algorithm to operate on values of multiple types. They are somewhat akin to templates
or generics.
.. Note:: Support is experimental and new in this release, there may be bugs!
Declaring Fused Types
=====================
Fused types may be declared as follows::
cimport cython
ctypedef fused my_fused_type:
cython.p_int
cython.p_float
This declares a new type called ``my_fused_type`` which is composed of a ``int *`` and a ``double *``.
Alternatively, the declaration may be written as::
my_fused_type = cython.fused_type(cython.p_int, cython.p_float)
Only names may be used for the constituent types, but they may be any (non-fused) type, including a typedef.
i.e. one may write::
ctypedef double *doublep
my_fused_type = cython.fused_type(cython.p_int, doublep)
Using Fused Types
=================
Fused types can be used to declare parameters of functions or methods::
cdef cfunc(my_fused_type arg1, my_fused_type arg2):
return cython.typeof(arg1) == cython.typeof(arg2)
This declares a function with two parameters. The type of both parameters is either a pointer to an int,
or a pointer to a float (according to the previous examples). So this function always True for every possible
invocation. You are allowed to mix fused types however::
def func(A x, B y):
...
where ``A`` and ``B`` are different fused types. This will result in all combination of types.
Note that specializations of only numeric types may not be very useful, as one can usually rely on
promotion of types. This is not true for arrays, pointers and typed views of memory however.
Indeed, one may write::
def myfunc(A[:, :] x):
...
# and
cdef otherfunc(A *x):
...
Selecting Specializations
=========================
You can select a specialization (an instance of the function with specific or specialized (i.e.,
non-fused) argument types) in two ways: either by indexing or by calling.
Indexing
--------
You can index functions with types to get certain specializations, i.e.::
cfunc[cython.p_double](p1, p2)
# From Cython space
func[float, double](myfloat, mydouble)
# From Python space
func[cython.float, cython.double](myfloat, mydouble)
If a fused type is used as a base type, this will mean that the base type is the fused type, so the
base type is what needs to be specialized::
cdef myfunc(A *x):
...
# Specialize using int, not int *
myfunc[int](myint)
Calling
-------
A fused function can also be called with arguments, where the dispatch is figured out automatically::
cfunc(p1, p2)
func(myfloat, mydouble)
For a ``cdef`` or ``cpdef`` function called from Cython this means that the specialization is figured
out at compile time. For ``def`` functions the arguments are typechecked at runtime, and a best-effort
approach is performed to figure out which specialization is needed. This means that this may result in
a runtime ``TypeError`` if no specialization was found. A ``cpdef`` function is treated the same way as
a ``def`` function if the type of the function is unknown (e.g. if it is external and there is no cimport
for it).
The automatic dispatching rules are typically as follows, in order of preference:
* try to find an exact match
* choose the biggest corresponding numerical type (biggest float, biggest complex, biggest int)
Built-in Fused Types
====================
There are some built-in fused types available for convenience, these are::
cython.integral # int, long
cython.floating # float, double
cython.numeric # long, double, double complex
Casting Fused Functions
=======================
Fused ``cdef`` and ``cpdef`` functions may be cast or assigned to C function pointers as follows::
cdef myfunc(cython.floating, cython.integral):
...
# assign directly
cdef object (*funcp)(float, int)
funcp = myfunc
funcp(f, i)
# alternatively, cast it
(<object (*)(float, int)> myfunc)(f, i)
# This is also valid
funcp = myfunc[float, int]
funcp(f, i)
Type Checking Specializations
=============================
Decisions can be made based on the specializations of the fused parameters. False conditions are pruned
to avoid invalid code. One may check with ``is``, ``is not`` and ``==`` and ``!=`` to see if a fused type
is equal to a certain other non-fused type (to check the specialization), or use ``in`` and ``not in`` to
figure out whether a specialization is part of another set of types (specified as a fused type). In
example::
ctypedef fused bunch_of_types:
...
ctypedef fused string_t:
cython.p_char
bytes
unicode
cdef cython.integral myfunc(cython.integral i, bunch_of_types s):
cdef int *int_pointer
cdef long *long_pointer
# Only one of these branches will be compiled for each specialization!
if cython.integral is int:
int_pointer = &i
else:
long_pointer = &i
if bunch_of_types in string_t:
print "s is a string!"
__signatures__
==============
Finally, function objects from ``def`` or ``cpdef`` functions have an attribute __signatures__, which maps
the signature strings to the actual specialized functions. This may be useful for inspection.
Listed signature strings may also be used as indices to the fused function::
specialized_function = fused_function["MyExtensionClass, int, float"]
It would usually be preferred to index like this, however::
specialized_function = fused_function[MyExtensionClass, int, float]
Although the latter will select the biggest types for ``int`` and ``float`` from Python space, as they are
not type identifiers but builtin types there. Passing ``cython.int`` and ``cython.float`` would resolve that,
however.
......@@ -15,6 +15,7 @@ Contents:
external_C_code
source_files_and_compilation
wrapping_CPlusPlus
fusedtypes
limitations
pyrex_differences
early_binding_for_speed
......
......@@ -716,6 +716,7 @@ def run_forked_test(result, run_func, test_name, fork=True):
gc.collect()
return
module_name = test_name.split()[-1]
# fork to make sure we do not keep the tested module loaded
result_handle, result_file = tempfile.mkstemp()
os.close(result_handle)
......
......@@ -9,5 +9,5 @@ cdef extern from *:
new Foo(1, 2)
_ERRORS = u"""
9:7: no suitable method found
9:7: Call with wrong number of arguments (expected 1, got 2)
"""
# mode: error
cimport cython
def closure(cython.integral i):
def inner(cython.floating f):
pass
def closure2(cython.integral i):
return lambda cython.integral i: i
def closure3(cython.integral i):
def inner():
return lambda cython.floating f: f
def generator(cython.integral i):
yield i
_ERRORS = u"""
e_fused_closure.pyx:6:4: Cannot nest fused functions
e_fused_closure.pyx:10:11: Cannot nest fused functions
e_fused_closure.pyx:14:15: Cannot nest fused functions
e_fused_closure.pyx:16:0: Fused generators not supported
"""
# mode: error
ctypedef char *string_t
ctypedef public char *public_string_t
ctypedef api char *api_string_t
# This should all fail
cdef public pub_func1(string_t x):
pass
cdef api api_func1(string_t x):
pass
cdef public string_t pub_func2():
pass
cdef api string_t api_func2():
pass
cdef public opt_pub_func(x = None):
pass
cdef api opt_api_func(x = None):
pass
# This should all work
cdef public pub_func3(public_string_t x, api_string_t y):
pass
cdef api api_func3(public_string_t x, api_string_t y):
pass
cdef opt_func(x = None):
pass
_ERRORS = u"""
e_public_cdef_private_types.pyx:8:22: Function declared public or api may not have private types
e_public_cdef_private_types.pyx:11:19: Function declared public or api may not have private types
e_public_cdef_private_types.pyx:14:5: Function declared public or api may not have private types
e_public_cdef_private_types.pyx:17:5: Function declared public or api may not have private types
e_public_cdef_private_types.pyx:20:25: Function with optional arguments may not be declared public or api
e_public_cdef_private_types.pyx:23:22: Function with optional arguments may not be declared public or api
"""
# mode: error
cdef fused my_fused_type: int a; char b
_ERRORS = u"""
fused_syntax.pyx:3:26: Expected a newline
"""
# mode: error
cimport cython
ctypedef cython.fused_type(int, float) fused_t
_ERRORS = u"""
fused_syntax_ctypedef.pyx:5:39: Syntax error in ctypedef statement
"""
# mode: error
cimport cython
from cython import fused_type
# This is all invalid
# ctypedef foo(int) dtype1
# ctypedef foo.bar(float) dtype2
# ctypedef fused_type(foo) dtype3
dtype4 = cython.fused_type(int, long, kw=None)
# ctypedef public cython.fused_type(int, long) dtype7
# ctypedef api cython.fused_type(int, long) dtype8
int_t = cython.fused_type(short, short, int)
int2_t = cython.fused_type(int, long)
dtype9 = cython.fused_type(int2_t, int)
floating = cython.fused_type(float, double)
cdef func(floating x, int2_t y):
print x, y
cdef float x = 10.0
cdef int y = 10
func[float](x, y)
func[float][int](x, y)
func[float, int](x)
func[float, int](x, y, y)
func(x, y=y)
ctypedef fused memslice_dtype_t:
cython.p_int # invalid dtype
cython.long
def f(memslice_dtype_t[:, :] a):
pass
# This is all valid
dtype5 = fused_type(int, long, float)
dtype6 = cython.fused_type(int, long)
func[float, int](x, y)
cdef fused fused1:
int
long long
ctypedef fused fused2:
int
long long
func(x, y)
_ERRORS = u"""
fused_types.pyx:10:15: fused_type does not take keyword arguments
fused_types.pyx:15:38: Type specified multiple times
fused_types.pyx:17:33: Cannot fuse a fused type
fused_types.pyx:26:4: Not enough types specified to specialize the function, int2_t is still fused
fused_types.pyx:27:4: Not enough types specified to specialize the function, int2_t is still fused
fused_types.pyx:28:16: Call with wrong number of arguments (expected 2, got 1)
fused_types.pyx:29:16: Call with wrong number of arguments (expected 2, got 3)
fused_types.pyx:30:4: Keyword and starred arguments not allowed in cdef functions.
fused_types.pyx:36:6: Invalid base type for memoryview slice: int *
"""
......@@ -59,10 +59,10 @@ _ERRORS = u'''
20:22: Invalid axis specification.
21:25: Invalid axis specification.
22:22: no expressions allowed in axis spec, only names and literals.
25:51: Memoryview 'object[::contiguous, :]' not conformable to memoryview 'object[:, ::contiguous]'.
25:51: Memoryview 'object[::1, :]' not conformable to memoryview 'object[:, ::1]'.
28:36: Different base types for memoryviews (int, Python object)
31:9: Dimension may not be contiguous
37:9: Only one direct contiguous axis may be specified.
38:9:Only dimensions 3 and 2 may be contiguous and direct
44:10: Invalid base type for memoryview slice
44:10: Invalid base type for memoryview slice: intp
'''
cimport cython
cimport check_fused_types_pxd
import math
ctypedef char *string_t
fused_t = cython.fused_type(int, long, float, string_t)
other_t = cython.fused_type(int, long)
base_t = cython.fused_type(short, int)
# complex_t = cython.fused_type(cython.floatcomplex, cython.doublecomplex)
cdef fused complex_t:
float complex
double complex
ctypedef base_t **base_t_p_p
# ctypedef cython.fused_type(char, base_t_p_p, fused_t, complex_t) composed_t
cdef fused composed_t:
char
int
float
string_t
cython.pp_int
float complex
double complex
int complex
long complex
cdef func(fused_t a, other_t b):
cdef int int_a
cdef string_t string_a
cdef other_t other_a
if fused_t is other_t:
print 'fused_t is other_t'
other_a = a
if fused_t is int:
print 'fused_t is int'
int_a = a
if fused_t is string_t:
print 'fused_t is string_t'
string_a = a
if fused_t in check_fused_types_pxd.unresolved_t:
print 'fused_t in unresolved_t'
if int in check_fused_types_pxd.unresolved_t:
print 'int in unresolved_t'
if string_t in check_fused_types_pxd.unresolved_t:
print 'string_t in unresolved_t'
def test_int_int():
"""
>>> test_int_int()
fused_t is other_t
fused_t is int
fused_t in unresolved_t
int in unresolved_t
"""
cdef int x = 1
cdef int y = 2
func(x, y)
def test_int_long():
"""
>>> test_int_long()
fused_t is int
fused_t in unresolved_t
int in unresolved_t
"""
cdef int x = 1
cdef long y = 2
func(x, y)
def test_float_int():
"""
>>> test_float_int()
fused_t in unresolved_t
int in unresolved_t
"""
cdef float x = 1
cdef int y = 2
func(x, y)
def test_string_int():
"""
>>> test_string_int()
fused_t is string_t
int in unresolved_t
"""
cdef string_t x = b"spam"
cdef int y = 2
func(x, y)
cdef if_then_else(fused_t a, other_t b):
cdef other_t other_a
cdef string_t string_a
cdef fused_t specific_a
if fused_t is other_t:
print 'fused_t is other_t'
other_a = a
elif fused_t is string_t:
print 'fused_t is string_t'
string_a = a
else:
print 'none of the above'
specific_a = a
def test_if_then_else_long_long():
"""
>>> test_if_then_else_long_long()
fused_t is other_t
"""
cdef long x = 0, y = 0
if_then_else(x, y)
def test_if_then_else_string_int():
"""
>>> test_if_then_else_string_int()
fused_t is string_t
"""
cdef string_t x = b"spam"
cdef int y = 0
if_then_else(x, y)
def test_if_then_else_float_int():
"""
>>> test_if_then_else_float_int()
none of the above
"""
cdef float x = 0.0
cdef int y = 1
if_then_else(x, y)
cdef composed_t composed(composed_t x, composed_t y):
if composed_t in base_t_p_p or composed_t is string_t:
if string_t == composed_t:
print x.decode('ascii'), y.decode('ascii')
else:
print x[0][0], y[0][0]
return x
elif composed_t == string_t:
print 'this is never executed'
elif list():
print 'neither is this one'
else:
if composed_t not in complex_t:
print 'not a complex number'
print <int> x, <int> y
else:
print 'it is a complex number'
print x.real, x.imag
return x + y
def test_composed_types():
"""
>>> test_composed_types()
it is a complex number
0.5 0.6
9 4
<BLANKLINE>
not a complex number
7 8
15
<BLANKLINE>
7 8
<BLANKLINE>
spam eggs
spam
"""
cdef double complex a = 0.5 + 0.6j, b = 0.4 -0.2j, result
cdef int c = 7, d = 8
cdef int *cp = &c, *dp = &d
cdef string_t e = "spam", f = "eggs"
result = composed(a, b)
print int(math.ceil(result.real * 10)), int(math.ceil(result.imag * 10))
print
print composed(c, d)
print
composed(&cp, &dp)
print
print composed(e, f).decode('ascii')
cimport cython
unresolved_t = cython.fused_type(int, float)
cimport cython
cy = __import__("cython")
cpdef func1(self, cython.integral x):
print "%s," % (self,),
if cython.integral is int:
print 'x is int', x, cython.typeof(x)
else:
print 'x is long', x, cython.typeof(x)
class A(object):
meth = func1
def __str__(self):
return "A"
pyfunc = func1
def test_fused_cpdef():
"""
>>> test_fused_cpdef()
None, x is int 2 int
None, x is long 2 long
None, x is long 2 long
<BLANKLINE>
None, x is int 2 int
None, x is long 2 long
<BLANKLINE>
A, x is int 2 int
A, x is long 2 long
A, x is long 2 long
A, x is long 2 long
"""
func1[int](None, 2)
func1[long](None, 2)
func1(None, 2)
print
pyfunc[cy.int](None, 2)
pyfunc(None, 2)
print
A.meth[cy.int](A(), 2)
A.meth(A(), 2)
A().meth[cy.long](2)
A().meth(2)
def assert_raise(func, *args):
try:
func(*args)
except TypeError:
pass
else:
assert False, "Function call did not raise TypeError"
def test_badcall():
"""
>>> test_badcall()
"""
assert_raise(pyfunc)
assert_raise(pyfunc, 1, 2, 3)
assert_raise(pyfunc[cy.int], 10, 11, 12)
assert_raise(pyfunc, None, object())
assert_raise(A().meth)
assert_raise(A.meth)
assert_raise(A().meth[cy.int])
assert_raise(A.meth[cy.int])
ctypedef long double long_double
cpdef multiarg(cython.integral x, cython.floating y):
if cython.integral is int:
print "x is an int,",
else:
print "x is a long,",
if cython.floating is long_double:
print "y is a long double:",
elif float is cython.floating:
print "y is a float:",
else:
print "y is a double:",
print x, y
def test_multiarg():
"""
>>> test_multiarg()
x is an int, y is a float: 1 2.0
x is an int, y is a float: 1 2.0
x is a long, y is a double: 4 5.0
"""
multiarg[int, float](1, 2.0)
multiarg[cy.int, cy.float](1, 2.0)
multiarg(4, 5.0)
# mode: run
"""
Test Python def functions without extern types
"""
cy = __import__("cython")
cimport cython
cdef class Base(object):
def __repr__(self):
return type(self).__name__
cdef class ExtClassA(Base):
pass
cdef class ExtClassB(Base):
pass
cdef enum MyEnum:
entry0
entry1
entry2
entry3
entry4
ctypedef fused fused_t:
str
int
long
complex
ExtClassA
ExtClassB
MyEnum
f = 5.6
i = 9
def opt_func(fused_t obj, cython.floating myf = 1.2, cython.integral myi = 7):
"""
Test runtime dispatch, indexing of various kinds and optional arguments
>>> opt_func("spam", f, i)
str object double long
spam 5.60 9 5.60 9
>>> opt_func[str, float, int]("spam", f, i)
str object float int
spam 5.60 9 5.60 9
>>> opt_func["str, double, long"]("spam", f, i)
str object double long
spam 5.60 9 5.60 9
>>> opt_func[str, float, cy.int]("spam", f, i)
str object float int
spam 5.60 9 5.60 9
>>> opt_func(ExtClassA(), f, i)
ExtClassA double long
ExtClassA 5.60 9 5.60 9
>>> opt_func[ExtClassA, float, int](ExtClassA(), f, i)
ExtClassA float int
ExtClassA 5.60 9 5.60 9
>>> opt_func["ExtClassA, double, long"](ExtClassA(), f, i)
ExtClassA double long
ExtClassA 5.60 9 5.60 9
>>> opt_func(ExtClassB(), f, i)
ExtClassB double long
ExtClassB 5.60 9 5.60 9
>>> opt_func[ExtClassB, cy.double, cy.long](ExtClassB(), f, i)
ExtClassB double long
ExtClassB 5.60 9 5.60 9
>>> opt_func(10, f)
long double long
10 5.60 7 5.60 9
>>> opt_func[int, float, int](10, f)
int float int
10 5.60 7 5.60 9
>>> opt_func(10 + 2j, myf = 2.6)
double complex double long
(10+2j) 2.60 7 5.60 9
>>> opt_func[cy.py_complex, float, int](10 + 2j, myf = 2.6)
double complex float int
(10+2j) 2.60 7 5.60 9
>>> opt_func[cy.doublecomplex, cy.float, cy.int](10 + 2j, myf = 2.6)
double complex float int
(10+2j) 2.60 7 5.60 9
>>> opt_func(object(), f)
Traceback (most recent call last):
...
TypeError: Function call with ambiguous argument types
>>> opt_func[ExtClassA, cy.float, cy.long](object(), f)
Traceback (most recent call last):
...
TypeError: Argument 'obj' has incorrect type (expected fused_def.ExtClassA, got object)
"""
print cython.typeof(obj), cython.typeof(myf), cython.typeof(myi)
print obj, "%.2f" % myf, myi, "%.2f" % f, i
def test_opt_func():
"""
>>> test_opt_func()
str object double long
ham 5.60 4 5.60 9
"""
opt_func("ham", f, entry4)
def args_kwargs(fused_t obj, cython.floating myf = 1.2, *args, **kwargs):
"""
>>> args_kwargs("foo")
str object double
foo 1.20 5.60 () {}
>>> args_kwargs("eggs", f, 1, 2, [], d={})
str object double
eggs 5.60 5.60 (1, 2, []) {'d': {}}
>>> args_kwargs[str, float]("eggs", f, 1, 2, [], d={})
str object float
eggs 5.60 5.60 (1, 2, []) {'d': {}}
"""
print cython.typeof(obj), cython.typeof(myf)
print obj, "%.2f" % myf, "%.2f" % f, args, kwargs
# mode: run
cimport cython
from cython cimport integral
from cpython cimport Py_INCREF
from Cython import Shadow as pure_cython
ctypedef char * string_t
# floating = cython.fused_type(float, double) floating
# integral = cython.fused_type(int, long) integral
ctypedef cython.floating floating
fused_type1 = cython.fused_type(int, long, float, double, string_t)
fused_type2 = cython.fused_type(string_t)
ctypedef fused_type1 *composed_t
other_t = cython.fused_type(int, double)
ctypedef double *p_double
ctypedef int *p_int
def test_pure():
"""
>>> test_pure()
10
"""
mytype = pure_cython.typedef(pure_cython.fused_type(int, long, complex))
print mytype(10)
cdef cdef_func_with_fused_args(fused_type1 x, fused_type1 y, fused_type2 z):
if fused_type1 is string_t:
print x.decode('ascii'), y.decode('ascii'), z.decode('ascii')
else:
print x, y, z.decode('ascii')
return x + y
def test_cdef_func_with_fused_args():
"""
>>> test_cdef_func_with_fused_args()
spam ham eggs
spamham
10 20 butter
30
4.2 8.6 bunny
12.8
"""
print cdef_func_with_fused_args('spam', 'ham', 'eggs').decode('ascii')
print cdef_func_with_fused_args(10, 20, 'butter')
print cdef_func_with_fused_args(4.2, 8.6, 'bunny')
cdef fused_type1 fused_with_pointer(fused_type1 *array):
for i in range(5):
if fused_type1 is string_t:
print array[i].decode('ascii')
else:
print array[i]
obj = array[0] + array[1] + array[2] + array[3] + array[4]
# if cython.typeof(fused_type1) is string_t:
Py_INCREF(obj)
return obj
def test_fused_with_pointer():
"""
>>> test_fused_with_pointer()
0
1
2
3
4
10
<BLANKLINE>
0
1
2
3
4
10
<BLANKLINE>
0.0
1.0
2.0
3.0
4.0
10.0
<BLANKLINE>
humpty
dumpty
fall
splatch
breakfast
humptydumptyfallsplatchbreakfast
"""
cdef int int_array[5]
cdef long long_array[5]
cdef float float_array[5]
cdef string_t string_array[5]
cdef char *s
strings = [b"humpty", b"dumpty", b"fall", b"splatch", b"breakfast"]
for i in range(5):
int_array[i] = i
long_array[i] = i
float_array[i] = i
s = strings[i]
string_array[i] = s
print fused_with_pointer(int_array)
print
print fused_with_pointer(long_array)
print
print fused_with_pointer(float_array)
print
print fused_with_pointer(string_array).decode('ascii')
include "cythonarrayutil.pxi"
cpdef cython.integral test_fused_memoryviews(cython.integral[:, ::1] a):
"""
>>> import cython
>>> a = create_array((3, 5), mode="c")
>>> test_fused_memoryviews[cython.int](a)
7
"""
return a[1, 2]
ctypedef int[:, ::1] memview_int
ctypedef long[:, ::1] memview_long
memview_t = cython.fused_type(memview_int, memview_long)
def test_fused_memoryview_def(memview_t a):
"""
>>> a = create_array((3, 5), mode="c")
>>> test_fused_memoryview_def["memview_int"](a)
7
"""
return a[1, 2]
cdef test_specialize(fused_type1 x, fused_type1 *y, composed_t z, other_t *a):
cdef fused_type1 result
if composed_t is p_double:
print "double pointer"
if fused_type1 in floating:
result = x + y[0] + z[0] + a[0]
return result
def test_specializations():
"""
>>> test_specializations()
double pointer
double pointer
double pointer
double pointer
double pointer
"""
cdef object (*f)(double, double *, double *, int *)
cdef double somedouble = 2.2
cdef double otherdouble = 3.3
cdef int someint = 4
cdef p_double somedouble_p = &somedouble
cdef p_double otherdouble_p = &otherdouble
cdef p_int someint_p = &someint
f = test_specialize
assert f(1.1, somedouble_p, otherdouble_p, someint_p) == 10.6
f = <object (*)(double, double *, double *, int *)> test_specialize
assert f(1.1, somedouble_p, otherdouble_p, someint_p) == 10.6
assert (<object (*)(double, double *, double *, int *)>
test_specialize)(1.1, somedouble_p, otherdouble_p, someint_p) == 10.6
f = test_specialize[double, int]
assert f(1.1, somedouble_p, otherdouble_p, someint_p) == 10.6
assert test_specialize[double, int](1.1, somedouble_p, otherdouble_p, someint_p) == 10.6
# The following cases are not supported
# f = test_specialize[double][p_int]
# print f(1.1, somedouble_p, otherdouble_p)
# print
# print test_specialize[double][p_int](1.1, somedouble_p, otherdouble_p)
# print
# print test_specialize[double](1.1, somedouble_p, otherdouble_p)
# print
cdef opt_args(integral x, floating y = 4.0):
print x, y
def test_opt_args():
"""
>>> test_opt_args()
3 4.0
3 4.0
3 4.0
3 4.0
"""
opt_args[int, float](3)
opt_args[int, double](3)
opt_args[int, float](3, 4.0)
opt_args[int, double](3, 4.0)
PYTHON setup.py build_ext --inplace
PYTHON -c "import b"
######## setup.py ########
from Cython.Build import cythonize
from distutils.core import setup
setup(
ext_modules = cythonize("*.pyx"),
)
######## a.pxd ########
cimport cython
cdef extern from "header.h":
ctypedef int extern_int
ctypedef long extern_long
cdef struct mystruct_t:
extern_int a
ctypedef union myunion_t:
extern_long a
cdef public class MyExt [ type MyExtType, object MyExtObject ]:
cdef unsigned char a
ctypedef char *string_t
simple_t = cython.fused_type(int, float)
less_simple_t = cython.fused_type(int, float, string_t)
struct_t = cython.fused_type(mystruct_t, myunion_t, MyExt)
builtin_t = cython.fused_type(str, unicode, bytes)
ctypedef fused fusedbunch:
int
long
complex
string_t
ctypedef fused fused1:
short
string_t
cdef fused fused2:
float
double
string_t
cdef struct_t add_simple(struct_t obj, simple_t simple)
cdef less_simple_t add_to_simple(struct_t obj, less_simple_t simple)
cdef public_optional_args(struct_t obj, simple_t simple = *)
cdef class TestFusedExtMethods(object):
cdef cython.floating method(self, cython.integral x, cython.floating y)
cpdef cpdef_method(self, cython.integral x, cython.floating y)
object_t = cython.fused_type(TestFusedExtMethods, object, list)
cpdef public_cpdef(cython.integral x, cython.floating y, object_t z)
######## header.h ########
typedef int extern_int;
typedef long extern_long;
######## a.pyx ########
cimport cython
cdef struct_t add_simple(struct_t obj, simple_t simple):
obj.a = <int> (obj.a + simple)
return obj
cdef less_simple_t add_to_simple(struct_t obj, less_simple_t simple):
return obj.a + simple
cdef public_optional_args(struct_t obj, simple_t simple = 6):
return obj.a, simple
cdef class TestFusedExtMethods(object):
cdef cython.floating method(self, cython.integral x, cython.floating y):
if cython.integral is int:
x += 1
if cython.floating is double:
y += 2.0
return x + y
cpdef cpdef_method(self, cython.integral x, cython.floating y):
return cython.typeof(x), cython.typeof(y)
def def_method(self, fused1 x, fused2 y):
if (fused1 is string_t and fused2 is not string_t or
not fused1 is string_t and fused2 is string_t):
return x, y
else:
return <fused1> x + y
cpdef public_cpdef(cython.integral x, cython.floating y, object_t z):
if cython.integral is int:
pass
return cython.typeof(x), cython.typeof(y), cython.typeof(z)
######## b.pyx ########
cimport cython
cimport a as a_cmod
from a cimport *
cdef mystruct_t mystruct
cdef myunion_t myunion
cdef MyExt myext = MyExt()
mystruct.a = 5
myunion.a = 5
myext.a = 5
assert add_simple(mystruct, 5).a == 10
assert add_simple(myunion, 5.0).a == 10.0
assert add_to_simple(mystruct, 5.0) == 10.0
assert add_to_simple(myunion, b"spamhameggs") == b"ameggs"
assert add_to_simple(myext, 5) == 10
cdef mystruct_t (*f)(mystruct_t, int)
f = add_simple
assert f(mystruct, 5).a == 10
f = <mystruct_t (*)(mystruct_t, int)> add_simple
assert f(mystruct, 5).a == 10
f = add_simple[mystruct_t, int]
assert f(mystruct, 5).a == 10
assert public_optional_args(mystruct, 5) == (5, 5)
assert public_optional_args[mystruct_t, int](mystruct) == (5, 6)
assert public_optional_args[mystruct_t, float](mystruct) == (5, 6.0)
assert public_optional_args[mystruct_t, float](mystruct, 7.0) == (5, 7.0)
cdef TestFusedExtMethods obj = TestFusedExtMethods()
cdef int x = 4
cdef float y = 5.0
cdef long a = 6
cdef double b = 7.0
cdef double (*func)(TestFusedExtMethods, long, double)
func = obj.method
result = func(obj, a, b)
assert result == 15.0, result
func = <double (*)(TestFusedExtMethods, long, double)> obj.method
assert func(obj, x, y) == 11.0
func = obj.method[long, double]
assert func(obj, a, y) == 13.0
assert obj.method(x, <double> a) == 13.0
assert obj.method[int, double](x, b) == 14.0
# Test inheritance
cdef class Subclass(TestFusedExtMethods):
cdef cython.floating method(self, cython.integral x, cython.floating y):
return -x -y
cpdef cpdef_method(self, cython.integral x, cython.floating y):
return x, y
cdef Subclass myobj = Subclass()
assert myobj.method[int, float](5, 5.0) == -10
cdef float (*meth)(Subclass, int, float)
meth = myobj.method
assert meth(myobj, 5, 5.0) == -10
meth = myobj.method[int, float]
assert meth(myobj, 5, 5.0) == -10
# Test cpdef functions and methods
cy = __import__("cython")
import a as a_mod
def ae(result, expected):
"assert equals"
if result != expected:
print 'result :', result
print 'expected:', expected
assert result == expected
ae(a_mod.public_cpdef["int, float, list"](5, 6, [7]), ("int", "float", "list object"))
ae(a_mod.public_cpdef[int, float, list](5, 6, [7]), ("int", "float", "list object"))
idx = cy.typeof(0), cy.typeof(0.0), cy.typeof([])
ae(a_mod.public_cpdef[idx](5, 6, [7]), ("int", "float", "list object"))
ae(a_mod.public_cpdef[cy.int, cy.double, cython.typeof(obj)](5, 6, obj), ("int", "double", "TestFusedExtMethods"))
ae(a_mod.public_cpdef[cy.int, cy.double, cython.typeof(obj)](5, 6, myobj), ("int", "double", "TestFusedExtMethods"))
ae(public_cpdef[int, float, list](5, 6, [7]), ("int", "float", "list object"))
ae(public_cpdef[int, double, TestFusedExtMethods](5, 6, obj), ("int", "double", "TestFusedExtMethods"))
ae(public_cpdef[int, double, TestFusedExtMethods](5, 6, myobj), ("int", "double", "TestFusedExtMethods"))
ae(obj.cpdef_method(10, 10.0), ("long", "double"))
ae(myobj.cpdef_method(10, 10.0), (10, 10.0))
ae(obj.cpdef_method[int, float](10, 10.0), ("int", "float"))
ae(myobj.cpdef_method[int, float](10, 10.0), (10, 10.0))
s = """\
import cython as cy
ae(obj.cpdef_method[cy.int, cy.float](10, 10.0), ("int", "float"))
ae(myobj.cpdef_method[cy.int, cy.float](10, 10.0), (10, 10.0))
"""
d = {'obj': obj, 'myobj': myobj, 'ae': ae}
# FIXME: uncomment after subclassing CyFunction
#exec s in d
# Test def methods
# ae(obj.def_method(12, 14.9), 26)
# ae(obj.def_method(13, "spam"), (13, "spam"))
# ae(obj.def_method[cy.short, cy.float](13, 16.3), 29)
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