xref: /petsc/src/binding/petsc4py/conf/cyautodoc.py (revision bcee047adeeb73090d7e36cc71e39fc287cdbb97)
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 _fmt_relative_position(self, pos):
143        return 'Source code at ' + ':'.join((str(pos[0].get_filenametable_entry()), str(pos[1])))
144
145    def _embed_signature(self, signature, pos, node_doc):
146        pos = self._fmt_relative_position(pos)
147        if node_doc:
148            docfmt = self._select_format("%s\n%s\n%s", "%s\n--\n\n%s")
149            return docfmt % (signature, node_doc, pos)
150        else:
151            docfmt = self._select_format("%s\n%s", "%s\n--\n\n%s")
152            return docfmt % (signature, pos)
153
154    def __call__(self, node):
155        if not Options.docstrings:
156            return node
157        else:
158            return super(EmbedSignature, self).__call__(node)
159
160    def visit_ClassDefNode(self, node):
161        oldname = self.class_name
162        oldclass = self.class_node
163        self.class_node = node
164        try:
165            # PyClassDefNode
166            self.class_name = node.name
167        except AttributeError:
168            # CClassDefNode
169            self.class_name = node.class_name
170        self.visitchildren(node)
171        self.class_name = oldname
172        self.class_node = oldclass
173        return node
174
175    def visit_LambdaNode(self, node):
176        # lambda expressions so not have signature or inner functions
177        return node
178
179    def visit_DefNode(self, node):
180        if not self.current_directives['embedsignature']:
181            return node
182
183        is_constructor = False
184        hide_self = False
185        if node.entry.is_special:
186            is_constructor = self.class_node and node.name == '__init__'
187            if not is_constructor:
188                return node
189            class_name, func_name = None, self.class_name
190            hide_self = True
191        else:
192            class_name, func_name = self.class_name, node.name
193
194        npoargs = getattr(node, 'num_posonly_args', 0)
195        nkargs = getattr(node, 'num_kwonly_args', 0)
196        npargs = len(node.args) - nkargs - npoargs
197        signature = self._fmt_signature(
198            class_name, func_name, node.args,
199            npoargs, npargs, node.star_arg,
200            nkargs, node.starstar_arg,
201            return_expr=node.return_type_annotation,
202            return_type=None, hide_self=hide_self)
203        if signature:
204            if is_constructor:
205                doc_holder = self.class_node.entry.type.scope
206            else:
207                doc_holder = node.entry
208
209            if doc_holder.doc is not None:
210                old_doc = doc_holder.doc
211            elif not is_constructor and getattr(node, 'py_func', None) is not None:
212                old_doc = node.py_func.entry.doc
213            else:
214                old_doc = None
215            new_doc = self._embed_signature(signature, node.pos, old_doc)
216            doc_holder.doc = EncodedString(new_doc)
217            if not is_constructor and getattr(node, 'py_func', None) is not None:
218                node.py_func.entry.doc = EncodedString(new_doc)
219        return node
220
221    def visit_CFuncDefNode(self, node):
222        if not self.current_directives['embedsignature']:
223            return node
224        if not node.overridable:  # not cpdef FOO(...):
225            return node
226
227        signature = self._fmt_signature(
228            self.class_name, node.declarator.base.name,
229            node.declarator.args,
230            return_type=node.return_type)
231        if signature:
232            if node.entry.doc is not None:
233                old_doc = node.entry.doc
234            elif getattr(node, 'py_func', None) is not None:
235                old_doc = node.py_func.entry.doc
236            else:
237                old_doc = None
238            new_doc = self._embed_signature(signature, node.pos, old_doc)
239            node.entry.doc = EncodedString(new_doc)
240            py_func = getattr(node, 'py_func', None)
241            if py_func is not None:
242                py_func.entry.doc = EncodedString(new_doc)
243        return node
244
245    def visit_PropertyNode(self, node):
246        if not self.current_directives['embedsignature']:
247            return node
248
249        entry = node.entry
250        body = node.body
251        prop_name = entry.name
252        type_name = None
253        if entry.visibility == 'public':
254            # property synthesised from a cdef public attribute
255            type_name = entry.type.declaration_code("", for_display=1)
256            if not entry.type.is_pyobject:
257                type_name = "'%s'" % type_name
258            elif entry.type.is_extension_type:
259                type_name = entry.type.module_name + '.' + type_name
260        if type_name is None:
261            for stat in body.stats:
262                if stat.name != '__get__':
263                    continue
264                cls_name = self.class_name
265                if cls_name:
266                    namespace = self._select_format('%s.' % cls_name, '')
267                    prop_name = '%s%s' % (namespace, prop_name)
268                ret_annotation = stat.return_type_annotation
269                if ret_annotation:
270                    type_name = self._fmt_annotation(ret_annotation)
271        if type_name is not None:
272            signature = '%s: %s' % (prop_name, type_name)
273            new_doc = self._embed_signature(signature, node.pos, entry.doc)
274            entry.doc = EncodedString(new_doc)
275        return node
276
277
278# Monkeypatch AutoDocTransforms.EmbedSignature
279try:
280    from Cython.Compiler import AutoDocTransforms
281    AutoDocTransforms.EmbedSignature = EmbedSignature
282except Exception as exc:
283    import logging
284    logging.Logger(__name__).exception(exc)
285
286# Monkeypatch Nodes.raise_utility_code
287try:
288    from Cython.Compiler.Nodes import raise_utility_code
289    code = raise_utility_code.impl
290    try:
291        ipos = code.index("if (tb) {\n#if CYTHON_COMPILING_IN_PYPY\n")
292    except ValueError:
293        ipos = None
294    else:
295        raise_utility_code.impl = code[:ipos] + code[ipos:].replace(
296            'CYTHON_COMPILING_IN_PYPY', '!CYTHON_FAST_THREAD_STATE', 1)
297    del raise_utility_code, code, ipos
298except Exception as exc:
299    import logging
300    logging.Logger(__name__).exception(exc)
301