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