make_function36.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. # Copyright (c) 2019-2022 by Rocky Bernstein
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. """
  16. All the crazy things we have to do to handle Python functions in 3.6 and above.
  17. The saga of changes before 3.6 is in other files.
  18. """
  19. from xdis import (
  20. CO_ASYNC_GENERATOR,
  21. CO_GENERATOR,
  22. code_has_star_arg,
  23. code_has_star_star_arg,
  24. iscode,
  25. )
  26. from uncompyle6.parser import ParserError as ParserError2
  27. from uncompyle6.scanner import Code
  28. from uncompyle6.semantics.helper import (
  29. find_all_globals,
  30. find_globals_and_nonlocals,
  31. find_none,
  32. )
  33. from uncompyle6.semantics.parser_error import ParserError
  34. def make_function36(self, node, is_lambda, nested=1, code_node=None):
  35. """Dump function definition, doc string, and function body in
  36. Python version 3.6 and above.
  37. """
  38. # MAKE_CLOSURE adds a closure slot
  39. # In Python 3.6 and above stack change again. I understand
  40. # 3.7 changes some of those changes, although I don't
  41. # see it in this code yet. Yes, it is hard to follow,
  42. # and I am sure I haven't been able to keep up.
  43. # Thank you, Python.
  44. def build_param(ast, name, default, annotation=None):
  45. """build parameters:
  46. - handle defaults
  47. - handle format tuple parameters
  48. """
  49. value = default
  50. if annotation:
  51. result = "%s: %s=%s" % (name, annotation, value)
  52. else:
  53. result = "%s=%s" % (name, value)
  54. # The below can probably be removed. This is probably
  55. # a holdover from days when LOAD_CONST erroneously
  56. # didn't handle LOAD_CONST None properly
  57. if result[-2:] == "= ": # default was 'LOAD_CONST None'
  58. result += "None"
  59. return result
  60. # MAKE_FUNCTION_... or MAKE_CLOSURE_...
  61. assert node[-1].kind.startswith("MAKE_")
  62. # Python 3.3+ adds a qualified name at TOS (-1)
  63. # moving down the LOAD_LAMBDA instruction
  64. lambda_index = -3
  65. args_node = node[-1]
  66. annotate_dict = {}
  67. # Get a list of tree nodes that constitute the values for the "default
  68. # parameters"; these are default values that appear before any *, and are
  69. # not to be confused with keyword parameters which may appear after *.
  70. args_attr = args_node.attr
  71. if len(args_attr) == 3:
  72. _, kw_args, annotate_argc = args_attr
  73. else:
  74. _, kw_args, annotate_argc, closure = args_attr
  75. if node[-2] != "docstring":
  76. i = -4
  77. else:
  78. i = -5
  79. if annotate_argc:
  80. # Turn into subroutine and DRY with other use
  81. annotate_node = node[i]
  82. if annotate_node == "expr":
  83. annotate_node = annotate_node[0]
  84. annotate_name_node = annotate_node[-1]
  85. if annotate_node == "dict" and annotate_name_node.kind.startswith(
  86. "BUILD_CONST_KEY_MAP"
  87. ):
  88. types = [self.traverse(n, indent="") for n in annotate_node[:-2]]
  89. names = annotate_node[-2].attr
  90. length = len(types)
  91. assert length == len(names)
  92. for i in range(length):
  93. annotate_dict[names[i]] = types[i]
  94. pass
  95. pass
  96. i -= 1
  97. if closure:
  98. # FIXME: fill in
  99. # annotate = node[i]
  100. i -= 1
  101. defparams = []
  102. # FIXME: DRY with code below
  103. default, kw_args, annotate_argc = args_node.attr[0:3]
  104. if default:
  105. expr_node = node[0]
  106. if node[0] == "pos_arg":
  107. expr_node = expr_node[0]
  108. assert expr_node == "expr", "expecting mkfunc default node to be an expr"
  109. if expr_node[0] == "LOAD_CONST" and isinstance(expr_node[0].attr, tuple):
  110. defparams = [repr(a) for a in expr_node[0].attr]
  111. elif expr_node[0] in frozenset(("list", "tuple", "dict", "set")):
  112. defparams = [self.traverse(n, indent="") for n in expr_node[0][:-1]]
  113. else:
  114. defparams = []
  115. pass
  116. if lambda_index and is_lambda and iscode(node[lambda_index].attr):
  117. assert node[lambda_index].kind == "LOAD_LAMBDA"
  118. code = node[lambda_index].attr
  119. else:
  120. code = code_node.attr
  121. assert iscode(code)
  122. debug_opts = self.debug_opts["asm"] if self.debug_opts else None
  123. scanner_code = Code(code, self.scanner, self.currentclass, debug_opts)
  124. # add defaults values to parameter names
  125. argc = code.co_argcount
  126. kwonlyargcount = code.co_kwonlyargcount
  127. paramnames = list(scanner_code.co_varnames[:argc])
  128. kwargs = list(scanner_code.co_varnames[argc : argc + kwonlyargcount])
  129. paramnames.reverse()
  130. defparams.reverse()
  131. try:
  132. tree = self.build_ast(
  133. scanner_code._tokens,
  134. scanner_code._customize,
  135. scanner_code,
  136. is_lambda=is_lambda,
  137. noneInNames=("None" in code.co_names),
  138. )
  139. except (ParserError, ParserError2) as p:
  140. self.write(str(p))
  141. if not self.tolerate_errors:
  142. self.ERROR = p
  143. return
  144. i = len(paramnames) - len(defparams)
  145. # build parameters
  146. params = []
  147. if defparams:
  148. for i, defparam in enumerate(defparams):
  149. params.append(
  150. build_param(
  151. tree, paramnames[i], defparam, annotate_dict.get(paramnames[i])
  152. )
  153. )
  154. for param in paramnames[i + 1 :]:
  155. if param in annotate_dict:
  156. params.append("%s: %s" % (param, annotate_dict[param]))
  157. else:
  158. params.append(param)
  159. else:
  160. for param in paramnames:
  161. if param in annotate_dict:
  162. params.append("%s: %s" % (param, annotate_dict[param]))
  163. else:
  164. params.append(param)
  165. params.reverse() # back to correct order
  166. if code_has_star_arg(code):
  167. star_arg = code.co_varnames[argc + kwonlyargcount]
  168. if star_arg in annotate_dict:
  169. params.append("*%s: %s" % (star_arg, annotate_dict[star_arg]))
  170. else:
  171. params.append("*%s" % star_arg)
  172. argc += 1
  173. # dump parameter list (with default values)
  174. if is_lambda:
  175. self.write("lambda")
  176. if len(params):
  177. self.write(" ", ", ".join(params))
  178. elif kwonlyargcount > 0 and not (4 & code.co_flags):
  179. assert argc == 0
  180. self.write(" ")
  181. # If the last statement is None (which is the
  182. # same thing as "return None" in a lambda) and the
  183. # next to last statement is a "yield". Then we want to
  184. # drop the (return) None since that was just put there
  185. # to have something to after the yield finishes.
  186. # FIXME: this is a bit hoaky and not general
  187. if (
  188. len(tree) > 1
  189. and self.traverse(tree[-1]) == "None"
  190. and self.traverse(tree[-2]).strip().startswith("yield")
  191. ):
  192. del tree[-1]
  193. # Now pick out the expr part of the last statement
  194. tree_expr = tree[-1]
  195. while tree_expr.kind != "expr":
  196. tree_expr = tree_expr[0]
  197. tree[-1] = tree_expr
  198. pass
  199. else:
  200. self.write("(", ", ".join(params))
  201. # self.println(indent, '#flags:\t', int(code.co_flags))
  202. ends_in_comma = False
  203. if kwonlyargcount > 0:
  204. if not 4 & code.co_flags:
  205. if argc > 0:
  206. self.write(", *, ")
  207. else:
  208. self.write("*, ")
  209. pass
  210. else:
  211. if argc > 0:
  212. self.write(", ")
  213. # ann_dict = kw_dict = default_tup = None
  214. kw_dict = None
  215. fn_bits = node[-1].attr
  216. # Skip over:
  217. # MAKE_FUNCTION,
  218. # optional docstring
  219. # LOAD_CONST qualified name,
  220. # LOAD_CONST code object
  221. index = -5 if node[-2] == "docstring" else -4
  222. if fn_bits[-1]:
  223. index -= 1
  224. if fn_bits[-2]:
  225. # ann_dict = node[index]
  226. index -= 1
  227. if fn_bits[-3]:
  228. kw_dict = node[index]
  229. index -= 1
  230. if fn_bits[-4]:
  231. # default_tup = node[index]
  232. pass
  233. if kw_dict == "expr":
  234. kw_dict = kw_dict[0]
  235. kw_args = [None] * kwonlyargcount
  236. # FIXME: handle free_tup, ann_dict, and default_tup
  237. if kw_dict:
  238. assert kw_dict == "dict"
  239. const_list = kw_dict[0]
  240. if kw_dict[0] == "const_list":
  241. add_consts = const_list[1]
  242. assert add_consts == "add_consts"
  243. names = add_consts[-1].attr
  244. defaults = [v.pattr for v in add_consts[:-1]]
  245. else:
  246. defaults = [self.traverse(n, indent="") for n in kw_dict[:-2]]
  247. names = eval(self.traverse(kw_dict[-2]))
  248. assert len(defaults) == len(names)
  249. # FIXME: possibly handle line breaks
  250. for i, n in enumerate(names):
  251. idx = kwargs.index(n)
  252. if annotate_dict and n in annotate_dict:
  253. t = "%s: %s=%s" % (n, annotate_dict[n], defaults[i])
  254. else:
  255. t = "%s=%s" % (n, defaults[i])
  256. kw_args[idx] = t
  257. pass
  258. pass
  259. # handle others
  260. other_kw = [c is None for c in kw_args]
  261. for i, flag in enumerate(other_kw):
  262. if flag:
  263. n = kwargs[i]
  264. if n in annotate_dict:
  265. kw_args[i] = "%s: %s" % (n, annotate_dict[n])
  266. else:
  267. kw_args[i] = "%s" % n
  268. self.write(", ".join(kw_args))
  269. ends_in_comma = False
  270. pass
  271. else:
  272. if argc == 0:
  273. ends_in_comma = True
  274. if code_has_star_star_arg(code):
  275. if not ends_in_comma:
  276. self.write(", ")
  277. star_star_arg = code.co_varnames[argc + kwonlyargcount]
  278. if annotate_dict and star_star_arg in annotate_dict:
  279. self.write("**%s: %s" % (star_star_arg, annotate_dict[star_star_arg]))
  280. else:
  281. self.write("**%s" % star_star_arg)
  282. if is_lambda:
  283. self.write(": ")
  284. else:
  285. self.write(")")
  286. if annotate_dict and "return" in annotate_dict:
  287. self.write(" -> %s" % annotate_dict["return"])
  288. self.println(":")
  289. if node[-2] == "docstring" and not is_lambda:
  290. # docstring exists, dump it
  291. self.println(self.traverse(node[-2]))
  292. assert tree in ("stmts", "lambda_start")
  293. all_globals = find_all_globals(tree, set())
  294. globals, nonlocals = find_globals_and_nonlocals(
  295. tree, set(), set(), code, self.version
  296. )
  297. for g in sorted((all_globals & self.mod_globs) | globals):
  298. self.println(self.indent, "global ", g)
  299. for nl in sorted(nonlocals):
  300. self.println(self.indent, "nonlocal ", nl)
  301. self.mod_globs -= all_globals
  302. has_none = "None" in code.co_names
  303. rn = has_none and not find_none(tree)
  304. self.gen_source(
  305. tree,
  306. code.co_name,
  307. scanner_code._customize,
  308. is_lambda=is_lambda,
  309. returnNone=rn,
  310. debug_opts=self.debug_opts,
  311. )
  312. # In obscure cases, a function may be a generator but the "yield"
  313. # was optimized away. Here, we need to put in unreachable code to
  314. # add in "yield" just so that the compiler will mark
  315. # the GENERATOR bit of the function. See for example
  316. # Python 3.x's test_connection.py and test_contexlib_async test programs.
  317. if not is_lambda and code.co_flags & (CO_GENERATOR | CO_ASYNC_GENERATOR):
  318. need_bogus_yield = True
  319. for token in scanner_code._tokens:
  320. if token == "YIELD_VALUE":
  321. need_bogus_yield = False
  322. break
  323. pass
  324. if need_bogus_yield:
  325. self.template_engine(("%|if False:\n%+%|yield None%-",), node)
  326. scanner_code._tokens = None # save memory
  327. scanner_code._customize = None # save memory