Commit c036607a authored by Stefan Behnel's avatar Stefan Behnel

implement support for extracting type declarations from signature annotations

parent cbe735b8
......@@ -9,6 +9,9 @@ Latest
Features added
--------------
* Simple support for declaring Python object types in Python signature
annotations.
Bugs fixed
----------
......
......@@ -289,15 +289,16 @@ class ExprNode(Node):
#
#
is_sequence_constructor = 0
is_string_literal = 0
is_attribute = 0
is_subscript = 0
is_sequence_constructor = False
is_dict_literal = False
is_string_literal = False
is_attribute = False
is_subscript = False
saved_subexpr_nodes = None
is_temp = 0
is_target = 0
is_starred = 0
is_temp = False
is_target = False
is_starred = False
constant_result = constant_value_not_set
......@@ -1174,6 +1175,20 @@ class FloatNode(ConstNode):
self.result_code = c_value
def _analyse_name_as_type(name, pos, env):
type = PyrexTypes.parse_basic_type(name)
if type is not None:
return type
from TreeFragment import TreeFragment
pos = (pos[0], pos[1], pos[2]-7)
declaration = TreeFragment(u"sizeof(%s)" % name, name=pos[0].filename, initial_pos=pos)
sizeof_node = declaration.root.stats[0].expr
sizeof_node = sizeof_node.analyse_types(env)
if isinstance(sizeof_node, SizeofTypeNode):
return sizeof_node.arg_type
return None
class BytesNode(ConstNode):
# A char* or bytes literal
#
......@@ -1196,16 +1211,7 @@ class BytesNode(ConstNode):
return self.value
def analyse_as_type(self, env):
type = PyrexTypes.parse_basic_type(self.value)
if type is not None:
return type
from TreeFragment import TreeFragment
pos = (self.pos[0], self.pos[1], self.pos[2]-7)
declaration = TreeFragment(u"sizeof(%s)" % self.value, name=pos[0].filename, initial_pos=pos)
sizeof_node = declaration.root.stats[0].expr
sizeof_node = sizeof_node.analyse_types(env)
if isinstance(sizeof_node, SizeofTypeNode):
return sizeof_node.arg_type
return _analyse_name_as_type(self.value.decode('ISO8859-1'), self.pos, env)
def can_coerce_to_char_literal(self):
return len(self.value) == 1
......@@ -1279,6 +1285,9 @@ class UnicodeNode(ConstNode):
def calculate_constant_result(self):
self.constant_result = self.value
def analyse_as_type(self, env):
return _analyse_name_as_type(self.value, self.pos, env)
def as_sliced_node(self, start, stop, step=None):
if StringEncoding.string_contains_surrogates(self.value[:stop]):
# this is unsafe as it may give different results
......@@ -1388,6 +1397,9 @@ class StringNode(PyConstNode):
# only the Unicode value is portable across Py2/3
self.constant_result = self.unicode_value
def analyse_as_type(self, env):
return _analyse_name_as_type(self.unicode_value or self.value.decode('ISO8859-1'), self.pos, env)
def as_sliced_node(self, start, stop, step=None):
value = type(self.value)(self.value[start:stop:step])
value.encoding = self.value.encoding
......@@ -6706,6 +6718,7 @@ class DictNode(ExprNode):
is_temp = 1
exclude_null_values = False
type = dict_type
is_dict_literal = True
obj_conversion_errors = []
......
......@@ -612,8 +612,8 @@ class CFuncDeclaratorNode(CDeclaratorNode):
nonempty -= 1
func_type_args = []
for i, arg_node in enumerate(self.args):
name_declarator, type = arg_node.analyse(env, nonempty = nonempty,
is_self_arg = (i == 0 and env.is_c_class_scope))
name_declarator, type = arg_node.analyse(
env, nonempty=nonempty, is_self_arg=(i == 0 and env.is_c_class_scope))
name = name_declarator.name
if name in directive_locals:
type_node = directive_locals[name]
......@@ -800,7 +800,7 @@ class CArgDeclNode(Node):
if nonempty:
if self.base_type.is_basic_c_type:
# char, short, long called "int"
type = self.base_type.analyse(env, could_be_name = True)
type = self.base_type.analyse(env, could_be_name=True)
arg_name = type.declaration_code("")
else:
arg_name = self.base_type.name
......@@ -811,9 +811,10 @@ class CArgDeclNode(Node):
else:
could_be_name = False
self.base_type.is_arg = True
base_type = self.base_type.analyse(env, could_be_name = could_be_name)
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
# The parser is unable to resolve the ambiguity of [] as part of the
# type (e.g. in buffers) or empty declarator (as with arrays).
# This is only arises for empty multi-dimensional arrays.
......@@ -825,10 +826,45 @@ class CArgDeclNode(Node):
declarator = declarator.base
declarator.base = self.base_type.array_declarator
base_type = base_type.base_type
return self.declarator.analyse(base_type, env, nonempty = nonempty)
# inject type declaration from annotations
if self.annotation and env.directives['annotation_typing'] and self.base_type.name is None:
arg_type = self.inject_type_from_annotations(env)
if arg_type is not None:
base_type = arg_type
return self.declarator.analyse(base_type, env, nonempty=nonempty)
else:
return self.name_declarator, self.type
def inject_type_from_annotations(self, env):
annotation = self.annotation
if not annotation:
return
explicit_pytype = explicit_ctype = False
if annotation.is_dict_literal:
for name, value in annotation.key_value_pairs:
if not name.is_string_literal:
continue
if name.value == 'type':
explicit_pytype = True
if not explicit_ctype:
annotation = value
elif name.value == 'ctype':
explicit_ctype = True
annotation = value
if explicit_pytype and explicit_ctype:
warning(annotation.pos, "Duplicate type declarations found in signature annotation")
arg_type = annotation.analyse_as_type(env)
if arg_type is not None:
if explicit_pytype and not explicit_ctype and not arg_type.is_pyobject:
warning(annotation.pos,
"Python type declaration in signature annotation does not refer to a Python type")
self.base_type = CAnalysedBaseTypeNode(
annotation.pos, type=arg_type, is_arg=True)
else:
warning(annotation.pos, "Unknown type declaration found in signature annotation")
return arg_type
def calculate_default_value_code(self, code):
if self.default_value is None:
if self.default:
......@@ -1524,19 +1560,25 @@ class FuncDefNode(StatNode, BlockNode):
error(arg.pos, "Non-default argument following default argument")
def align_argument_type(self, env, arg):
# @cython.locals()
directive_locals = self.directive_locals
type = arg.type
orig_type = arg.type
if arg.name in directive_locals:
type_node = directive_locals[arg.name]
other_type = type_node.analyse_as_type(env)
if other_type is None:
error(type_node.pos, "Not a type")
elif (type is not PyrexTypes.py_object_type
and not type.same_as(other_type)):
error(arg.base_type.pos, "Signature does not agree with previous declaration")
error(type_node.pos, "Previous declaration here")
else:
arg.type = other_type
elif isinstance(arg, CArgDeclNode) and arg.annotation:
type_node = arg.annotation
other_type = arg.inject_type_from_annotations(env)
else:
return arg
if other_type is None:
error(type_node.pos, "Not a type")
elif (orig_type is not PyrexTypes.py_object_type
and not orig_type.same_as(other_type)):
error(arg.base_type.pos, "Signature does not agree with previous declaration")
error(type_node.pos, "Previous declaration here")
else:
arg.type = other_type
return arg
def need_gil_acquisition(self, lenv):
......
......@@ -101,6 +101,7 @@ directive_defaults = {
'profile': False,
'no_gc_clear': False,
'linetrace': False,
'annotation_typing': False, # read type declarations from Python function annotations
'infer_types': None,
'infer_types.verbose': False,
'autotestdict': True,
......@@ -228,6 +229,7 @@ directive_scopes = { # defaults to available everywhere
'test_assert_path_exists' : ('function', 'class', 'cclass'),
'test_fail_if_path_exists' : ('function', 'class', 'cclass'),
'freelist': ('cclass',),
'annotation_typing': ('module',), # FIXME: analysis currently lacks more specific function scope
# Avoid scope-specific to/from_py_functions for c_string.
'c_string_type': ('module',),
'c_string_encoding': ('module',),
......
......@@ -36,6 +36,16 @@ The currently supported attributes of the ``cython`` module are:
def foo(a, b, x, y):
...
* Starting with Cython 0.21, Python signature annotations can be used to
declare argument types. Cython recognises three ways to do this, as
shown in the following example::
def func(plain_python_type: dict,
named_python_type: 'dict',
explicit_python_type: {'type': dict},
explicit_c_type: {'ctype': 'int'}):
...
* ``address`` is used in place of the ``&`` operator::
cython.declare(x=cython.int, x_ptr=cython.p_int)
......
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