Cython 3.3.0a1__cp315-cp315-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. Cython/Build/BuildExecutable.py +156 -0
  2. Cython/Build/Cache.py +199 -0
  3. Cython/Build/Cythonize.py +349 -0
  4. Cython/Build/Dependencies.py +1276 -0
  5. Cython/Build/Distutils.py +1 -0
  6. Cython/Build/Inline.py +467 -0
  7. Cython/Build/IpythonMagic.py +559 -0
  8. Cython/Build/SharedModule.py +84 -0
  9. Cython/Build/Tests/TestCyCache.py +195 -0
  10. Cython/Build/Tests/TestCythonizeArgsParser.py +480 -0
  11. Cython/Build/Tests/TestDependencies.py +133 -0
  12. Cython/Build/Tests/TestInline.py +177 -0
  13. Cython/Build/Tests/TestIpythonMagic.py +303 -0
  14. Cython/Build/Tests/TestRecythonize.py +212 -0
  15. Cython/Build/Tests/TestStripLiterals.py +155 -0
  16. Cython/Build/Tests/__init__.py +1 -0
  17. Cython/Build/__init__.py +11 -0
  18. Cython/CodeWriter.py +815 -0
  19. Cython/Compiler/AnalysedTreeTransforms.py +97 -0
  20. Cython/Compiler/Annotate.py +328 -0
  21. Cython/Compiler/AutoDocTransforms.py +320 -0
  22. Cython/Compiler/Buffer.py +680 -0
  23. Cython/Compiler/Builtin.py +997 -0
  24. Cython/Compiler/CmdLine.py +263 -0
  25. Cython/Compiler/Code.cp315-win_amd64.pyd +0 -0
  26. Cython/Compiler/Code.pxd +152 -0
  27. Cython/Compiler/Code.py +3907 -0
  28. Cython/Compiler/CodeGeneration.py +33 -0
  29. Cython/Compiler/CythonScope.py +194 -0
  30. Cython/Compiler/Dataclass.py +890 -0
  31. Cython/Compiler/DebugFlags.py +24 -0
  32. Cython/Compiler/Errors.py +310 -0
  33. Cython/Compiler/ExprNodes.py +15983 -0
  34. Cython/Compiler/FlowControl.cp315-win_amd64.pyd +0 -0
  35. Cython/Compiler/FlowControl.pxd +99 -0
  36. Cython/Compiler/FlowControl.py +1571 -0
  37. Cython/Compiler/FusedNode.cp315-win_amd64.pyd +0 -0
  38. Cython/Compiler/FusedNode.py +976 -0
  39. Cython/Compiler/Future.py +16 -0
  40. Cython/Compiler/Interpreter.py +57 -0
  41. Cython/Compiler/Lexicon.py +422 -0
  42. Cython/Compiler/LineTable.cp315-win_amd64.pyd +0 -0
  43. Cython/Compiler/LineTable.py +114 -0
  44. Cython/Compiler/Main.py +856 -0
  45. Cython/Compiler/MatchCaseNodes.py +2197 -0
  46. Cython/Compiler/MemoryView.py +930 -0
  47. Cython/Compiler/ModuleNode.py +4517 -0
  48. Cython/Compiler/Naming.py +367 -0
  49. Cython/Compiler/Nodes.py +10941 -0
  50. Cython/Compiler/Optimize.py +5455 -0
  51. Cython/Compiler/Options.py +838 -0
  52. Cython/Compiler/ParseTreeTransforms.pxd +79 -0
  53. Cython/Compiler/ParseTreeTransforms.py +4744 -0
  54. Cython/Compiler/Parsing.cp315-win_amd64.pyd +0 -0
  55. Cython/Compiler/Parsing.pxd +9 -0
  56. Cython/Compiler/Parsing.py +4792 -0
  57. Cython/Compiler/Pipeline.py +439 -0
  58. Cython/Compiler/PyrexTypes.py +6111 -0
  59. Cython/Compiler/Pythran.py +232 -0
  60. Cython/Compiler/Scanning.cp315-win_amd64.pyd +0 -0
  61. Cython/Compiler/Scanning.pxd +70 -0
  62. Cython/Compiler/Scanning.py +720 -0
  63. Cython/Compiler/StringEncoding.py +297 -0
  64. Cython/Compiler/Symtab.py +3092 -0
  65. Cython/Compiler/Tests/TestBuffer.py +105 -0
  66. Cython/Compiler/Tests/TestBuiltin.py +117 -0
  67. Cython/Compiler/Tests/TestCmdLine.py +587 -0
  68. Cython/Compiler/Tests/TestCode.py +145 -0
  69. Cython/Compiler/Tests/TestFlowControl.py +65 -0
  70. Cython/Compiler/Tests/TestGrammar.py +202 -0
  71. Cython/Compiler/Tests/TestMemView.py +71 -0
  72. Cython/Compiler/Tests/TestParseTreeTransforms.py +285 -0
  73. Cython/Compiler/Tests/TestScanning.py +132 -0
  74. Cython/Compiler/Tests/TestSignatureMatching.py +73 -0
  75. Cython/Compiler/Tests/TestStringEncoding.py +20 -0
  76. Cython/Compiler/Tests/TestTreeFragment.py +63 -0
  77. Cython/Compiler/Tests/TestTreePath.py +103 -0
  78. Cython/Compiler/Tests/TestTypes.py +118 -0
  79. Cython/Compiler/Tests/TestUtilityLoad.py +112 -0
  80. Cython/Compiler/Tests/TestVisitor.py +61 -0
  81. Cython/Compiler/Tests/Utils.py +36 -0
  82. Cython/Compiler/Tests/__init__.py +1 -0
  83. Cython/Compiler/TreeFragment.py +278 -0
  84. Cython/Compiler/TreePath.py +303 -0
  85. Cython/Compiler/TypeInference.py +611 -0
  86. Cython/Compiler/TypeSlots.py +1329 -0
  87. Cython/Compiler/UFuncs.py +311 -0
  88. Cython/Compiler/UtilNodes.py +413 -0
  89. Cython/Compiler/UtilityCode.py +348 -0
  90. Cython/Compiler/Version.py +8 -0
  91. Cython/Compiler/Visitor.cp315-win_amd64.pyd +0 -0
  92. Cython/Compiler/Visitor.pxd +53 -0
  93. Cython/Compiler/Visitor.py +864 -0
  94. Cython/Compiler/__init__.py +1 -0
  95. Cython/Coverage.py +448 -0
  96. Cython/Debugger/Cygdb.py +177 -0
  97. Cython/Debugger/DebugWriter.py +82 -0
  98. Cython/Debugger/Tests/TestLibCython.py +280 -0
  99. Cython/Debugger/Tests/__init__.py +1 -0
  100. Cython/Debugger/Tests/cfuncs.c +8 -0
  101. Cython/Debugger/Tests/codefile +49 -0
  102. Cython/Debugger/Tests/test_libcython_in_gdb.py +580 -0
  103. Cython/Debugger/Tests/test_libpython_in_gdb.py +90 -0
  104. Cython/Debugger/__init__.py +1 -0
  105. Cython/Debugger/libcython.py +1548 -0
  106. Cython/Debugger/libpython.py +2821 -0
  107. Cython/Debugging.py +20 -0
  108. Cython/Distutils/__init__.py +2 -0
  109. Cython/Distutils/build_ext.py +139 -0
  110. Cython/Distutils/extension.py +96 -0
  111. Cython/Distutils/old_build_ext.py +351 -0
  112. Cython/Includes/cpython/__init__.pxd +173 -0
  113. Cython/Includes/cpython/array.pxd +152 -0
  114. Cython/Includes/cpython/bool.pxd +37 -0
  115. Cython/Includes/cpython/buffer.pxd +112 -0
  116. Cython/Includes/cpython/bytearray.pxd +33 -0
  117. Cython/Includes/cpython/bytes.pxd +200 -0
  118. Cython/Includes/cpython/cellobject.pxd +35 -0
  119. Cython/Includes/cpython/ceval.pxd +8 -0
  120. Cython/Includes/cpython/codecs.pxd +121 -0
  121. Cython/Includes/cpython/complex.pxd +60 -0
  122. Cython/Includes/cpython/contextvars.pxd +145 -0
  123. Cython/Includes/cpython/conversion.pxd +36 -0
  124. Cython/Includes/cpython/datetime.pxd +395 -0
  125. Cython/Includes/cpython/descr.pxd +26 -0
  126. Cython/Includes/cpython/dict.pxd +268 -0
  127. Cython/Includes/cpython/exc.pxd +263 -0
  128. Cython/Includes/cpython/fileobject.pxd +57 -0
  129. Cython/Includes/cpython/float.pxd +56 -0
  130. Cython/Includes/cpython/frozendict.pxd +37 -0
  131. Cython/Includes/cpython/function.pxd +65 -0
  132. Cython/Includes/cpython/genobject.pxd +25 -0
  133. Cython/Includes/cpython/getargs.pxd +12 -0
  134. Cython/Includes/cpython/instance.pxd +25 -0
  135. Cython/Includes/cpython/iterator.pxd +36 -0
  136. Cython/Includes/cpython/iterobject.pxd +24 -0
  137. Cython/Includes/cpython/list.pxd +144 -0
  138. Cython/Includes/cpython/long.pxd +180 -0
  139. Cython/Includes/cpython/longintrepr.pxd +14 -0
  140. Cython/Includes/cpython/mapping.pxd +63 -0
  141. Cython/Includes/cpython/marshal.pxd +66 -0
  142. Cython/Includes/cpython/mem.pxd +120 -0
  143. Cython/Includes/cpython/memoryview.pxd +50 -0
  144. Cython/Includes/cpython/method.pxd +49 -0
  145. Cython/Includes/cpython/module.pxd +208 -0
  146. Cython/Includes/cpython/number.pxd +258 -0
  147. Cython/Includes/cpython/object.pxd +430 -0
  148. Cython/Includes/cpython/pycapsule.pxd +143 -0
  149. Cython/Includes/cpython/pylifecycle.pxd +68 -0
  150. Cython/Includes/cpython/pyport.pxd +8 -0
  151. Cython/Includes/cpython/pystate.pxd +95 -0
  152. Cython/Includes/cpython/pythread.pxd +53 -0
  153. Cython/Includes/cpython/ref.pxd +141 -0
  154. Cython/Includes/cpython/sentinel.pxd +17 -0
  155. Cython/Includes/cpython/sequence.pxd +134 -0
  156. Cython/Includes/cpython/set.pxd +119 -0
  157. Cython/Includes/cpython/slice.pxd +70 -0
  158. Cython/Includes/cpython/time.pxd +129 -0
  159. Cython/Includes/cpython/tuple.pxd +72 -0
  160. Cython/Includes/cpython/type.pxd +146 -0
  161. Cython/Includes/cpython/unicode.pxd +639 -0
  162. Cython/Includes/cpython/version.pxd +32 -0
  163. Cython/Includes/cpython/weakref.pxd +78 -0
  164. Cython/Includes/libc/__init__.pxd +1 -0
  165. Cython/Includes/libc/complex.pxd +35 -0
  166. Cython/Includes/libc/errno.pxd +127 -0
  167. Cython/Includes/libc/float.pxd +43 -0
  168. Cython/Includes/libc/limits.pxd +28 -0
  169. Cython/Includes/libc/locale.pxd +46 -0
  170. Cython/Includes/libc/math.pxd +209 -0
  171. Cython/Includes/libc/setjmp.pxd +10 -0
  172. Cython/Includes/libc/signal.pxd +64 -0
  173. Cython/Includes/libc/stddef.pxd +9 -0
  174. Cython/Includes/libc/stdint.pxd +105 -0
  175. Cython/Includes/libc/stdio.pxd +80 -0
  176. Cython/Includes/libc/stdlib.pxd +72 -0
  177. Cython/Includes/libc/string.pxd +50 -0
  178. Cython/Includes/libc/threads.pxd +234 -0
  179. Cython/Includes/libc/time.pxd +52 -0
  180. Cython/Includes/libcpp/__init__.pxd +4 -0
  181. Cython/Includes/libcpp/algorithm.pxd +320 -0
  182. Cython/Includes/libcpp/any.pxd +16 -0
  183. Cython/Includes/libcpp/atomic.pxd +59 -0
  184. Cython/Includes/libcpp/barrier.pxd +22 -0
  185. Cython/Includes/libcpp/bit.pxd +29 -0
  186. Cython/Includes/libcpp/cast.pxd +12 -0
  187. Cython/Includes/libcpp/cmath.pxd +518 -0
  188. Cython/Includes/libcpp/complex.pxd +106 -0
  189. Cython/Includes/libcpp/condition_variable.pxd +322 -0
  190. Cython/Includes/libcpp/deque.pxd +165 -0
  191. Cython/Includes/libcpp/exception.pxd +216 -0
  192. Cython/Includes/libcpp/execution.pxd +15 -0
  193. Cython/Includes/libcpp/forward_list.pxd +63 -0
  194. Cython/Includes/libcpp/functional.pxd +26 -0
  195. Cython/Includes/libcpp/future.pxd +103 -0
  196. Cython/Includes/libcpp/iterator.pxd +34 -0
  197. Cython/Includes/libcpp/latch.pxd +17 -0
  198. Cython/Includes/libcpp/limits.pxd +61 -0
  199. Cython/Includes/libcpp/list.pxd +117 -0
  200. Cython/Includes/libcpp/map.pxd +252 -0
  201. Cython/Includes/libcpp/memory.pxd +115 -0
  202. Cython/Includes/libcpp/mutex.pxd +387 -0
  203. Cython/Includes/libcpp/numbers.pxd +15 -0
  204. Cython/Includes/libcpp/numeric.pxd +131 -0
  205. Cython/Includes/libcpp/optional.pxd +34 -0
  206. Cython/Includes/libcpp/pair.pxd +1 -0
  207. Cython/Includes/libcpp/queue.pxd +25 -0
  208. Cython/Includes/libcpp/random.pxd +166 -0
  209. Cython/Includes/libcpp/semaphore.pxd +43 -0
  210. Cython/Includes/libcpp/set.pxd +228 -0
  211. Cython/Includes/libcpp/shared_mutex.pxd +96 -0
  212. Cython/Includes/libcpp/span.pxd +87 -0
  213. Cython/Includes/libcpp/stack.pxd +11 -0
  214. Cython/Includes/libcpp/stop_token.pxd +117 -0
  215. Cython/Includes/libcpp/string.pxd +355 -0
  216. Cython/Includes/libcpp/string_view.pxd +183 -0
  217. Cython/Includes/libcpp/typeindex.pxd +15 -0
  218. Cython/Includes/libcpp/typeinfo.pxd +10 -0
  219. Cython/Includes/libcpp/unordered_map.pxd +193 -0
  220. Cython/Includes/libcpp/unordered_set.pxd +152 -0
  221. Cython/Includes/libcpp/utility.pxd +30 -0
  222. Cython/Includes/libcpp/vector.pxd +186 -0
  223. Cython/Includes/numpy/math.pxd +150 -0
  224. Cython/Includes/openmp.pxd +50 -0
  225. Cython/Includes/posix/__init__.pxd +1 -0
  226. Cython/Includes/posix/dlfcn.pxd +14 -0
  227. Cython/Includes/posix/fcntl.pxd +86 -0
  228. Cython/Includes/posix/ioctl.pxd +4 -0
  229. Cython/Includes/posix/mman.pxd +101 -0
  230. Cython/Includes/posix/resource.pxd +57 -0
  231. Cython/Includes/posix/select.pxd +21 -0
  232. Cython/Includes/posix/signal.pxd +73 -0
  233. Cython/Includes/posix/stat.pxd +98 -0
  234. Cython/Includes/posix/stdio.pxd +37 -0
  235. Cython/Includes/posix/stdlib.pxd +29 -0
  236. Cython/Includes/posix/strings.pxd +9 -0
  237. Cython/Includes/posix/time.pxd +71 -0
  238. Cython/Includes/posix/types.pxd +30 -0
  239. Cython/Includes/posix/uio.pxd +26 -0
  240. Cython/Includes/posix/unistd.pxd +271 -0
  241. Cython/Includes/posix/wait.pxd +38 -0
  242. Cython/LZSS.py +170 -0
  243. Cython/Plex/Actions.cp315-win_amd64.pyd +0 -0
  244. Cython/Plex/Actions.pxd +24 -0
  245. Cython/Plex/Actions.py +119 -0
  246. Cython/Plex/DFA.cp315-win_amd64.pyd +0 -0
  247. Cython/Plex/DFA.pxd +14 -0
  248. Cython/Plex/DFA.py +164 -0
  249. Cython/Plex/Errors.py +48 -0
  250. Cython/Plex/Lexicons.py +178 -0
  251. Cython/Plex/Machines.cp315-win_amd64.pyd +0 -0
  252. Cython/Plex/Machines.pxd +36 -0
  253. Cython/Plex/Machines.py +238 -0
  254. Cython/Plex/Regexps.py +535 -0
  255. Cython/Plex/Scanners.cp315-win_amd64.pyd +0 -0
  256. Cython/Plex/Scanners.pxd +45 -0
  257. Cython/Plex/Scanners.py +328 -0
  258. Cython/Plex/Transitions.cp315-win_amd64.pyd +0 -0
  259. Cython/Plex/Transitions.pxd +14 -0
  260. Cython/Plex/Transitions.py +239 -0
  261. Cython/Plex/__init__.py +34 -0
  262. Cython/Runtime/__init__.py +1 -0
  263. Cython/Runtime/refnanny.cp315-win_amd64.pyd +0 -0
  264. Cython/Runtime/refnanny.pyx +237 -0
  265. Cython/Shadow.py +1167 -0
  266. Cython/StringIOTree.cp315-win_amd64.pyd +0 -0
  267. Cython/StringIOTree.py +169 -0
  268. Cython/Tempita/__init__.py +4 -0
  269. Cython/Tempita/_looper.py +154 -0
  270. Cython/Tempita/_tempita.cp315-win_amd64.pyd +0 -0
  271. Cython/Tempita/_tempita.py +1087 -0
  272. Cython/TestUtils.py +464 -0
  273. Cython/Tests/TestCodeWriter.py +128 -0
  274. Cython/Tests/TestCythonUtils.py +202 -0
  275. Cython/Tests/TestJediTyper.py +223 -0
  276. Cython/Tests/TestShadow.py +110 -0
  277. Cython/Tests/TestStringIOTree.py +68 -0
  278. Cython/Tests/TestTestUtils.py +89 -0
  279. Cython/Tests/__init__.py +1 -0
  280. Cython/Tests/xmlrunner.py +390 -0
  281. Cython/Utility/AsyncGen.c +1073 -0
  282. Cython/Utility/Buffer.c +866 -0
  283. Cython/Utility/BufferFormatFromTypeInfo.pxd +2 -0
  284. Cython/Utility/Builtins.c +933 -0
  285. Cython/Utility/CConvert.pyx +149 -0
  286. Cython/Utility/CMath.c +104 -0
  287. Cython/Utility/CommonStructures.c +244 -0
  288. Cython/Utility/Complex.c +378 -0
  289. Cython/Utility/Coroutine.c +2337 -0
  290. Cython/Utility/CpdefEnums.pyx +107 -0
  291. Cython/Utility/CppConvert.pyx +282 -0
  292. Cython/Utility/CppSupport.cpp +151 -0
  293. Cython/Utility/CythonFunction.c +2072 -0
  294. Cython/Utility/Dataclasses.c +101 -0
  295. Cython/Utility/Embed.c +129 -0
  296. Cython/Utility/Exceptions.c +1038 -0
  297. Cython/Utility/ExtensionTypes.c +1158 -0
  298. Cython/Utility/FunctionArguments.c +1045 -0
  299. Cython/Utility/FusedFunction.pyx +44 -0
  300. Cython/Utility/ImportExport.c +930 -0
  301. Cython/Utility/MatchCase.c +979 -0
  302. Cython/Utility/MatchCase_Cy.pyx +12 -0
  303. Cython/Utility/MemoryView.pxd +108 -0
  304. Cython/Utility/MemoryView.pyx +1499 -0
  305. Cython/Utility/MemoryView_C.c +1056 -0
  306. Cython/Utility/ModuleSetupCode.c +3352 -0
  307. Cython/Utility/NumpyImportArray.c +46 -0
  308. Cython/Utility/ObjectHandling.c +3372 -0
  309. Cython/Utility/Optimize.c +2563 -0
  310. Cython/Utility/Overflow.c +378 -0
  311. Cython/Utility/Profile.c +736 -0
  312. Cython/Utility/StringTools.c +1414 -0
  313. Cython/Utility/Synchronization.c +438 -0
  314. Cython/Utility/TString.c +369 -0
  315. Cython/Utility/TestCyUtilityLoader.pyx +8 -0
  316. Cython/Utility/TestCythonScope.pyx +75 -0
  317. Cython/Utility/TestUtilityLoader.c +12 -0
  318. Cython/Utility/TypeConversion.c +1556 -0
  319. Cython/Utility/UFuncs.pyx +50 -0
  320. Cython/Utility/UFuncs_C.c +89 -0
  321. Cython/Utility/__init__.py +28 -0
  322. Cython/Utility/arrayarray.h +172 -0
  323. Cython/Utils.cp315-win_amd64.pyd +0 -0
  324. Cython/Utils.py +677 -0
  325. Cython/__init__.py +12 -0
  326. Cython/py.typed +0 -0
  327. cython-3.3.0a1.dist-info/METADATA +394 -0
  328. cython-3.3.0a1.dist-info/RECORD +335 -0
  329. cython-3.3.0a1.dist-info/WHEEL +5 -0
  330. cython-3.3.0a1.dist-info/entry_points.txt +4 -0
  331. cython-3.3.0a1.dist-info/top_level.txt +3 -0
  332. cython.py +29 -0
  333. pyximport/__init__.py +4 -0
  334. pyximport/pyxbuild.py +160 -0
  335. pyximport/pyximport.py +482 -0
@@ -0,0 +1,4744 @@
1
+ import cython
2
+ cython.declare(PyrexTypes=object, Naming=object, ExprNodes=object, Nodes=object,
3
+ Options=object, UtilNodes=object, LetNode=object,
4
+ LetRefNode=object, TreeFragment=object, EncodedString=object,
5
+ error=object, warning=object, copy=object, hashlib=object,
6
+ itemgetter=object)
7
+
8
+ import copy
9
+ import hashlib
10
+ from operator import itemgetter
11
+
12
+ from . import Code
13
+ from . import PyrexTypes
14
+ from . import Naming
15
+ from . import ExprNodes
16
+ from . import Nodes
17
+ from . import Options
18
+ from . import Builtin
19
+ from . import Errors
20
+
21
+ from .Visitor import VisitorTransform, TreeVisitor
22
+ from .Visitor import CythonTransform, EnvTransform, ScopeTrackingTransform
23
+ from .UtilNodes import LetNode, LetRefNode
24
+ from .TreeFragment import TreeFragment
25
+ from .StringEncoding import EncodedString
26
+ from .Errors import error, warning, CompileError, InternalError
27
+
28
+
29
+ class SkipDeclarations:
30
+ """
31
+ Variable and function declarations can often have a deep tree structure,
32
+ and yet most transformations don't need to descend to this depth.
33
+
34
+ Declaration nodes are removed after AnalyseDeclarationsTransform, so there
35
+ is no need to use this for transformations after that point.
36
+ """
37
+ def visit_CTypeDefNode(self, node):
38
+ return node
39
+
40
+ def visit_CVarDefNode(self, node):
41
+ return node
42
+
43
+ def visit_CDeclaratorNode(self, node):
44
+ return node
45
+
46
+ def visit_CBaseTypeNode(self, node):
47
+ return node
48
+
49
+ def visit_CEnumDefNode(self, node):
50
+ return node
51
+
52
+ def visit_CStructOrUnionDefNode(self, node):
53
+ return node
54
+
55
+ def visit_CppClassNode(self, node):
56
+ if node.visibility != "extern":
57
+ # Need to traverse methods.
58
+ self.visitchildren(node)
59
+ return node
60
+
61
+
62
+ class NormalizeTree(CythonTransform):
63
+ """
64
+ This transform fixes up a few things after parsing
65
+ in order to make the parse tree more suitable for
66
+ transforms.
67
+
68
+ a) After parsing, blocks with only one statement will
69
+ be represented by that statement, not by a StatListNode.
70
+ When doing transforms this is annoying and inconsistent,
71
+ as one cannot in general remove a statement in a consistent
72
+ way and so on. This transform wraps any single statements
73
+ in a StatListNode containing a single statement.
74
+
75
+ b) The PassStatNode is a noop and serves no purpose beyond
76
+ plugging such one-statement blocks; i.e., once parsed a
77
+ ` "pass" can just as well be represented using an empty
78
+ StatListNode. This means less special cases to worry about
79
+ in subsequent transforms (one always checks to see if a
80
+ StatListNode has no children to see if the block is empty).
81
+ """
82
+
83
+ def __init__(self, context):
84
+ super().__init__(context)
85
+ self.is_in_statlist = False
86
+ self.is_in_expr = False
87
+
88
+ def visit_ModuleNode(self, node):
89
+ self.visitchildren(node)
90
+ if not isinstance(node.body, Nodes.StatListNode):
91
+ # This can happen when the body only consists of a single (unused) declaration and no statements.
92
+ node.body = Nodes.StatListNode(pos=node.pos, stats=[node.body])
93
+ return node
94
+
95
+ def visit_ExprNode(self, node):
96
+ stacktmp = self.is_in_expr
97
+ self.is_in_expr = True
98
+ self.visitchildren(node)
99
+ self.is_in_expr = stacktmp
100
+ return node
101
+
102
+ def visit_StatNode(self, node, is_listcontainer=False):
103
+ stacktmp = self.is_in_statlist
104
+ self.is_in_statlist = is_listcontainer
105
+ self.visitchildren(node)
106
+ self.is_in_statlist = stacktmp
107
+ if not self.is_in_statlist and not self.is_in_expr:
108
+ return Nodes.StatListNode(pos=node.pos, stats=[node])
109
+ else:
110
+ return node
111
+
112
+ def visit_StatListNode(self, node):
113
+ self.is_in_statlist = True
114
+ self.visitchildren(node)
115
+ self.is_in_statlist = False
116
+ return node
117
+
118
+ def visit_ParallelAssignmentNode(self, node):
119
+ return self.visit_StatNode(node, True)
120
+
121
+ def visit_CEnumDefNode(self, node):
122
+ return self.visit_StatNode(node, True)
123
+
124
+ def visit_CStructOrUnionDefNode(self, node):
125
+ return self.visit_StatNode(node, True)
126
+
127
+ def visit_ExprStatNode(self, node):
128
+ """Eliminate useless string literals"""
129
+ if node.expr.is_string_literal:
130
+ return Nodes.PassStatNode(node.expr.pos)
131
+ else:
132
+ return self.visit_StatNode(node)
133
+
134
+ def visit_CDeclaratorNode(self, node):
135
+ return node
136
+
137
+
138
+ class PostParseError(CompileError): pass
139
+
140
+ # error strings checked by unit tests, so define them
141
+ ERR_CDEF_INCLASS = 'Cannot assign default value to fields in cdef classes, structs or unions'
142
+ ERR_BUF_DEFAULTS = 'Invalid buffer defaults specification (see docs)'
143
+ ERR_INVALID_SPECIALATTR_TYPE = 'Special attributes must not have a type declared'
144
+ class PostParse(ScopeTrackingTransform):
145
+ """
146
+ Basic interpretation of the parse tree, as well as validity
147
+ checking that can be done on a very basic level on the parse
148
+ tree (while still not being a problem with the basic syntax,
149
+ as such).
150
+
151
+ Specifically:
152
+ - Default values to cdef assignments are turned into single
153
+ assignments following the declaration (everywhere but in class
154
+ bodies, where they raise a compile error)
155
+
156
+ - Interpret some node structures into Python runtime values.
157
+ Some nodes take compile-time arguments (currently:
158
+ TemplatedTypeNode[args] and __cythonbufferdefaults__ = {args}),
159
+ which should be interpreted. This happens in a general way
160
+ and other steps should be taken to ensure validity.
161
+
162
+ Type arguments cannot be interpreted in this way.
163
+
164
+ - For __cythonbufferdefaults__ the arguments are checked for
165
+ validity.
166
+
167
+ TemplatedTypeNode has its directives interpreted:
168
+ Any first positional argument goes into the "dtype" attribute,
169
+ any "ndim" keyword argument goes into the "ndim" attribute and
170
+ so on. Also it is checked that the directive combination is valid.
171
+ - __cythonbufferdefaults__ attributes are parsed and put into the
172
+ type information.
173
+
174
+ Note: Currently Parsing.py does a lot of interpretation and
175
+ reorganization that can be refactored into this transform
176
+ if a more pure Abstract Syntax Tree is wanted.
177
+
178
+ - Some invalid uses of := assignment expressions are detected
179
+ """
180
+ def __init__(self, context):
181
+ super().__init__(context)
182
+ self.specialattribute_handlers = {
183
+ '__cythonbufferdefaults__' : self.handle_bufferdefaults
184
+ }
185
+ self.in_pattern_node = False
186
+
187
+ def visit_LambdaNode(self, node):
188
+ # unpack a lambda expression into the corresponding DefNode
189
+ collector = YieldNodeCollector()
190
+ collector.visitchildren(node.result_expr)
191
+ if collector.has_yield or collector.has_await or isinstance(node.result_expr, ExprNodes.YieldExprNode):
192
+ body = Nodes.ExprStatNode(
193
+ node.result_expr.pos, expr=node.result_expr)
194
+ else:
195
+ body = Nodes.ReturnStatNode(
196
+ node.result_expr.pos, value=node.result_expr)
197
+ node.def_node = Nodes.DefNode(
198
+ node.pos, name=node.name,
199
+ args=node.args, star_arg=node.star_arg,
200
+ starstar_arg=node.starstar_arg,
201
+ body=body, doc=None)
202
+ self.visitchildren(node)
203
+ return node
204
+
205
+ def visit_GeneratorExpressionNode(self, node):
206
+ # unpack a generator expression into the corresponding DefNode
207
+ collector = YieldNodeCollector()
208
+ collector.visitchildren(node.loop, attrs=None, exclude=["iterator"])
209
+ node.def_node = Nodes.DefNode(
210
+ node.pos, name=node.name, doc=None,
211
+ args=[], star_arg=None, starstar_arg=None,
212
+ body=node.loop, is_async_def=collector.has_await,
213
+ is_generator_expression=True)
214
+ _AssignmentExpressionChecker.do_checks(node.loop, scope_is_class=self.scope_type in ("pyclass", "cclass"))
215
+ self.visitchildren(node)
216
+ return node
217
+
218
+ def visit_ComprehensionNode(self, node):
219
+ # enforce local scope also in Py2 for async generators (seriously, that's a Py3.6 feature...)
220
+ if not node.has_local_scope:
221
+ collector = YieldNodeCollector()
222
+ collector.visitchildren(node.loop)
223
+ if collector.has_await:
224
+ node.has_local_scope = True
225
+ _AssignmentExpressionChecker.do_checks(node.loop, scope_is_class=self.scope_type in ("pyclass", "cclass"))
226
+ self.visitchildren(node)
227
+ return node
228
+
229
+ # cdef variables
230
+ def handle_bufferdefaults(self, decl):
231
+ if not isinstance(decl.default, ExprNodes.DictNode):
232
+ raise PostParseError(decl.pos, ERR_BUF_DEFAULTS)
233
+ self.scope_node.buffer_defaults_node = decl.default
234
+ self.scope_node.buffer_defaults_pos = decl.pos
235
+
236
+ def visit_CVarDefNode(self, node):
237
+ # This assumes only plain names and pointers are assignable on
238
+ # declaration. Also, it makes use of the fact that a cdef decl
239
+ # must appear before the first use, so we don't have to deal with
240
+ # "i = 3; cdef int i = i" and can simply move the nodes around.
241
+ try:
242
+ self.visitchildren(node)
243
+ stats = [node]
244
+ newdecls = []
245
+ for decl in node.declarators:
246
+ declbase = decl
247
+ while isinstance(declbase, (Nodes.CPtrDeclaratorNode, Nodes.CQualifierDeclaratorNode)):
248
+ declbase = declbase.base
249
+ if isinstance(declbase, Nodes.CNameDeclaratorNode):
250
+ if declbase.default is not None:
251
+ if self.scope_type in ('cclass', 'pyclass', 'struct'):
252
+ if isinstance(self.scope_node, Nodes.CClassDefNode):
253
+ handler = self.specialattribute_handlers.get(decl.name)
254
+ if handler:
255
+ if decl is not declbase:
256
+ raise PostParseError(decl.pos, ERR_INVALID_SPECIALATTR_TYPE)
257
+ handler(decl)
258
+ continue # Remove declaration
259
+ raise PostParseError(decl.pos, ERR_CDEF_INCLASS)
260
+ first_assignment = self.scope_type != 'module'
261
+ stats.append(Nodes.SingleAssignmentNode(node.pos,
262
+ lhs=ExprNodes.NameNode(node.pos, name=declbase.name),
263
+ rhs=declbase.default, first=first_assignment,
264
+ from_pxd_cvardef=node.in_pxd))
265
+ declbase.default = None
266
+ newdecls.append(decl)
267
+ node.declarators = newdecls
268
+ return stats
269
+ except PostParseError as e:
270
+ # An error in a cdef clause is ok, simply remove the declaration
271
+ # and try to move on to report more errors
272
+ self.context.nonfatal_error(e)
273
+ return None
274
+
275
+ # Split parallel assignments (a,b = b,a) into separate partial
276
+ # assignments that are executed rhs-first using temps. This
277
+ # restructuring must be applied before type analysis so that known
278
+ # types on rhs and lhs can be matched directly. It is required in
279
+ # the case that the types cannot be coerced to a Python type in
280
+ # order to assign from a tuple.
281
+
282
+ def visit_SingleAssignmentNode(self, node):
283
+ self.visitchildren(node)
284
+ return self._visit_assignment_node(node, [node.lhs, node.rhs])
285
+
286
+ def visit_CascadedAssignmentNode(self, node):
287
+ self.visitchildren(node)
288
+ return self._visit_assignment_node(node, node.lhs_list + [node.rhs])
289
+
290
+ def _visit_assignment_node(self, node, expr_list):
291
+ """Flatten parallel assignments into separate single
292
+ assignments or cascaded assignments.
293
+ """
294
+ if sum([ 1 for expr in expr_list
295
+ if expr.is_sequence_constructor or expr.is_string_literal ]) < 2:
296
+ # no parallel assignments => nothing to do
297
+ return node
298
+
299
+ expr_list_list = []
300
+ flatten_parallel_assignments(expr_list, expr_list_list)
301
+ temp_refs = []
302
+ eliminate_rhs_duplicates(expr_list_list, temp_refs)
303
+
304
+ nodes = []
305
+ for expr_list in expr_list_list:
306
+ lhs_list = expr_list[:-1]
307
+ rhs = expr_list[-1]
308
+ if len(lhs_list) == 1:
309
+ node = Nodes.SingleAssignmentNode(rhs.pos,
310
+ lhs = lhs_list[0], rhs = rhs)
311
+ else:
312
+ node = Nodes.CascadedAssignmentNode(rhs.pos,
313
+ lhs_list = lhs_list, rhs = rhs)
314
+ nodes.append(node)
315
+
316
+ if len(nodes) == 1:
317
+ assign_node = nodes[0]
318
+ else:
319
+ assign_node = Nodes.ParallelAssignmentNode(nodes[0].pos, stats = nodes)
320
+
321
+ if temp_refs:
322
+ duplicates_and_temps = [ (temp.expression, temp)
323
+ for temp in temp_refs ]
324
+ sort_common_subsequences(duplicates_and_temps)
325
+ for _, temp_ref in duplicates_and_temps[::-1]:
326
+ assign_node = LetNode(temp_ref, assign_node)
327
+
328
+ return assign_node
329
+
330
+ def _flatten_sequence(self, seq, result):
331
+ for arg in seq.args:
332
+ if arg.is_sequence_constructor:
333
+ self._flatten_sequence(arg, result)
334
+ else:
335
+ result.append(arg)
336
+ return result
337
+
338
+ def visit_DelStatNode(self, node):
339
+ self.visitchildren(node)
340
+ node.args = self._flatten_sequence(node, [])
341
+ return node
342
+
343
+ def visit_ExceptClauseNode(self, node):
344
+ if node.is_except_as:
345
+ # except-as must delete NameNode target at the end
346
+ del_target = Nodes.DelStatNode(
347
+ node.pos,
348
+ args=[ExprNodes.NameNode(
349
+ node.target.pos, name=node.target.name)],
350
+ ignore_nonexisting=True)
351
+ node.body = Nodes.StatListNode(
352
+ node.pos,
353
+ stats=[Nodes.TryFinallyStatNode(
354
+ node.pos,
355
+ body=node.body,
356
+ finally_clause=Nodes.StatListNode(
357
+ node.pos,
358
+ stats=[del_target]))])
359
+ self.visitchildren(node)
360
+ return node
361
+
362
+ def visit_AssertStatNode(self, node):
363
+ """Extract the exception raising into a RaiseStatNode to simplify GIL handling.
364
+ """
365
+ if node.exception is None:
366
+ node.exception = Nodes.RaiseStatNode(
367
+ node.pos,
368
+ exc_type=ExprNodes.NameNode(node.pos, name=EncodedString("AssertionError")),
369
+ exc_value=node.value,
370
+ exc_tb=None,
371
+ cause=None,
372
+ builtin_exc_name="AssertionError",
373
+ wrap_tuple_value=True,
374
+ )
375
+ node.value = None
376
+ self.visitchildren(node)
377
+ return node
378
+
379
+ def visit_ErrorNode(self, node):
380
+ error(node.pos, node.what)
381
+ return None
382
+
383
+ def visit_MatchCaseNode(self, node):
384
+ node.validate_targets()
385
+ self.visitchildren(node)
386
+ return node
387
+
388
+ def visit_MatchNode(self, node):
389
+ node.validate_irrefutable()
390
+ self.visitchildren(node)
391
+ return node
392
+
393
+ def visit_PatternNode(self, node):
394
+ in_pattern_node, self.in_pattern_node = self.in_pattern_node, True
395
+ self.visitchildren(node)
396
+ self.in_pattern_node = in_pattern_node
397
+ return node
398
+
399
+ def visit_JoinedStrNode(self, node):
400
+ if self.in_pattern_node:
401
+ error(node.pos, "f-strings are not accepted for pattern matching")
402
+ self.visitchildren(node)
403
+ return node
404
+
405
+ def visit_DefNode(self, node):
406
+ if (self.scope_type == "cclass" and
407
+ node.name in ["__getreadbuffer__", "__getwritebuffer__", "__getsegcount__", "__getcharbuffer__"]):
408
+ warning(node.pos, f"'{node.name}' relates to the old Python 2 buffer protocol "
409
+ "and is no longer used.", 2)
410
+ return None # drop the node - the arguments are invalid for a def node
411
+ return self.visit_FuncDefNode(node)
412
+
413
+
414
+ class _AssignmentExpressionTargetNameFinder(TreeVisitor):
415
+ def __init__(self):
416
+ super().__init__()
417
+ self.target_names = {}
418
+
419
+ def find_target_names(self, target):
420
+ if target.is_name:
421
+ return [target.name]
422
+ elif target.is_sequence_constructor:
423
+ names = []
424
+ for arg in target.args:
425
+ names.extend(self.find_target_names(arg))
426
+ return names
427
+ # other targets are possible, but it isn't necessary to investigate them here
428
+ return []
429
+
430
+ def visit_ForInStatNode(self, node):
431
+ self.target_names[node] = tuple(self.find_target_names(node.target))
432
+ self.visitchildren(node)
433
+
434
+ def visit_ComprehensionNode(self, node):
435
+ pass # don't recurse into nested comprehensions
436
+
437
+ def visit_LambdaNode(self, node):
438
+ pass # don't recurse into nested lambdas/generator expressions
439
+
440
+ def visit_Node(self, node):
441
+ self.visitchildren(node)
442
+
443
+
444
+ class _AssignmentExpressionChecker(TreeVisitor):
445
+ """
446
+ Enforces rules on AssignmentExpressions within generator expressions and comprehensions
447
+ """
448
+ def __init__(self, loop_node, scope_is_class):
449
+ super().__init__()
450
+
451
+ target_name_finder = _AssignmentExpressionTargetNameFinder()
452
+ target_name_finder.visit(loop_node)
453
+ self.target_names_dict = target_name_finder.target_names
454
+ self.in_iterator = False
455
+ self.in_nested_generator = False
456
+ self.scope_is_class = scope_is_class
457
+ self.current_target_names = ()
458
+ self.all_target_names = set()
459
+ for names in self.target_names_dict.values():
460
+ self.all_target_names.update(names)
461
+
462
+ def _reset_state(self):
463
+ old_state = (self.in_iterator, self.in_nested_generator, self.scope_is_class, self.all_target_names, self.current_target_names)
464
+ # note: not resetting self.in_iterator here, see visit_LambdaNode() below
465
+ self.in_nested_generator = False
466
+ self.scope_is_class = False
467
+ self.current_target_names = ()
468
+ self.all_target_names = set()
469
+ return old_state
470
+
471
+ def _set_state(self, old_state):
472
+ self.in_iterator, self.in_nested_generator, self.scope_is_class, self.all_target_names, self.current_target_names = old_state
473
+
474
+ @classmethod
475
+ def do_checks(cls, loop_node, scope_is_class):
476
+ checker = cls(loop_node, scope_is_class)
477
+ checker.visit(loop_node)
478
+
479
+ def visit_ForInStatNode(self, node):
480
+ if self.in_nested_generator:
481
+ self.visitchildren(node) # once nested, don't do anything special
482
+ return
483
+
484
+ current_target_names = self.current_target_names
485
+ target_name = self.target_names_dict.get(node, None)
486
+ if target_name:
487
+ self.current_target_names += target_name
488
+
489
+ self.in_iterator = True
490
+ self.visit(node.iterator)
491
+ self.in_iterator = False
492
+ self.visitchildren(node, exclude=("iterator",))
493
+
494
+ self.current_target_names = current_target_names
495
+
496
+ def visit_AssignmentExpressionNode(self, node):
497
+ if self.in_iterator:
498
+ error(node.pos, "assignment expression cannot be used in a comprehension iterable expression")
499
+ if self.scope_is_class:
500
+ error(node.pos, "assignment expression within a comprehension cannot be used in a class body")
501
+ if node.target_name in self.current_target_names:
502
+ error(node.pos, "assignment expression cannot rebind comprehension iteration variable '%s'" %
503
+ node.target_name)
504
+ elif node.target_name in self.all_target_names:
505
+ error(node.pos, "comprehension inner loop cannot rebind assignment expression target '%s'" %
506
+ node.target_name)
507
+
508
+ def visit_LambdaNode(self, node):
509
+ # Don't reset "in_iterator" - an assignment expression in a lambda in an
510
+ # iterator is explicitly tested by the Python testcases and banned.
511
+ old_state = self._reset_state()
512
+ # the lambda node's "def_node" is not set up at this point, so we need to recurse into it explicitly.
513
+ self.visit(node.result_expr)
514
+ self._set_state(old_state)
515
+
516
+ def visit_ComprehensionNode(self, node):
517
+ in_nested_generator = self.in_nested_generator
518
+ self.in_nested_generator = True
519
+ self.visitchildren(node)
520
+ self.in_nested_generator = in_nested_generator
521
+
522
+ def visit_GeneratorExpressionNode(self, node):
523
+ in_nested_generator = self.in_nested_generator
524
+ self.in_nested_generator = True
525
+ # def_node isn't set up yet, so we need to visit the loop directly.
526
+ self.visit(node.loop)
527
+ self.in_nested_generator = in_nested_generator
528
+
529
+ def visit_Node(self, node):
530
+ self.visitchildren(node)
531
+
532
+
533
+ def eliminate_rhs_duplicates(expr_list_list, ref_node_sequence):
534
+ """Replace rhs items by LetRefNodes if they appear more than once.
535
+ Creates a sequence of LetRefNodes that set up the required temps
536
+ and appends them to ref_node_sequence. The input list is modified
537
+ in-place.
538
+ """
539
+ seen_nodes = set()
540
+ ref_nodes = {}
541
+ def find_duplicates(node):
542
+ if node.is_literal or node.is_name:
543
+ # no need to replace those; can't include attributes here
544
+ # as their access is not necessarily side-effect free
545
+ return
546
+ if node in seen_nodes:
547
+ if node not in ref_nodes:
548
+ ref_node = LetRefNode(node)
549
+ ref_nodes[node] = ref_node
550
+ ref_node_sequence.append(ref_node)
551
+ else:
552
+ seen_nodes.add(node)
553
+ if node.is_sequence_constructor:
554
+ for item in node.args:
555
+ find_duplicates(item)
556
+
557
+ for expr_list in expr_list_list:
558
+ rhs = expr_list[-1]
559
+ find_duplicates(rhs)
560
+ if not ref_nodes:
561
+ return
562
+
563
+ def substitute_nodes(node):
564
+ if node in ref_nodes:
565
+ return ref_nodes[node]
566
+ elif node.is_sequence_constructor:
567
+ node.args = list(map(substitute_nodes, node.args))
568
+ return node
569
+
570
+ # replace nodes inside of the common subexpressions
571
+ for node in ref_nodes:
572
+ if node.is_sequence_constructor:
573
+ node.args = list(map(substitute_nodes, node.args))
574
+
575
+ # replace common subexpressions on all rhs items
576
+ for expr_list in expr_list_list:
577
+ expr_list[-1] = substitute_nodes(expr_list[-1])
578
+
579
+ def sort_common_subsequences(items):
580
+ """Sort items/subsequences so that all items and subsequences that
581
+ an item contains appear before the item itself. This is needed
582
+ because each rhs item must only be evaluated once, so its value
583
+ must be evaluated first and then reused when packing sequences
584
+ that contain it.
585
+
586
+ This implies a partial order, and the sort must be stable to
587
+ preserve the original order as much as possible, so we use a
588
+ simple insertion sort (which is very fast for short sequences, the
589
+ normal case in practice).
590
+ """
591
+ def contains(seq, x):
592
+ for item in seq:
593
+ if item is x:
594
+ return True
595
+ elif item.is_sequence_constructor and contains(item.args, x):
596
+ return True
597
+ return False
598
+ def lower_than(a,b):
599
+ return b.is_sequence_constructor and contains(b.args, a)
600
+
601
+ for pos, item in enumerate(items):
602
+ key = item[1] # the ResultRefNode which has already been injected into the sequences
603
+ new_pos = pos
604
+ for i in range(pos-1, -1, -1):
605
+ if lower_than(key, items[i][0]):
606
+ new_pos = i
607
+ if new_pos != pos:
608
+ for i in range(pos, new_pos, -1):
609
+ items[i] = items[i-1]
610
+ items[new_pos] = item
611
+
612
+
613
+ def unpack_string_to_character_literals(literal):
614
+ chars = []
615
+ pos = literal.pos
616
+ stype = literal.__class__
617
+ sval = literal.value
618
+ sval_type = sval.__class__
619
+ for char in sval:
620
+ cval = sval_type(char)
621
+ chars.append(stype(pos, value=cval))
622
+ return chars
623
+
624
+
625
+ @cython.cfunc
626
+ def flatten_parallel_assignments(input: list, output: list):
627
+ # The input is a list of expression nodes, representing the LHSs
628
+ # and RHS of one (possibly cascaded) assignment statement. For
629
+ # sequence constructors, rearranges the matching parts of both
630
+ # sides into a list of equivalent assignments between the
631
+ # individual elements. This transformation is applied
632
+ # recursively, so that nested structures get matched as well.
633
+ rhs = input[-1]
634
+ if (not (rhs.is_sequence_constructor or isinstance(rhs, ExprNodes.UnicodeNode))
635
+ or not sum([lhs.is_sequence_constructor for lhs in input[:-1]])):
636
+ output.append(input)
637
+ return
638
+
639
+ complete_assignments = []
640
+
641
+ if rhs.is_sequence_constructor:
642
+ rhs_args = rhs.args
643
+ elif rhs.is_string_literal:
644
+ rhs_args = unpack_string_to_character_literals(rhs)
645
+
646
+ starred_targets: cython.Py_ssize_t
647
+ lhs_size: cython.Py_ssize_t
648
+ rhs_size: cython.Py_ssize_t = len(rhs_args)
649
+ lhs_targets = [[] for _ in range(rhs_size)]
650
+ starred_assignments = []
651
+
652
+ for lhs in input[:-1]:
653
+ if not lhs.is_sequence_constructor:
654
+ if lhs.is_starred:
655
+ error(lhs.pos, "starred assignment target must be in a list or tuple")
656
+ complete_assignments.append(lhs)
657
+ continue
658
+ lhs_size = len(lhs.args)
659
+ starred_targets = 0
660
+ for expr in lhs.args:
661
+ starred_targets += bool(expr.is_starred)
662
+ if starred_targets > 1:
663
+ error(lhs.pos, "more than 1 starred expression in assignment")
664
+ output.append([lhs,rhs])
665
+ continue
666
+ elif lhs_size - starred_targets > rhs_size:
667
+ error(lhs.pos, "need more than %d value%s to unpack"
668
+ % (rhs_size, (rhs_size != 1) and 's' or ''))
669
+ output.append([lhs,rhs])
670
+ continue
671
+ elif starred_targets:
672
+ map_starred_assignment(lhs_targets, starred_assignments,
673
+ lhs.args, rhs_args)
674
+ elif lhs_size < rhs_size:
675
+ error(lhs.pos, "too many values to unpack (expected %d, got %d)"
676
+ % (lhs_size, rhs_size))
677
+ output.append([lhs,rhs])
678
+ continue
679
+ else:
680
+ for targets, expr in zip(lhs_targets, lhs.args):
681
+ targets.append(expr)
682
+
683
+ if complete_assignments:
684
+ complete_assignments.append(rhs)
685
+ output.append(complete_assignments)
686
+
687
+ # recursively flatten partial assignments
688
+ for cascade, rhs in zip(lhs_targets, rhs_args):
689
+ if cascade:
690
+ cascade.append(rhs)
691
+ flatten_parallel_assignments(cascade, output)
692
+
693
+ # recursively flatten starred assignments
694
+ for cascade in starred_assignments:
695
+ if cascade[0].is_sequence_constructor:
696
+ flatten_parallel_assignments(cascade, output)
697
+ else:
698
+ output.append(cascade)
699
+
700
+
701
+ @cython.cfunc
702
+ def map_starred_assignment(lhs_targets: list, starred_assignments: list, lhs_args: list, rhs_args: list):
703
+ # Appends the fixed-position LHS targets to the target list that
704
+ # appear left and right of the starred argument.
705
+ #
706
+ # The starred_assignments list receives a new tuple
707
+ # (lhs_target, rhs_values_list) that maps the remaining arguments
708
+ # (those that match the starred target) to a list.
709
+
710
+ # left side of the starred target
711
+ i: cython.Py_ssize_t
712
+ starred: cython.Py_ssize_t
713
+ lhs_remaining: cython.Py_ssize_t
714
+ for i, (targets, expr) in enumerate(zip(lhs_targets, lhs_args)):
715
+ if expr.is_starred:
716
+ starred = i
717
+ lhs_remaining = len(lhs_args) - i - 1
718
+ break
719
+ targets.append(expr)
720
+ else:
721
+ raise InternalError("no starred arg found when splitting starred assignment")
722
+
723
+ # right side of the starred target
724
+ for i, (targets, expr) in enumerate(zip(lhs_targets[-lhs_remaining:],
725
+ lhs_args[starred + 1:])):
726
+ targets.append(expr)
727
+
728
+ # the starred target itself, must be assigned a (potentially empty) list
729
+ target = lhs_args[starred].target # unpack starred node
730
+ starred_rhs = rhs_args[starred:]
731
+ if lhs_remaining:
732
+ starred_rhs = starred_rhs[:-lhs_remaining]
733
+ if starred_rhs:
734
+ pos = starred_rhs[0].pos
735
+ else:
736
+ pos = target.pos
737
+ starred_assignments.append([
738
+ target, ExprNodes.ListNode(pos=pos, args=starred_rhs)])
739
+
740
+
741
+ class PxdPostParse(CythonTransform, SkipDeclarations):
742
+ """
743
+ Basic interpretation/validity checking that should only be
744
+ done on pxd trees.
745
+
746
+ A lot of this checking currently happens in the parser; but
747
+ what is listed below happens here.
748
+
749
+ - "def" functions are let through only if they fill the
750
+ getbuffer/releasebuffer slots
751
+
752
+ - cdef functions are let through only if they are on the
753
+ top level and are declared "inline"
754
+ """
755
+ ERR_INLINE_ONLY = "function definition in pxd file must be declared 'cdef inline'"
756
+ ERR_NOGO_WITH_INLINE = "inline function definition in pxd file cannot be '%s'"
757
+
758
+ def __call__(self, node):
759
+ self.scope_type = 'pxd'
760
+ return super().__call__(node)
761
+
762
+ def visit_CClassDefNode(self, node):
763
+ old = self.scope_type
764
+ self.scope_type = 'cclass'
765
+ self.visitchildren(node)
766
+ self.scope_type = old
767
+ return node
768
+
769
+ def visit_FuncDefNode(self, node):
770
+ # FuncDefNode always come with an implementation (without
771
+ # an imp they are CVarDefNodes..)
772
+ err = self.ERR_INLINE_ONLY
773
+
774
+ if (isinstance(node, Nodes.DefNode) and self.scope_type == 'cclass'
775
+ and node.name in ('__getbuffer__', '__releasebuffer__')):
776
+ err = None # allow these slots
777
+
778
+ if isinstance(node, Nodes.CFuncDefNode):
779
+ if ('inline' in node.modifiers and
780
+ self.scope_type in ('pxd', 'cclass')):
781
+ node.inline_in_pxd = True
782
+ if node.visibility != 'private':
783
+ err = self.ERR_NOGO_WITH_INLINE % node.visibility
784
+ elif node.api:
785
+ err = self.ERR_NOGO_WITH_INLINE % 'api'
786
+ else:
787
+ err = None # allow inline function
788
+ else:
789
+ err = self.ERR_INLINE_ONLY
790
+
791
+ if err:
792
+ self.context.nonfatal_error(PostParseError(node.pos, err))
793
+ return None
794
+ else:
795
+ return node
796
+
797
+
798
+ class TrackNumpyAttributes(VisitorTransform, SkipDeclarations):
799
+ # TODO: Make name handling as good as in InterpretCompilerDirectives() below - probably best to merge the two.
800
+ def __init__(self):
801
+ super().__init__()
802
+ self.numpy_module_names = set()
803
+
804
+ def visit_CImportStatNode(self, node):
805
+ if node.module_name == "numpy":
806
+ self.numpy_module_names.add(node.as_name or "numpy")
807
+ return node
808
+
809
+ def visit_AttributeNode(self, node):
810
+ self.visitchildren(node)
811
+ obj = node.obj
812
+ if (obj.is_name and obj.name in self.numpy_module_names) or obj.is_numpy_attribute:
813
+ node.is_numpy_attribute = True
814
+ return node
815
+
816
+ visit_Node = VisitorTransform.recurse_to_children
817
+
818
+
819
+ class InterpretCompilerDirectives(CythonTransform):
820
+ """
821
+ After parsing, directives can be stored in a number of places:
822
+ - #cython-comments at the top of the file (stored in ModuleNode)
823
+ - Command-line arguments overriding these
824
+ - @cython.directivename decorators
825
+ - with cython.directivename: statements
826
+ - replaces "cython.compiled" with BoolNode(value=True)
827
+ allowing unreachable blocks to be removed at a fairly early stage
828
+ before cython typing rules are forced on applied
829
+
830
+ This transform is responsible for interpreting these various sources
831
+ and store the directive in two ways:
832
+ - Set the directives attribute of the ModuleNode for global directives.
833
+ - Use a CompilerDirectivesNode to override directives for a subtree.
834
+
835
+ (The first one is primarily to not have to modify with the tree
836
+ structure, so that ModuleNode stay on top.)
837
+
838
+ The directives are stored in dictionaries from name to value in effect.
839
+ Each such dictionary is always filled in for all possible directives,
840
+ using default values where no value is given by the user.
841
+
842
+ The available directives are controlled in Options.py.
843
+
844
+ Note that we have to run this prior to analysis, and so some minor
845
+ duplication of functionality has to occur: We manually track cimports
846
+ and which names the "cython" module may have been imported to.
847
+ """
848
+ unop_method_nodes = {
849
+ 'typeof': ExprNodes.TypeofNode,
850
+
851
+ 'operator.address': ExprNodes.AmpersandNode,
852
+ 'operator.dereference': ExprNodes.DereferenceNode,
853
+ 'operator.preincrement' : ExprNodes.inc_dec_constructor(True, '++'),
854
+ 'operator.predecrement' : ExprNodes.inc_dec_constructor(True, '--'),
855
+ 'operator.postincrement': ExprNodes.inc_dec_constructor(False, '++'),
856
+ 'operator.postdecrement': ExprNodes.inc_dec_constructor(False, '--'),
857
+ 'operator.typeid' : ExprNodes.TypeidNode,
858
+
859
+ # For backwards compatibility.
860
+ 'address': ExprNodes.AmpersandNode,
861
+ }
862
+
863
+ binop_method_nodes = {
864
+ 'operator.comma' : ExprNodes.c_binop_constructor(','),
865
+ }
866
+
867
+ special_methods = {
868
+ 'declare', 'union', 'struct', 'typedef',
869
+ 'sizeof', 'cast', 'pointer', 'compiled',
870
+ 'NULL', 'fused_type', 'parallel',
871
+ }
872
+ special_methods.update(unop_method_nodes)
873
+
874
+ valid_cython_submodules = {
875
+ 'cimports',
876
+ 'dataclasses',
877
+ 'operator',
878
+ 'parallel',
879
+ 'view',
880
+ }
881
+
882
+ valid_parallel_directives = {
883
+ "parallel",
884
+ "prange",
885
+ "threadid",
886
+ #"threadsavailable",
887
+ }
888
+
889
+ def __init__(self, context, compilation_directive_defaults):
890
+ super().__init__(context)
891
+ self.cython_module_names = set()
892
+ self.directive_names = {'staticmethod': 'staticmethod'}
893
+ self.parallel_directives = {}
894
+ directives = copy.deepcopy(Options.get_directive_defaults())
895
+ for key, value in compilation_directive_defaults.items():
896
+ directives[str(key)] = copy.deepcopy(value)
897
+ self.directives = directives
898
+
899
+ def check_directive_scope(self, pos, directive, scope):
900
+ legal_scopes = Options.directive_scopes.get(directive, None)
901
+ if legal_scopes and scope not in legal_scopes:
902
+ self.context.nonfatal_error(PostParseError(pos, 'The %s compiler directive '
903
+ 'is not allowed in %s scope' % (directive, scope)))
904
+ return False
905
+ else:
906
+ if directive not in Options.directive_types:
907
+ error(pos, "Invalid directive: '%s'." % (directive,))
908
+ return True
909
+
910
+ def _check_valid_cython_module(self, pos, module_name):
911
+ if not module_name.startswith("cython."):
912
+ return
913
+ submodule = module_name.split('.', 2)[1]
914
+ if submodule in self.valid_cython_submodules:
915
+ return
916
+
917
+ extra = ""
918
+ # This is very rarely used, so don't waste space on static tuples.
919
+ hints = [
920
+ line.split() for line in """\
921
+ imp cimports
922
+ cimp cimports
923
+ para parallel
924
+ parra parallel
925
+ dataclass dataclasses
926
+ """.splitlines()[:-1]
927
+ ]
928
+ for wrong, correct in hints:
929
+ if module_name.startswith("cython." + wrong):
930
+ extra = "Did you mean 'cython.%s' ?" % correct
931
+ break
932
+ if not extra:
933
+ is_simple_cython_name = submodule in Options.directive_types
934
+ if not is_simple_cython_name and not submodule.startswith("_"):
935
+ # Try to find it in the Shadow module (i.e. the pure Python namespace of cython.*).
936
+ # FIXME: use an internal reference of "cython.*" names instead of Shadow.py
937
+ from .. import Shadow
938
+ is_simple_cython_name = hasattr(Shadow, submodule)
939
+ if is_simple_cython_name:
940
+ extra = "Instead, use 'import cython' and then 'cython.%s'." % submodule
941
+
942
+ error(pos, "'%s' is not a valid cython.* module%s%s" % (
943
+ module_name,
944
+ ". " if extra else "",
945
+ extra,
946
+ ))
947
+
948
+ # Set up processing and handle the cython: comments.
949
+ def visit_ModuleNode(self, node):
950
+ for key in sorted(node.directive_comments):
951
+ if not self.check_directive_scope(node.pos, key, 'module'):
952
+ self.wrong_scope_error(node.pos, key, 'module')
953
+ del node.directive_comments[key]
954
+
955
+ self.module_scope = node.scope
956
+
957
+ self.directives.update(node.directive_comments)
958
+ node.directives = self.directives
959
+ node.parallel_directives = self.parallel_directives
960
+ self.visitchildren(node)
961
+ node.cython_module_names = self.cython_module_names
962
+ return node
963
+
964
+ def visit_CompilerDirectivesMixin(self, node):
965
+ with node.apply_directives(self):
966
+ self.visitchildren(node)
967
+ return node
968
+
969
+ # The following four functions track imports and cimports that
970
+ # begin with "cython"
971
+ def is_cython_directive(self, name):
972
+ return (name in Options.directive_types or
973
+ name in self.special_methods or
974
+ PyrexTypes.parse_basic_type(name))
975
+
976
+ def is_parallel_directive(self, full_name, pos):
977
+ """
978
+ Checks to see if fullname (e.g. cython.parallel.prange) is a valid
979
+ parallel directive. If it is a star import it also updates the
980
+ parallel_directives.
981
+ """
982
+ result = (full_name + ".").startswith("cython.parallel.")
983
+
984
+ if result:
985
+ directive = full_name.split('.')
986
+ if full_name == "cython.parallel":
987
+ self.parallel_directives["parallel"] = "cython.parallel"
988
+ elif full_name == "cython.parallel.*":
989
+ for name in self.valid_parallel_directives:
990
+ self.parallel_directives[name] = "cython.parallel.%s" % name
991
+ elif (len(directive) != 3 or
992
+ directive[-1] not in self.valid_parallel_directives):
993
+ error(pos, "No such directive: %s" % full_name)
994
+
995
+ return result
996
+
997
+ def visit_CImportStatNode(self, node):
998
+ module_name = node.module_name
999
+ if module_name == "cython.cimports":
1000
+ error(node.pos, "Cannot cimport the 'cython.cimports' package directly, only submodules.")
1001
+ if module_name.startswith("cython.cimports."):
1002
+ if node.as_name and node.as_name != 'cython':
1003
+ node.module_name = module_name[len("cython.cimports."):]
1004
+ return node
1005
+ error(node.pos,
1006
+ "Python cimports must use 'from cython.cimports... import ...'"
1007
+ " or 'import ... as ...', not just 'import ...'")
1008
+
1009
+ if module_name == "cython":
1010
+ self.cython_module_names.add(node.as_name or "cython")
1011
+ elif module_name.startswith("cython."):
1012
+ if module_name.startswith("cython.parallel."):
1013
+ error(node.pos, node.module_name + " is not a module")
1014
+ else:
1015
+ self._check_valid_cython_module(node.pos, module_name)
1016
+
1017
+ if module_name == "cython.parallel":
1018
+ if node.as_name and node.as_name != "cython":
1019
+ self.parallel_directives[node.as_name] = module_name
1020
+ else:
1021
+ self.cython_module_names.add("cython")
1022
+ self.parallel_directives[
1023
+ "cython.parallel"] = module_name
1024
+ elif node.as_name:
1025
+ self.directive_names[node.as_name] = module_name[7:]
1026
+ else:
1027
+ self.cython_module_names.add("cython")
1028
+ # if this cimport was a compiler directive, we don't
1029
+ # want to leave the cimport node sitting in the tree
1030
+ return None
1031
+ return node
1032
+
1033
+ def visit_FromCImportStatNode(self, node):
1034
+ module_name = node.module_name
1035
+ if module_name == "cython.cimports" or module_name.startswith("cython.cimports."):
1036
+ # only supported for convenience
1037
+ return self._create_cimport_from_import(
1038
+ node.pos, module_name, node.relative_level, node.imported_names)
1039
+ elif not node.relative_level and (
1040
+ module_name == "cython" or module_name.startswith("cython.")):
1041
+ self._check_valid_cython_module(node.pos, module_name)
1042
+ submodule = (module_name + ".")[7:]
1043
+ newimp = []
1044
+ for pos, name, as_name in node.imported_names:
1045
+ full_name = submodule + name
1046
+ qualified_name = "cython." + full_name
1047
+ if self.is_parallel_directive(qualified_name, node.pos):
1048
+ # from cython cimport parallel, or
1049
+ # from cython.parallel cimport parallel, prange, ...
1050
+ self.parallel_directives[as_name or name] = qualified_name
1051
+ elif self.is_cython_directive(full_name):
1052
+ self.directive_names[as_name or name] = full_name
1053
+ elif full_name in ['dataclasses', 'typing']:
1054
+ self.directive_names[as_name or name] = full_name
1055
+ # unlike many directives, still treat it as a regular module
1056
+ newimp.append((pos, name, as_name))
1057
+ else:
1058
+ newimp.append((pos, name, as_name))
1059
+
1060
+ if not newimp:
1061
+ return None
1062
+
1063
+ node.imported_names = newimp
1064
+ return node
1065
+
1066
+ def visit_FromImportStatNode(self, node):
1067
+ import_node = node.module
1068
+ module_name = import_node.module_name.value
1069
+ if module_name == "cython.cimports" or module_name.startswith("cython.cimports."):
1070
+ imported_names = []
1071
+ for name, name_node in node.items:
1072
+ imported_names.append(
1073
+ (name_node.pos, name, None if name == name_node.name else name_node.name))
1074
+ return self._create_cimport_from_import(
1075
+ node.pos, module_name, import_node.level, imported_names)
1076
+ elif module_name == "cython" or module_name.startswith("cython."):
1077
+ self._check_valid_cython_module(import_node.module_name.pos, module_name)
1078
+ submodule = (module_name + ".")[7:]
1079
+ newimp = []
1080
+ for name, name_node in node.items:
1081
+ full_name = submodule + name
1082
+ qualified_name = "cython." + full_name
1083
+ if self.is_parallel_directive(qualified_name, node.pos):
1084
+ self.parallel_directives[name_node.name] = qualified_name
1085
+ elif self.is_cython_directive(full_name):
1086
+ self.directive_names[name_node.name] = full_name
1087
+ else:
1088
+ newimp.append((name, name_node))
1089
+ if not newimp:
1090
+ return None
1091
+ node.items = newimp
1092
+ return node
1093
+
1094
+ def _create_cimport_from_import(self, node_pos, module_name, level, imported_names):
1095
+ if module_name == "cython.cimports" or module_name.startswith("cython.cimports."):
1096
+ module_name = EncodedString(module_name[len("cython.cimports."):]) # may be empty
1097
+
1098
+ if module_name:
1099
+ # from cython.cimports.a.b import x, y, z => from a.b cimport x, y, z
1100
+ return Nodes.FromCImportStatNode(
1101
+ node_pos, module_name=module_name,
1102
+ relative_level=level,
1103
+ imported_names=imported_names)
1104
+ else:
1105
+ # from cython.cimports import x, y, z => cimport x; cimport y; cimport z
1106
+ return [
1107
+ Nodes.CImportStatNode(
1108
+ pos,
1109
+ module_name=dotted_name,
1110
+ as_name=as_name,
1111
+ is_absolute=level == 0)
1112
+ for pos, dotted_name, as_name in imported_names
1113
+ ]
1114
+
1115
+ def visit_SingleAssignmentNode(self, node):
1116
+ if isinstance(node.rhs, ExprNodes.ImportNode):
1117
+ module_name = node.rhs.module_name.value
1118
+ if module_name != "cython" and not module_name.startswith("cython."):
1119
+ return node
1120
+
1121
+ node = Nodes.CImportStatNode(node.pos, module_name=module_name, as_name=node.lhs.name)
1122
+ node = self.visit_CImportStatNode(node)
1123
+ else:
1124
+ self.visitchildren(node)
1125
+
1126
+ return node
1127
+
1128
+ def visit_NameNode(self, node):
1129
+ if node.annotation:
1130
+ self.visitchild(node, 'annotation')
1131
+ if node.name in self.cython_module_names:
1132
+ node.is_cython_module = True
1133
+ else:
1134
+ directive = self.directive_names.get(node.name)
1135
+ if directive is not None:
1136
+ node.cython_attribute = directive
1137
+ if node.as_cython_attribute() == "compiled":
1138
+ return ExprNodes.BoolNode(node.pos, value=True) # replace early so unused branches can be dropped
1139
+ # before they have a chance to cause compile-errors
1140
+ return node
1141
+
1142
+ def visit_AttributeNode(self, node):
1143
+ self.visitchildren(node)
1144
+ if node.as_cython_attribute() == "compiled":
1145
+ return ExprNodes.BoolNode(node.pos, value=True) # replace early so unused branches can be dropped
1146
+ # before they have a chance to cause compile-errors
1147
+ return node
1148
+
1149
+ def visit_AnnotationNode(self, node):
1150
+ # for most transforms annotations are left unvisited (because they're unevaluated)
1151
+ # however, it is important to pick up compiler directives from them
1152
+ if node.expr:
1153
+ self.visit(node.expr)
1154
+ return node
1155
+
1156
+ def visit_NewExprNode(self, node):
1157
+ self.visitchild(node, 'cppclass')
1158
+ self.visitchildren(node)
1159
+ return node
1160
+
1161
+ def try_to_parse_directives(self, node):
1162
+ # If node is the contents of an directive (in a with statement or
1163
+ # decorator), returns a list of (directivename, value) pairs.
1164
+ # Otherwise, returns None
1165
+ if isinstance(node, ExprNodes.CallNode):
1166
+ self.visitchild(node, 'function')
1167
+ optname = node.function.as_cython_attribute()
1168
+ if optname:
1169
+ directivetype = Options.directive_types.get(optname)
1170
+ if directivetype:
1171
+ args, kwds = node.explicit_args_kwds()
1172
+ directives = []
1173
+ key_value_pairs = []
1174
+ if kwds is not None and directivetype is not dict:
1175
+ for keyvalue in kwds.key_value_pairs:
1176
+ key, value = keyvalue
1177
+ sub_optname = "%s.%s" % (optname, key.value)
1178
+ if Options.directive_types.get(sub_optname):
1179
+ directives.append(self.try_to_parse_directive(sub_optname, [value], None, keyvalue.pos))
1180
+ else:
1181
+ key_value_pairs.append(keyvalue)
1182
+ if not key_value_pairs:
1183
+ kwds = None
1184
+ else:
1185
+ kwds.key_value_pairs = key_value_pairs
1186
+ if directives and not kwds and not args:
1187
+ return directives
1188
+ directives.append(self.try_to_parse_directive(optname, args, kwds, node.function.pos))
1189
+ return directives
1190
+ elif isinstance(node, (ExprNodes.AttributeNode, ExprNodes.NameNode)):
1191
+ self.visit(node)
1192
+ optname = node.as_cython_attribute()
1193
+ if optname:
1194
+ directivetype = Options.directive_types.get(optname)
1195
+ if directivetype is bool:
1196
+ arg = ExprNodes.BoolNode(node.pos, value=True)
1197
+ return [self.try_to_parse_directive(optname, [arg], None, node.pos)]
1198
+ elif directivetype is None or directivetype is Options.DEFER_ANALYSIS_OF_ARGUMENTS:
1199
+ return [(optname, None)]
1200
+ else:
1201
+ raise PostParseError(
1202
+ node.pos, "The '%s' directive should be used as a function call." % optname)
1203
+ return None
1204
+
1205
+ def try_to_parse_directive(self, optname, args, kwds, pos):
1206
+ if optname == 'np_pythran' and not self.context.cpp:
1207
+ raise PostParseError(pos, 'The %s directive can only be used in C++ mode.' % optname)
1208
+ elif optname == 'exceptval':
1209
+ # default: exceptval(None, check=True)
1210
+ arg_error = len(args) > 1
1211
+ check = True
1212
+ if kwds and kwds.key_value_pairs:
1213
+ kw = kwds.key_value_pairs[0]
1214
+ if (len(kwds.key_value_pairs) == 1 and
1215
+ kw.key.is_string_literal and kw.key.value == 'check' and
1216
+ isinstance(kw.value, ExprNodes.BoolNode)):
1217
+ check = kw.value.value
1218
+ else:
1219
+ arg_error = True
1220
+ if arg_error:
1221
+ raise PostParseError(
1222
+ pos, 'The exceptval directive takes 0 or 1 positional arguments and the boolean keyword "check"')
1223
+ return ('exceptval', (args[0] if args else None, check))
1224
+
1225
+ directivetype = Options.directive_types.get(optname)
1226
+ if len(args) == 1 and isinstance(args[0], ExprNodes.NoneNode):
1227
+ return optname, Options.get_directive_defaults()[optname]
1228
+ elif directivetype is bool:
1229
+ if kwds is not None or len(args) != 1 or not isinstance(args[0], ExprNodes.BoolNode):
1230
+ raise PostParseError(pos,
1231
+ 'The %s directive takes one compile-time boolean argument' % optname)
1232
+ return (optname, args[0].value)
1233
+ elif directivetype is int:
1234
+ if kwds is not None or len(args) != 1 or not isinstance(args[0], ExprNodes.IntNode):
1235
+ raise PostParseError(pos,
1236
+ 'The %s directive takes one compile-time integer argument' % optname)
1237
+ return (optname, int(args[0].value))
1238
+ elif directivetype is str:
1239
+ if kwds is not None or len(args) != 1 or not isinstance(args[0], ExprNodes.UnicodeNode):
1240
+ raise PostParseError(pos,
1241
+ 'The %s directive takes one compile-time string argument' % optname)
1242
+ return (optname, str(args[0].value))
1243
+ elif directivetype is type:
1244
+ if kwds is not None or len(args) != 1:
1245
+ raise PostParseError(pos,
1246
+ 'The %s directive takes one type argument' % optname)
1247
+ return (optname, args[0])
1248
+ elif directivetype is dict:
1249
+ if len(args) != 0:
1250
+ raise PostParseError(pos,
1251
+ 'The %s directive takes no prepositional arguments' % optname)
1252
+ return optname, kwds.as_python_dict()
1253
+ elif directivetype is list:
1254
+ if kwds and len(kwds.key_value_pairs) != 0:
1255
+ raise PostParseError(pos,
1256
+ 'The %s directive takes no keyword arguments' % optname)
1257
+ return optname, [ str(arg.value) for arg in args ]
1258
+ elif callable(directivetype):
1259
+ if kwds is not None or len(args) != 1 or not isinstance(args[0], ExprNodes.UnicodeNode):
1260
+ raise PostParseError(pos,
1261
+ 'The %s directive takes one compile-time string argument' % optname)
1262
+ return (optname, directivetype(optname, str(args[0].value)))
1263
+ elif directivetype is Options.DEFER_ANALYSIS_OF_ARGUMENTS:
1264
+ # signal to pass things on without processing
1265
+ return (optname, (args, kwds.as_python_dict() if kwds else {}))
1266
+ else:
1267
+ assert False
1268
+
1269
+ def visit_with_directives(self, node, directives, contents_directives):
1270
+ # contents_directives may be None
1271
+ if not directives:
1272
+ assert not contents_directives
1273
+ return self.visit_Node(node)
1274
+
1275
+ old_directives = self.directives
1276
+ new_directives = Options.copy_inherited_directives(old_directives, **directives)
1277
+ if contents_directives is not None:
1278
+ new_contents_directives = Options.copy_inherited_directives(
1279
+ old_directives, **contents_directives)
1280
+ else:
1281
+ new_contents_directives = new_directives
1282
+
1283
+ if new_directives == old_directives:
1284
+ return self.visit_Node(node)
1285
+
1286
+ self.directives = new_directives
1287
+ if (contents_directives is not None and
1288
+ new_contents_directives != new_directives):
1289
+ # we need to wrap the node body in a compiler directives node
1290
+ node.body = Nodes.StatListNode(
1291
+ node.body.pos,
1292
+ stats=[
1293
+ Nodes.CompilerDirectivesNode(
1294
+ node.body.pos,
1295
+ directives=new_contents_directives,
1296
+ body=node.body)
1297
+ ]
1298
+ )
1299
+ retbody = self.visit_Node(node)
1300
+ self.directives = old_directives
1301
+
1302
+ if isinstance(retbody, Nodes.CompilerDirectivesNode):
1303
+ new_directives.update(retbody.directives)
1304
+ retbody = retbody.body
1305
+ if not isinstance(retbody, Nodes.StatListNode):
1306
+ retbody = Nodes.StatListNode(node.pos, stats=[retbody])
1307
+ return Nodes.CompilerDirectivesNode(
1308
+ retbody.pos, body=retbody, directives=new_directives, is_terminator=retbody.is_terminator)
1309
+
1310
+ # Handle decorators
1311
+ def visit_FuncDefNode(self, node):
1312
+ directives, contents_directives = self._extract_directives(node, 'function')
1313
+ return self.visit_with_directives(node, directives, contents_directives)
1314
+
1315
+ def visit_CVarDefNode(self, node):
1316
+ directives, _ = self._extract_directives(node, 'function')
1317
+ for name, value in directives.items():
1318
+ if name == 'locals':
1319
+ node.directive_locals = value
1320
+ elif name not in ('final', 'staticmethod'):
1321
+ self.context.nonfatal_error(PostParseError(
1322
+ node.pos,
1323
+ "Cdef functions can only take cython.locals(), "
1324
+ "staticmethod, or final decorators, got %s." % name))
1325
+ return self.visit_with_directives(node, directives, contents_directives=None)
1326
+
1327
+ def visit_CClassDefNode(self, node):
1328
+ directives, contents_directives = self._extract_directives(node, 'cclass')
1329
+ return self.visit_with_directives(node, directives, contents_directives)
1330
+
1331
+ def visit_CppClassNode(self, node):
1332
+ directives, contents_directives = self._extract_directives(node, 'cppclass')
1333
+ return self.visit_with_directives(node, directives, contents_directives)
1334
+
1335
+ def visit_PyClassDefNode(self, node):
1336
+ directives, contents_directives = self._extract_directives(node, 'class')
1337
+ return self.visit_with_directives(node, directives, contents_directives)
1338
+
1339
+ def _extract_directives(self, node, scope_name):
1340
+ """
1341
+ Returns two dicts - directives applied to this function/class
1342
+ and directives applied to its contents. They aren't always the
1343
+ same (since e.g. cfunc should not be applied to inner functions)
1344
+ """
1345
+ if not node.decorators:
1346
+ return {}, {}
1347
+ # Split the decorators into two lists -- real decorators and directives
1348
+ directives = []
1349
+ realdecs = []
1350
+ both = []
1351
+ current_opt_dict = dict(self.directives)
1352
+ missing = object()
1353
+ # Decorators coming first take precedence.
1354
+ for dec in node.decorators[::-1]:
1355
+ new_directives = self.try_to_parse_directives(dec.decorator)
1356
+ if new_directives is not None:
1357
+ for directive in new_directives:
1358
+ if self.check_directive_scope(node.pos, directive[0], scope_name):
1359
+ name, value = directive
1360
+ if name in ('nogil', 'with_gil'):
1361
+ if value is None:
1362
+ value = True
1363
+ else:
1364
+ args, kwds = value
1365
+ if kwds or len(args) != 1 or not isinstance(args[0], ExprNodes.BoolNode):
1366
+ raise PostParseError(dec.pos, 'The %s directive takes one compile-time boolean argument' % name)
1367
+ value = args[0].value
1368
+ directive = (name, value)
1369
+ if current_opt_dict.get(name, missing) != value:
1370
+ if name == 'cfunc' and 'ufunc' in current_opt_dict:
1371
+ error(dec.pos, "Cannot apply @cfunc to @ufunc, please reverse the decorators.")
1372
+ directives.append(directive)
1373
+ current_opt_dict[name] = value
1374
+ elif name not in ['profile', 'linetrace']:
1375
+ # Exclude some decorators that people may leave around, but warn about useless ones.
1376
+ warning(dec.pos, "Directive does not change previous value (%s%s)" % (
1377
+ name, '=%r' % value if value is not None else ''))
1378
+ if directive[0] == 'staticmethod':
1379
+ both.append(dec)
1380
+ # Adapt scope type based on decorators that change it.
1381
+ if directive[0] == 'cclass' and scope_name == 'class':
1382
+ scope_name = 'cclass'
1383
+ else:
1384
+ realdecs.append(dec)
1385
+ node.decorators = realdecs[::-1] + both[::-1]
1386
+ # merge or override repeated directives
1387
+ optdict = {}
1388
+ contents_optdict = {}
1389
+ for name, value in directives:
1390
+ if name in optdict:
1391
+ old_value = optdict[name]
1392
+ # keywords and arg lists can be merged, everything
1393
+ # else overrides completely
1394
+ if isinstance(old_value, dict):
1395
+ old_value.update(value)
1396
+ elif isinstance(old_value, list):
1397
+ old_value.extend(value)
1398
+ else:
1399
+ if name == "collection_type" and value != optdict[name]:
1400
+ error(node.pos, "Multiple values of collection_type are not supported")
1401
+ optdict[name] = value
1402
+ else:
1403
+ optdict[name] = value
1404
+ if name not in Options.immediate_decorator_directives:
1405
+ contents_optdict[name] = value
1406
+ return optdict, contents_optdict
1407
+
1408
+ # Handle with-statements
1409
+ def visit_WithStatNode(self, node):
1410
+ directive_dict = {}
1411
+ for directive in self.try_to_parse_directives(node.manager) or []:
1412
+ if directive is None:
1413
+ continue
1414
+ if node.target is not None:
1415
+ self.context.nonfatal_error(
1416
+ PostParseError(node.pos, "Compiler directive with statements cannot contain 'as'"))
1417
+ continue
1418
+ name, value = directive
1419
+ if name in ('nogil', 'gil'):
1420
+ # special case: in pure mode, "with nogil" spells "with cython.nogil"
1421
+ return self._transform_with_gil(node, name)
1422
+ elif name == "critical_section":
1423
+ args, kwds = value
1424
+ return self._transform_critical_section(node, args, kwds)
1425
+ elif self.check_directive_scope(node.pos, name, 'with statement'):
1426
+ directive_dict[name] = value
1427
+ if directive_dict:
1428
+ return self.visit_with_directives(node.body, directive_dict, contents_directives=None)
1429
+ return self.visit_Node(node)
1430
+
1431
+ def _transform_with_gil(self, node, state):
1432
+ assert state in ('gil', 'nogil')
1433
+ manager = node.manager
1434
+ condition = None
1435
+ if isinstance(manager, ExprNodes.SimpleCallNode) and manager.args:
1436
+ if len(manager.args) > 1:
1437
+ self.context.nonfatal_error(
1438
+ PostParseError(node.pos, "Compiler directive %s accepts one positional argument." % state))
1439
+ condition = manager.args[0]
1440
+ elif isinstance(manager, ExprNodes.GeneralCallNode):
1441
+ self.context.nonfatal_error(
1442
+ PostParseError(node.pos, "Compiler directive %s accepts one positional argument." % state))
1443
+ node = Nodes.GILStatNode(node.pos, state=state, body=node.body, condition=condition)
1444
+ return self.visit_Node(node)
1445
+
1446
+ def _transform_critical_section(self, node, args, kwds):
1447
+ if len(args) < 1 or len(args) > 2 or kwds:
1448
+ self.context.nonfatal_error(
1449
+ PostParseError(node.pos, "critical_section directive accepts one or two positional arguments")
1450
+ )
1451
+ node = Nodes.CriticalSectionStatNode(
1452
+ node.pos, args=args, body=node.body
1453
+ )
1454
+ return self.visit_Node(node)
1455
+
1456
+
1457
+ class ParallelRangeTransform(CythonTransform, SkipDeclarations):
1458
+ """
1459
+ Transform cython.parallel stuff. The parallel_directives come from the
1460
+ module node, set there by InterpretCompilerDirectives.
1461
+
1462
+ x = cython.parallel.threadavailable() -> ParallelThreadAvailableNode
1463
+ with nogil, cython.parallel.parallel(): -> ParallelWithBlockNode
1464
+ print cython.parallel.threadid() -> ParallelThreadIdNode
1465
+ for i in cython.parallel.prange(...): -> ParallelRangeNode
1466
+ ...
1467
+ """
1468
+
1469
+ # a list of names, maps 'cython.parallel.prange' in the code to
1470
+ # ['cython', 'parallel', 'prange']
1471
+ parallel_directive = None
1472
+
1473
+ # Indicates whether a namenode in an expression is the cython module
1474
+ namenode_is_cython_module = False
1475
+
1476
+ # Keep track of whether we are the context manager of a 'with' statement
1477
+ in_context_manager_section = False
1478
+
1479
+ # One of 'prange' or 'with parallel'. This is used to disallow closely
1480
+ # nested 'with parallel:' blocks
1481
+ state = None
1482
+
1483
+ directive_to_node = {
1484
+ "cython.parallel.parallel": Nodes.ParallelWithBlockNode,
1485
+ # u"cython.parallel.threadsavailable": ExprNodes.ParallelThreadsAvailableNode,
1486
+ "cython.parallel.threadid": ExprNodes.ParallelThreadIdNode,
1487
+ "cython.parallel.prange": Nodes.ParallelRangeNode,
1488
+ }
1489
+
1490
+ def node_is_parallel_directive(self, node):
1491
+ return node.name in self.parallel_directives or node.is_cython_module
1492
+
1493
+ def get_directive_class_node(self, node):
1494
+ """
1495
+ Figure out which parallel directive was used and return the associated
1496
+ Node class.
1497
+
1498
+ E.g. for a cython.parallel.prange() call we return ParallelRangeNode
1499
+ """
1500
+ if self.namenode_is_cython_module:
1501
+ directive = '.'.join(self.parallel_directive)
1502
+ else:
1503
+ directive = self.parallel_directives[self.parallel_directive[0]]
1504
+ directive = '%s.%s' % (directive,
1505
+ '.'.join(self.parallel_directive[1:]))
1506
+ directive = directive.rstrip('.')
1507
+
1508
+ cls = self.directive_to_node.get(directive)
1509
+ if cls is None and not (self.namenode_is_cython_module and
1510
+ self.parallel_directive[0] != 'parallel'):
1511
+ error(node.pos, "Invalid directive: %s" % directive)
1512
+
1513
+ self.namenode_is_cython_module = False
1514
+ self.parallel_directive = None
1515
+
1516
+ return cls
1517
+
1518
+ def visit_ModuleNode(self, node):
1519
+ """
1520
+ If any parallel directives were imported, copy them over and visit
1521
+ the AST
1522
+ """
1523
+ if node.parallel_directives:
1524
+ self.parallel_directives = node.parallel_directives
1525
+ return self.visit_Node(node)
1526
+
1527
+ # No parallel directives were imported, so they can't be used :)
1528
+ return node
1529
+
1530
+ def visit_NameNode(self, node):
1531
+ if self.node_is_parallel_directive(node):
1532
+ self.parallel_directive = [node.name]
1533
+ self.namenode_is_cython_module = node.is_cython_module
1534
+ return node
1535
+
1536
+ def visit_AttributeNode(self, node):
1537
+ self.visitchildren(node)
1538
+ if self.parallel_directive:
1539
+ self.parallel_directive.append(node.attribute)
1540
+ return node
1541
+
1542
+ def visit_CallNode(self, node):
1543
+ self.visitchild(node, 'function')
1544
+ if not self.parallel_directive:
1545
+ self.visitchildren(node, exclude=('function',))
1546
+ return node
1547
+
1548
+ # We are a parallel directive, replace this node with the
1549
+ # corresponding ParallelSomethingSomething node
1550
+
1551
+ if isinstance(node, ExprNodes.GeneralCallNode):
1552
+ args = node.positional_args.args
1553
+ kwargs = node.keyword_args
1554
+ else:
1555
+ args = node.args
1556
+ kwargs = {}
1557
+
1558
+ parallel_directive_class = self.get_directive_class_node(node)
1559
+ if parallel_directive_class:
1560
+ # Note: in case of a parallel() the body is set by
1561
+ # visit_WithStatNode
1562
+ node = parallel_directive_class(node.pos, args=args, kwargs=kwargs)
1563
+
1564
+ return node
1565
+
1566
+ def visit_WithStatNode(self, node):
1567
+ "Rewrite with cython.parallel.parallel() blocks"
1568
+ newnode = self.visit(node.manager)
1569
+
1570
+ if isinstance(newnode, Nodes.ParallelWithBlockNode):
1571
+ if self.state == 'parallel with':
1572
+ error(node.manager.pos,
1573
+ "Nested parallel with blocks are disallowed")
1574
+
1575
+ self.state = 'parallel with'
1576
+ body = self.visitchild(node, 'body')
1577
+ self.state = None
1578
+
1579
+ newnode.body = body
1580
+ return newnode
1581
+ elif self.parallel_directive:
1582
+ parallel_directive_class = self.get_directive_class_node(node)
1583
+
1584
+ if not parallel_directive_class:
1585
+ # There was an error, stop here and now
1586
+ return None
1587
+
1588
+ if parallel_directive_class is Nodes.ParallelWithBlockNode:
1589
+ error(node.pos, "The parallel directive must be called")
1590
+ return None
1591
+
1592
+ self.visitchild(node, 'body')
1593
+ return node
1594
+
1595
+ def visit_ForInStatNode(self, node):
1596
+ "Rewrite 'for i in cython.parallel.prange(...):'"
1597
+ self.visitchild(node, 'iterator')
1598
+ self.visitchild(node, 'target')
1599
+
1600
+ in_prange = isinstance(node.iterator.sequence,
1601
+ Nodes.ParallelRangeNode)
1602
+ previous_state = self.state
1603
+
1604
+ if in_prange:
1605
+ # This will replace the entire ForInStatNode, so copy the
1606
+ # attributes
1607
+ parallel_range_node = node.iterator.sequence
1608
+
1609
+ parallel_range_node.target = node.target
1610
+ parallel_range_node.body = node.body
1611
+ parallel_range_node.else_clause = node.else_clause
1612
+
1613
+ node = parallel_range_node
1614
+
1615
+ if not isinstance(node.target, ExprNodes.NameNode):
1616
+ error(node.target.pos,
1617
+ "Can only iterate over an iteration variable")
1618
+
1619
+ self.state = 'prange'
1620
+
1621
+ self.visitchild(node, 'body')
1622
+ self.state = previous_state
1623
+ self.visitchild(node, 'else_clause')
1624
+ return node
1625
+
1626
+ def visit(self, node):
1627
+ "Visit a node that may be None"
1628
+ if node is not None:
1629
+ return super().visit(node)
1630
+
1631
+
1632
+ class WithTransform(VisitorTransform, SkipDeclarations):
1633
+ def visit_WithStatNode(self, node):
1634
+ self.visitchildren(node, ['body'])
1635
+ pos = node.pos
1636
+ is_async = node.is_async
1637
+ body, target, manager = node.body, node.target, node.manager
1638
+ manager = node.manager = ExprNodes.ProxyNode(manager)
1639
+ node.enter_call = ExprNodes.SimpleCallNode(
1640
+ pos, function=ExprNodes.AttributeNode(
1641
+ pos, obj=ExprNodes.CloneNode(manager),
1642
+ attribute=EncodedString('__aenter__' if is_async else '__enter__'),
1643
+ is_special_lookup=True),
1644
+ args=[],
1645
+ is_temp=True)
1646
+
1647
+ if is_async:
1648
+ node.enter_call = ExprNodes.AwaitExprNode(pos, arg=node.enter_call)
1649
+
1650
+ if target is not None:
1651
+ body = Nodes.StatListNode(
1652
+ pos, stats=[
1653
+ Nodes.WithTargetAssignmentStatNode(
1654
+ pos, lhs=target, with_node=node),
1655
+ body])
1656
+
1657
+ excinfo_target = ExprNodes.TupleNode(pos, slow=True, args=[
1658
+ ExprNodes.ExcValueNode(pos) for _ in range(3)])
1659
+ except_clause = Nodes.ExceptClauseNode(
1660
+ pos, body=Nodes.IfStatNode(
1661
+ pos, if_clauses=[
1662
+ Nodes.IfClauseNode(
1663
+ pos, condition=ExprNodes.NotNode(
1664
+ pos, operand=ExprNodes.WithExitCallNode(
1665
+ pos, with_stat=node,
1666
+ test_if_run=False,
1667
+ args=excinfo_target,
1668
+ await_expr=ExprNodes.AwaitExprNode(pos, arg=None) if is_async else None)),
1669
+ body=Nodes.ReraiseStatNode(pos),
1670
+ ),
1671
+ ],
1672
+ else_clause=None),
1673
+ pattern=None,
1674
+ target=None,
1675
+ excinfo_target=excinfo_target,
1676
+ )
1677
+
1678
+ node.body = Nodes.TryFinallyStatNode(
1679
+ pos, body=Nodes.TryExceptStatNode(
1680
+ pos, body=body,
1681
+ except_clauses=[except_clause],
1682
+ else_clause=None,
1683
+ ),
1684
+ finally_clause=Nodes.ExprStatNode(
1685
+ pos, expr=ExprNodes.WithExitCallNode(
1686
+ pos, with_stat=node,
1687
+ test_if_run=True,
1688
+ args=ExprNodes.TupleNode(
1689
+ pos, args=[ExprNodes.NoneNode(pos) for _ in range(3)]),
1690
+ await_expr=ExprNodes.AwaitExprNode(pos, arg=None) if is_async else None)),
1691
+ handle_error_case=False,
1692
+ )
1693
+ return node
1694
+
1695
+ def visit_ExprNode(self, node):
1696
+ # With statements are never inside expressions.
1697
+ return node
1698
+
1699
+ visit_Node = VisitorTransform.recurse_to_children
1700
+
1701
+
1702
+ class _GeneratorExpressionArgumentsMarker(TreeVisitor, SkipDeclarations):
1703
+ # called from "MarkClosureVisitor"
1704
+ def __init__(self, gen_expr):
1705
+ super().__init__()
1706
+ self.gen_expr = gen_expr
1707
+
1708
+ def visit_ExprNode(self, node):
1709
+ if not node.is_literal:
1710
+ # Don't bother tagging literal nodes
1711
+ assert (not node.generator_arg_tag) # nobody has tagged this first
1712
+ node.generator_arg_tag = self.gen_expr
1713
+ self.visitchildren(node)
1714
+
1715
+ def visit_Node(self, node):
1716
+ # We're only interested in the expressions that make up the iterator sequence,
1717
+ # so don't go beyond ExprNodes (e.g. into ForFromStatNode).
1718
+ return
1719
+
1720
+ def visit_GeneratorExpressionNode(self, node):
1721
+ node.generator_arg_tag = self.gen_expr
1722
+ # don't visit children, can't handle overlapping tags
1723
+ # (and assume generator expressions don't end up optimized out in a way
1724
+ # that would require overlapping tags)
1725
+
1726
+
1727
+ class _HandleGeneratorArguments(VisitorTransform, SkipDeclarations):
1728
+ # used from within CreateClosureClasses
1729
+
1730
+ def __call__(self, node):
1731
+ from . import Visitor
1732
+ assert isinstance(node, ExprNodes.GeneratorExpressionNode)
1733
+ self.gen_node = node
1734
+
1735
+ self.args = list(node.def_node.args)
1736
+ self.call_parameters = list(node.call_parameters)
1737
+ self.tag_count = 0
1738
+ self.substitutions = {}
1739
+
1740
+ self.visitchildren(node)
1741
+
1742
+ for k, v in self.substitutions.items():
1743
+ # doing another search for replacements here (at the end) allows us to sweep up
1744
+ # CloneNodes too (which are often generated by the optimizer)
1745
+ # (it could arguably be done more efficiently with a single traversal though)
1746
+ Visitor.recursively_replace_node(node, k, v)
1747
+
1748
+ node.def_node.args = self.args
1749
+ node.call_parameters = self.call_parameters
1750
+ return node
1751
+
1752
+ def visit_GeneratorExpressionNode(self, node):
1753
+ # a generator can also be substituted itself, so handle that case
1754
+ new_node = self._handle_ExprNode(node, do_visit_children=False)
1755
+ # However do not traverse into it. A new _HandleGeneratorArguments visitor will be used
1756
+ # elsewhere to do that.
1757
+ return node
1758
+
1759
+ def _handle_ExprNode(self, node, do_visit_children):
1760
+ if (node.generator_arg_tag is not None and self.gen_node is not None and
1761
+ self.gen_node == node.generator_arg_tag):
1762
+ pos = node.pos
1763
+ # The reason for using ".x" as the name is that this is how CPython
1764
+ # tracks internal variables in loops (e.g.
1765
+ # { locals() for v in range(10) }
1766
+ # will produce "v" and ".0"). We don't replicate this behaviour completely
1767
+ # but use it as a starting point
1768
+ name_source = self.tag_count
1769
+ self.tag_count += 1
1770
+ name = EncodedString(".{}".format(name_source))
1771
+ def_node = self.gen_node.def_node
1772
+ if not def_node.local_scope.lookup_here(name):
1773
+ from . import Symtab
1774
+ cname = EncodedString(Naming.genexpr_arg_prefix + Symtab.punycodify_name(str(name_source)))
1775
+ name_decl = Nodes.CNameDeclaratorNode(pos=pos, name=name)
1776
+ type = node.type
1777
+
1778
+ # strip away cv types - they shouldn't be applied to the
1779
+ # function argument or to the closure struct.
1780
+ # It isn't obvious whether the right thing to do would be to capture by reference or by
1781
+ # value (C++ itself doesn't know either for lambda functions and forces a choice).
1782
+ # However, capture by reference involves converting to FakeReference which would require
1783
+ # re-analysing AttributeNodes. Therefore I've picked capture-by-value out of convenience
1784
+ # TODO - could probably be optimized by making the arg a reference but the closure not
1785
+ # (see https://github.com/cython/cython/issues/2468)
1786
+ type = PyrexTypes.remove_cv_ref(type, remove_fakeref=False)
1787
+
1788
+ name_decl.type = type
1789
+ new_arg = Nodes.CArgDeclNode(pos=pos, declarator=name_decl,
1790
+ base_type=None, default=None, annotation=None)
1791
+ new_arg.name = name_decl.name
1792
+ new_arg.type = type
1793
+
1794
+ self.args.append(new_arg)
1795
+ node.generator_arg_tag = None # avoid the possibility of this being caught again
1796
+ self.call_parameters.append(node)
1797
+ new_arg.entry = def_node.declare_argument(def_node.local_scope, new_arg)
1798
+ new_arg.entry.cname = cname
1799
+ new_arg.entry.in_closure = True
1800
+
1801
+ if do_visit_children:
1802
+ # now visit the Nodes's children (but remove self.gen_node to not to further
1803
+ # argument substitution)
1804
+ gen_node, self.gen_node = self.gen_node, None
1805
+ self.visitchildren(node)
1806
+ self.gen_node = gen_node
1807
+
1808
+ # replace the node inside the generator with a looked-up name
1809
+ # (initialized_check can safely be False because the source variable will be checked
1810
+ # before it is captured if the check is required)
1811
+ name_node = ExprNodes.NameNode(pos, name=name, initialized_check=False)
1812
+ name_node.entry = self.gen_node.def_node.gbody.local_scope.lookup(name_node.name)
1813
+ name_node.type = name_node.entry.type
1814
+ self.substitutions[node] = name_node
1815
+ return name_node
1816
+ if do_visit_children:
1817
+ self.visitchildren(node)
1818
+ return node
1819
+
1820
+ def visit_ExprNode(self, node):
1821
+ return self._handle_ExprNode(node, True)
1822
+
1823
+ visit_Node = VisitorTransform.recurse_to_children
1824
+
1825
+
1826
+ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
1827
+ """
1828
+ Transforms method decorators in cdef classes into nested calls or properties.
1829
+
1830
+ Python-style decorator properties are transformed into a PropertyNode
1831
+ with up to the three getter, setter and deleter DefNodes.
1832
+ The functional style isn't supported yet.
1833
+ """
1834
+ _properties = None
1835
+
1836
+ _map_property_attribute = {
1837
+ 'getter': EncodedString('__get__'),
1838
+ 'setter': EncodedString('__set__'),
1839
+ 'deleter': EncodedString('__del__'),
1840
+ }.get
1841
+
1842
+ def visit_CClassDefNode(self, node):
1843
+ if self._properties is None:
1844
+ self._properties = []
1845
+ self._properties.append({})
1846
+ node = super().visit_CClassDefNode(node)
1847
+ self._properties.pop()
1848
+ return node
1849
+
1850
+ def visit_PropertyNode(self, node):
1851
+ # Low-level warning for other code until we can convert all our uses over.
1852
+ level = 2 if isinstance(node.pos[0], str) else 0
1853
+ warning(node.pos, "'property %s:' syntax is deprecated, use '@property'" % node.name, level)
1854
+ return node
1855
+
1856
+ def visit_CFuncDefNode(self, node):
1857
+ node = self.visit_FuncDefNode(node)
1858
+ if not node.decorators:
1859
+ return node
1860
+ elif self.scope_type != 'cclass' or self.scope_node.visibility != "extern":
1861
+ # at the moment cdef functions are very restricted in what decorators they can take
1862
+ # so it's simple to test for the small number of allowed decorators....
1863
+ if not (len(node.decorators) == 1 and node.decorators[0].decorator.is_name and
1864
+ node.decorators[0].decorator.name == "staticmethod"):
1865
+ error(node.decorators[0].pos, "Cdef functions cannot take arbitrary decorators.")
1866
+ return node
1867
+
1868
+ ret_node = node
1869
+ decorator_node = self._find_property_decorator(node)
1870
+ if decorator_node and (name := node.declared_name()):
1871
+ if decorator_node.decorator.is_name:
1872
+ ret_node = self._add_property(node, name, decorator_node)
1873
+ else:
1874
+ handler_name = self._map_property_attribute(decorator_node.decorator.attribute)
1875
+ if handler_name:
1876
+ if handler_name == "__del__":
1877
+ error(decorator_node.pos,
1878
+ "Cannot have deleter for C property")
1879
+ ret_node = None
1880
+ elif decorator_node.decorator.obj.name != name:
1881
+ # CPython does not generate an error or warning, but not something useful either.
1882
+ error(decorator_node.pos,
1883
+ "Mismatching C property names, expected '%s', got '%s'" % (
1884
+ decorator_node.decorator.obj.name, name))
1885
+ ret_node = None
1886
+ else:
1887
+ ret_node = self._add_to_property(node, handler_name, decorator_node)
1888
+
1889
+ if node.decorators:
1890
+ return self._reject_decorated_property(node, node.decorators[0])
1891
+ return ret_node
1892
+
1893
+ def visit_DefNode(self, node):
1894
+ scope_type = self.scope_type
1895
+ node = self.visit_FuncDefNode(node)
1896
+ if scope_type != 'cclass' or not node.decorators:
1897
+ return node
1898
+
1899
+ # transform @property decorators
1900
+ decorator_node = self._find_property_decorator(node)
1901
+ if decorator_node is not None:
1902
+ decorator = decorator_node.decorator
1903
+ if decorator.is_name:
1904
+ return self._add_property(node, node.name, decorator_node)
1905
+ else:
1906
+ handler_name = self._map_property_attribute(decorator.attribute)
1907
+ if handler_name:
1908
+ if decorator.obj.name != node.name:
1909
+ # CPython does not generate an error or warning, but not something useful either.
1910
+ error(decorator_node.pos,
1911
+ "Mismatching property names, expected '%s', got '%s'" % (
1912
+ decorator.obj.name, node.name))
1913
+ elif len(node.decorators) > 1:
1914
+ return self._reject_decorated_property(node, decorator_node)
1915
+ else:
1916
+ return self._add_to_property(node, handler_name, decorator_node)
1917
+
1918
+ # we clear node.decorators, so we need to set the
1919
+ # is_staticmethod/is_classmethod attributes now
1920
+ for decorator in node.decorators:
1921
+ func = decorator.decorator
1922
+ if func.is_name:
1923
+ node.is_classmethod |= func.name == 'classmethod'
1924
+ node.is_staticmethod |= func.name == 'staticmethod'
1925
+
1926
+ # transform normal decorators
1927
+ decs = node.decorators
1928
+ node.decorators = None
1929
+ return self.chain_decorators(node, decs, node.name)
1930
+
1931
+ def _find_property_decorator(self, node):
1932
+ properties = self._properties[-1]
1933
+ for decorator_node in node.decorators[::-1]:
1934
+ decorator = decorator_node.decorator
1935
+ if decorator.is_name and decorator.name == 'property':
1936
+ # @property
1937
+ return decorator_node
1938
+ elif decorator.is_attribute and decorator.obj.name in properties:
1939
+ # @prop.setter etc.
1940
+ return decorator_node
1941
+ return None
1942
+
1943
+ @staticmethod
1944
+ def _reject_decorated_property(node, decorator_node):
1945
+ # restrict transformation to outermost decorator as wrapped properties will probably not work
1946
+ for deco in node.decorators:
1947
+ if deco != decorator_node:
1948
+ error(deco.pos, "Property methods with additional decorators are not supported")
1949
+ return node
1950
+
1951
+ def _get_property_function_name(self, node):
1952
+ if isinstance(node, Nodes.CFuncDefNode):
1953
+ return node.declared_name()
1954
+ return node.name
1955
+
1956
+ def _rename_property_function(self, node, name):
1957
+ if isinstance(node, Nodes.CFuncDefNode):
1958
+ node.declarator.set_declared_name(name)
1959
+ else:
1960
+ node.name = name
1961
+
1962
+ def _add_property(self, node, name, decorator_node):
1963
+ if len(node.decorators) > 1:
1964
+ return self._reject_decorated_property(node, decorator_node)
1965
+ node.decorators.remove(decorator_node)
1966
+ properties = self._properties[-1]
1967
+ is_cproperty = isinstance(node, Nodes.CFuncDefNode)
1968
+ body = Nodes.StatListNode(node.pos, stats=[node])
1969
+ node_type = Nodes.PropertyNode
1970
+ if is_cproperty:
1971
+ if 'inline' not in node.modifiers:
1972
+ error(node.pos, "C property method must be declared 'inline'")
1973
+ node_type = Nodes.CPropertyNode
1974
+ if name in properties:
1975
+ prop = properties[name]
1976
+ if prop.is_cproperty:
1977
+ error(node.pos, "C property redeclared")
1978
+ else:
1979
+ self._rename_property_function(node, EncodedString("__get__"))
1980
+ prop.pos = node.pos
1981
+ prop.doc = node.doc
1982
+ prop.body.stats = [node]
1983
+ return None
1984
+ else:
1985
+ self._rename_property_function(node, EncodedString("__get__"))
1986
+ prop = node_type(
1987
+ node.pos, name=name, doc=node.doc, body=body)
1988
+ properties[name] = prop
1989
+ return prop
1990
+
1991
+ def _add_to_property(self, node, name, decorator):
1992
+ properties = self._properties[-1]
1993
+ prop = properties[self._get_property_function_name(node)]
1994
+ self._rename_property_function(node, name)
1995
+ if isinstance(node, Nodes.CFuncDefNode):
1996
+ if 'inline' not in node.modifiers:
1997
+ error(node.pos, "C property method must be declared 'inline'")
1998
+ node.decorators.remove(decorator)
1999
+ stats = prop.body.stats
2000
+ for i, stat in enumerate(stats):
2001
+ if self._get_property_function_name(stat) == name:
2002
+ if prop.is_cproperty:
2003
+ error(node.pos, "C property redeclared")
2004
+ return None
2005
+ stats[i] = node
2006
+ break
2007
+ else:
2008
+ stats.append(node)
2009
+ return None
2010
+
2011
+ @staticmethod
2012
+ def chain_decorators(node, decorators, name):
2013
+ """
2014
+ Decorators are applied directly in DefNode and PyClassDefNode to avoid
2015
+ reassignments to the function/class name - except for cdef class methods.
2016
+ For those, the reassignment is required as methods are originally
2017
+ defined in the PyMethodDef struct.
2018
+
2019
+ The IndirectionNode allows DefNode to override the decorator.
2020
+ """
2021
+ decorator_result = ExprNodes.NameNode(node.pos, name=name)
2022
+ for decorator in decorators[::-1]:
2023
+ decorator_result = ExprNodes.SimpleCallNode(
2024
+ decorator.pos,
2025
+ function=decorator.decorator,
2026
+ args=[decorator_result])
2027
+
2028
+ name_node = ExprNodes.NameNode(node.pos, name=name)
2029
+ reassignment = Nodes.SingleAssignmentNode(
2030
+ node.pos,
2031
+ lhs=name_node,
2032
+ rhs=decorator_result)
2033
+
2034
+ reassignment = Nodes.IndirectionNode([reassignment])
2035
+ node.decorator_indirection = reassignment
2036
+ return [node, reassignment]
2037
+
2038
+
2039
+ class CnameDirectivesTransform(CythonTransform, SkipDeclarations):
2040
+ """
2041
+ Only part of the CythonUtilityCode pipeline. Must be run before
2042
+ DecoratorTransform in case this is a decorator for a cdef class.
2043
+ It filters out @cname('my_cname') decorators and rewrites them to
2044
+ CnameDecoratorNodes.
2045
+ """
2046
+
2047
+ def handle_function(self, node):
2048
+ if not getattr(node, 'decorators', None):
2049
+ return self.visit_Node(node)
2050
+
2051
+ for i, decorator in enumerate(node.decorators):
2052
+ decorator = decorator.decorator
2053
+
2054
+ if (isinstance(decorator, ExprNodes.CallNode) and
2055
+ decorator.function.is_name and
2056
+ decorator.function.name == 'cname'):
2057
+ args, kwargs = decorator.explicit_args_kwds()
2058
+
2059
+ if kwargs:
2060
+ raise AssertionError(
2061
+ "cname decorator does not take keyword arguments")
2062
+
2063
+ if len(args) != 1:
2064
+ raise AssertionError(
2065
+ "cname decorator takes exactly one argument")
2066
+
2067
+ if not (args[0].is_literal and args[0].type is Builtin.unicode_type):
2068
+ raise AssertionError(
2069
+ "argument to cname decorator must be a string literal")
2070
+
2071
+ cname = args[0].compile_time_value(None)
2072
+ del node.decorators[i]
2073
+ node = Nodes.CnameDecoratorNode(pos=node.pos, node=node,
2074
+ cname=cname)
2075
+ break
2076
+
2077
+ return self.visit_Node(node)
2078
+
2079
+ visit_FuncDefNode = handle_function
2080
+ visit_CClassDefNode = handle_function
2081
+ visit_CEnumDefNode = handle_function
2082
+ visit_CStructOrUnionDefNode = handle_function
2083
+ visit_CVarDefNode = handle_function
2084
+
2085
+
2086
+ class ForwardDeclareTypes(CythonTransform):
2087
+ """
2088
+ Declare all global cdef names that we allow referencing in other places,
2089
+ before declaring everything (else) in source code order.
2090
+ """
2091
+
2092
+ def visit_CompilerDirectivesMixin(self, node):
2093
+ env = self.module_scope
2094
+ with node.apply_directives(env):
2095
+ self.visitchildren(node)
2096
+ return node
2097
+
2098
+ def visit_ModuleNode(self, node):
2099
+ self.module_scope = node.scope
2100
+ self.module_scope.directives = node.directives
2101
+ self.visitchildren(node)
2102
+ return node
2103
+
2104
+ def visit_CDefExternNode(self, node):
2105
+ old_cinclude_flag = self.module_scope.in_cinclude
2106
+ self.module_scope.in_cinclude = 1
2107
+ self.visitchildren(node)
2108
+ self.module_scope.in_cinclude = old_cinclude_flag
2109
+ return node
2110
+
2111
+ def visit_CEnumDefNode(self, node):
2112
+ node.declare(self.module_scope)
2113
+ return node
2114
+
2115
+ def visit_CStructOrUnionDefNode(self, node):
2116
+ if node.name not in self.module_scope.entries:
2117
+ node.declare(self.module_scope)
2118
+ return node
2119
+
2120
+ def visit_CClassDefNode(self, node):
2121
+ if node.class_name not in self.module_scope.entries:
2122
+ node.declare(self.module_scope)
2123
+ # Expand fused methods of .pxd declared types to construct the final vtable order.
2124
+ type = self.module_scope.entries[node.class_name].type
2125
+ if type is not None and type.is_extension_type and not type.is_builtin_type and type.scope:
2126
+ scope = type.scope
2127
+ for entry in scope.cfunc_entries:
2128
+ if entry.type and entry.type.is_fused:
2129
+ entry.type.get_all_specialized_function_types()
2130
+ return node
2131
+
2132
+ def visit_FuncDefNode(self, node):
2133
+ # no traversal needed
2134
+ return node
2135
+
2136
+ def visit_PyClassDefNode(self, node):
2137
+ # no traversal needed
2138
+ return node
2139
+
2140
+
2141
+ class AnalyseDeclarationsTransform(EnvTransform):
2142
+
2143
+ basic_property = TreeFragment("""
2144
+ property NAME:
2145
+ def __get__(self):
2146
+ with CRITICAL_SECTION(self):
2147
+ return ATTR
2148
+ def __set__(self, value):
2149
+ with CRITICAL_SECTION(self):
2150
+ ATTR = value
2151
+ """, level='c_class', pipeline=[NormalizeTree(None)])
2152
+ basic_pyobject_property = TreeFragment("""
2153
+ property NAME:
2154
+ def __get__(self):
2155
+ with CRITICAL_SECTION(self):
2156
+ return ATTR
2157
+ def __set__(self, value):
2158
+ with CRITICAL_SECTION(self):
2159
+ ATTR = value
2160
+ def __del__(self):
2161
+ with CRITICAL_SECTION(self):
2162
+ ATTR = None
2163
+ """, level='c_class', pipeline=[NormalizeTree(None)])
2164
+ basic_property_ro = TreeFragment("""
2165
+ property NAME:
2166
+ def __get__(self):
2167
+ with CRITICAL_SECTION(self):
2168
+ return ATTR
2169
+ """, level='c_class', pipeline=[NormalizeTree(None)])
2170
+
2171
+ struct_or_union_wrapper = TreeFragment("""
2172
+ cdef class NAME:
2173
+ cdef TYPE value
2174
+ def __init__(self, MEMBER=None):
2175
+ cdef int count
2176
+ count = 0
2177
+ INIT_ASSIGNMENTS
2178
+ if IS_UNION and count > 1:
2179
+ raise ValueError, "At most one union member should be specified."
2180
+ def __str__(self):
2181
+ return STR_FORMAT % MEMBER_TUPLE
2182
+ def __repr__(self):
2183
+ return REPR_FORMAT % MEMBER_TUPLE
2184
+ """, pipeline=[NormalizeTree(None)])
2185
+
2186
+ init_assignment = TreeFragment("""
2187
+ if VALUE is not None:
2188
+ ATTR = VALUE
2189
+ count += 1
2190
+ """, pipeline=[NormalizeTree(None)])
2191
+
2192
+ fused_function = None
2193
+ in_lambda = 0
2194
+
2195
+ def __call__(self, root):
2196
+ # needed to determine if a cdef var is declared after it's used.
2197
+ self.seen_vars_stack = []
2198
+ self.fused_error_funcs = set()
2199
+ super_class = super()
2200
+ self._super_visit_FuncDefNode = super_class.visit_FuncDefNode
2201
+ return super_class.__call__(root)
2202
+
2203
+ def visit_NameNode(self, node):
2204
+ self.seen_vars_stack[-1].add(node.name)
2205
+ return node
2206
+
2207
+ def visit_ModuleNode(self, node):
2208
+ # Pickling support requires injecting module-level nodes.
2209
+ self.extra_module_declarations = []
2210
+ self.seen_vars_stack.append(set())
2211
+ node.analyse_declarations(self.current_env())
2212
+ self.visitchildren(node)
2213
+ self.seen_vars_stack.pop()
2214
+ node.body.stats.extend(self.extra_module_declarations)
2215
+ return node
2216
+
2217
+ def visit_LambdaNode(self, node):
2218
+ self.in_lambda += 1
2219
+ node.analyse_declarations(self.current_env())
2220
+ self.visitchildren(node)
2221
+ self.in_lambda -= 1
2222
+ return node
2223
+
2224
+ def visit_CClassDefNode(self, node):
2225
+ node = self.visit_ClassDefNode(node)
2226
+ if node.scope and 'dataclasses.dataclass' in node.scope.directives:
2227
+ from .Dataclass import handle_cclass_dataclass
2228
+ handle_cclass_dataclass(node, node.scope.directives['dataclasses.dataclass'], self)
2229
+ if node.scope and node.scope.implemented and node.body:
2230
+ stats = []
2231
+ for entry in node.scope.var_entries:
2232
+ if entry.needs_property:
2233
+ property = self.create_Property(entry)
2234
+ property.analyse_declarations(node.scope)
2235
+ self.visit(property)
2236
+ stats.append(property)
2237
+ if stats:
2238
+ node.body.stats += stats
2239
+ if (node.visibility != 'extern'
2240
+ and not node.scope.lookup('__reduce__')
2241
+ and not node.scope.lookup('__reduce_ex__')):
2242
+ self._inject_pickle_methods(node)
2243
+ return node
2244
+
2245
+ def _inject_pickle_methods(self, node):
2246
+ env = self.current_env()
2247
+ if node.scope.directives['auto_pickle'] is False: # None means attempt it.
2248
+ # Old behavior of not doing anything.
2249
+ return
2250
+ auto_pickle_forced = node.scope.directives['auto_pickle'] is True
2251
+
2252
+ all_members = []
2253
+ cls = node.entry.type
2254
+ cinit = None
2255
+ inherited_reduce = None
2256
+ while cls is not None:
2257
+ all_members.extend(e for e in cls.scope.var_entries if e.name not in ('__weakref__', '__dict__'))
2258
+ cinit = cinit or cls.scope.lookup('__cinit__')
2259
+ inherited_reduce = inherited_reduce or cls.scope.lookup('__reduce__') or cls.scope.lookup('__reduce_ex__')
2260
+ cls = cls.base_type
2261
+ all_members.sort(key=lambda e: e.name)
2262
+
2263
+ if inherited_reduce:
2264
+ # This is not failsafe, as we may not know whether a cimported class defines a __reduce__.
2265
+ # This is why we define __reduce_cython__ and only replace __reduce__
2266
+ # (via ExtensionTypes.SetupReduce utility code) at runtime on class creation.
2267
+ return
2268
+
2269
+ non_py = [
2270
+ e for e in all_members
2271
+ if not e.type.is_pyobject and (not e.type.can_coerce_to_pyobject(env)
2272
+ or not e.type.can_coerce_from_pyobject(env))
2273
+ ]
2274
+
2275
+ structs = [e for e in all_members if e.type.is_struct_or_union]
2276
+
2277
+ if cinit or non_py or (structs and not auto_pickle_forced):
2278
+ if cinit:
2279
+ # TODO(robertwb): We could allow this if __cinit__ has no require arguments.
2280
+ msg = 'no default __reduce__ due to non-trivial __cinit__'
2281
+ elif non_py:
2282
+ msg = "%s cannot be converted to a Python object for pickling" % ','.join("self.%s" % e.name for e in non_py)
2283
+ else:
2284
+ # Extern structs may be only partially defined.
2285
+ # TODO(robertwb): Limit the restriction to extern
2286
+ # (and recursively extern-containing) structs.
2287
+ msg = ("Pickling of struct members such as %s must be explicitly requested "
2288
+ "with @auto_pickle(True)" % ','.join("self.%s" % e.name for e in structs))
2289
+
2290
+ if auto_pickle_forced:
2291
+ error(node.pos, msg)
2292
+
2293
+ pickle_func = TreeFragment("""
2294
+ def __reduce_cython__(self):
2295
+ raise TypeError, "%(msg)s"
2296
+ def __setstate_cython__(self, __pyx_state):
2297
+ raise TypeError, "%(msg)s"
2298
+ """ % {'msg': msg},
2299
+ level='c_class', pipeline=[NormalizeTree(None)]).substitute({})
2300
+ pickle_func.analyse_declarations(node.scope)
2301
+ self.visit(pickle_func)
2302
+ node.body.stats.append(pickle_func)
2303
+
2304
+ else:
2305
+ for e in all_members:
2306
+ if not e.type.is_pyobject:
2307
+ e.type.create_to_py_utility_code(env)
2308
+ e.type.create_from_py_utility_code(env)
2309
+
2310
+ all_members_names = [e.name for e in all_members]
2311
+ assignments = '; '.join([
2312
+ '__pyx_result.%s = __pyx_state[%s]' % (v, ix)
2313
+ for ix, v in enumerate(all_members_names)
2314
+ ])
2315
+ checksums = _calculate_pickle_checksums(all_members_names)
2316
+ if len(checksums) != 3:
2317
+ # If we don't have enough checksums to call the check function, we just repeat the last one.
2318
+ checksums = (checksums + [checksums[-1] * 2])[:3]
2319
+
2320
+ unpickle_func_name = f'__pyx_unpickle_{node.punycode_class_name}'
2321
+ num_members = len(all_members_names)
2322
+
2323
+ env.use_utility_code(Code.UtilityCode.load_cached("UpdateUnpickledDict", "ExtensionTypes.c"))
2324
+
2325
+ # TODO(robertwb): Move the state into the third argument
2326
+ # so it can be pickled *after* self is memoized.
2327
+ unpickle_code = f"""
2328
+ cdef extern from *:
2329
+ int __Pyx_CheckUnpickleChecksum(long, long, long, long, const char*) except -1
2330
+ int __Pyx_UpdateUnpickledDict(object, object, Py_ssize_t) except -1
2331
+
2332
+ def {unpickle_func_name}(__pyx_type, long __pyx_checksum, tuple __pyx_state):
2333
+ cdef object __pyx_result
2334
+ __Pyx_CheckUnpickleChecksum(__pyx_checksum, {', '.join(checksums)}, {', '.join(all_members_names).encode('UTF-8')!r})
2335
+ __pyx_result = {node.class_name}.__new__(__pyx_type)
2336
+ if __pyx_state is not None:
2337
+ {unpickle_func_name}__set_state(<{node.class_name}> __pyx_result, __pyx_state)
2338
+ return __pyx_result
2339
+
2340
+ cdef {unpickle_func_name}__set_state({node.class_name} __pyx_result, __pyx_state: tuple):
2341
+ {assignments}
2342
+ __Pyx_UpdateUnpickledDict(__pyx_result, __pyx_state, {num_members:d})
2343
+ """
2344
+
2345
+ env.use_utility_code(Code.UtilityCode.load_cached("CheckUnpickleChecksum", "ExtensionTypes.c"))
2346
+
2347
+ unpickle_func = TreeFragment(unpickle_code, level='module', pipeline=[NormalizeTree(None)]).substitute({})
2348
+ unpickle_func.analyse_declarations(node.entry.scope)
2349
+
2350
+ self.visit(unpickle_func)
2351
+ self.extra_module_declarations.append(unpickle_func)
2352
+
2353
+ members = ', '.join(f'self.{v}' for v in all_members_names) + (',' if len(all_members_names) == 1 else '')
2354
+ # Even better, we could check PyType_IS_GC.
2355
+ any_notnone_members = ' or '.join([f'self.{e.name} is not None' for e in all_members if e.type.is_pyobject] or ['False'])
2356
+
2357
+ pickle_code = f"""
2358
+ def __reduce_cython__(self):
2359
+ cdef tuple state
2360
+ cdef object _dict
2361
+ cdef bint use_setstate
2362
+ with CRITICAL_SECTION(self):
2363
+ state = ({members})
2364
+ _dict = getattr(self, '__dict__', None)
2365
+ if _dict is not None and _dict:
2366
+ state += (_dict,)
2367
+ use_setstate = True
2368
+ else:
2369
+ use_setstate = {any_notnone_members}
2370
+ if use_setstate:
2371
+ return {unpickle_func_name}, (type(self), {checksums[0]}, None), state
2372
+ else:
2373
+ return {unpickle_func_name}, (type(self), {checksums[0]}, state)
2374
+
2375
+ def __setstate_cython__(self, __pyx_state):
2376
+ {unpickle_func_name}__set_state(self, __pyx_state)
2377
+ """
2378
+
2379
+ pickle_func = TreeFragment(pickle_code, level='c_class', pipeline=[NormalizeTree(None)]).substitute(
2380
+ {'CRITICAL_SECTION': self._create_critical_section_name_node(node.scope, node.pos)}
2381
+ )
2382
+ pickle_func = InterpretCompilerDirectives(None, {})(pickle_func)
2383
+ pickle_func.analyse_declarations(node.scope)
2384
+
2385
+ self.enter_scope(node, node.scope) # functions should be visited in the class scope
2386
+ self.visit(pickle_func)
2387
+ self.exit_scope()
2388
+ node.body.stats.append(pickle_func)
2389
+
2390
+ def _handle_fused_def_decorators(self, old_decorators, env, node):
2391
+ """
2392
+ Create function calls to the decorators and reassignments to
2393
+ the function.
2394
+ """
2395
+ # Delete staticmethod and classmethod decorators, this is
2396
+ # handled directly by the fused function object.
2397
+ decorators = []
2398
+ for decorator in old_decorators:
2399
+ func = decorator.decorator
2400
+ if (not func.is_name or
2401
+ func.name not in ('staticmethod', 'classmethod') or
2402
+ env.lookup_here(func.name)):
2403
+ # not a static or classmethod
2404
+ decorators.append(decorator)
2405
+
2406
+ if decorators:
2407
+ transform = DecoratorTransform(self.context)
2408
+ def_node = node.node
2409
+ _, reassignments = transform.chain_decorators(
2410
+ def_node, decorators, def_node.name)
2411
+ reassignments.analyse_declarations(env)
2412
+ node = [node, reassignments]
2413
+
2414
+ return node
2415
+
2416
+ def _handle_def(self, decorators, env, node):
2417
+ "Handle def or cpdef fused functions"
2418
+ # Create PyCFunction nodes for each specialization
2419
+ node.stats.insert(0, node.py_func)
2420
+ self.visitchild(node, 'py_func')
2421
+ node.update_fused_defnode_entry(env)
2422
+ # For the moment, fused functions do not support METH_FASTCALL
2423
+ call_signature = node.py_func.entry.signature
2424
+ call_signature.use_fastcall = call_signature.FastcallUsed.NO
2425
+ pycfunc = ExprNodes.PyCFunctionNode.from_defnode(node.py_func, binding=True)
2426
+ pycfunc = ExprNodes.ProxyNode(pycfunc.coerce_to_temp(env))
2427
+ node.resulting_fused_function = pycfunc
2428
+ # Create assignment node for our def function
2429
+ node.fused_func_assignment = self._create_assignment(
2430
+ node.py_func, ExprNodes.CloneNode(pycfunc), env)
2431
+
2432
+ if decorators:
2433
+ node = self._handle_fused_def_decorators(decorators, env, node)
2434
+
2435
+ return node
2436
+
2437
+ def _create_fused_function(self, env, node):
2438
+ "Create a fused function for a DefNode with fused arguments"
2439
+ from . import FusedNode
2440
+
2441
+ if self.fused_function or self.in_lambda:
2442
+ if self.fused_function not in self.fused_error_funcs:
2443
+ if self.in_lambda:
2444
+ error(node.pos, "Fused lambdas not allowed")
2445
+ else:
2446
+ error(node.pos, "Cannot nest fused functions")
2447
+
2448
+ self.fused_error_funcs.add(self.fused_function)
2449
+
2450
+ node.body = Nodes.PassStatNode(node.pos)
2451
+ for arg in node.args:
2452
+ if arg.type.is_fused:
2453
+ arg.type = arg.type.get_fused_types()[0]
2454
+
2455
+ return node
2456
+
2457
+ decorators = getattr(node, 'decorators', None)
2458
+ node = FusedNode.FusedCFuncDefNode(node, env)
2459
+ self.fused_function = node
2460
+ self.visitchildren(node)
2461
+ self.fused_function = None
2462
+ if node.py_func:
2463
+ node = self._handle_def(decorators, env, node)
2464
+
2465
+ return node
2466
+
2467
+ def _handle_fused(self, node):
2468
+ if node.is_generator and node.has_fused_arguments:
2469
+ error(node.pos, "Fused generators not supported")
2470
+ node.has_fused_arguments = False
2471
+ node.gbody.body = Nodes.StatListNode(node.pos, stats=[])
2472
+
2473
+ return node.has_fused_arguments
2474
+
2475
+ def visit_FuncDefNode(self, node):
2476
+ """
2477
+ Analyse a function and its body, as that hasn't happened yet. Also
2478
+ analyse the directive_locals set by @cython.locals().
2479
+
2480
+ Then, if we are a function with fused arguments, replace the function
2481
+ (after it has declared itself in the symbol table!) with a
2482
+ FusedCFuncDefNode, and analyse its children (which are in turn normal
2483
+ functions). If we're a normal function, just analyse the body of the
2484
+ function.
2485
+ """
2486
+ env = self.current_env()
2487
+
2488
+ self.seen_vars_stack.append(set())
2489
+ lenv = node.local_scope
2490
+ node.declare_arguments(lenv)
2491
+
2492
+ # @cython.locals(...)
2493
+ for var, type_node in node.directive_locals.items():
2494
+ if not lenv.lookup_here(var): # don't redeclare args
2495
+ type = type_node.analyse_as_type(lenv)
2496
+ if type and type.is_fused and lenv.fused_to_specific:
2497
+ type = type.specialize(lenv.fused_to_specific)
2498
+ if type:
2499
+ lenv.declare_var(var, type, type_node.pos)
2500
+ else:
2501
+ error(type_node.pos, "Not a type")
2502
+
2503
+ if self._handle_fused(node):
2504
+ node = self._create_fused_function(env, node)
2505
+ else:
2506
+ node.body.analyse_declarations(lenv)
2507
+ node = self._super_visit_FuncDefNode(node)
2508
+
2509
+ self.seen_vars_stack.pop()
2510
+
2511
+ if "ufunc" in lenv.directives:
2512
+ from . import UFuncs
2513
+ return UFuncs.convert_to_ufunc(node)
2514
+ return node
2515
+
2516
+ def visit_DefNode(self, node):
2517
+ node = self.visit_FuncDefNode(node)
2518
+ if not isinstance(node, Nodes.DefNode):
2519
+ return node
2520
+ env = self.current_env()
2521
+ if node.code_object is None:
2522
+ node.code_object = ExprNodes.CodeObjectNode(node)
2523
+ node.code_object.analyse_declarations(env)
2524
+ if node.fused_py_func or node.is_generator_body:
2525
+ return node
2526
+ if not node.needs_assignment_synthesis(env):
2527
+ return node
2528
+ return [node, self._synthesize_assignment(node, env)]
2529
+
2530
+ def visit_CFuncDefNode(self, node):
2531
+ if node.code_object is None and node.py_func is None:
2532
+ node.code_object = ExprNodes.CodeObjectNode.for_cfunc(node)
2533
+ node.code_object.analyse_declarations(self.current_env())
2534
+ return self.visit_FuncDefNode(node)
2535
+
2536
+ def visit_GeneratorBodyDefNode(self, node):
2537
+ return self.visit_FuncDefNode(node)
2538
+
2539
+ def visit_GeneratorDefNode(self, node):
2540
+ # The generator body should use the same code object as the (user facing) generator function that creates it.
2541
+ result = self.visit_DefNode(node)
2542
+ # 'result' will usually be a list of statements, but we still have the original node.
2543
+ node.gbody.code_object = node.code_object
2544
+ return result
2545
+
2546
+ def _synthesize_assignment(self, node, env):
2547
+ # Synthesize assignment node and put it right after defnode
2548
+ genv = env
2549
+ while genv.is_py_class_scope or genv.is_c_class_scope:
2550
+ genv = genv.outer_scope
2551
+
2552
+ binding = env.is_py_class_scope or self.current_directives.get('binding')
2553
+ if genv.is_closure_scope:
2554
+ rhs = node.py_cfunc_node = ExprNodes.InnerFunctionNode.from_defnode(node, binding)
2555
+ else:
2556
+ rhs = ExprNodes.PyCFunctionNode.from_defnode(node, binding)
2557
+
2558
+ node.is_cyfunction = rhs.binding
2559
+ return self._create_assignment(node, rhs, env)
2560
+
2561
+ def _create_assignment(self, def_node, rhs, env):
2562
+ if def_node.decorators:
2563
+ for decorator in def_node.decorators[::-1]:
2564
+ rhs = ExprNodes.SimpleCallNode(
2565
+ decorator.pos,
2566
+ function = decorator.decorator,
2567
+ args = [rhs])
2568
+ def_node.decorators = None
2569
+
2570
+ assmt = Nodes.SingleAssignmentNode(
2571
+ def_node.pos,
2572
+ lhs=ExprNodes.NameNode(def_node.pos, name=def_node.name),
2573
+ rhs=rhs)
2574
+ assmt.analyse_declarations(env)
2575
+ return assmt
2576
+
2577
+ def visit_func_outer_attrs(self, node):
2578
+ # any names in the outer attrs should not be looked up in the function "seen_vars_stack"
2579
+ stack = self.seen_vars_stack.pop()
2580
+ super().visit_func_outer_attrs(node)
2581
+ self.seen_vars_stack.append(stack)
2582
+
2583
+ def visit_ScopedExprNode(self, node):
2584
+ env = self.current_env()
2585
+ node.analyse_declarations(env)
2586
+ # the node may or may not have a local scope
2587
+ if node.expr_scope:
2588
+ self.seen_vars_stack.append(set(self.seen_vars_stack[-1]))
2589
+ self.enter_scope(node, node.expr_scope)
2590
+ node.analyse_scoped_declarations(node.expr_scope)
2591
+ self.visitchildren(node)
2592
+ self.exit_scope()
2593
+ self.seen_vars_stack.pop()
2594
+ else:
2595
+
2596
+ node.analyse_scoped_declarations(env)
2597
+ self.visitchildren(node)
2598
+ return node
2599
+
2600
+ def visit_TempResultFromStatNode(self, node):
2601
+ self.visitchildren(node)
2602
+ node.analyse_declarations(self.current_env())
2603
+ return node
2604
+
2605
+ def visit_CppClassNode(self, node):
2606
+ if node.visibility == 'extern':
2607
+ return None
2608
+ else:
2609
+ return self.visit_ClassDefNode(node)
2610
+
2611
+ def visit_CStructOrUnionDefNode(self, node):
2612
+ # Create a wrapper node if needed.
2613
+ # We want to use the struct type information (so it can't happen
2614
+ # before this phase) but also create new objects to be declared
2615
+ # (so it can't happen later).
2616
+ # Note that we don't return the original node, as it is
2617
+ # never used after this phase.
2618
+ if True: # private (default)
2619
+ return None
2620
+
2621
+ self_value = ExprNodes.AttributeNode(
2622
+ pos = node.pos,
2623
+ obj = ExprNodes.NameNode(pos=node.pos, name="self"),
2624
+ attribute = EncodedString("value"))
2625
+ var_entries = node.entry.type.scope.var_entries
2626
+ attributes = []
2627
+ for entry in var_entries:
2628
+ attributes.append(ExprNodes.AttributeNode(pos = entry.pos,
2629
+ obj = self_value,
2630
+ attribute = entry.name))
2631
+ # __init__ assignments
2632
+ init_assignments = []
2633
+ for entry, attr in zip(var_entries, attributes):
2634
+ # TODO: branch on visibility
2635
+ init_assignments.append(
2636
+ self.init_assignment.substitute(
2637
+ {
2638
+ "VALUE": ExprNodes.NameNode(entry.pos, name = entry.name),
2639
+ "ATTR": attr,
2640
+ },
2641
+ pos=entry.pos,
2642
+ )
2643
+ )
2644
+
2645
+ # create the class
2646
+ str_format = "%s(%s)" % (node.entry.type.name, ("%s, " * len(attributes))[:-2])
2647
+ wrapper_class = self.struct_or_union_wrapper.substitute({
2648
+ "INIT_ASSIGNMENTS": Nodes.StatListNode(node.pos, stats = init_assignments),
2649
+ "IS_UNION": ExprNodes.BoolNode(node.pos, value = not node.entry.type.is_struct),
2650
+ "MEMBER_TUPLE": ExprNodes.TupleNode(node.pos, args=attributes),
2651
+ "STR_FORMAT": ExprNodes.UnicodeNode(node.pos, value = EncodedString(str_format)),
2652
+ "REPR_FORMAT": ExprNodes.UnicodeNode(node.pos, value = EncodedString(str_format.replace("%s", "%r"))),
2653
+ }, pos = node.pos).stats[0]
2654
+ wrapper_class.class_name = node.name
2655
+ wrapper_class.shadow = True
2656
+ class_body = wrapper_class.body.stats
2657
+
2658
+ # fix value type
2659
+ assert isinstance(class_body[0].base_type, Nodes.CSimpleBaseTypeNode)
2660
+ class_body[0].base_type.name = node.name
2661
+
2662
+ # fix __init__ arguments
2663
+ init_method = class_body[1]
2664
+ assert isinstance(init_method, Nodes.DefNode) and init_method.name == '__init__'
2665
+ arg_template = init_method.args[1]
2666
+ if not node.entry.type.is_struct:
2667
+ arg_template.kw_only = True
2668
+ del init_method.args[1]
2669
+ for entry, attr in zip(var_entries, attributes):
2670
+ arg = copy.deepcopy(arg_template)
2671
+ arg.declarator.name = entry.name
2672
+ init_method.args.append(arg)
2673
+
2674
+ # setters/getters
2675
+ for entry, attr in zip(var_entries, attributes):
2676
+ # TODO: branch on visibility
2677
+ if entry.type.is_pyobject:
2678
+ template = self.basic_pyobject_property
2679
+ else:
2680
+ template = self.basic_property
2681
+ property = template.substitute(
2682
+ {
2683
+ "ATTR": attr,
2684
+ },
2685
+ pos=entry.pos,
2686
+ ).stats[0]
2687
+ property.name = entry.name
2688
+ wrapper_class.body.stats.append(property)
2689
+
2690
+ wrapper_class.analyse_declarations(self.current_env())
2691
+ return self.visit_CClassDefNode(wrapper_class)
2692
+
2693
+ # Some nodes are no longer needed after declaration
2694
+ # analysis and can be dropped. The analysis was performed
2695
+ # on these nodes in a separate recursive process from the
2696
+ # enclosing function or module, so we can simply drop them.
2697
+ def visit_CDeclaratorNode(self, node):
2698
+ # necessary to ensure that all CNameDeclaratorNodes are visited.
2699
+ self.visitchildren(node)
2700
+ return node
2701
+
2702
+ def visit_CTypeDefNode(self, node):
2703
+ return node
2704
+
2705
+ def visit_CBaseTypeNode(self, node):
2706
+ return None
2707
+
2708
+ def visit_CEnumDefNode(self, node):
2709
+ if node.visibility == 'public':
2710
+ return node
2711
+ else:
2712
+ return None
2713
+
2714
+ def visit_CNameDeclaratorNode(self, node):
2715
+ if node.name in self.seen_vars_stack[-1]:
2716
+ entry = self.current_env().lookup(node.name)
2717
+ if (entry is None or entry.visibility != 'extern'
2718
+ and not entry.scope.is_c_class_scope):
2719
+ error(node.pos, "cdef variable '%s' declared after it is used" % node.name)
2720
+ self.visitchildren(node)
2721
+ return node
2722
+
2723
+ def visit_CVarDefNode(self, node):
2724
+ # to ensure all CNameDeclaratorNodes are visited.
2725
+ self.visitchildren(node)
2726
+ return None
2727
+
2728
+ def visit_CnameDecoratorNode(self, node):
2729
+ child_node = self.visitchild(node, 'node')
2730
+ if not child_node:
2731
+ return None
2732
+ if type(child_node) is list: # Assignment synthesized
2733
+ node.node = child_node[0]
2734
+ return [node] + child_node[1:]
2735
+ return node
2736
+
2737
+ def create_Property(self, entry):
2738
+ if entry.visibility == 'public':
2739
+ if entry.type.is_pyobject:
2740
+ template = self.basic_pyobject_property
2741
+ else:
2742
+ template = self.basic_property
2743
+ elif entry.visibility == 'readonly':
2744
+ template = self.basic_property_ro
2745
+ property = template.substitute(
2746
+ {
2747
+ "ATTR": ExprNodes.AttributeNode(pos=entry.pos,
2748
+ obj=ExprNodes.NameNode(pos=entry.pos, name="self"),
2749
+ attribute=entry.name),
2750
+ "CRITICAL_SECTION": self._create_critical_section_name_node(self.current_env(), entry.pos)
2751
+ },
2752
+ pos=entry.pos
2753
+ ).stats[0]
2754
+ property = InterpretCompilerDirectives(None, {})(property)
2755
+ property.name = entry.name
2756
+ property.doc = entry.doc
2757
+ return property
2758
+
2759
+ def visit_AssignmentExpressionNode(self, node):
2760
+ self.visitchildren(node)
2761
+ node.analyse_declarations(self.current_env())
2762
+ return node
2763
+
2764
+ def _create_critical_section_name_node(self, env, pos):
2765
+ return ExprNodes.NameNode(
2766
+ pos,
2767
+ name="critical_section",
2768
+ cython_attribute="critical_section"
2769
+ )
2770
+
2771
+
2772
+ def _calculate_pickle_checksums(member_names):
2773
+ # Cython 0.x used MD5 for the checksum, which a few Python installations remove for security reasons.
2774
+ # SHA-256 should be ok for years to come, but early Cython 3.0 alpha releases used SHA-1,
2775
+ # which may not be.
2776
+ member_names_string = ' '.join(member_names).encode('utf-8')
2777
+ checksums = []
2778
+ for algo_name in ['sha256', 'sha1', 'md5']:
2779
+ try:
2780
+ mkchecksum = getattr(hashlib, algo_name)
2781
+ checksum = mkchecksum(member_names_string, usedforsecurity=False).hexdigest()
2782
+ except (AttributeError, ValueError):
2783
+ # The algorithm (i.e. MD5) might not be there at all, or might be blocked at runtime.
2784
+ continue
2785
+ checksums.append('0x' + checksum[:7])
2786
+ return checksums
2787
+
2788
+
2789
+ class CalculateQualifiedNamesTransform(EnvTransform):
2790
+ """
2791
+ Calculate and store the '__qualname__' and the global
2792
+ module name on some nodes.
2793
+ """
2794
+ needs_qualname_assignment = False
2795
+ needs_module_assignment = False
2796
+
2797
+ def visit_ModuleNode(self, node):
2798
+ self.module_name = self.global_scope().qualified_name
2799
+ self.qualified_name = []
2800
+ _super = super()
2801
+ self._super_visit_FuncDefNode = _super.visit_FuncDefNode
2802
+ self._super_visit_ClassDefNode = _super.visit_ClassDefNode
2803
+ self.visitchildren(node)
2804
+ return node
2805
+
2806
+ def _set_qualname(self, node, name=None):
2807
+ if name:
2808
+ qualname = self.qualified_name[:]
2809
+ qualname.append(name)
2810
+ else:
2811
+ qualname = self.qualified_name
2812
+ node.qualname = EncodedString('.'.join(qualname))
2813
+ node.module_name = self.module_name
2814
+
2815
+ def _append_entry(self, entry):
2816
+ if entry.is_pyglobal and not entry.is_pyclass_attr:
2817
+ self.qualified_name = [entry.name]
2818
+ else:
2819
+ self.qualified_name.append(entry.name)
2820
+
2821
+ def visit_ClassNode(self, node):
2822
+ self._set_qualname(node, node.name)
2823
+ self.visitchildren(node)
2824
+ return node
2825
+
2826
+ def visit_PyClassNamespaceNode(self, node):
2827
+ # class name was already added by parent node
2828
+ self._set_qualname(node)
2829
+ self.visitchildren(node)
2830
+ return node
2831
+
2832
+ def visit_PyCFunctionNode(self, node):
2833
+ orig_qualified_name = self.qualified_name[:]
2834
+ if node.def_node.is_wrapper and self.qualified_name and self.qualified_name[-1] == '<locals>':
2835
+ self.qualified_name.pop()
2836
+ self._set_qualname(node)
2837
+ else:
2838
+ self._set_qualname(node, node.def_node.name)
2839
+ self.visitchildren(node)
2840
+ self.qualified_name = orig_qualified_name
2841
+ return node
2842
+
2843
+ def visit_DefNode(self, node):
2844
+ if node.is_wrapper and self.qualified_name:
2845
+ assert self.qualified_name[-1] == '<locals>', self.qualified_name
2846
+ orig_qualified_name = self.qualified_name[:]
2847
+ self.qualified_name.pop()
2848
+ self._set_qualname(node)
2849
+ self._super_visit_FuncDefNode(node)
2850
+ self.qualified_name = orig_qualified_name
2851
+ else:
2852
+ self._set_qualname(node, node.name)
2853
+ self.visit_FuncDefNode(node)
2854
+ return node
2855
+
2856
+ def visit_FuncDefNode(self, node):
2857
+ orig_qualified_name = self.qualified_name[:]
2858
+ if getattr(node, 'name', None) == '<lambda>':
2859
+ self.qualified_name.append('<lambda>')
2860
+ else:
2861
+ self._append_entry(node.entry)
2862
+ self.qualified_name.append('<locals>')
2863
+ self._super_visit_FuncDefNode(node)
2864
+ self.qualified_name = orig_qualified_name
2865
+ return node
2866
+
2867
+ def generate_assignment(self, node, name, value):
2868
+ entry = node.scope.lookup_here(name)
2869
+ lhs = ExprNodes.NameNode(
2870
+ node.pos,
2871
+ name=EncodedString(name),
2872
+ entry=entry,
2873
+ is_target=True)
2874
+ rhs = ExprNodes.UnicodeNode(node.pos, value=value)
2875
+ node.body.stats.insert(0, Nodes.SingleAssignmentNode(
2876
+ node.pos,
2877
+ lhs=lhs,
2878
+ rhs=rhs,
2879
+ ).analyse_expressions(self.current_env()))
2880
+
2881
+ def visit_ClassDefNode(self, node):
2882
+ orig_needs_qualname_assignment = self.needs_qualname_assignment
2883
+ self.needs_qualname_assignment = False
2884
+ orig_needs_module_assignment = self.needs_module_assignment
2885
+ self.needs_module_assignment = False
2886
+ orig_qualified_name = self.qualified_name[:]
2887
+ entry = (getattr(node, 'entry', None) or # PyClass
2888
+ self.current_env().lookup_here(node.target.name)) # CClass
2889
+ self._append_entry(entry)
2890
+ self._super_visit_ClassDefNode(node)
2891
+ if self.needs_qualname_assignment:
2892
+ self.generate_assignment(node, "__qualname__",
2893
+ EncodedString(".".join(self.qualified_name)))
2894
+ if self.needs_module_assignment:
2895
+ self.generate_assignment(node, "__module__",
2896
+ EncodedString(self.module_name))
2897
+ self.qualified_name = orig_qualified_name
2898
+ self.needs_qualname_assignment = orig_needs_qualname_assignment
2899
+ self.needs_module_assignment = orig_needs_module_assignment
2900
+ return node
2901
+
2902
+ def visit_NameNode(self, node):
2903
+ scope = self.current_env()
2904
+ if scope.is_c_class_scope:
2905
+ # unlike for a PyClass scope, these attributes aren't defined in the
2906
+ # dictionary when the class definition is executed, therefore we ask
2907
+ # the compiler to generate an assignment to them at the start of the
2908
+ # body.
2909
+ # NOTE: this doesn't put them in locals()
2910
+ if node.name == "__qualname__":
2911
+ self.needs_qualname_assignment = True
2912
+ elif node.name == "__module__":
2913
+ self.needs_module_assignment = True
2914
+ return node
2915
+
2916
+
2917
+ class AnalyseExpressionsTransform(CythonTransform):
2918
+
2919
+ def visit_ModuleNode(self, node):
2920
+ node.scope.infer_types()
2921
+ node.body = node.body.analyse_expressions(node.scope)
2922
+ self.positions = [{node.pos}]
2923
+ self.visitchildren(node)
2924
+ self._build_positions(node)
2925
+ return node
2926
+
2927
+ def visit_FuncDefNode(self, node):
2928
+ node.local_scope.infer_types()
2929
+ node.body = node.body.analyse_expressions(node.local_scope)
2930
+ self.positions[-1].add(node.pos)
2931
+
2932
+ if node.is_wrapper:
2933
+ # Share positions between function and Python wrapper.
2934
+ local_positions = self.positions[-1]
2935
+ else:
2936
+ local_positions = {node.pos}
2937
+ self.positions.append(local_positions)
2938
+
2939
+ self.visitchildren(node)
2940
+ self._build_positions(node)
2941
+ return node
2942
+
2943
+ def visit_ScopedExprNode(self, node):
2944
+ if node.has_local_scope:
2945
+ node.expr_scope.infer_types()
2946
+ node = node.analyse_scoped_expressions(node.expr_scope)
2947
+ self.visit_ExprNode(node)
2948
+ return node
2949
+
2950
+ def visit_IndexNode(self, node):
2951
+ """
2952
+ Replace index nodes used to specialize cdef functions with fused
2953
+ argument types with the Attribute- or NameNode referring to the
2954
+ function. We then need to copy over the specialization properties to
2955
+ the attribute or name node.
2956
+
2957
+ Because the indexing might be a Python indexing operation on a fused
2958
+ function, or (usually) a Cython indexing operation, we need to
2959
+ re-analyse the types.
2960
+ """
2961
+ self.visit_ExprNode(node)
2962
+ if node.is_fused_index and not node.type.is_error:
2963
+ node = node.base
2964
+ return node
2965
+
2966
+ # Build the line table according to PEP-626.
2967
+ # We mostly just do this here to avoid yet another transform traversal.
2968
+
2969
+ def visit_ExprNode(self, node):
2970
+ self.positions[-1].add(node.pos)
2971
+ self.visitchildren(node)
2972
+ return node
2973
+
2974
+ def visit_StatNode(self, node):
2975
+ self.positions[-1].add(node.pos)
2976
+ self.visitchildren(node)
2977
+ return node
2978
+
2979
+ def _build_positions(self, func_node):
2980
+ """
2981
+ Build the PEP-626 line table and "bytecode-to-position" mapping used for CodeObjects.
2982
+ """
2983
+ # Code can originate from different source files and string code fragments, even within a single function.
2984
+ # Thus, it's not completely correct to just ignore the source files when sorting the line numbers,
2985
+ # but it also doesn't hurt much for the moment. Eventually, we might need different CodeObjects
2986
+ # even within a single function if it uses code from different sources / line number ranges.
2987
+ positions: list = sorted(
2988
+ self.positions.pop(),
2989
+ key=itemgetter(1, 2), # (line, column)
2990
+ # Build ranges backwards to know the end column before we see the start column in the same line.
2991
+ reverse=True,
2992
+ )
2993
+
2994
+ next_line = -1
2995
+ next_column_in_line = 0
2996
+
2997
+ ranges = []
2998
+ for _, line, start_column in positions:
2999
+ ranges.append((line, line, start_column, next_column_in_line if line == next_line else start_column + 1))
3000
+ next_line, next_column_in_line = line, start_column
3001
+
3002
+ ranges.reverse()
3003
+ func_node.node_positions = ranges
3004
+
3005
+ positions.reverse()
3006
+ i: cython.Py_ssize_t
3007
+ func_node.local_scope.node_positions_to_offset = {
3008
+ position: i
3009
+ for i, position in enumerate(positions)
3010
+ }
3011
+
3012
+
3013
+ class FindInvalidUseOfFusedTypes(TreeVisitor):
3014
+
3015
+ def __call__(self, tree):
3016
+ self._in_fused_function = False
3017
+ self.visit(tree)
3018
+ return tree
3019
+
3020
+ def visit_Node(self, node):
3021
+ self.visitchildren(node)
3022
+
3023
+ def visit_FuncDefNode(self, node):
3024
+ outer_status = self._in_fused_function
3025
+ self._in_fused_function = node.has_fused_arguments
3026
+
3027
+ if not self._in_fused_function:
3028
+ # Errors related to use in functions with fused args will already
3029
+ # have been detected.
3030
+ if not node.is_generator_body and node.return_type.is_fused:
3031
+ error(node.pos, "Return type is not specified as argument type")
3032
+
3033
+ self.visitchildren(node)
3034
+ self._in_fused_function = outer_status
3035
+
3036
+ def visit_ExprNode(self, node):
3037
+ if not self._in_fused_function and node.type and node.type.is_fused:
3038
+ error(node.pos, "Invalid use of fused types, type cannot be specialized")
3039
+ # Errors in subtrees are likely related, so do not recurse.
3040
+ else:
3041
+ self.visitchildren(node)
3042
+
3043
+
3044
+ class ExpandInplaceOperators(EnvTransform):
3045
+
3046
+ def visit_InPlaceAssignmentNode(self, node):
3047
+ lhs = node.lhs
3048
+ rhs = node.rhs
3049
+ if lhs.type.is_cpp_class:
3050
+ # No getting around this exact operator here.
3051
+ return node
3052
+ if isinstance(lhs, ExprNodes.BufferIndexNode):
3053
+ # There is code to handle this case in InPlaceAssignmentNode
3054
+ return node
3055
+
3056
+ env = self.current_env()
3057
+ def side_effect_free_reference(node, setting=False):
3058
+ if node.is_name:
3059
+ return node, []
3060
+ elif node.type.is_pyobject and not setting:
3061
+ node = LetRefNode(node)
3062
+ return node, [node]
3063
+ elif node.is_subscript:
3064
+ base, temps = side_effect_free_reference(node.base)
3065
+ index = LetRefNode(node.index)
3066
+ return ExprNodes.IndexNode(node.pos, base=base, index=index), temps + [index]
3067
+ elif node.is_attribute:
3068
+ obj, temps = side_effect_free_reference(node.obj, setting=setting)
3069
+ return ExprNodes.AttributeNode(node.pos, obj=obj, attribute=node.attribute), temps
3070
+ elif isinstance(node, ExprNodes.BufferIndexNode):
3071
+ raise ValueError("Don't allow things like attributes of buffer indexing operations")
3072
+ else:
3073
+ node = LetRefNode(node)
3074
+ return node, [node]
3075
+ try:
3076
+ lhs, let_ref_nodes = side_effect_free_reference(lhs, setting=True)
3077
+ except ValueError:
3078
+ return node
3079
+ dup = lhs.__class__(**lhs.__dict__)
3080
+ binop = ExprNodes.binop_node(node.pos,
3081
+ operator = node.operator,
3082
+ operand1 = dup,
3083
+ operand2 = rhs,
3084
+ inplace=True)
3085
+ # Manually analyse types for new node.
3086
+ lhs.is_target = True
3087
+ lhs = lhs.analyse_target_types(env)
3088
+ dup.analyse_types(env) # FIXME: no need to reanalyse the copy, right?
3089
+ binop.analyse_operation(env)
3090
+ node = Nodes.SingleAssignmentNode(
3091
+ node.pos,
3092
+ lhs = lhs,
3093
+ rhs=binop.coerce_to(lhs.type, env))
3094
+ # Use LetRefNode to avoid side effects.
3095
+ let_ref_nodes.reverse()
3096
+ for t in let_ref_nodes:
3097
+ node = LetNode(t, node)
3098
+ return node
3099
+
3100
+ def visit_ExprNode(self, node):
3101
+ # In-place assignments can't happen within an expression.
3102
+ return node
3103
+
3104
+
3105
+ class AdjustDefByDirectives(CythonTransform, SkipDeclarations):
3106
+ """
3107
+ Adjust function and class definitions by the decorator directives:
3108
+
3109
+ @cython.cfunc
3110
+ @cython.cclass
3111
+ @cython.ccall
3112
+ @cython.inline
3113
+ @cython.nogil
3114
+ @cython.critical_section
3115
+ """
3116
+ # list of directives that cause conversion to cclass
3117
+ converts_to_cclass = ('cclass', 'total_ordering', 'dataclasses.dataclass')
3118
+
3119
+ def visit_ModuleNode(self, node):
3120
+ self.directives = node.directives
3121
+ self.in_py_class = False
3122
+ self.visitchildren(node)
3123
+ return node
3124
+
3125
+ def visit_CompilerDirectivesMixin(self, node):
3126
+ with node.apply_directives(self):
3127
+ self.visitchildren(node)
3128
+ return node
3129
+
3130
+ def visit_DefNode(self, node):
3131
+ modifiers = []
3132
+ if 'inline' in self.directives:
3133
+ modifiers.append('inline')
3134
+ nogil = self.directives.get('nogil')
3135
+ with_gil = self.directives.get('with_gil')
3136
+ except_val = self.directives.get('exceptval')
3137
+ has_explicit_exc_clause = False if except_val is None else True
3138
+ return_type_node = self.directives.get('returns')
3139
+ if return_type_node is None and self.directives['annotation_typing']:
3140
+ return_type_node = node.return_type_annotation
3141
+ # for Python annotations, prefer safe exception handling by default
3142
+ if return_type_node is not None and except_val is None:
3143
+ except_val = (None, True) # except *
3144
+ elif except_val is None:
3145
+ # backward compatible default: no exception check, unless there's also a "@returns" declaration
3146
+ except_val = (None, True if return_type_node else False)
3147
+ if self.directives.get('c_compile_guard') and 'cfunc' not in self.directives:
3148
+ error(node.pos, "c_compile_guard only allowed on C functions")
3149
+ if 'ccall' in self.directives:
3150
+ if 'cfunc' in self.directives:
3151
+ error(node.pos, "cfunc and ccall directives cannot be combined")
3152
+ if with_gil:
3153
+ error(node.pos, "ccall functions cannot be declared 'with_gil'")
3154
+ node = node.as_cfunction(
3155
+ overridable=True, modifiers=modifiers, nogil=nogil,
3156
+ returns=return_type_node, except_val=except_val, has_explicit_exc_clause=has_explicit_exc_clause)
3157
+ return self.visit(node)
3158
+ if 'cfunc' in self.directives:
3159
+ if self.in_py_class:
3160
+ error(node.pos, "cfunc directive is not allowed here")
3161
+ else:
3162
+ node = node.as_cfunction(
3163
+ overridable=False, modifiers=modifiers, nogil=nogil, with_gil=with_gil,
3164
+ returns=return_type_node, except_val=except_val, has_explicit_exc_clause=has_explicit_exc_clause)
3165
+ return self.visit(node)
3166
+ if 'inline' in modifiers:
3167
+ error(node.pos, "Python functions cannot be declared 'inline'")
3168
+ if nogil:
3169
+ # TODO: turn this into a "with gil" declaration.
3170
+ error(node.pos, "Python functions cannot be declared 'nogil'")
3171
+ if with_gil:
3172
+ error(node.pos, "Python functions cannot be declared 'with_gil'")
3173
+ self.visit_FuncDefNode(node)
3174
+ return node
3175
+
3176
+ def visit_FuncDefNode(self, node):
3177
+ if "critical_section" in self.directives:
3178
+ value = self.directives["critical_section"]
3179
+ if value is not None:
3180
+ error(node.pos, "critical_section decorator does not take arguments")
3181
+ if self.in_py_class:
3182
+ warning(
3183
+ node.pos,
3184
+ "@critical_section on method of a class that is not an extension type is unlikely to be useful",
3185
+ 2)
3186
+ new_body = Nodes.CriticalSectionStatNode(
3187
+ node.pos,
3188
+ args=[ExprNodes.FirstArgumentForCriticalSectionNode(node.pos, func_node=node)],
3189
+ body=node.body
3190
+ )
3191
+ node.body = new_body
3192
+ self.visitchildren(node)
3193
+ return node
3194
+
3195
+ def visit_LambdaNode(self, node):
3196
+ # No directives should modify lambdas or generator expressions (and also nothing in them).
3197
+ return node
3198
+
3199
+ def visit_PyClassDefNode(self, node):
3200
+ if any(directive in self.directives for directive in self.converts_to_cclass):
3201
+ node = node.as_cclass()
3202
+ return self.visit(node)
3203
+ else:
3204
+ old_in_pyclass = self.in_py_class
3205
+ self.in_py_class = True
3206
+ self.visitchildren(node)
3207
+ self.in_py_class = old_in_pyclass
3208
+ return node
3209
+
3210
+ def visit_CClassDefNode(self, node):
3211
+ old_in_pyclass = self.in_py_class
3212
+ self.in_py_class = False
3213
+ self.visitchildren(node)
3214
+ self.in_py_class = old_in_pyclass
3215
+ return node
3216
+
3217
+
3218
+ class AlignFunctionDefinitions(CythonTransform):
3219
+ """
3220
+ This class takes the signatures from a .pxd file and applies them to
3221
+ the def methods in a .py file.
3222
+ """
3223
+
3224
+ def visit_ModuleNode(self, node):
3225
+ self.scope = node.scope
3226
+ self.visitchildren(node)
3227
+ return node
3228
+
3229
+ def visit_PyClassDefNode(self, node):
3230
+ pxd_def = self.scope.lookup(node.name)
3231
+ if pxd_def:
3232
+ if pxd_def.is_cclass:
3233
+ return self.visit_CClassDefNode(node.as_cclass(), pxd_def)
3234
+ elif not pxd_def.scope or not pxd_def.scope.is_builtin_scope:
3235
+ error(node.pos, "'%s' redeclared" % node.name)
3236
+ if pxd_def.pos:
3237
+ error(pxd_def.pos, "previous declaration here")
3238
+ return None
3239
+ return node
3240
+
3241
+ def visit_CClassDefNode(self, node, pxd_def=None):
3242
+ if pxd_def is None:
3243
+ pxd_def = self.scope.lookup(node.class_name)
3244
+ if pxd_def:
3245
+ if not pxd_def.defined_in_pxd:
3246
+ return node
3247
+ outer_scope = self.scope
3248
+ self.scope = pxd_def.type.scope
3249
+ self.visitchildren(node)
3250
+ if pxd_def:
3251
+ self.scope = outer_scope
3252
+ return node
3253
+
3254
+ def visit_DefNode(self, node):
3255
+ pxd_def = self.scope.lookup(node.name)
3256
+ if pxd_def and (not pxd_def.scope or not pxd_def.scope.is_builtin_scope):
3257
+ if not pxd_def.is_cfunction:
3258
+ error(node.pos, "'%s' redeclared" % node.name)
3259
+ if pxd_def.pos:
3260
+ error(pxd_def.pos, "previous declaration here")
3261
+ return None
3262
+ node = node.as_cfunction(pxd_def)
3263
+ # Enable this when nested cdef functions are allowed.
3264
+ # self.visitchildren(node)
3265
+ return node
3266
+
3267
+ def visit_ExprNode(self, node):
3268
+ # ignore lambdas and everything else that appears in expressions
3269
+ return node
3270
+
3271
+
3272
+ class AutoCpdefFunctionDefinitions(CythonTransform):
3273
+
3274
+ def visit_ModuleNode(self, node):
3275
+ self.directives = node.directives
3276
+ self.imported_names = set() # hack, see visit_FromImportStatNode()
3277
+ self.scope = node.scope
3278
+ self.visitchildren(node)
3279
+ return node
3280
+
3281
+ def visit_DefNode(self, node):
3282
+ if (self.scope.is_module_scope and self.directives['auto_cpdef']
3283
+ and node.name not in self.imported_names
3284
+ and node.is_cdef_func_compatible()):
3285
+ # FIXME: cpdef-ing should be done in analyse_declarations()
3286
+ node = node.as_cfunction(scope=self.scope)
3287
+ return node
3288
+
3289
+ def visit_CClassDefNode(self, node, pxd_def=None):
3290
+ if pxd_def is None:
3291
+ pxd_def = self.scope.lookup(node.class_name)
3292
+ if pxd_def:
3293
+ if not pxd_def.defined_in_pxd:
3294
+ return node
3295
+ outer_scope = self.scope
3296
+ self.scope = pxd_def.type.scope
3297
+ self.visitchildren(node)
3298
+ if pxd_def:
3299
+ self.scope = outer_scope
3300
+ return node
3301
+
3302
+ def visit_FromImportStatNode(self, node):
3303
+ # hack to prevent conditional import fallback functions from
3304
+ # being cdpef-ed (global Python variables currently conflict
3305
+ # with imports)
3306
+ if self.scope.is_module_scope:
3307
+ for name, _ in node.items:
3308
+ self.imported_names.add(name)
3309
+ return node
3310
+
3311
+ def visit_ExprNode(self, node):
3312
+ # ignore lambdas and everything else that appears in expressions
3313
+ return node
3314
+
3315
+
3316
+ class RemoveUnreachableCode(CythonTransform):
3317
+
3318
+ def visit_StatListNode(self, node):
3319
+ if not self.current_directives['remove_unreachable']:
3320
+ return node
3321
+ self.visitchildren(node)
3322
+ if len(node.stats) == 1 and isinstance(node.stats[0], Nodes.StatListNode) and not node.stats[0].stats:
3323
+ del node.stats[:]
3324
+ for idx, stat in enumerate(node.stats, 1):
3325
+ if stat.is_terminator:
3326
+ if idx < len(node.stats):
3327
+ if self.current_directives['warn.unreachable']:
3328
+ warning(node.stats[idx].pos, "Unreachable code", 2)
3329
+ node.stats = node.stats[:idx]
3330
+ node.is_terminator = True
3331
+ break
3332
+ return node
3333
+
3334
+ def visit_IfClauseNode(self, node):
3335
+ self.visitchildren(node)
3336
+ if node.body.is_terminator:
3337
+ node.is_terminator = True
3338
+ return node
3339
+
3340
+ def visit_IfStatNode(self, node):
3341
+ self.visitchildren(node)
3342
+ if node.else_clause and node.else_clause.is_terminator:
3343
+ for clause in node.if_clauses:
3344
+ if not clause.is_terminator:
3345
+ break
3346
+ else:
3347
+ node.is_terminator = True
3348
+ return node
3349
+
3350
+ def visit_TryExceptStatNode(self, node):
3351
+ self.visitchildren(node)
3352
+ if node.body.is_terminator and node.else_clause:
3353
+ if self.current_directives['warn.unreachable']:
3354
+ warning(node.else_clause.pos, "Unreachable code", 2)
3355
+ node.else_clause = None
3356
+ return node
3357
+
3358
+ def visit_TryFinallyStatNode(self, node):
3359
+ self.visitchildren(node)
3360
+ if node.finally_clause.is_terminator:
3361
+ node.is_terminator = True
3362
+ return node
3363
+
3364
+ def visit_PassStatNode(self, node):
3365
+ """Eliminate useless PassStatNode"""
3366
+ # 'pass' statements often appear in a separate line and must be traced.
3367
+ if not self.current_directives['linetrace']:
3368
+ node = Nodes.StatListNode(pos=node.pos, stats=[])
3369
+ return node
3370
+
3371
+
3372
+ class YieldNodeCollector(TreeVisitor):
3373
+
3374
+ def __init__(self, excludes=[]):
3375
+ super().__init__()
3376
+ self.yields = []
3377
+ self.returns = []
3378
+ self.finallys = []
3379
+ self.excepts = []
3380
+ self.has_return_value = False
3381
+ self.has_yield = False
3382
+ self.has_await = False
3383
+ self.excludes = excludes
3384
+
3385
+ def visit_Node(self, node):
3386
+ if node not in self.excludes:
3387
+ self.visitchildren(node)
3388
+
3389
+ def visit_YieldExprNode(self, node):
3390
+ self.yields.append(node)
3391
+ self.has_yield = True
3392
+ self.visitchildren(node)
3393
+
3394
+ def visit_AwaitExprNode(self, node):
3395
+ self.yields.append(node)
3396
+ self.has_await = True
3397
+ self.visitchildren(node)
3398
+
3399
+ def visit_ReturnStatNode(self, node):
3400
+ self.visitchildren(node)
3401
+ if node.value:
3402
+ self.has_return_value = True
3403
+ self.returns.append(node)
3404
+
3405
+ def visit_TryFinallyStatNode(self, node):
3406
+ self.visitchildren(node)
3407
+ self.finallys.append(node)
3408
+
3409
+ def visit_TryExceptStatNode(self, node):
3410
+ self.visitchildren(node)
3411
+ self.excepts.append(node)
3412
+
3413
+ def visit_ClassDefNode(self, node):
3414
+ pass
3415
+
3416
+ def visit_FuncDefNode(self, node):
3417
+ pass
3418
+
3419
+ def visit_LambdaNode(self, node):
3420
+ pass
3421
+
3422
+ def visit_GeneratorExpressionNode(self, node):
3423
+ # node.loop iterator is evaluated outside the generator expression
3424
+ if isinstance(node.loop, Nodes._ForInStatNode):
3425
+ # Possibly should handle ForFromStatNode
3426
+ # but for now do nothing
3427
+ self.visit(node.loop.iterator)
3428
+
3429
+ def visit_CArgDeclNode(self, node):
3430
+ # do not look into annotations
3431
+ # FIXME: support (yield) in default arguments (currently crashes)
3432
+ pass
3433
+
3434
+
3435
+ class MarkClosureVisitor(CythonTransform):
3436
+ # In addition to marking closures this is also responsible to finding parts of the
3437
+ # generator iterable and marking them
3438
+
3439
+ def visit_ModuleNode(self, node):
3440
+ self.needs_closure = Nodes.FuncDefNode.NeedsClosure.NO_CLOSURE
3441
+ self.excludes = []
3442
+ self.visitchildren(node)
3443
+ return node
3444
+
3445
+ def visit_FuncDefNode(self, node):
3446
+ self.needs_closure = Nodes.FuncDefNode.NeedsClosure.NO_CLOSURE
3447
+ self.visitchildren(node)
3448
+ node.needs_closure = self.needs_closure
3449
+ self.needs_closure = Nodes.FuncDefNode.NeedsClosure.FULL_CLOSURE
3450
+
3451
+ collector = YieldNodeCollector(self.excludes)
3452
+ collector.visitchildren(node)
3453
+
3454
+ if node.is_async_def:
3455
+ coroutine_type = Nodes.AsyncDefNode
3456
+ if collector.has_yield:
3457
+ coroutine_type = Nodes.AsyncGenNode
3458
+ for yield_expr in collector.yields + collector.returns:
3459
+ yield_expr.in_async_gen = True
3460
+ elif self.current_directives['iterable_coroutine']:
3461
+ coroutine_type = Nodes.IterableAsyncDefNode
3462
+ elif collector.has_await:
3463
+ found = next(y for y in collector.yields if y.is_await)
3464
+ error(found.pos, "'await' not allowed in generators (use 'yield')")
3465
+ return node
3466
+ elif collector.has_yield:
3467
+ coroutine_type = Nodes.GeneratorDefNode
3468
+ else:
3469
+ return node
3470
+
3471
+ for i, yield_expr in enumerate(collector.yields, 1):
3472
+ yield_expr.label_num = i
3473
+ for retnode in collector.returns + collector.finallys + collector.excepts:
3474
+ retnode.in_generator = True
3475
+
3476
+ gbody = Nodes.GeneratorBodyDefNode(
3477
+ pos=node.pos, name=node.name, body=node.body,
3478
+ is_coroutine_body=node.is_async_def,
3479
+ is_async_gen_body=node.is_async_def and collector.has_yield)
3480
+ coroutine = coroutine_type(
3481
+ pos=node.pos, name=node.name, args=node.args,
3482
+ star_arg=node.star_arg, starstar_arg=node.starstar_arg,
3483
+ doc=node.doc, decorators=node.decorators,
3484
+ gbody=gbody, lambda_name=node.lambda_name,
3485
+ return_type_annotation=node.return_type_annotation,
3486
+ is_generator_expression=node.is_generator_expression)
3487
+ if node.needs_closure:
3488
+ # We may have determined that we need a "full closure"
3489
+ # so upgrade the coroutine to signal that
3490
+ coroutine.needs_closure = node.needs_closure
3491
+ return coroutine
3492
+
3493
+ def visit_CFuncDefNode(self, node):
3494
+ self.needs_closure = Nodes.FuncDefNode.NeedsClosure.NO_CLOSURE
3495
+ self.visitchildren(node)
3496
+ node.needs_closure = self.needs_closure
3497
+ self.needs_closure = Nodes.FuncDefNode.NeedsClosure.FULL_CLOSURE
3498
+ if node.needs_closure and node.overridable:
3499
+ error(node.pos, "closures inside cpdef functions not yet supported")
3500
+ return node
3501
+
3502
+ def visit_LambdaNode(self, node):
3503
+ self.needs_closure = Nodes.FuncDefNode.NeedsClosure.NO_CLOSURE
3504
+ self.visitchildren(node)
3505
+ node.needs_closure = self.needs_closure
3506
+ self.needs_closure = Nodes.FuncDefNode.NeedsClosure.FULL_CLOSURE
3507
+ return node
3508
+
3509
+ def visit_ClassDefNode(self, node):
3510
+ self.visitchildren(node)
3511
+ self.needs_closure = Nodes.FuncDefNode.NeedsClosure.FULL_CLOSURE
3512
+ return node
3513
+
3514
+ def visit_GeneratorExpressionNode(self, node):
3515
+ excludes = self.excludes
3516
+ if isinstance(node.loop, Nodes._ForInStatNode):
3517
+ self.excludes = [node.loop.iterator]
3518
+ node = self.visit_LambdaNode(node)
3519
+ self.excludes = excludes
3520
+ if not isinstance(node.loop, Nodes._ForInStatNode):
3521
+ # Possibly should handle ForFromStatNode
3522
+ # but for now do nothing
3523
+ return node
3524
+ itseq = node.loop.iterator.sequence
3525
+ # literals do not need replacing with an argument
3526
+ if itseq.is_literal:
3527
+ return node
3528
+ _GeneratorExpressionArgumentsMarker(node).visit(itseq)
3529
+ return node
3530
+
3531
+
3532
+ class CreateClosureClasses(CythonTransform):
3533
+ # Output closure classes in module scope for all functions
3534
+ # that really need it.
3535
+
3536
+ def __init__(self, context):
3537
+ super().__init__(context)
3538
+ self.path = []
3539
+ self.in_lambda = False
3540
+
3541
+ def visit_ModuleNode(self, node):
3542
+ self.module_scope = node.scope
3543
+ self.visitchildren(node)
3544
+ return node
3545
+
3546
+ def find_entries_used_in_closures(self, node):
3547
+ from_closure = []
3548
+ in_closure = []
3549
+ for scope in node.local_scope.iter_local_scopes():
3550
+ for name, entry in scope.entries.items():
3551
+ if not name:
3552
+ continue
3553
+ if entry.from_closure:
3554
+ from_closure.append((name, entry))
3555
+ elif entry.in_closure:
3556
+ in_closure.append((name, entry))
3557
+ return from_closure, in_closure
3558
+
3559
+ def create_class_from_scope(self, node, target_module_scope, inner_node=None):
3560
+ # move local variables into closure
3561
+ if node.is_generator:
3562
+ for scope in node.local_scope.iter_local_scopes():
3563
+ for entry in scope.entries.values():
3564
+ if not (entry.from_closure or entry.is_pyglobal or entry.is_cglobal):
3565
+ entry.in_closure = True
3566
+
3567
+ from_closure, in_closure = self.find_entries_used_in_closures(node)
3568
+ in_closure.sort()
3569
+
3570
+ # Now from the beginning
3571
+ node.needs_closure = Nodes.FuncDefNode.NeedsClosure.NO_CLOSURE
3572
+ node.needs_outer_scope = False
3573
+
3574
+ func_scope = node.local_scope
3575
+ cscope = node.entry.scope
3576
+ while cscope.is_py_class_scope or cscope.is_c_class_scope:
3577
+ cscope = cscope.outer_scope
3578
+
3579
+ if not from_closure and (self.path or inner_node):
3580
+ if not inner_node:
3581
+ if not node.py_cfunc_node:
3582
+ raise InternalError("DefNode does not have assignment node")
3583
+ inner_node = node.py_cfunc_node
3584
+ inner_node.needs_closure_code = False
3585
+ node.needs_outer_scope = False
3586
+
3587
+ if node.is_generator:
3588
+ pass
3589
+ elif not in_closure and not from_closure:
3590
+ return
3591
+ elif not in_closure:
3592
+ func_scope.is_passthrough = True
3593
+ func_scope.scope_class = cscope.scope_class
3594
+ node.needs_outer_scope = True
3595
+ return
3596
+
3597
+ # entry.cname can contain periods (eg. a derived C method of a class).
3598
+ # We want to use the cname as part of a C struct name, so we replace
3599
+ # periods with double underscores.
3600
+ as_name = '%s_%s' % (
3601
+ target_module_scope.next_id(Naming.closure_class_prefix),
3602
+ node.entry.cname.replace('.','__'))
3603
+ as_name = EncodedString(as_name)
3604
+
3605
+ entry = target_module_scope.declare_c_class(
3606
+ name=as_name, pos=node.pos, defining=True,
3607
+ implementing=True)
3608
+ entry.type.is_final_type = True
3609
+
3610
+ func_scope.scope_class = entry
3611
+ class_scope = entry.type.scope
3612
+ class_scope.is_internal = True
3613
+ class_scope.is_closure_class_scope = True
3614
+ if node.is_async_def or node.is_generator:
3615
+ # Generators need their closure intact during cleanup as they resume to handle GeneratorExit
3616
+ class_scope.directives['no_gc_clear'] = True
3617
+ if Options.closure_freelist_size:
3618
+ class_scope.directives['freelist'] = Options.closure_freelist_size
3619
+
3620
+ if from_closure:
3621
+ assert cscope.is_closure_scope
3622
+ class_scope.declare_var(pos=node.pos,
3623
+ name=Naming.outer_scope_cname,
3624
+ cname=Naming.outer_scope_cname,
3625
+ type=cscope.scope_class.type,
3626
+ is_cdef=True)
3627
+ node.needs_outer_scope = True
3628
+ for name, entry in in_closure:
3629
+ closure_entry = class_scope.declare_var(
3630
+ pos=entry.pos,
3631
+ name=entry.name if not entry.in_subscope else None,
3632
+ cname=entry.cname,
3633
+ type=entry.type,
3634
+ is_cdef=True)
3635
+ if entry.is_declared_generic:
3636
+ closure_entry.is_declared_generic = 1
3637
+ node.needs_closure = Nodes.FuncDefNode.NeedsClosure.FULL_CLOSURE
3638
+ # Do it here because other classes are already checked
3639
+ target_module_scope.check_c_class(func_scope.scope_class)
3640
+
3641
+ def visit_LambdaNode(self, node):
3642
+ if not isinstance(node.def_node, Nodes.DefNode):
3643
+ # fused function, an error has been previously issued
3644
+ return node
3645
+
3646
+ was_in_lambda = self.in_lambda
3647
+ self.in_lambda = True
3648
+ self.create_class_from_scope(node.def_node, self.module_scope, node)
3649
+ self.visitchildren(node)
3650
+ self.in_lambda = was_in_lambda
3651
+ return node
3652
+
3653
+ def visit_FuncDefNode(self, node):
3654
+ if self.in_lambda:
3655
+ self.visitchildren(node)
3656
+ return node
3657
+ if node.needs_closure or self.path:
3658
+ self.create_class_from_scope(node, self.module_scope)
3659
+ self.path.append(node)
3660
+ self.visitchildren(node)
3661
+ self.path.pop()
3662
+ return node
3663
+
3664
+ def visit_GeneratorBodyDefNode(self, node):
3665
+ self.visitchildren(node)
3666
+ return node
3667
+
3668
+ def visit_CFuncDefNode(self, node):
3669
+ if not node.overridable:
3670
+ return self.visit_FuncDefNode(node)
3671
+ else:
3672
+ self.visitchildren(node)
3673
+ return node
3674
+
3675
+ def visit_GeneratorExpressionNode(self, node):
3676
+ node = _HandleGeneratorArguments()(node)
3677
+ return self.visit_LambdaNode(node)
3678
+
3679
+
3680
+ class InjectGilHandling(VisitorTransform, SkipDeclarations):
3681
+ """
3682
+ Allow certain Python operations inside of nogil blocks by implicitly acquiring the GIL.
3683
+
3684
+ Must run before the AnalyseDeclarationsTransform to make sure the GILStatNodes get
3685
+ set up, parallel sections know that the GIL is acquired inside of them, etc.
3686
+ """
3687
+ nogil = False
3688
+
3689
+ # special node handling
3690
+
3691
+ def _inject_gil_in_nogil(self, node):
3692
+ """Allow the (Python statement) node in nogil sections by wrapping it in a 'with gil' block."""
3693
+ if self.nogil:
3694
+ node = Nodes.GILStatNode(node.pos, state='gil', body=node, internally_generated=True)
3695
+ return node
3696
+
3697
+ visit_RaiseStatNode = _inject_gil_in_nogil
3698
+ visit_PrintStatNode = _inject_gil_in_nogil # sadly, not the function
3699
+
3700
+ # further candidates:
3701
+ # def visit_ReraiseStatNode(self, node):
3702
+
3703
+ # nogil tracking
3704
+
3705
+ def visit_GILStatNode(self, node):
3706
+ was_nogil = self.nogil
3707
+ self.nogil = (node.state == 'nogil')
3708
+ self.visitchildren(node)
3709
+ self.nogil = was_nogil
3710
+ return node
3711
+
3712
+ def visit_CFuncDefNode(self, node):
3713
+ was_nogil = self.nogil
3714
+ if isinstance(node.declarator, Nodes.CFuncDeclaratorNode):
3715
+ self.nogil = node.declarator.nogil and not node.declarator.with_gil
3716
+ self.visitchildren(node)
3717
+ self.nogil = was_nogil
3718
+ return node
3719
+
3720
+ def visit_ParallelRangeNode(self, node):
3721
+ was_nogil = self.nogil
3722
+ self.nogil = node.nogil
3723
+ self.visitchildren(node)
3724
+ self.nogil = was_nogil
3725
+ return node
3726
+
3727
+ def visit_ExprNode(self, node):
3728
+ # No special GIL handling inside of expressions for now.
3729
+ return node
3730
+
3731
+ visit_Node = VisitorTransform.recurse_to_children
3732
+
3733
+
3734
+ class GilCheck(VisitorTransform):
3735
+ """
3736
+ Call `node.gil_check(env)` on each node to make sure we hold the
3737
+ GIL when we need it. Raise an error when on Python operations
3738
+ inside a `nogil` environment.
3739
+
3740
+ Additionally, raise exceptions for closely nested with gil or with nogil
3741
+ statements. The latter would abort Python.
3742
+ """
3743
+
3744
+ def __call__(self, root):
3745
+ self.env_stack = [root.scope]
3746
+ self.nogil_state = Nodes.NoGilState.HasGil
3747
+ self.in_critical_section = False
3748
+
3749
+ self.nogil_state_at_current_gilstatnode = Nodes.NoGilState.HasGil
3750
+ return super().__call__(root)
3751
+
3752
+ def _visit_scoped_children(self, node, nogil_state):
3753
+ was_nogil = self.nogil_state
3754
+ outer_attrs = node.outer_attrs
3755
+ if outer_attrs and len(self.env_stack) > 1:
3756
+ self.nogil_state = (
3757
+ Nodes.NoGilState.NoGil if self.env_stack[-2].nogil else Nodes.NoGilState.HasGil)
3758
+ self.visitchildren(node, outer_attrs)
3759
+
3760
+ self.nogil_state = nogil_state
3761
+ self.visitchildren(node, attrs=None, exclude=outer_attrs)
3762
+ self.nogil_state = was_nogil
3763
+
3764
+ def visit_FuncDefNode(self, node):
3765
+ self.env_stack.append(node.local_scope)
3766
+ inner_nogil = node.local_scope.nogil
3767
+
3768
+ nogil_state = self.nogil_state
3769
+ if inner_nogil:
3770
+ self.nogil_state = Nodes.NoGilState.NoGilScope
3771
+
3772
+ if inner_nogil and node.nogil_check:
3773
+ node.nogil_check(node.local_scope)
3774
+
3775
+ in_critical_section, self.in_critical_section = self.in_critical_section, False
3776
+ self._visit_scoped_children(node, self.nogil_state)
3777
+ self.in_critical_section = in_critical_section
3778
+
3779
+ # FuncDefNodes can be nested, because a cpdef function contains a def function
3780
+ # inside it. Therefore restore to previous state
3781
+ self.nogil_state = nogil_state
3782
+
3783
+ self.env_stack.pop()
3784
+ return node
3785
+
3786
+ def visit_GILStatNode(self, node):
3787
+ if node.condition is not None:
3788
+ error(node.condition.pos,
3789
+ "Non-constant condition in a "
3790
+ "`with %s(<condition>)` statement" % node.state)
3791
+ return node
3792
+
3793
+ if self.nogil_state and node.nogil_check:
3794
+ node.nogil_check()
3795
+
3796
+ was_nogil = self.nogil_state
3797
+ is_nogil = (node.state == 'nogil')
3798
+
3799
+ if was_nogil == is_nogil and not self.nogil_state == Nodes.NoGilState.NoGilScope:
3800
+ if not was_nogil:
3801
+ error(node.pos, "Trying to acquire the GIL while it is "
3802
+ "already held.")
3803
+ else:
3804
+ error(node.pos, "Trying to release the GIL while it was "
3805
+ "previously released.")
3806
+ if self.nogil_state == Nodes.NoGilState.NoGilScope:
3807
+ node.scope_gil_state_known = False
3808
+
3809
+ if isinstance(node.finally_clause, Nodes.StatListNode):
3810
+ # The finally clause of the GILStatNode is a GILExitNode,
3811
+ # which is wrapped in a StatListNode. Just unpack that.
3812
+ node.finally_clause, = node.finally_clause.stats
3813
+
3814
+ nogil_state_at_current_gilstatnode = self.nogil_state_at_current_gilstatnode
3815
+ self.nogil_state_at_current_gilstatnode = self.nogil_state
3816
+ nogil_state = Nodes.NoGilState.NoGil if is_nogil else Nodes.NoGilState.HasGil
3817
+ self._visit_scoped_children(node, nogil_state)
3818
+ self.nogil_state_at_current_gilstatnode = nogil_state_at_current_gilstatnode
3819
+
3820
+ # Drop pointless nested `with gil: with nogil:` blocks.
3821
+ # These can occur during the generation of prange/parallel sections for example.
3822
+ node_body = node.body
3823
+ if (isinstance(node_body, Nodes.StatListNode) and len(node_body.stats) == 1):
3824
+ node_body = node_body.stats[0]
3825
+ if isinstance(node_body, Nodes.GILStatNode) and node_body.state != node.state and (
3826
+ # Don't optimize out user-inserted `with nogil` for now. They have the
3827
+ # side-effect of deliberately allowing a thread-switch even if they might
3828
+ # appear useless, so they might be deliberate.
3829
+ (node_body.state == "nogil" and node_body.internally_generated) or
3830
+ (node.state == "nogil" and node.internally_generated)):
3831
+ if not node.scope_gil_state_known:
3832
+ node_body.scope_gil_state_known = False
3833
+ node_body.finally_clause.scope_gil_state_known = False
3834
+ if node_body.finally_except_clause is not None:
3835
+ node_body.finally_except_clause.scope_gil_state_known = False
3836
+ return node_body
3837
+ else:
3838
+ return node_body.body
3839
+
3840
+ return node
3841
+
3842
+ def visit_ParallelRangeNode(self, node):
3843
+ if self.nogil_state == Nodes.NoGilState.HasGil and not node.nogil:
3844
+ # We still release the GIL then reacquire it to avoid deadlocks.
3845
+ node.acquire_gil = True
3846
+ if (node.nogil or self.nogil_state == Nodes.NoGilState.NoGilScope or
3847
+ (self.nogil_state == Nodes.NoGilState.HasGil and not node.parent)):
3848
+ node_was_nogil, node.nogil = node.nogil, False
3849
+ node = Nodes.GILStatNode(node.pos, state='nogil', body=node, internally_generated=True)
3850
+ if not node_was_nogil and self.nogil_state == Nodes.NoGilState.NoGilScope:
3851
+ # We're in a "nogil" function, but that doesn't prove we
3852
+ # didn't have the gil
3853
+ node.scope_gil_state_known = False
3854
+ return self.visit_GILStatNode(node)
3855
+
3856
+ if node.acquire_gil:
3857
+ if not self.env_stack[-1].directives['freethreading_compatible']:
3858
+ warning(
3859
+ node.pos,
3860
+ "prange without releasing the GIL will only work well on freethreaded Python",
3861
+ level=2)
3862
+ was_nogil = self.nogil_state
3863
+ self.nogil_state = Nodes.NoGilState.HasGil
3864
+
3865
+ node.nogil_check(self.env_stack[-1])
3866
+ self.visitchildren(node)
3867
+
3868
+ if node.acquire_gil:
3869
+ self.nogil_state = was_nogil
3870
+ return node
3871
+
3872
+ def visit_ParallelWithBlockNode(self, node):
3873
+ if self.nogil_state != Nodes.NoGilState.NoGil:
3874
+ # Ensure that the GIL is released
3875
+ if self.nogil_state == Nodes.NoGilState.HasGil:
3876
+ # Even if we intend the block to have the GIL it's easier to release
3877
+ # and reacquire it to void deadlocks.
3878
+ node.acquire_gil = True
3879
+ node = Nodes.GILStatNode(node.pos, state='nogil', body=node, internally_generated=True)
3880
+ if self.nogil_state == Nodes.NoGilState.NoGilScope:
3881
+ node.scope_gil_state_known = False
3882
+ return self.visit_GILStatNode(node)
3883
+
3884
+ if node.acquire_gil:
3885
+ if not self.env_stack[-1].directives['freethreading_compatible']:
3886
+ warning(
3887
+ node.pos,
3888
+ "Parallel section without releasing the GIL will only work well on freethreaded Python",
3889
+ level=2)
3890
+ was_nogil = self.nogil_state
3891
+ self.nogil_state = Nodes.NoGilState.HasGil
3892
+
3893
+ if node.nogil_check:
3894
+ # It does not currently implement this, but test for it anyway to
3895
+ # avoid potential future surprises
3896
+ node.nogil_check(self.env_stack[-1])
3897
+
3898
+ self.visitchildren(node)
3899
+ if node.acquire_gil:
3900
+ self.nogil_state = was_nogil
3901
+ return node
3902
+
3903
+ def visit_TryFinallyStatNode(self, node):
3904
+ """
3905
+ Take care of try/finally statements in nogil code sections.
3906
+ """
3907
+ if not self.nogil_state:
3908
+ return self.visit_Node(node)
3909
+
3910
+ node.nogil_check = None
3911
+ node.is_try_finally_in_nogil = True
3912
+ self.visitchildren(node)
3913
+ return node
3914
+
3915
+ def visit_CriticalSectionStatNode(self, node):
3916
+ # skip normal "try/finally node" handling
3917
+ in_critical_section, self.in_critical_section = self.in_critical_section, True
3918
+ result = self.visit_Node(node)
3919
+ self.in_critical_section = in_critical_section
3920
+ return result
3921
+
3922
+ def visit_CythonLockStatNode(self, node):
3923
+ # skip normal "try/finally node" handling
3924
+ return self.visit_Node(node)
3925
+
3926
+ def visit_GILExitNode(self, node):
3927
+ if self.nogil_state_at_current_gilstatnode == Nodes.NoGilState.NoGilScope:
3928
+ node.scope_gil_state_known = False
3929
+ self.visitchildren(node)
3930
+ return node
3931
+
3932
+ def visit_Node(self, node):
3933
+ if self.env_stack and self.nogil_state and node.nogil_check:
3934
+ node.nogil_check(self.env_stack[-1])
3935
+ if node.outer_attrs:
3936
+ self._visit_scoped_children(node, self.nogil_state)
3937
+ else:
3938
+ self.visitchildren(node)
3939
+ if self.nogil_state:
3940
+ node.in_nogil_context = self.nogil_state
3941
+ return node
3942
+
3943
+ def visit_SimpleCallNode(self, node):
3944
+ if (node.self and node.self.type.is_cython_lock_type and
3945
+ node.function.is_attribute and node.function.attribute == "acquire" and
3946
+ len(node.args) == 1):
3947
+ # For the cython lock types we can optimize if we know the GIL state.
3948
+ # (Remove this in the distant future when it's all PyMutexes because for these
3949
+ # it doesn't matter)
3950
+ suffix = None
3951
+ if self.nogil_state == Nodes.NoGilState.NoGil:
3952
+ suffix = "Nogil"
3953
+ elif self.nogil_state == Nodes.NoGilState.HasGil:
3954
+ suffix = "Gil"
3955
+ if suffix:
3956
+ node = ExprNodes.PythonCapiCallNode(
3957
+ node.pos,
3958
+ node.function.entry.cname + suffix,
3959
+ node.function.type,
3960
+ args=[node.self],
3961
+ )
3962
+ return self.visit_Node(node)
3963
+
3964
+ def visit_AttributeNode(self, node):
3965
+ if self.in_critical_section and node.is_py_attr:
3966
+ warning(
3967
+ node.pos,
3968
+ "Python attribute access is not usefully protected by critical_section",
3969
+ 1)
3970
+ return self.visit_Node(node)
3971
+
3972
+
3973
+ class CoerceCppTemps(EnvTransform, SkipDeclarations):
3974
+ """
3975
+ For temporary expression that are implemented using std::optional it's necessary the temps are
3976
+ assigned using `__pyx_t_x = value;` but accessed using `something = (*__pyx_t_x)`. This transform
3977
+ inserts a coercion node to take care of this, and runs absolutely last (once nothing else can be
3978
+ inserted into the tree)
3979
+
3980
+ TODO: a possible alternative would be to split ExprNode.result() into ExprNode.rhs_result() and ExprNode.lhs_result()???
3981
+ """
3982
+ def visit_ModuleNode(self, node):
3983
+ if self.current_env().cpp:
3984
+ # skipping this makes it essentially free for C files
3985
+ self.visitchildren(node)
3986
+ return node
3987
+
3988
+ def visit_ExprNode(self, node):
3989
+ self.visitchildren(node)
3990
+ if (self.current_env().directives['cpp_locals'] and
3991
+ node.result_in_temp() and node.type.is_cpp_class and
3992
+ # Fake references are not replaced with "std::optional()".
3993
+ not node.type.is_fake_reference):
3994
+ node = ExprNodes.CppOptionalTempCoercion(node)
3995
+
3996
+ return node
3997
+
3998
+ def visit_CppOptionalTempCoercion(self, node):
3999
+ return node
4000
+
4001
+ def visit_CppIteratorNode(self, node):
4002
+ return node
4003
+
4004
+ def visit_ExprStatNode(self, node):
4005
+ # Deliberately skip `expr` in ExprStatNode - we don't need to access it.
4006
+ self.visitchildren(node.expr)
4007
+ return node
4008
+
4009
+
4010
+ class TransformBuiltinMethods(EnvTransform):
4011
+ """
4012
+ Replace Cython's own cython.* builtins by the corresponding tree nodes.
4013
+ Also handle some Python special builtin functions (e.g. super()/locals())
4014
+ that require introspection by the compiler.
4015
+ """
4016
+ def __init__(self, *args, **kwds):
4017
+ super().__init__(*args, **kwds)
4018
+ self.def_node_body_insertions = {}
4019
+
4020
+ def visit_SingleAssignmentNode(self, node):
4021
+ if node.declaration_only:
4022
+ return None
4023
+ else:
4024
+ self.visitchildren(node)
4025
+ return node
4026
+
4027
+ def visit_AttributeNode(self, node):
4028
+ self.visitchildren(node)
4029
+ return self.visit_cython_attribute(node)
4030
+
4031
+ def visit_NameNode(self, node):
4032
+ if node.name == u'__class__':
4033
+ lenv = self.current_env()
4034
+ entry = lenv.lookup_here(u'__class__')
4035
+ if not entry:
4036
+ node = self._inject_class(node)
4037
+ return self.visit_cython_attribute(node)
4038
+
4039
+ def visit_cython_attribute(self, node):
4040
+ attribute = node.as_cython_attribute()
4041
+ if attribute:
4042
+ if attribute == '__version__':
4043
+ from .. import __version__ as version
4044
+ node = ExprNodes.UnicodeNode(node.pos, value=EncodedString(version))
4045
+ elif attribute == 'NULL':
4046
+ node = ExprNodes.NullNode(node.pos)
4047
+ elif attribute in ('set', 'frozenset', 'staticmethod'):
4048
+ node = ExprNodes.NameNode(node.pos, name=EncodedString(attribute),
4049
+ entry=self.current_env().builtin_scope().lookup_here(attribute))
4050
+ elif PyrexTypes.parse_basic_type(attribute):
4051
+ pass
4052
+ elif self.context.cython_scope.lookup_qualified_name(attribute):
4053
+ pass
4054
+ else:
4055
+ error(node.pos, "'%s' not a valid cython attribute or is being used incorrectly" % attribute)
4056
+ return node
4057
+
4058
+ def visit_ExecStatNode(self, node):
4059
+ lenv = self.current_env()
4060
+ self.visitchildren(node)
4061
+ if len(node.args) == 1:
4062
+ node.args.append(ExprNodes.GlobalsExprNode(node.pos))
4063
+ if not lenv.is_module_scope:
4064
+ node.args.append(
4065
+ ExprNodes.LocalsExprNode(
4066
+ node.pos, self.current_scope_node(), lenv))
4067
+ return node
4068
+
4069
+ def _inject_locals(self, node, func_name):
4070
+ # locals()/dir()/vars() builtins
4071
+ lenv = self.current_env()
4072
+ entry = lenv.lookup_here(func_name)
4073
+ if entry:
4074
+ # not the builtin
4075
+ return node
4076
+ pos = node.pos
4077
+ if func_name in ('locals', 'vars'):
4078
+ if func_name == 'locals' and len(node.args) > 0:
4079
+ error(self.pos, "Builtin 'locals()' called with wrong number of args, expected 0, got %d"
4080
+ % len(node.args))
4081
+ return node
4082
+ elif func_name == 'vars':
4083
+ if len(node.args) > 1:
4084
+ error(self.pos, "Builtin 'vars()' called with wrong number of args, expected 0-1, got %d"
4085
+ % len(node.args))
4086
+ if len(node.args) > 0:
4087
+ return node # nothing to do
4088
+ return ExprNodes.LocalsExprNode(pos, self.current_scope_node(), lenv)
4089
+ else: # dir()
4090
+ if len(node.args) > 1:
4091
+ error(self.pos, "Builtin 'dir()' called with wrong number of args, expected 0-1, got %d"
4092
+ % len(node.args))
4093
+ if len(node.args) > 0:
4094
+ # optimised in Builtin.py
4095
+ return node
4096
+ if lenv.is_py_class_scope or lenv.is_module_scope:
4097
+ if lenv.is_py_class_scope:
4098
+ pyclass = self.current_scope_node()
4099
+ locals_dict = ExprNodes.CloneNode(pyclass.dict)
4100
+ else:
4101
+ locals_dict = ExprNodes.GlobalsExprNode(pos)
4102
+ return ExprNodes.SortedDictKeysNode(locals_dict)
4103
+ local_names = sorted(var.name for var in lenv.entries.values() if var.name)
4104
+ items = [ExprNodes.IdentifierStringNode(pos, value=var)
4105
+ for var in local_names]
4106
+ return ExprNodes.ListNode(pos, args=items)
4107
+
4108
+ def visit_PrimaryCmpNode(self, node):
4109
+ # special case: for in/not-in test, we do not need to sort locals()
4110
+ self.visitchildren(node)
4111
+ if node.operator in 'not_in': # in/not_in
4112
+ if isinstance(node.operand2, ExprNodes.SortedDictKeysNode):
4113
+ arg = node.operand2.arg
4114
+ if isinstance(arg, ExprNodes.NoneCheckNode):
4115
+ arg = arg.arg
4116
+ node.operand2 = arg
4117
+ return node
4118
+
4119
+ def visit_CascadedCmpNode(self, node):
4120
+ return self.visit_PrimaryCmpNode(node)
4121
+
4122
+ def _inject_eval(self, node, func_name):
4123
+ lenv = self.current_env()
4124
+ entry = lenv.lookup(func_name)
4125
+ if len(node.args) != 1 or (entry and not entry.is_builtin):
4126
+ return node
4127
+ # Inject globals and locals
4128
+ node.args.append(ExprNodes.GlobalsExprNode(node.pos))
4129
+ if not lenv.is_module_scope:
4130
+ node.args.append(
4131
+ ExprNodes.LocalsExprNode(
4132
+ node.pos, self.current_scope_node(), lenv))
4133
+ return node
4134
+
4135
+ def _inject_class(self, node):
4136
+ # bare __class__ reference inside function
4137
+ current_def_node = self.current_scope_node()
4138
+
4139
+ if not isinstance(current_def_node, Nodes.FuncDefNode):
4140
+ return node
4141
+
4142
+ # Go up the stack, find the first class node and its direct method (i.e. function).
4143
+ fdef_node = class_node = generator_node = None
4144
+ for stack_node, stack_scope in reversed(self.env_stack):
4145
+ if isinstance(stack_node, Nodes.ClassDefNode):
4146
+ class_node = stack_node
4147
+ class_scope = stack_scope
4148
+ break
4149
+ elif isinstance(stack_node, Nodes.GeneratorDefNode):
4150
+ generator_node = stack_node
4151
+ fdef_node = stack_node.gbody
4152
+ fdef_scope = stack_scope
4153
+ elif isinstance(stack_node, Nodes.FuncDefNode):
4154
+ fdef_node = stack_node
4155
+ fdef_scope = stack_scope
4156
+
4157
+ if not fdef_node or not class_node:
4158
+ # failed to find a class or function
4159
+ return node
4160
+
4161
+ # now we arrange to inject:
4162
+ # __class__ = ... at the start of the def_node body
4163
+ # The advantage of doing it like this is that it automatically appears in locals()
4164
+ # and it can be captured by inner functions
4165
+ if fdef_node not in self.def_node_body_insertions:
4166
+ pos = fdef_node.body.pos
4167
+ if class_scope.is_c_class_scope:
4168
+ # c-classes can be resolved at compile-time, so they have a simpler
4169
+ # implementation
4170
+ rhs = ExprNodes.NameNode(
4171
+ pos, name=class_node.scope.name,
4172
+ entry=class_node.entry)
4173
+ elif class_scope.is_py_class_scope:
4174
+ rhs = ExprNodes.ClassCellNode(pos, is_generator=generator_node is not None)
4175
+ if generator_node:
4176
+ generator_node.requires_classobj = True
4177
+ else:
4178
+ fdef_node.requires_classobj = True
4179
+ class_node.class_cell.is_active = True
4180
+ else:
4181
+ return node # should never happen
4182
+
4183
+ assign_node = Nodes.SingleAssignmentNode(pos,
4184
+ lhs=ExprNodes.NameNode(pos, name=EncodedString("__class__")),
4185
+ rhs=rhs)
4186
+
4187
+ assign_node.analyse_declarations(fdef_scope)
4188
+
4189
+ assert fdef_node not in self.def_node_body_insertions
4190
+ self.def_node_body_insertions[fdef_node] = assign_node
4191
+
4192
+ return node
4193
+
4194
+ def _inject_super(self, node, func_name):
4195
+ lenv = self.current_env()
4196
+ entry = lenv.lookup_here(func_name)
4197
+ if entry or node.args:
4198
+ return node
4199
+ # Inject no-args super
4200
+ def_node = self.current_scope_node()
4201
+ if not isinstance(def_node, Nodes.DefNode) or not def_node.args or len(self.env_stack) < 2:
4202
+ return node
4203
+ class_node, class_scope = self.env_stack[-2]
4204
+ if class_scope.is_py_class_scope:
4205
+ def_node.requires_classobj = True
4206
+ class_node.class_cell.is_active = True
4207
+ node.args = [
4208
+ ExprNodes.ClassCellNode(
4209
+ node.pos, is_generator=def_node.is_generator),
4210
+ ExprNodes.NameNode(node.pos, name=def_node.args[0].name)
4211
+ ]
4212
+ elif class_scope.is_c_class_scope:
4213
+ node.args = [
4214
+ ExprNodes.NameNode(
4215
+ node.pos, name=class_node.scope.name,
4216
+ entry=class_node.entry),
4217
+ ExprNodes.NameNode(node.pos, name=def_node.args[0].name)
4218
+ ]
4219
+ return node
4220
+
4221
+ def _do_body_insertion(self, node):
4222
+ body_insertion = self.def_node_body_insertions.pop(node, None)
4223
+ if body_insertion:
4224
+ if isinstance(node.body, Nodes.StatListNode):
4225
+ node.body.stats.insert(0, body_insertion)
4226
+ else:
4227
+ node.body = Nodes.StatListNode(node.body.pos,
4228
+ stats=[body_insertion, node.body])
4229
+
4230
+ def visit_FuncDefNode(self, node):
4231
+ node = super().visit_FuncDefNode(node)
4232
+ self._do_body_insertion(node)
4233
+ return node
4234
+
4235
+ def visit_GeneratorBodyDefNode(self, node):
4236
+ node = super().visit_GeneratorBodyDefNode(node)
4237
+ self._do_body_insertion(node)
4238
+ return node
4239
+
4240
+ def visit_SimpleCallNode(self, node):
4241
+ # cython.foo
4242
+ function = node.function.as_cython_attribute()
4243
+ if function:
4244
+ if function in InterpretCompilerDirectives.unop_method_nodes:
4245
+ if len(node.args) != 1:
4246
+ error(node.function.pos, "%s() takes exactly one argument" % function)
4247
+ else:
4248
+ node = InterpretCompilerDirectives.unop_method_nodes[function](
4249
+ node.function.pos, operand=node.args[0])
4250
+ elif function in InterpretCompilerDirectives.binop_method_nodes:
4251
+ if len(node.args) != 2:
4252
+ error(node.function.pos, "%s() takes exactly two arguments" % function)
4253
+ else:
4254
+ node = InterpretCompilerDirectives.binop_method_nodes[function](
4255
+ node.function.pos, operand1=node.args[0], operand2=node.args[1])
4256
+ elif function == 'cast':
4257
+ if len(node.args) != 2:
4258
+ error(node.function.pos,
4259
+ "cast() takes exactly two arguments and an optional typecheck keyword")
4260
+ else:
4261
+ type = node.args[0].analyse_as_type(self.current_env())
4262
+ if type:
4263
+ node = ExprNodes.TypecastNode(
4264
+ node.function.pos, type=type, operand=node.args[1], typecheck=False)
4265
+ else:
4266
+ error(node.args[0].pos, "Not a type")
4267
+ elif function == 'sizeof':
4268
+ if len(node.args) != 1:
4269
+ error(node.function.pos, "sizeof() takes exactly one argument")
4270
+ else:
4271
+ type = node.args[0].analyse_as_type(self.current_env())
4272
+ if type:
4273
+ node = ExprNodes.SizeofTypeNode(node.function.pos, arg_type=type)
4274
+ else:
4275
+ node = ExprNodes.SizeofVarNode(node.function.pos, operand=node.args[0])
4276
+ elif function == 'cmod':
4277
+ if len(node.args) != 2:
4278
+ error(node.function.pos, "cmod() takes exactly two arguments")
4279
+ else:
4280
+ node = ExprNodes.binop_node(node.function.pos, '%', node.args[0], node.args[1])
4281
+ node.cdivision = True
4282
+ elif function == 'cdiv':
4283
+ if len(node.args) != 2:
4284
+ error(node.function.pos, "cdiv() takes exactly two arguments")
4285
+ else:
4286
+ node = ExprNodes.binop_node(node.function.pos, '/', node.args[0], node.args[1])
4287
+ node.cdivision = True
4288
+ elif function == 'set':
4289
+ node.function = ExprNodes.NameNode(node.pos, name=EncodedString('set'))
4290
+ elif function == 'staticmethod':
4291
+ node.function = ExprNodes.NameNode(node.pos, name=EncodedString('staticmethod'))
4292
+ elif self.context.cython_scope.lookup_qualified_name(function):
4293
+ pass
4294
+ else:
4295
+ error(node.function.pos,
4296
+ "'%s' not a valid cython language construct" % function)
4297
+
4298
+ self.visitchildren(node)
4299
+
4300
+ if isinstance(node, ExprNodes.SimpleCallNode) and node.function.is_name:
4301
+ func_name = node.function.name
4302
+ if func_name in ('dir', 'locals', 'vars'):
4303
+ return self._inject_locals(node, func_name)
4304
+ if func_name == 'eval':
4305
+ return self._inject_eval(node, func_name)
4306
+ if func_name == 'super':
4307
+ return self._inject_super(node, func_name)
4308
+ return node
4309
+
4310
+ def visit_GeneralCallNode(self, node):
4311
+ function = node.function.as_cython_attribute()
4312
+ if function == 'cast':
4313
+ # NOTE: assuming simple tuple/dict nodes for positional_args and keyword_args
4314
+ args = node.positional_args.args
4315
+ kwargs = node.keyword_args.compile_time_value(None)
4316
+ if (len(args) != 2 or len(kwargs) > 1 or
4317
+ (len(kwargs) == 1 and 'typecheck' not in kwargs)):
4318
+ error(node.function.pos,
4319
+ "cast() takes exactly two arguments and an optional typecheck keyword")
4320
+ else:
4321
+ type = args[0].analyse_as_type(self.current_env())
4322
+ if type:
4323
+ typecheck = kwargs.get('typecheck', False)
4324
+ node = ExprNodes.TypecastNode(
4325
+ node.function.pos, type=type, operand=args[1], typecheck=typecheck)
4326
+ else:
4327
+ error(args[0].pos, "Not a type")
4328
+
4329
+ self.visitchildren(node)
4330
+ return node
4331
+
4332
+
4333
+ class ReplaceFusedTypeChecks(VisitorTransform):
4334
+ """
4335
+ This is not a transform in the pipeline. It is invoked on the specific
4336
+ versions of a cdef function with fused argument types. It filters out any
4337
+ type branches that don't match. e.g.
4338
+
4339
+ if fused_t is mytype:
4340
+ ...
4341
+ elif fused_t in other_fused_type:
4342
+ ...
4343
+ """
4344
+ def __init__(self, local_scope):
4345
+ super().__init__()
4346
+ self.local_scope = local_scope
4347
+ # defer the import until now to avoid circular import time dependencies
4348
+ from .Optimize import ConstantFolding
4349
+ self.transform = ConstantFolding(reevaluate=True)
4350
+
4351
+ def visit_IfStatNode(self, node):
4352
+ """
4353
+ Filters out any if clauses with false compile time type check
4354
+ expression.
4355
+ """
4356
+ self.visitchildren(node)
4357
+ return self.transform(node)
4358
+
4359
+ def visit_GILStatNode(self, node):
4360
+ """
4361
+ Fold constant condition of GILStatNode.
4362
+ """
4363
+ self.visitchildren(node)
4364
+ return self.transform(node)
4365
+
4366
+ def visit_PrimaryCmpNode(self, node):
4367
+ with Errors.local_errors(ignore=True):
4368
+ type1 = node.operand1.analyse_as_type(self.local_scope)
4369
+ type2 = node.operand2.analyse_as_type(self.local_scope)
4370
+
4371
+ if type1 and type2:
4372
+ false_node = ExprNodes.BoolNode(node.pos, value=False)
4373
+ true_node = ExprNodes.BoolNode(node.pos, value=True)
4374
+
4375
+ type1 = self.specialize_type(type1, node.operand1.pos)
4376
+ op = node.operator
4377
+
4378
+ if op in ('is', 'is_not', '==', '!='):
4379
+ type2 = self.specialize_type(type2, node.operand2.pos)
4380
+
4381
+ is_same = type1.same_as(type2)
4382
+ eq = op in ('is', '==')
4383
+
4384
+ if (is_same and eq) or (not is_same and not eq):
4385
+ return true_node
4386
+
4387
+ elif op in ('in', 'not_in'):
4388
+ # We have to do an instance check directly, as operand2
4389
+ # needs to be a fused type and not a type with a subtype
4390
+ # that is fused. First unpack the typedef
4391
+ if isinstance(type2, PyrexTypes.CTypedefType):
4392
+ type2 = type2.typedef_base_type
4393
+
4394
+ if type1.is_fused:
4395
+ error(node.operand1.pos, "Type is fused")
4396
+ elif not type2.is_fused:
4397
+ error(node.operand2.pos,
4398
+ "Can only use 'in' or 'not in' on a fused type")
4399
+ else:
4400
+ types = PyrexTypes.get_specialized_types(type2)
4401
+
4402
+ for specialized_type in types:
4403
+ if type1.same_as(specialized_type):
4404
+ if op == 'in':
4405
+ return true_node
4406
+ else:
4407
+ return false_node
4408
+
4409
+ if op == 'not_in':
4410
+ return true_node
4411
+
4412
+ return false_node
4413
+
4414
+ return node
4415
+
4416
+ def specialize_type(self, type, pos):
4417
+ try:
4418
+ return type.specialize(self.local_scope.fused_to_specific)
4419
+ except KeyError:
4420
+ error(pos, "Type is not specific")
4421
+ return type
4422
+
4423
+ def visit_Node(self, node):
4424
+ self.visitchildren(node)
4425
+ return node
4426
+
4427
+
4428
+ class DebugTransform(CythonTransform):
4429
+ """
4430
+ Write debug information for this Cython module.
4431
+ """
4432
+
4433
+ def __init__(self, context, options, result):
4434
+ super().__init__(context)
4435
+ self.visited = set()
4436
+ # our treebuilder and debug output writer
4437
+ # (see Cython.Debugger.debug_output.CythonDebugWriter)
4438
+ self.tb = self.context.gdb_debug_outputwriter
4439
+ #self.c_output_file = options.output_file
4440
+ self.c_output_file = result.c_file
4441
+
4442
+ # Closure support, basically treat nested functions as if the AST were
4443
+ # never nested
4444
+ self.nested_funcdefs = []
4445
+
4446
+ # tells visit_NameNode whether it should register step-into functions
4447
+ self.register_stepinto = False
4448
+
4449
+ def visit_ModuleNode(self, node):
4450
+ self.tb.module_name = node.full_module_name
4451
+ attrs = dict(
4452
+ module_name=node.full_module_name,
4453
+ filename=node.pos[0].filename,
4454
+ c_filename=self.c_output_file)
4455
+
4456
+ self.tb.start('Module', attrs)
4457
+
4458
+ # serialize functions
4459
+ self.tb.start('Functions')
4460
+ # First, serialize functions normally...
4461
+ self.visitchildren(node)
4462
+
4463
+ # ... then, serialize nested functions
4464
+ for nested_funcdef in self.nested_funcdefs:
4465
+ self.visit_FuncDefNode(nested_funcdef)
4466
+
4467
+ self.register_stepinto = True
4468
+ self.serialize_modulenode_as_function(node)
4469
+ self.register_stepinto = False
4470
+ self.tb.end('Functions')
4471
+
4472
+ # 2.3 compatibility. Serialize global variables
4473
+ self.tb.start('Globals')
4474
+ entries = {}
4475
+
4476
+ for k, v in node.scope.entries.items():
4477
+ if (v.qualified_name not in self.visited and not
4478
+ v.name.startswith('__pyx_') and not
4479
+ v.type.is_cfunction and not
4480
+ v.type.is_extension_type):
4481
+ entries[k]= v
4482
+
4483
+ self.serialize_local_variables(entries)
4484
+ self.tb.end('Globals')
4485
+ # self.tb.end('Module') # end Module after the line number mapping in
4486
+ # Cython.Compiler.ModuleNode.ModuleNode._serialize_lineno_map
4487
+ return node
4488
+
4489
+ def visit_FuncDefNode(self, node):
4490
+ self.visited.add(node.local_scope.qualified_name)
4491
+
4492
+ if getattr(node, 'is_wrapper', False):
4493
+ return node
4494
+
4495
+ if self.register_stepinto:
4496
+ self.nested_funcdefs.append(node)
4497
+ return node
4498
+
4499
+ # node.entry.visibility = 'extern'
4500
+ if node.py_func is None:
4501
+ pf_cname = ''
4502
+ else:
4503
+ pf_cname = node.py_func.entry.func_cname
4504
+
4505
+ # For functions defined using def, cname will be pyfunc_cname=__pyx_pf_*
4506
+ # For functions defined using cpdef or cdef, cname will be func_cname=__pyx_f_*
4507
+ # In all cases, cname will be the name of the function containing the actual code
4508
+ cname = node.entry.pyfunc_cname or node.entry.func_cname
4509
+
4510
+ attrs = dict(
4511
+ name=node.entry.name or getattr(node, 'name', '<unknown>'),
4512
+ cname=cname,
4513
+ pf_cname=pf_cname,
4514
+ qualified_name=node.local_scope.qualified_name,
4515
+ lineno=str(node.pos[1]))
4516
+
4517
+ self.tb.start('Function', attrs=attrs)
4518
+
4519
+ self.tb.start('Locals')
4520
+ self.serialize_local_variables(node.local_scope.entries)
4521
+ self.tb.end('Locals')
4522
+
4523
+ self.tb.start('Arguments')
4524
+ for arg in node.local_scope.arg_entries:
4525
+ self.tb.start(arg.name)
4526
+ self.tb.end(arg.name)
4527
+ self.tb.end('Arguments')
4528
+
4529
+ self.tb.start('StepIntoFunctions')
4530
+ self.register_stepinto = True
4531
+ self.visitchildren(node)
4532
+ self.register_stepinto = False
4533
+ self.tb.end('StepIntoFunctions')
4534
+ self.tb.end('Function')
4535
+
4536
+ return node
4537
+
4538
+ def visit_NameNode(self, node):
4539
+ if (self.register_stepinto and
4540
+ node.type is not None and
4541
+ node.type.is_cfunction and
4542
+ getattr(node, 'is_called', False) and
4543
+ node.entry.func_cname is not None):
4544
+ # don't check node.entry.in_cinclude, as 'cdef extern: ...'
4545
+ # declared functions are not 'in_cinclude'.
4546
+ # This means we will list called 'cdef' functions as
4547
+ # "step into functions", but this is not an issue as they will be
4548
+ # recognized as Cython functions anyway.
4549
+ attrs = dict(name=node.entry.func_cname)
4550
+ self.tb.start('StepIntoFunction', attrs=attrs)
4551
+ self.tb.end('StepIntoFunction')
4552
+
4553
+ self.visitchildren(node)
4554
+ return node
4555
+
4556
+ def serialize_modulenode_as_function(self, node):
4557
+ """
4558
+ Serialize the module-level code as a function so the debugger will know
4559
+ it's a "relevant frame" and it will know where to set the breakpoint
4560
+ for 'break modulename'.
4561
+ """
4562
+ self._serialize_modulenode_as_function(node, dict(
4563
+ name=node.full_module_name.rpartition('.')[-1],
4564
+ cname=node.module_init_func_cname(),
4565
+ pf_cname='',
4566
+ # Ignore the qualified_name, breakpoints should be set using
4567
+ # `cy break modulename:lineno` for module-level breakpoints.
4568
+ qualified_name='',
4569
+ lineno='1',
4570
+ is_initmodule_function="True",
4571
+ ))
4572
+
4573
+ def _serialize_modulenode_as_function(self, node, attrs):
4574
+ self.tb.start('Function', attrs=attrs)
4575
+
4576
+ self.tb.start('Locals')
4577
+ self.serialize_local_variables(node.scope.entries)
4578
+ self.tb.end('Locals')
4579
+
4580
+ self.tb.start('Arguments')
4581
+ self.tb.end('Arguments')
4582
+
4583
+ self.tb.start('StepIntoFunctions')
4584
+ self.register_stepinto = True
4585
+ self.visitchildren(node)
4586
+ self.register_stepinto = False
4587
+ self.tb.end('StepIntoFunctions')
4588
+
4589
+ self.tb.end('Function')
4590
+
4591
+ def serialize_local_variables(self, entries):
4592
+ for entry in entries.values():
4593
+ if not entry.cname:
4594
+ # not a local variable
4595
+ continue
4596
+ if entry.type.is_pyobject:
4597
+ vartype = 'PythonObject'
4598
+ else:
4599
+ vartype = 'CObject'
4600
+
4601
+ if entry.from_closure:
4602
+ # We're dealing with a closure where a variable from an outer
4603
+ # scope is accessed, get it from the scope object.
4604
+ cname = '%s->%s' % (Naming.cur_scope_cname,
4605
+ entry.outer_entry.cname)
4606
+
4607
+ qname = '%s.%s.%s' % (entry.scope.outer_scope.qualified_name,
4608
+ entry.scope.name,
4609
+ entry.name)
4610
+ elif entry.in_closure:
4611
+ cname = '%s->%s' % (Naming.cur_scope_cname,
4612
+ entry.cname)
4613
+ qname = entry.qualified_name
4614
+ else:
4615
+ cname = entry.cname
4616
+ qname = entry.qualified_name
4617
+
4618
+ if not entry.pos:
4619
+ # this happens for variables that are not in the user's code,
4620
+ # e.g. for the global __builtins__, __doc__, etc. We can just
4621
+ # set the lineno to 0 for those.
4622
+ lineno = '0'
4623
+ else:
4624
+ lineno = str(entry.pos[1])
4625
+
4626
+ attrs = dict(
4627
+ name=entry.name,
4628
+ cname=cname,
4629
+ qualified_name=qname,
4630
+ type=vartype,
4631
+ lineno=lineno)
4632
+
4633
+ self.tb.start('LocalVar', attrs)
4634
+ self.tb.end('LocalVar')
4635
+
4636
+
4637
+ class HasNoExceptionHandlingVisitor(TreeVisitor):
4638
+ """
4639
+ Used by finalExceptClauseNode to work out if the body
4640
+ needs to handle exceptions at all. This includes:
4641
+
4642
+ 1. Can raise an exception.
4643
+ 2. May try to access the traceback.
4644
+ """
4645
+ def __init__(self):
4646
+ self.uses_no_exceptions = True
4647
+ self.assignment_lhs = None
4648
+ super().__init__()
4649
+
4650
+ def __call__(self, node) -> bool:
4651
+ self.visit(node)
4652
+ return self.uses_no_exceptions
4653
+
4654
+ def visit_Node(self, node):
4655
+ self.uses_no_exceptions = False # In general, nodes use exceptions
4656
+
4657
+ def visit_ExprStatNode(self, node):
4658
+ self.visitchildren(node)
4659
+
4660
+ def visit_StatListNode(self, node):
4661
+ self.visitchildren(node)
4662
+
4663
+ def visit_ExprNode(self, node):
4664
+ if not node.is_literal:
4665
+ self.uses_no_exceptions = False
4666
+
4667
+ def visit_CallNode(self, node):
4668
+ # Implement this to make the behaviour as explicit as possible.
4669
+ # Even noexcept functions might end up printing a traceback.
4670
+ self.uses_no_exceptions = False
4671
+
4672
+ def visit_PassStatNode(self, node):
4673
+ pass # Does nothing. Good.
4674
+
4675
+ def visit_ReturnStatNode(self, node):
4676
+ if not self.uses_no_exceptions:
4677
+ return # shortcut
4678
+ self.visitchildren(node)
4679
+
4680
+ def visit_SingleAssignmentNode(self, node):
4681
+ if not self.uses_no_exceptions:
4682
+ return # shortcut
4683
+ self.assignment_lhs = node.lhs
4684
+ self.visit(node.lhs)
4685
+ self.assignment_lhs = None
4686
+ rhs_type = node.rhs.type
4687
+ if not (rhs_type.is_numeric or rhs_type.is_pyobject or rhs_type.is_memoryviewslice):
4688
+ # Treat everything we haven't explicitly thought about as potentially dubious.
4689
+ # cpp classes may have non-trivial assignment operators for example.
4690
+ self.uses_no_exceptions = False
4691
+ if not self.uses_no_exceptions:
4692
+ return
4693
+ self.visitchildren(node, exclude=["lhs"])
4694
+
4695
+ def visit_NameNode(self, node):
4696
+ if not self.uses_no_exceptions:
4697
+ return # shortcut
4698
+ entry = node.entry
4699
+ if self.assignment_lhs is node:
4700
+ if not (entry.is_cglobal or entry.is_arg or
4701
+ entry.is_local or entry.in_closure or entry.from_closure):
4702
+ self.uses_no_exceptions = False
4703
+ return
4704
+ else:
4705
+ if entry.is_cglobal:
4706
+ if entry.is_cpp_optional and node.initialized_check:
4707
+ # Otherwise, reading C globals should be safe.
4708
+ self.uses_no_exceptions = False
4709
+ return
4710
+ elif entry.is_arg or entry.is_local or entry.in_closure or entry.from_closure:
4711
+ if (node.cf_is_null or node.cf_maybe_null) and not node.type.is_numeric:
4712
+ # The logic here is slightly simpler than for NameNode error checking.
4713
+ # This gives a few false negatives (which is always the safe thing to do)
4714
+ # for memoryviews and cpp_optionals
4715
+ self.uses_no_exceptions = False
4716
+ return
4717
+ else:
4718
+ # Probably a py_global.
4719
+ self.uses_no_exceptions = False
4720
+ return
4721
+
4722
+ def visit_AttributeNode(self, node):
4723
+ if node.is_py_attr:
4724
+ self.uses_no_exceptions = False
4725
+ elif (node.type.is_memoryviewslice or node.entry.is_cpp_optional) and self.assignment_lhs is not node:
4726
+ # Memoryviewslices and cpp_optional are OK as a target, but reading them involves checks.
4727
+ # (Although cpp optionals are currently banned elsewhere
4728
+ # because C++ classes may have non-trivial assignment).
4729
+ self.uses_no_exceptions = False
4730
+ # Python objects just need an incref and simple C types are fine, too. Others may not be.
4731
+ if not (node.type.is_pyobject or node.type.is_numeric or node.type.is_memoryviewslice):
4732
+ self.uses_no_exceptions = False
4733
+ if self.uses_no_exceptions:
4734
+ self.visitchildren(node)
4735
+
4736
+ def visit_IndexNode(self, node):
4737
+ if not (node.base.type.is_array or node.base.type.is_ptr):
4738
+ self.uses_no_exceptions = False
4739
+ if not self.uses_no_exceptions:
4740
+ return
4741
+ self.visitchildren(node)
4742
+
4743
+ def visit_CoerceToTempNode(self, node):
4744
+ self.visitchildren(node)