xref: /petsc/src/binding/petsc4py/conf/cyautodoc.py (revision fbf9dbe564678ed6eff1806adbc4c4f01b9743f4)
1# ruff: noqa: UP008,UP031
2from Cython.Compiler import Options
3from Cython.Compiler import PyrexTypes
4from Cython.Compiler.ExprNodes import TupleNode
5from Cython.Compiler.Visitor import CythonTransform
6from Cython.Compiler.StringEncoding import EncodedString
7from Cython.Compiler.AutoDocTransforms import (
8    ExpressionWriter as BaseExpressionWriter,
9    AnnotationWriter as BaseAnnotationWriter,
10)
11
12
13class ExpressionWriter(BaseExpressionWriter):
14
15    def visit_IndexNode(self, node):
16        self.visit(node.base)
17        self.put(u"[")
18        if isinstance(node.index, TupleNode):
19            if node.index.subexpr_nodes():
20                self.emit_sequence(node.index)
21            else:
22                self.put(u"()")
23        else:
24            self.visit(node.index)
25        self.put(u"]")
26
27    def visit_UnicodeNode(self, node):
28        self.emit_string(node, "")
29
30
31class AnnotationWriter(ExpressionWriter, BaseAnnotationWriter):
32    pass
33
34
35class EmbedSignature(CythonTransform):
36
37    def __init__(self, context):
38        super(EmbedSignature, self).__init__(context)
39        self.class_name = None
40        self.class_node = None
41
42    def _select_format(self, embed, clinic):
43        return embed
44
45    def _fmt_expr(self, node):
46        writer = ExpressionWriter()
47        result = writer.write(node)
48        # print(type(node).__name__, '-->', result)
49        return result
50
51    def _fmt_annotation(self, node):
52        writer = AnnotationWriter()
53        result = writer.write(node)
54        # print(type(node).__name__, '-->', result)
55        return result
56
57    def _fmt_arg(self, arg):
58        annotation = None
59        if arg.is_self_arg:
60            doc = self._select_format(arg.name, '$self')
61        elif arg.is_type_arg:
62            doc = self._select_format(arg.name, '$type')
63        else:
64            doc = arg.name
65            if arg.type is PyrexTypes.py_object_type:
66                annotation = None
67            else:
68                annotation = arg.type.declaration_code('', for_display=1)
69                if arg.default and arg.default.is_none:
70                    annotation += ' | None'
71        if arg.annotation:
72            annotation = self._fmt_annotation(arg.annotation)
73        annotation = self._select_format(annotation, None)
74        if annotation:
75            doc = doc + (': %s' % annotation)
76            if arg.default:
77                default = self._fmt_expr(arg.default)
78                doc = doc + (' = %s' % default)
79        elif arg.default:
80            default = self._fmt_expr(arg.default)
81            doc = doc + ('=%s' % default)
82        return doc
83
84    def _fmt_star_arg(self, arg):
85        arg_doc = arg.name
86        if arg.annotation:
87            annotation = self._fmt_annotation(arg.annotation)
88            arg_doc = arg_doc + (': %s' % annotation)
89        return arg_doc
90
91    def _fmt_arglist(self, args,
92                     npoargs=0, npargs=0, pargs=None,
93                     nkargs=0, kargs=None,
94                     hide_self=False):
95        arglist = []
96        for arg in args:
97            if not hide_self or not arg.entry.is_self_arg:
98                arg_doc = self._fmt_arg(arg)
99                arglist.append(arg_doc)
100        if pargs:
101            arg_doc = self._fmt_star_arg(pargs)
102            arglist.insert(npargs + npoargs, '*%s' % arg_doc)
103        elif nkargs:
104            arglist.insert(npargs + npoargs, '*')
105        if npoargs:
106            arglist.insert(npoargs, '/')
107        if kargs:
108            arg_doc = self._fmt_star_arg(kargs)
109            arglist.append('**%s' % arg_doc)
110        return arglist
111
112    def _fmt_ret_type(self, ret):
113        if ret is PyrexTypes.py_object_type:
114            return None
115        else:
116            return ret.declaration_code("", for_display=1)
117
118    def _fmt_signature(self, cls_name, func_name, args,
119                       npoargs=0, npargs=0, pargs=None,
120                       nkargs=0, kargs=None,
121                       return_expr=None,
122                       return_type=None, hide_self=False):
123        arglist = self._fmt_arglist(args,
124                                    npoargs, npargs, pargs,
125                                    nkargs, kargs,
126                                    hide_self=hide_self)
127        arglist_doc = ', '.join(arglist)
128        func_doc = '%s(%s)' % (func_name, arglist_doc)
129        if cls_name:
130            namespace = self._select_format('%s.' % cls_name, '')
131            func_doc = '%s%s' % (namespace, func_doc)
132        ret_doc = None
133        if return_expr:
134            ret_doc = self._fmt_annotation(return_expr)
135        elif return_type:
136            ret_doc = self._fmt_ret_type(return_type)
137        if ret_doc:
138            docfmt = self._select_format('%s -> %s', '%s -> (%s)')
139            func_doc = docfmt % (func_doc, ret_doc)
140        return func_doc
141
142    def _embed_signature(self, signature, node_doc):
143        if node_doc:
144            docfmt = self._select_format("%s\n%s", "%s\n--\n\n%s")
145            return docfmt % (signature, node_doc)
146        else:
147            return signature
148
149    def __call__(self, node):
150        if not Options.docstrings:
151            return node
152        else:
153            return super(EmbedSignature, self).__call__(node)
154
155    def visit_ClassDefNode(self, node):
156        oldname = self.class_name
157        oldclass = self.class_node
158        self.class_node = node
159        try:
160            # PyClassDefNode
161            self.class_name = node.name
162        except AttributeError:
163            # CClassDefNode
164            self.class_name = node.class_name
165        self.visitchildren(node)
166        self.class_name = oldname
167        self.class_node = oldclass
168        return node
169
170    def visit_LambdaNode(self, node):
171        # lambda expressions so not have signature or inner functions
172        return node
173
174    def visit_DefNode(self, node):
175        if not self.current_directives['embedsignature']:
176            return node
177
178        is_constructor = False
179        hide_self = False
180        if node.entry.is_special:
181            is_constructor = self.class_node and node.name == '__init__'
182            if not is_constructor:
183                return node
184            class_name, func_name = None, self.class_name
185            hide_self = True
186        else:
187            class_name, func_name = self.class_name, node.name
188
189        npoargs = getattr(node, 'num_posonly_args', 0)
190        nkargs = getattr(node, 'num_kwonly_args', 0)
191        npargs = len(node.args) - nkargs - npoargs
192        signature = self._fmt_signature(
193            class_name, func_name, node.args,
194            npoargs, npargs, node.star_arg,
195            nkargs, node.starstar_arg,
196            return_expr=node.return_type_annotation,
197            return_type=None, hide_self=hide_self)
198        if signature:
199            if is_constructor:
200                doc_holder = self.class_node.entry.type.scope
201            else:
202                doc_holder = node.entry
203
204            if doc_holder.doc is not None:
205                old_doc = doc_holder.doc
206            elif not is_constructor and getattr(node, 'py_func', None) is not None:
207                old_doc = node.py_func.entry.doc
208            else:
209                old_doc = None
210            new_doc = self._embed_signature(signature, old_doc)
211            doc_holder.doc = EncodedString(new_doc)
212            if not is_constructor and getattr(node, 'py_func', None) is not None:
213                node.py_func.entry.doc = EncodedString(new_doc)
214        return node
215
216    def visit_CFuncDefNode(self, node):
217        if not self.current_directives['embedsignature']:
218            return node
219        if not node.overridable:  # not cpdef FOO(...):
220            return node
221
222        signature = self._fmt_signature(
223            self.class_name, node.declarator.base.name,
224            node.declarator.args,
225            return_type=node.return_type)
226        if signature:
227            if node.entry.doc is not None:
228                old_doc = node.entry.doc
229            elif getattr(node, 'py_func', None) is not None:
230                old_doc = node.py_func.entry.doc
231            else:
232                old_doc = None
233            new_doc = self._embed_signature(signature, old_doc)
234            node.entry.doc = EncodedString(new_doc)
235            py_func = getattr(node, 'py_func', None)
236            if py_func is not None:
237                py_func.entry.doc = EncodedString(new_doc)
238        return node
239
240    def visit_PropertyNode(self, node):
241        if not self.current_directives['embedsignature']:
242            return node
243
244        entry = node.entry
245        body = node.body
246        prop_name = entry.name
247        type_name = None
248        if entry.visibility == 'public':
249            # property synthesised from a cdef public attribute
250            type_name = entry.type.declaration_code("", for_display=1)
251            if not entry.type.is_pyobject:
252                type_name = "'%s'" % type_name
253            elif entry.type.is_extension_type:
254                type_name = entry.type.module_name + '.' + type_name
255        if type_name is None:
256            for stat in body.stats:
257                if stat.name != '__get__':
258                    continue
259                cls_name = self.class_name
260                if cls_name:
261                    namespace = self._select_format('%s.' % cls_name, '')
262                    prop_name = '%s%s' % (namespace, prop_name)
263                ret_annotation = stat.return_type_annotation
264                if ret_annotation:
265                    type_name = self._fmt_annotation(ret_annotation)
266        if type_name is not None:
267            signature = '%s: %s' % (prop_name, type_name)
268            new_doc = self._embed_signature(signature, entry.doc)
269            entry.doc = EncodedString(new_doc)
270        return node
271
272
273# Monkeypatch AutoDocTransforms.EmbedSignature
274try:
275    from Cython.Compiler import AutoDocTransforms
276    AutoDocTransforms.EmbedSignature = EmbedSignature
277except Exception as exc:
278    import logging
279    logging.Logger(__name__).exception(exc)
280
281# Monkeypatch Nodes.raise_utility_code
282try:
283    from Cython.Compiler.Nodes import raise_utility_code
284    code = raise_utility_code.impl
285    try:
286        ipos = code.index("if (tb) {\n#if CYTHON_COMPILING_IN_PYPY\n")
287    except ValueError:
288        ipos = None
289    else:
290        raise_utility_code.impl = code[:ipos] + code[ipos:].replace(
291            'CYTHON_COMPILING_IN_PYPY', '!CYTHON_FAST_THREAD_STATE', 1)
292    del raise_utility_code, code, ipos
293except Exception as exc:
294    import logging
295    logging.Logger(__name__).exception(exc)
296