Commit 717df790 authored by Stefan Behnel's avatar Stefan Behnel

implement __annotations__ attribute on CyFunction (PEP 3107)

parent ea569ef3
......@@ -8,6 +8,9 @@ Cython Changelog
Features added
--------------
* Cython implemented functions make their argument and return type annotations
available through the ``__annotations__`` attribute (PEP 3107).
* Access to non-cdef module globals and Python object attributes is faster.
* ``Py_UNICODE*`` coerces from and to Python unicode strings. This is
......
......@@ -6952,7 +6952,7 @@ class UnboundMethodNode(ExprNode):
class PyCFunctionNode(ExprNode, ModuleNameMixin):
# Helper class used in the implementation of Python
# class definitions. Constructs a PyCFunction object
# functions. Constructs a PyCFunction object
# from a PyMethodDef struct.
#
# pymethdef_cname string PyMethodDef structure
......@@ -6962,7 +6962,8 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
# module_name EncodedString Name of defining module
# code_object CodeObjectNode the PyCodeObject creator node
subexprs = ['code_object', 'defaults_tuple', 'defaults_kwdict']
subexprs = ['code_object', 'defaults_tuple', 'defaults_kwdict',
'annotations_dict']
self_object = None
code_object = None
......@@ -6973,6 +6974,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
defaults_pyobjects = 0
defaults_tuple = None
defaults_kwdict = None
annotations_dict = None
type = py_object_type
is_temp = 1
......@@ -7004,6 +7006,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
nonliteral_other = []
default_args = []
default_kwargs = []
annotations = []
for arg in self.def_node.args:
if arg.default:
if not arg.default.is_literal:
......@@ -7018,6 +7021,16 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
default_kwargs.append(arg)
else:
default_args.append(arg)
if arg.annotation:
arg.annotation = arg.annotation.analyse_types(env)
if not arg.annotation.type.is_pyobject:
arg.annotation = arg.annotation.coerce_to_pyobject(env)
annotations.append((arg.pos, arg.name, arg.annotation))
if self.def_node.return_type_annotation:
annotations.append((self.def_node.return_type_annotation.pos,
StringEncoding.EncodedString("return"),
self.def_node.return_type_annotation))
if nonliteral_objects or nonliteral_other:
module_scope = env.global_scope()
cname = module_scope.next_id(Naming.defaults_struct_prefix)
......@@ -7083,6 +7096,13 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
defaults_getter.py_wrapper_required = False
defaults_getter.pymethdef_required = False
self.def_node.defaults_getter = defaults_getter
if annotations:
annotations_dict = DictNode(self.pos, key_value_pairs=[
DictItemNode(
pos, key=IdentifierStringNode(pos, value=name),
value=value)
for pos, name, value in annotations])
self.annotations_dict = annotations_dict.analyse_types(env)
def may_be_none(self):
return False
......@@ -7192,6 +7212,9 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
if def_node.defaults_getter:
code.putln('__Pyx_CyFunction_SetDefaultsGetter(%s, %s);' % (
self.result(), def_node.defaults_getter.entry.pyfunc_cname))
if self.annotations_dict:
code.putln('__Pyx_CyFunction_SetAnnotationsDict(%s, %s);' % (
self.result(), self.annotations_dict.py_result()))
class InnerFunctionNode(PyCFunctionNode):
......
......@@ -729,7 +729,7 @@ class CArgDeclNode(Node):
# is_kw_only boolean Is a keyword-only argument
# is_dynamic boolean Non-literal arg stored inside CyFunction
child_attrs = ["base_type", "declarator", "default"]
child_attrs = ["base_type", "declarator", "default", "annotation"]
is_self_arg = 0
is_type_arg = 0
......
......@@ -31,14 +31,15 @@ typedef struct {
PyObject *func_closure;
PyObject *func_classobj; /* No-args super() class cell */
/* Dynamic default args*/
/* Dynamic default args and annotations */
void *defaults;
int defaults_pyobjects;
/* Defaults info */
PyObject *defaults_tuple; /* Const defaults tuple */
PyObject *defaults_kwdict; /* Const kwonly defaults dict */
PyObject *defaults_tuple; /* Const defaults tuple */
PyObject *defaults_kwdict; /* Const kwonly defaults dict */
PyObject *(*defaults_getter)(PyObject *);
PyObject *func_annotations; /* function annotations dict */
} __pyx_CyFunctionObject;
static PyTypeObject *__pyx_CyFunctionType = 0;
......@@ -58,6 +59,8 @@ static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsTuple(PyObject *m,
PyObject *tuple);
static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsKwDict(PyObject *m,
PyObject *dict);
static CYTHON_INLINE void __Pyx_CyFunction_SetAnnotationsDict(PyObject *m,
PyObject *dict);
static int __Pyx_CyFunction_init(void);
......@@ -319,6 +322,35 @@ __Pyx_CyFunction_get_kwdefaults(__pyx_CyFunctionObject *op) {
return result;
}
static int
__Pyx_CyFunction_set_annotations(__pyx_CyFunctionObject *op, PyObject* value) {
PyObject* tmp;
if (!value || value == Py_None) {
value = NULL;
} else if (!PyDict_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"__annotations__ must be set to a dict object");
return -1;
}
Py_XINCREF(value);
tmp = op->func_annotations;
op->func_annotations = value;
Py_XDECREF(tmp);
return 0;
}
static PyObject *
__Pyx_CyFunction_get_annotations(__pyx_CyFunctionObject *op) {
PyObject* result = op->func_annotations;
if (unlikely(!result)) {
result = PyDict_New();
if (unlikely(!result)) return NULL;
op->func_annotations = result;
}
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},
......@@ -337,6 +369,7 @@ static PyGetSetDef __pyx_CyFunction_getsets[] = {
{(char *) "func_defaults", (getter)__Pyx_CyFunction_get_defaults, (setter)__Pyx_CyFunction_set_defaults, 0, 0},
{(char *) "__defaults__", (getter)__Pyx_CyFunction_get_defaults, (setter)__Pyx_CyFunction_set_defaults, 0, 0},
{(char *) "__kwdefaults__", (getter)__Pyx_CyFunction_get_kwdefaults, (setter)__Pyx_CyFunction_set_kwdefaults, 0, 0},
{(char *) "__annotations__", (getter)__Pyx_CyFunction_get_annotations, (setter)__Pyx_CyFunction_set_annotations, 0, 0},
{0, 0, 0, 0, 0}
};
......@@ -392,6 +425,7 @@ static PyObject *__Pyx_CyFunction_New(PyTypeObject *type, PyMethodDef *ml, int f
op->defaults_tuple = NULL;
op->defaults_kwdict = NULL;
op->defaults_getter = NULL;
op->func_annotations = NULL;
PyObject_GC_Track(op);
return (PyObject *) op;
}
......@@ -409,6 +443,7 @@ __Pyx_CyFunction_clear(__pyx_CyFunctionObject *m)
Py_CLEAR(m->func_classobj);
Py_CLEAR(m->defaults_tuple);
Py_CLEAR(m->defaults_kwdict);
Py_CLEAR(m->func_annotations);
if (m->defaults) {
PyObject **pydefaults = __Pyx_CyFunction_Defaults(PyObject *, m);
......@@ -636,6 +671,12 @@ static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsKwDict(PyObject *func, PyO
Py_INCREF(dict);
}
static CYTHON_INLINE void __Pyx_CyFunction_SetAnnotationsDict(PyObject *func, PyObject *dict) {
__pyx_CyFunctionObject *m = (__pyx_CyFunctionObject *) func;
m->func_annotations = dict;
Py_INCREF(dict);
}
//////////////////// CyFunctionClassCell.proto ////////////////////
static CYTHON_INLINE void __Pyx_CyFunction_InitClassCell(PyObject *cyfunctions,
PyObject *classobj);
......
......@@ -220,3 +220,44 @@ def test_code():
>>> codeof(cy_default_args).co_varnames
('x', 'b', 'l', 'm')
"""
def test_annotations(a: "test", b: "other" = 2, c: 123 = 4) -> "ret":
"""
>>> isinstance(test_annotations.__annotations__, dict)
True
>>> sorted(test_annotations.__annotations__.items())
[('a', 'test'), ('b', 'other'), ('c', 123), ('return', 'ret')]
>>> def func_b(): return 42
>>> def func_c(): return 99
>>> inner = test_annotations(1, func_b, func_c)
>>> sorted(inner.__annotations__.items())
[('return', 99), ('x', 'banana'), ('y', 42)]
>>> inner.__annotations__ = {234: 567}
>>> inner.__annotations__
{234: 567}
>>> inner.__annotations__ = None
>>> inner.__annotations__
{}
>>> inner.__annotations__ = 321
Traceback (most recent call last):
TypeError: __annotations__ must be set to a dict object
>>> inner.__annotations__
{}
>>> inner = test_annotations(1, func_b, func_c)
>>> sorted(inner.__annotations__.items())
[('return', 99), ('x', 'banana'), ('y', 42)]
>>> inner.__annotations__['abc'] = 66
>>> sorted(inner.__annotations__.items())
[('abc', 66), ('return', 99), ('x', 'banana'), ('y', 42)]
>>> inner = test_annotations(1, func_b, func_c)
>>> sorted(inner.__annotations__.items())
[('return', 99), ('x', 'banana'), ('y', 42)]
"""
def inner(x: "banana", y: b()) -> c():
return x,y
return inner
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