Commit c95bae90 authored by Stefan Behnel's avatar Stefan Behnel

Prevent Cython types in annotations from ending up plainly in the...

Prevent Cython types in annotations from ending up plainly in the __annotations__ dict. Convert them into strings instead.
parent 253cf82d
...@@ -26,6 +26,12 @@ Bugs fixed ...@@ -26,6 +26,12 @@ Bugs fixed
* Some issues with the relaxed exception value handling were resolved. * Some issues with the relaxed exception value handling were resolved.
* Python classes as annotation types could prevent compilation.
(Github issue #1887)
* Cython annotation types in Python files could lead to import failures
with a "cython undefined" error. Recognised types are now turned into strings.
0.27 (2017-09-23) 0.27 (2017-09-23)
================= =================
......
...@@ -9015,22 +9015,19 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): ...@@ -9015,22 +9015,19 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
else: else:
default_args.append(arg) default_args.append(arg)
if arg.annotation: if arg.annotation:
arg.annotation = arg.annotation.analyse_types(env) arg.annotation = self.analyse_annotation(env, arg.annotation)
if not arg.annotation.type.is_pyobject:
arg.annotation = arg.annotation.coerce_to_pyobject(env)
annotations.append((arg.pos, arg.name, arg.annotation)) annotations.append((arg.pos, arg.name, arg.annotation))
for arg in (self.def_node.star_arg, self.def_node.starstar_arg): for arg in (self.def_node.star_arg, self.def_node.starstar_arg):
if arg and arg.annotation: if arg and arg.annotation:
arg.annotation = arg.annotation.analyse_types(env) arg.annotation = self.analyse_annotation(env, arg.annotation)
if not arg.annotation.type.is_pyobject:
arg.annotation = arg.annotation.coerce_to_pyobject(env)
annotations.append((arg.pos, arg.name, arg.annotation)) annotations.append((arg.pos, arg.name, arg.annotation))
if self.def_node.return_type_annotation: annotation = self.def_node.return_type_annotation
annotations.append((self.def_node.return_type_annotation.pos, if annotation:
StringEncoding.EncodedString("return"), annotation = self.analyse_annotation(env, annotation)
self.def_node.return_type_annotation)) self.def_node.return_type_annotation = annotation
annotations.append((annotation.pos, StringEncoding.EncodedString("return"), annotation))
if nonliteral_objects or nonliteral_other: if nonliteral_objects or nonliteral_other:
module_scope = env.global_scope() module_scope = env.global_scope()
...@@ -9107,6 +9104,20 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): ...@@ -9107,6 +9104,20 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
for pos, name, value in annotations]) for pos, name, value in annotations])
self.annotations_dict = annotations_dict.analyse_types(env) self.annotations_dict = annotations_dict.analyse_types(env)
def analyse_annotation(self, env, annotation):
if annotation is None:
return None
atype = annotation.analyse_as_type(env)
if atype is not None:
# Keep parsed types as strings as they might not be Python representable.
annotation = UnicodeNode(
annotation.pos,
value=StringEncoding.EncodedString(atype.declaration_code('', for_display=True)))
annotation = annotation.analyse_types(env)
if not annotation.type.is_pyobject:
annotation = annotation.coerce_to_pyobject(env)
return annotation
def may_be_none(self): def may_be_none(self):
return False return False
......
...@@ -332,6 +332,7 @@ VER_DEP_MODULES = { ...@@ -332,6 +332,7 @@ VER_DEP_MODULES = {
# to be unsafe... # to be unsafe...
(2,999): (operator.lt, lambda x: x in ['run.special_methods_T561_py3', (2,999): (operator.lt, lambda x: x in ['run.special_methods_T561_py3',
'run.test_raisefrom', 'run.test_raisefrom',
'pure_py3',
]), ]),
(3,): (operator.ge, lambda x: x in ['run.non_future_division', (3,): (operator.ge, lambda x: x in ['run.non_future_division',
'compile.extsetslice', 'compile.extsetslice',
......
# mode: run
# tag: annotation_typing
import cython
is_compiled = cython.compiled
MyUnion = cython.union(n=cython.int, x=cython.double)
MyStruct = cython.struct(is_integral=cython.bint, data=MyUnion)
MyStruct2 = cython.typedef(MyStruct[2])
@cython.ccall # cpdef => C return type
def test_return_type(n: cython.int) -> cython.double:
"""
>>> test_return_type(389)
389.0
"""
assert cython.typeof(n) == 'int', cython.typeof(n)
return n if is_compiled else float(n)
def test_struct(n: cython.int, x: cython.double) -> MyStruct2:
"""
>>> test_struct(389, 1.64493)
(389, 1.64493)
>>> d = test_struct.__annotations__
>>> sorted(d)
['n', 'return', 'x']
"""
assert cython.typeof(n) == 'int', cython.typeof(n)
if is_compiled:
assert cython.typeof(x) == 'double', cython.typeof(x) # C double
else:
assert cython.typeof(x) == 'float', cython.typeof(x) # Python float
a = cython.declare(MyStruct2)
a[0] = MyStruct(is_integral=True, data=MyUnion(n=n))
a[1] = MyStruct(is_integral=False, data={'x': x})
return a[0].data.n, a[1].data.x
@cython.ccall
def c_call(x) -> cython.double:
"""
Test that a declared return type is honoured when compiled.
>>> result, return_type = call_ccall(1)
>>> (not is_compiled and 'double') or return_type
'double'
>>> (is_compiled and 'int') or return_type
'int'
>>> (not is_compiled and 1.0) or result
1.0
>>> (is_compiled and 1) or result
1
"""
return x
def call_ccall(x):
ret = c_call(x)
return ret, cython.typeof(ret)
@cython.cfunc
@cython.inline
def cdef_inline(x) -> cython.double:
"""
>>> result, return_type = call_cdef_inline(1)
>>> (not is_compiled and 'float') or type(return_type).__name__
'float'
>>> (not is_compiled and 'double') or return_type
'double'
>>> (is_compiled and 'int') or return_type
'int'
>>> result == 2.0 or result
True
"""
return x + 1
def call_cdef_inline(x):
ret = cdef_inline(x)
return ret, cython.typeof(ret)
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