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