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