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