Commit 09a61d2a authored by Stefan Behnel's avatar Stefan Behnel

make the coercion between C unions and Python dicts usable

parent 372efbe8
......@@ -89,6 +89,8 @@ Bugs fixed
types could overflow for large buffer sizes. Original patch by
David Vierra.
* C unions use a saner way to coerce from and to Python dicts.
Other changes
-------------
......
......@@ -3149,11 +3149,27 @@ class CStructOrUnionType(CType):
return None # tri-state-ish
if self._convert_to_py_code is None:
is_union = not self.is_struct
unsafe_union_types = set()
safe_union_types = set()
for member in self.scope.var_entries:
if not member.type.create_to_py_utility_code(env):
member_type = member.type
if not member_type.create_to_py_utility_code(env):
self.to_py_function = None
self._convert_to_py_code = False
return False
if is_union:
if member_type.is_ptr or member_type.is_cpp_class:
unsafe_union_types.add(member_type)
else:
safe_union_types.add(member_type)
if unsafe_union_types and (safe_union_types or len(unsafe_union_types) > 1):
# unsafe mix of safe and unsafe to convert types
self.from_py_function = None
self._convert_from_py_code = False
return False
forward_decl = self.entry.visibility != 'extern' and not self.typedef_flag
self._convert_to_py_code = ToPyStructUtilityCode(self, forward_decl, env)
......@@ -3181,7 +3197,8 @@ class CStructOrUnionType(CType):
)
from .UtilityCode import CythonUtilityCode
self._convert_from_py_code = CythonUtilityCode.load(
"FromPyStructUtility", "CConvert.pyx",
"FromPyStructUtility" if self.is_struct else "FromPyUnionUtility",
"CConvert.pyx",
outer_module_scope=env.global_scope(), # need access to types declared in module
context=context)
......
......@@ -23,6 +23,41 @@ cdef {{struct_name}} {{funcname}}(obj) except *:
return result
#################### FromPyUnionUtility ####################
cdef extern from *:
ctypedef struct PyTypeObject:
char* tp_name
PyTypeObject *Py_TYPE(obj)
bint PyMapping_Check(obj)
object PyErr_Format(exc, const char *format, ...)
@cname("{{funcname}}")
cdef {{struct_name}} {{funcname}}(obj) except *:
cdef {{struct_name}} result
cdef Py_ssize_t length
if not PyMapping_Check(obj):
PyErr_Format(TypeError, b"Expected %.16s, got %.200s", b"a mapping", Py_TYPE(obj).tp_name)
last_found = None
length = len(obj)
if length:
{{for member in var_entries:}}
if '{{member.name}}' in obj:
if last_found is not None:
raise ValueError("More than one union attribute passed: '%s' and '%s'" % (last_found, '{{member.name}}'))
last_found = '{{member.name}}'
result.{{member.cname}} = obj['{{member.name}}']
length -= 1
if not length:
return result
{{endfor}}
if last_found is None:
raise ValueError("No value specified for any of the union attributes (%s)" %
'{{", ".join(member.name for member in var_entries)}}')
return result
#################### cfunc.to_py ####################
@cname("{{cname}}")
......
# mode: error
cdef union AllCharptr:
char *s1
char *s2
char *s3
def convert_ok():
cdef AllCharptr u
u.s1 = b"abc"
return u
cdef union IllegalMix:
char *s1
char *s2
int i
def convert_nok():
cdef IllegalMix u
u.i = 5
return u
_ERRORS = """
24:12: Cannot convert 'IllegalMix' to Python object
"""
......@@ -76,6 +76,36 @@ def c_types(int a, double b):
a_ptr, b_ptr = ab
return a_ptr[0], b_ptr[0]
cdef union Union:
int x
double y
def union_in_ctuple_literal():
"""
>>> union_in_ctuple_literal()
(1, 2.0)
"""
cdef (Union,) a = ({"x": 1},)
cdef (Union,) b = ({"y": 2},)
return a[0].x, b[0].y
def union_in_ctuple_dynamic(*values):
"""
>>> union_in_ctuple_dynamic(1, {'x': 1})
1
>>> union_in_ctuple_dynamic(2, {'y': 2})
2.0
>>> union_in_ctuple_dynamic(1, {'x': 1, 'y': 2})
Traceback (most recent call last):
ValueError: More than one union attribute passed: 'x' and 'y'
"""
cdef (int, Union) a = values
return a[1].x if a[0] == 1 else a[1].y
cdef (int, int*) cdef_ctuple_return_type(int x, int* x_ptr):
return x, x_ptr
......
......@@ -22,6 +22,7 @@ cdef void eggs_p(Spam s):
spam = ham
def test_i():
"""
>>> test_i()
......@@ -29,6 +30,7 @@ def test_i():
spam.i = 1
eggs_i(spam)
def test_c():
"""
>>> test_c()
......@@ -36,6 +38,7 @@ def test_c():
spam.c = c'a'
eggs_c(spam)
def test_p():
"""
>>> test_p()
......@@ -43,3 +46,80 @@ def test_p():
cdef float f
spam.p[0] = &f
eggs_p(spam)
cdef union AllCharptr:
char* s1
char* s2
char* s3
def test_charptr_to_py():
"""
>>> result = test_charptr_to_py()
>>> len(result)
3
>>> result['s1'] == b'abc'
True
>>> result['s2'] == b'abc'
True
>>> result['s3'] == b'abc'
True
"""
cdef AllCharptr u
u.s1 = b"abc"
return u
cdef union SafeMix:
char c
unsigned char uc
signed char sc
short w
int i
long l
size_t z
float f
double d
def test_safe_type_mix_from_to_py(v):
"""
>>> test_safe_type_mix_from_to_py({'l': 32, 'c': 32})
Traceback (most recent call last):
ValueError: More than one union attribute passed: 'c' and 'l'
>>> result = test_safe_type_mix_from_to_py({'c': 32})
>>> sorted(result)
['c', 'd', 'f', 'i', 'l', 'sc', 'uc', 'w', 'z']
>>> result['c']
32
>>> result['z'] != 0
True
>>> result = test_safe_type_mix_from_to_py({'uc': 32})
>>> len(result)
9
>>> result['uc']
32
>>> result = test_safe_type_mix_from_to_py({'l': 100})
>>> result['l']
100
>>> result = test_safe_type_mix_from_to_py({'z': 0})
>>> result['z']
0
>>> result['i']
0
>>> result['l']
0
>>> result = test_safe_type_mix_from_to_py({'d': 2**52 - 1})
>>> result['d']
4503599627370495.0
>>> result['z'] != 0
True
"""
cdef SafeMix u = v
return u
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