cinderx 2026.1.16.2__cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.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.
- __static__/__init__.py +641 -0
- __static__/compiler_flags.py +8 -0
- __static__/enum.py +160 -0
- __static__/native_utils.py +77 -0
- __static__/type_code.py +48 -0
- __strict__/__init__.py +39 -0
- _cinderx.so +0 -0
- cinderx/__init__.py +577 -0
- cinderx/__pycache__/__init__.cpython-314.pyc +0 -0
- cinderx/_asyncio.py +156 -0
- cinderx/compileall.py +710 -0
- cinderx/compiler/__init__.py +40 -0
- cinderx/compiler/__main__.py +137 -0
- cinderx/compiler/config.py +7 -0
- cinderx/compiler/consts.py +72 -0
- cinderx/compiler/debug.py +70 -0
- cinderx/compiler/dis_stable.py +283 -0
- cinderx/compiler/errors.py +151 -0
- cinderx/compiler/flow_graph_optimizer.py +1287 -0
- cinderx/compiler/future.py +91 -0
- cinderx/compiler/misc.py +32 -0
- cinderx/compiler/opcode_cinder.py +18 -0
- cinderx/compiler/opcode_static.py +100 -0
- cinderx/compiler/opcodebase.py +158 -0
- cinderx/compiler/opcodes.py +991 -0
- cinderx/compiler/optimizer.py +547 -0
- cinderx/compiler/pyassem.py +3711 -0
- cinderx/compiler/pycodegen.py +7660 -0
- cinderx/compiler/pysourceloader.py +62 -0
- cinderx/compiler/static/__init__.py +1404 -0
- cinderx/compiler/static/compiler.py +629 -0
- cinderx/compiler/static/declaration_visitor.py +335 -0
- cinderx/compiler/static/definite_assignment_checker.py +280 -0
- cinderx/compiler/static/effects.py +160 -0
- cinderx/compiler/static/module_table.py +666 -0
- cinderx/compiler/static/type_binder.py +2176 -0
- cinderx/compiler/static/types.py +10580 -0
- cinderx/compiler/static/util.py +81 -0
- cinderx/compiler/static/visitor.py +91 -0
- cinderx/compiler/strict/__init__.py +69 -0
- cinderx/compiler/strict/class_conflict_checker.py +249 -0
- cinderx/compiler/strict/code_gen_base.py +409 -0
- cinderx/compiler/strict/common.py +507 -0
- cinderx/compiler/strict/compiler.py +352 -0
- cinderx/compiler/strict/feature_extractor.py +130 -0
- cinderx/compiler/strict/flag_extractor.py +97 -0
- cinderx/compiler/strict/loader.py +827 -0
- cinderx/compiler/strict/preprocessor.py +11 -0
- cinderx/compiler/strict/rewriter/__init__.py +5 -0
- cinderx/compiler/strict/rewriter/remove_annotations.py +84 -0
- cinderx/compiler/strict/rewriter/rewriter.py +975 -0
- cinderx/compiler/strict/runtime.py +77 -0
- cinderx/compiler/symbols.py +1754 -0
- cinderx/compiler/unparse.py +414 -0
- cinderx/compiler/visitor.py +194 -0
- cinderx/jit.py +230 -0
- cinderx/opcode.py +202 -0
- cinderx/static.py +113 -0
- cinderx/strictmodule.py +6 -0
- cinderx/test_support.py +341 -0
- cinderx-2026.1.16.2.dist-info/METADATA +15 -0
- cinderx-2026.1.16.2.dist-info/RECORD +68 -0
- cinderx-2026.1.16.2.dist-info/WHEEL +6 -0
- cinderx-2026.1.16.2.dist-info/licenses/LICENSE +21 -0
- cinderx-2026.1.16.2.dist-info/top_level.txt +5 -0
- opcodes/__init__.py +0 -0
- opcodes/assign_opcode_numbers.py +272 -0
- opcodes/cinderx_opcodes.py +121 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
# pyre-strict
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import ast
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import symtable
|
|
11
|
+
import sys
|
|
12
|
+
from contextlib import nullcontext
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from symtable import SymbolTable as PythonSymbolTable
|
|
15
|
+
from types import CodeType
|
|
16
|
+
from typing import Callable, ContextManager, final, Iterable
|
|
17
|
+
|
|
18
|
+
from ..errors import TypedSyntaxError
|
|
19
|
+
from ..pycodegen import compile_code
|
|
20
|
+
from ..static import StaticCodeGenerator
|
|
21
|
+
from ..static.compiler import Compiler as StaticCompiler
|
|
22
|
+
from ..static.module_table import ModuleTable
|
|
23
|
+
from . import strict_compile
|
|
24
|
+
from .common import StrictModuleError
|
|
25
|
+
from .flag_extractor import FlagExtractor, Flags
|
|
26
|
+
from .rewriter import remove_annotations, rewrite
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
TIMING_LOGGER_TYPE = Callable[[str, str, str], ContextManager[None]]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class SourceInfo:
|
|
34
|
+
"""Data about a Python module source file."""
|
|
35
|
+
|
|
36
|
+
# module name
|
|
37
|
+
name: str
|
|
38
|
+
# import path prefix within which the module was found
|
|
39
|
+
prefix: str
|
|
40
|
+
# path to module file, relative to import path prefix
|
|
41
|
+
relative_path: str
|
|
42
|
+
# should always be os.path.join(prefix, relative_path)
|
|
43
|
+
path: str
|
|
44
|
+
# data we might need for pyc invalidation
|
|
45
|
+
source: bytes | None
|
|
46
|
+
mtime: int
|
|
47
|
+
size: int
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class Dependencies:
|
|
52
|
+
static: list[SourceInfo]
|
|
53
|
+
nonstatic: list[str]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
NOT_FOUND = object()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@final
|
|
60
|
+
class Compiler(StaticCompiler):
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
import_path: Iterable[str],
|
|
64
|
+
stub_root: str,
|
|
65
|
+
allow_list_prefix: Iterable[str],
|
|
66
|
+
allow_list_exact: Iterable[str],
|
|
67
|
+
log_time_func: Callable[[], TIMING_LOGGER_TYPE] | None = None,
|
|
68
|
+
raise_on_error: bool = False,
|
|
69
|
+
enable_patching: bool = False,
|
|
70
|
+
use_py_compiler: bool = False,
|
|
71
|
+
allow_list_regex: Iterable[str] | None = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
super().__init__(StaticCodeGenerator)
|
|
74
|
+
self.import_path: list[str] = list(import_path)
|
|
75
|
+
self.stub_root = stub_root
|
|
76
|
+
self.allow_list_prefix = allow_list_prefix
|
|
77
|
+
self.allow_list_exact = allow_list_exact
|
|
78
|
+
self.allow_list_regex: Iterable[str] = allow_list_regex or []
|
|
79
|
+
self.verbose = bool(
|
|
80
|
+
os.getenv("PYTHONSTRICTVERBOSE")
|
|
81
|
+
or sys._xoptions.get("strict-verbose") is True
|
|
82
|
+
)
|
|
83
|
+
self.disable_analysis = bool(
|
|
84
|
+
os.getenv("PYTHONSTRICTDISABLEANALYSIS")
|
|
85
|
+
or sys._xoptions.get("strict-disable-analysis") is True
|
|
86
|
+
)
|
|
87
|
+
self.raise_on_error = raise_on_error
|
|
88
|
+
self.log_time_func = log_time_func
|
|
89
|
+
self.enable_patching = enable_patching
|
|
90
|
+
self.not_static: set[str] = set()
|
|
91
|
+
self.use_py_compiler = use_py_compiler
|
|
92
|
+
self.original_builtins: dict[str, object] = dict(__builtins__)
|
|
93
|
+
self.logger: logging.Logger = self._setup_logging()
|
|
94
|
+
self.sources: dict[str, SourceInfo | None] = {}
|
|
95
|
+
|
|
96
|
+
def get_dependencies(self, name: str) -> Dependencies:
|
|
97
|
+
"""Get static and non-static dependencies of given module."""
|
|
98
|
+
mod = self.modules.get(name)
|
|
99
|
+
if mod is None:
|
|
100
|
+
return Dependencies(static=[], nonstatic=[])
|
|
101
|
+
|
|
102
|
+
static = []
|
|
103
|
+
nonstatic = []
|
|
104
|
+
for depmod in mod.get_dependencies():
|
|
105
|
+
if isinstance(depmod, ModuleTable):
|
|
106
|
+
# changes to these should be reflected in our compiler magic
|
|
107
|
+
# number, and don't need to be recorded as dependencies
|
|
108
|
+
if depmod.name in self.intrinsic_modules:
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
source_info = self.sources.get(depmod.name)
|
|
112
|
+
if source_info is None:
|
|
113
|
+
# the dependency may have been previously compiled as the
|
|
114
|
+
# main module in the same Compiler, in which case we'll have
|
|
115
|
+
# a ModuleTable already but no SourceInfo
|
|
116
|
+
source_info = self.get_source(depmod.name)
|
|
117
|
+
assert source_info is not None
|
|
118
|
+
static.append(source_info)
|
|
119
|
+
else: # UnknownModule, non-static
|
|
120
|
+
nonstatic.append(depmod.name)
|
|
121
|
+
|
|
122
|
+
return Dependencies(static=static, nonstatic=nonstatic)
|
|
123
|
+
|
|
124
|
+
def import_module(self, name: str, optimize: int) -> ModuleTable | None:
|
|
125
|
+
res = self.modules.get(name)
|
|
126
|
+
if res is not None:
|
|
127
|
+
return res
|
|
128
|
+
|
|
129
|
+
if name in self.not_static:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
source_info = self.get_source(name)
|
|
133
|
+
if source_info is None:
|
|
134
|
+
return None
|
|
135
|
+
source = source_info.source
|
|
136
|
+
assert source is not None
|
|
137
|
+
filename = source_info.relative_path
|
|
138
|
+
|
|
139
|
+
pyast = ast.parse(source)
|
|
140
|
+
flags = FlagExtractor().get_flags(pyast)
|
|
141
|
+
|
|
142
|
+
if name not in self.modules:
|
|
143
|
+
if flags.is_static:
|
|
144
|
+
# pyre-fixme[6]: For 1st argument expected `str` but got
|
|
145
|
+
# `Union[bytes, str]`.
|
|
146
|
+
symbols = symtable.symtable(source, filename, "exec")
|
|
147
|
+
root = pyast
|
|
148
|
+
|
|
149
|
+
if filename.endswith(".pyi"):
|
|
150
|
+
root = remove_annotations(root)
|
|
151
|
+
|
|
152
|
+
root = self._get_rewritten_ast(name, root, symbols, filename, optimize)
|
|
153
|
+
log = self.log_time_func
|
|
154
|
+
ctx = (
|
|
155
|
+
log()(name, filename, "declaration_visit") if log else nullcontext()
|
|
156
|
+
)
|
|
157
|
+
with ctx:
|
|
158
|
+
root = self.add_module(name, filename, root, source, optimize)
|
|
159
|
+
else:
|
|
160
|
+
self.not_static.add(name)
|
|
161
|
+
|
|
162
|
+
return self.modules.get(name)
|
|
163
|
+
|
|
164
|
+
def _get_rewritten_ast(
|
|
165
|
+
self,
|
|
166
|
+
name: str,
|
|
167
|
+
root: ast.Module,
|
|
168
|
+
symbols: PythonSymbolTable,
|
|
169
|
+
filename: str,
|
|
170
|
+
optimize: int,
|
|
171
|
+
) -> ast.Module:
|
|
172
|
+
return rewrite(
|
|
173
|
+
root,
|
|
174
|
+
symbols,
|
|
175
|
+
filename,
|
|
176
|
+
name,
|
|
177
|
+
optimize=optimize,
|
|
178
|
+
is_static=True,
|
|
179
|
+
builtins=self.original_builtins,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def _setup_logging(self) -> logging.Logger:
|
|
183
|
+
logger = logging.Logger(__name__)
|
|
184
|
+
if self.verbose:
|
|
185
|
+
logger.setLevel(logging.DEBUG)
|
|
186
|
+
return logger
|
|
187
|
+
|
|
188
|
+
def load_compiled_module_from_source(
|
|
189
|
+
self,
|
|
190
|
+
source: str | bytes,
|
|
191
|
+
filename: str,
|
|
192
|
+
name: str,
|
|
193
|
+
optimize: int,
|
|
194
|
+
submodule_search_locations: list[str] | None = None,
|
|
195
|
+
override_flags: Flags | None = None,
|
|
196
|
+
) -> tuple[CodeType | None, bool, bool]:
|
|
197
|
+
pyast = ast.parse(source)
|
|
198
|
+
# pyre-fixme[6]: For 1st argument expected `str` but got `Union[bytes, str]`.
|
|
199
|
+
symbols = symtable.symtable(source, filename, "exec")
|
|
200
|
+
flags = FlagExtractor().get_flags(pyast).merge(override_flags)
|
|
201
|
+
|
|
202
|
+
if not flags.is_static and not flags.is_strict:
|
|
203
|
+
code = self._compile_basic(name, pyast, filename, optimize)
|
|
204
|
+
return (code, False, False)
|
|
205
|
+
|
|
206
|
+
if flags.is_static:
|
|
207
|
+
code = self._compile_static(
|
|
208
|
+
pyast, source, symbols, filename, name, optimize
|
|
209
|
+
)
|
|
210
|
+
return (code, flags.is_strict, True)
|
|
211
|
+
else:
|
|
212
|
+
code = self._compile_strict(
|
|
213
|
+
pyast, source, symbols, filename, name, optimize
|
|
214
|
+
)
|
|
215
|
+
return (code, flags.is_strict, False)
|
|
216
|
+
|
|
217
|
+
def get_source(self, name: str, *, need_contents: bool = True) -> SourceInfo | None:
|
|
218
|
+
source_info = self.sources.get(name, NOT_FOUND)
|
|
219
|
+
if source_info is NOT_FOUND:
|
|
220
|
+
self.sources[name] = source_info = self._get_source(
|
|
221
|
+
name, need_contents=need_contents
|
|
222
|
+
)
|
|
223
|
+
assert source_info is None or isinstance(source_info, SourceInfo)
|
|
224
|
+
if source_info and source_info.source is None and need_contents:
|
|
225
|
+
# We don't need to re-do the sys.path finding, but we should update
|
|
226
|
+
# the size and mtime to match the current source.
|
|
227
|
+
self.sources[name] = source_info = self._get_source_info(
|
|
228
|
+
name, source_info.prefix, source_info.relative_path, need_contents=True
|
|
229
|
+
)
|
|
230
|
+
return source_info
|
|
231
|
+
|
|
232
|
+
def _get_source(self, name: str, *, need_contents: bool) -> SourceInfo | None:
|
|
233
|
+
module_path = name.replace(".", os.sep)
|
|
234
|
+
|
|
235
|
+
for path in self.import_path:
|
|
236
|
+
source_info = self._get_source_info(
|
|
237
|
+
name,
|
|
238
|
+
path,
|
|
239
|
+
module_path + os.sep + "__init__.py",
|
|
240
|
+
need_contents=need_contents,
|
|
241
|
+
)
|
|
242
|
+
if source_info is not None:
|
|
243
|
+
return source_info
|
|
244
|
+
|
|
245
|
+
source_info = self._get_source_info(
|
|
246
|
+
name, path, module_path + ".py", need_contents=need_contents
|
|
247
|
+
)
|
|
248
|
+
if source_info is not None:
|
|
249
|
+
return source_info
|
|
250
|
+
|
|
251
|
+
for path in self.import_path:
|
|
252
|
+
source_info = self._get_source_info(
|
|
253
|
+
name, path, module_path + ".pyi", need_contents=need_contents
|
|
254
|
+
)
|
|
255
|
+
if source_info is not None:
|
|
256
|
+
return source_info
|
|
257
|
+
|
|
258
|
+
return None
|
|
259
|
+
|
|
260
|
+
def _get_source_info(
|
|
261
|
+
self, name: str, prefix: str, relpath: str, *, need_contents: bool
|
|
262
|
+
) -> SourceInfo | None:
|
|
263
|
+
path = os.path.join(prefix, relpath)
|
|
264
|
+
if not os.path.exists(path):
|
|
265
|
+
return None
|
|
266
|
+
st = os.stat(path)
|
|
267
|
+
source = None
|
|
268
|
+
if need_contents:
|
|
269
|
+
with open(path, "rb") as f:
|
|
270
|
+
source = f.read()
|
|
271
|
+
return SourceInfo(
|
|
272
|
+
name=name,
|
|
273
|
+
prefix=prefix,
|
|
274
|
+
relative_path=relpath,
|
|
275
|
+
path=path,
|
|
276
|
+
source=source,
|
|
277
|
+
mtime=int(st.st_mtime),
|
|
278
|
+
size=st.st_size,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def _compile_basic(
|
|
282
|
+
self, name: str, root: ast.Module, filename: str, optimize: int
|
|
283
|
+
) -> CodeType:
|
|
284
|
+
compile_method = compile_code if self.use_py_compiler else compile
|
|
285
|
+
result = compile_method(
|
|
286
|
+
root,
|
|
287
|
+
filename,
|
|
288
|
+
"exec",
|
|
289
|
+
optimize=optimize,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
assert isinstance(result, CodeType)
|
|
293
|
+
return result
|
|
294
|
+
|
|
295
|
+
def _compile_strict(
|
|
296
|
+
self,
|
|
297
|
+
root: ast.Module,
|
|
298
|
+
source: str | bytes,
|
|
299
|
+
symbols: PythonSymbolTable,
|
|
300
|
+
filename: str,
|
|
301
|
+
name: str,
|
|
302
|
+
optimize: int,
|
|
303
|
+
) -> CodeType:
|
|
304
|
+
tree = rewrite(
|
|
305
|
+
root,
|
|
306
|
+
symbols,
|
|
307
|
+
filename,
|
|
308
|
+
name,
|
|
309
|
+
optimize=optimize,
|
|
310
|
+
builtins=self.original_builtins,
|
|
311
|
+
)
|
|
312
|
+
return strict_compile(
|
|
313
|
+
name, filename, tree, source, optimize, self.original_builtins
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
def _compile_static(
|
|
317
|
+
self,
|
|
318
|
+
root: ast.Module,
|
|
319
|
+
source: str | bytes,
|
|
320
|
+
symbols: PythonSymbolTable,
|
|
321
|
+
filename: str,
|
|
322
|
+
name: str,
|
|
323
|
+
optimize: int,
|
|
324
|
+
) -> CodeType | None:
|
|
325
|
+
root = self.ast_cache.get(source) or self._get_rewritten_ast(
|
|
326
|
+
name, root, symbols, filename, optimize
|
|
327
|
+
)
|
|
328
|
+
try:
|
|
329
|
+
log = self.log_time_func
|
|
330
|
+
ctx = log()(name, filename, "compile") if log else nullcontext()
|
|
331
|
+
with ctx:
|
|
332
|
+
return self.compile(
|
|
333
|
+
name,
|
|
334
|
+
filename,
|
|
335
|
+
root,
|
|
336
|
+
source,
|
|
337
|
+
optimize,
|
|
338
|
+
enable_patching=self.enable_patching,
|
|
339
|
+
builtins=self.original_builtins,
|
|
340
|
+
)
|
|
341
|
+
except TypedSyntaxError as e:
|
|
342
|
+
err = StrictModuleError(
|
|
343
|
+
e.msg or "unknown error during static compilation",
|
|
344
|
+
e.filename or filename,
|
|
345
|
+
e.lineno or 1,
|
|
346
|
+
0,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
if self.raise_on_error:
|
|
350
|
+
raise err
|
|
351
|
+
|
|
352
|
+
return None
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
# pyre-strict
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import ast
|
|
8
|
+
from ast import (
|
|
9
|
+
alias,
|
|
10
|
+
AsyncFunctionDef,
|
|
11
|
+
Call,
|
|
12
|
+
ClassDef,
|
|
13
|
+
FunctionDef,
|
|
14
|
+
Global,
|
|
15
|
+
Import,
|
|
16
|
+
ImportFrom,
|
|
17
|
+
Name,
|
|
18
|
+
Try,
|
|
19
|
+
)
|
|
20
|
+
from typing import Any, final
|
|
21
|
+
|
|
22
|
+
from ..symbols import ModuleScope, Scope, SymbolVisitor
|
|
23
|
+
from .common import imported_name
|
|
24
|
+
|
|
25
|
+
_IMPLICIT_GLOBALS = [
|
|
26
|
+
"__name__",
|
|
27
|
+
"__loader__",
|
|
28
|
+
"__package__",
|
|
29
|
+
"__spec__",
|
|
30
|
+
"__path__",
|
|
31
|
+
"__file__",
|
|
32
|
+
"__cached__",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@final
|
|
37
|
+
class FeatureExtractor(SymbolVisitor):
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
builtins: dict[str, Any],
|
|
41
|
+
future_flags: int,
|
|
42
|
+
) -> None:
|
|
43
|
+
super().__init__(future_flags)
|
|
44
|
+
self.builtins = builtins
|
|
45
|
+
self.globals: set[str] = set()
|
|
46
|
+
self.global_sets: set[str] = set()
|
|
47
|
+
self.global_dels: set[str] = set()
|
|
48
|
+
self.future_imports: set[alias] = set()
|
|
49
|
+
|
|
50
|
+
def is_global(self, name: str, scope: Scope) -> bool:
|
|
51
|
+
if isinstance(scope, ModuleScope) or scope.global_scope:
|
|
52
|
+
return True
|
|
53
|
+
if name in scope.module.globals or name in scope.module.explicit_globals:
|
|
54
|
+
return True
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
def load_name(self, name: str, scope: Scope) -> None:
|
|
58
|
+
if self.is_global(name, scope):
|
|
59
|
+
self.globals.add(name)
|
|
60
|
+
|
|
61
|
+
def store_name(self, name: str, scope: Scope) -> None:
|
|
62
|
+
if self.is_global(name, scope):
|
|
63
|
+
self.globals.add(name)
|
|
64
|
+
self.global_sets.add(name)
|
|
65
|
+
|
|
66
|
+
def del_name(self, name: str, scope: Scope) -> None:
|
|
67
|
+
if self.is_global(name, scope):
|
|
68
|
+
self.globals.add(name)
|
|
69
|
+
self.global_sets.add(name)
|
|
70
|
+
self.global_dels.add(name)
|
|
71
|
+
|
|
72
|
+
def visitGlobal(self, node: Global, scope: Scope) -> None:
|
|
73
|
+
super().visitGlobal(node, scope)
|
|
74
|
+
for name in node.names:
|
|
75
|
+
self.globals.add(name)
|
|
76
|
+
|
|
77
|
+
def visitName(self, node: Name, scope: Scope) -> None:
|
|
78
|
+
super().visitName(node, scope)
|
|
79
|
+
if isinstance(node.ctx, ast.Load):
|
|
80
|
+
self.load_name(node.id, scope)
|
|
81
|
+
elif isinstance(node.ctx, ast.Store):
|
|
82
|
+
self.store_name(node.id, scope)
|
|
83
|
+
elif isinstance(node.ctx, ast.Del):
|
|
84
|
+
self.del_name(node.id, scope)
|
|
85
|
+
|
|
86
|
+
def visitImportFrom(self, node: ImportFrom, scope: Scope) -> None:
|
|
87
|
+
super().visitImportFrom(node, scope)
|
|
88
|
+
if node.module == "__future__":
|
|
89
|
+
self.future_imports.update(node.names)
|
|
90
|
+
|
|
91
|
+
for name in node.names:
|
|
92
|
+
self.store_name(name.asname or name.name, scope)
|
|
93
|
+
|
|
94
|
+
def visitImport(self, node: Import, scope: Scope) -> None:
|
|
95
|
+
super().visitImport(node, scope)
|
|
96
|
+
for name in node.names:
|
|
97
|
+
self.store_name(imported_name(name), scope)
|
|
98
|
+
|
|
99
|
+
def visitCall(self, node: Call, scope: Scope) -> None:
|
|
100
|
+
func = node.func
|
|
101
|
+
if isinstance(func, ast.Name):
|
|
102
|
+
# We don't currently allow aliasing or shadowing exec/eval
|
|
103
|
+
# so this check is currently sufficient.
|
|
104
|
+
if (func.id == "exec" or func.id == "eval") and len(node.args) < 2:
|
|
105
|
+
# We'll need access to our globals() helper when we transform
|
|
106
|
+
# the ast
|
|
107
|
+
self.globals.add("globals")
|
|
108
|
+
self.globals.add("locals")
|
|
109
|
+
self.generic_visit(node, scope)
|
|
110
|
+
|
|
111
|
+
def visitTry(self, node: Try, scope: Scope) -> None:
|
|
112
|
+
super().visitTry(node, scope)
|
|
113
|
+
|
|
114
|
+
for handler in node.handlers:
|
|
115
|
+
name = handler.name
|
|
116
|
+
if name is None:
|
|
117
|
+
continue
|
|
118
|
+
self.del_name(name, scope)
|
|
119
|
+
|
|
120
|
+
def visitClassDef(self, node: ClassDef, parent: Scope) -> None:
|
|
121
|
+
self.store_name(node.name, parent)
|
|
122
|
+
super().visitClassDef(node, parent)
|
|
123
|
+
|
|
124
|
+
def visitFunctionDef(self, node: FunctionDef, parent: Scope) -> None:
|
|
125
|
+
self.store_name(node.name, parent)
|
|
126
|
+
super().visitFunctionDef(node, parent)
|
|
127
|
+
|
|
128
|
+
def visitAsyncFunctionDef(self, node: AsyncFunctionDef, parent: Scope) -> None:
|
|
129
|
+
self.store_name(node.name, parent)
|
|
130
|
+
super().visitAsyncFunctionDef(node, parent)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
# pyre-strict
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import ast
|
|
8
|
+
from ast import Import, Module
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any, Sequence
|
|
11
|
+
|
|
12
|
+
from ..symbols import ModuleScope, Scope, SymbolVisitor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Flags:
|
|
17
|
+
is_static: bool = False
|
|
18
|
+
is_strict: bool = False
|
|
19
|
+
|
|
20
|
+
def merge(self, other: Flags | None) -> Flags:
|
|
21
|
+
if other is None:
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
return Flags(
|
|
25
|
+
is_static=self.is_static or other.is_static,
|
|
26
|
+
is_strict=self.is_strict or other.is_strict,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BadFlagException(Exception):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FlagExtractor(SymbolVisitor):
|
|
35
|
+
is_static: bool
|
|
36
|
+
is_strict: bool
|
|
37
|
+
|
|
38
|
+
flag_may_appear: bool
|
|
39
|
+
seen_docstring: bool
|
|
40
|
+
seen_import: bool
|
|
41
|
+
|
|
42
|
+
def __init__(self, future_flags: int = 0) -> None:
|
|
43
|
+
super().__init__(future_flags)
|
|
44
|
+
self.is_static = False
|
|
45
|
+
self.is_strict = False
|
|
46
|
+
|
|
47
|
+
self.flag_may_appear = True
|
|
48
|
+
self.seen_docstring = False
|
|
49
|
+
self.seen_import = False
|
|
50
|
+
|
|
51
|
+
def get_flags(self, node: ast.AST) -> Flags:
|
|
52
|
+
assert isinstance(node, ast.Module)
|
|
53
|
+
self._handle_module(node)
|
|
54
|
+
return Flags(is_static=self.is_static, is_strict=self.is_strict)
|
|
55
|
+
|
|
56
|
+
def _handle_module(self, node: Module) -> None:
|
|
57
|
+
for child in node.body:
|
|
58
|
+
match child:
|
|
59
|
+
case ast.Expr(ast.Constant(value)) if (
|
|
60
|
+
isinstance(value, str) and not self.seen_docstring
|
|
61
|
+
):
|
|
62
|
+
self.seen_docstring = True
|
|
63
|
+
case ast.ImportFrom(module) if module == "__future__":
|
|
64
|
+
pass
|
|
65
|
+
case ast.Constant(_):
|
|
66
|
+
pass
|
|
67
|
+
case ast.Import(_) as import_node:
|
|
68
|
+
self._handle_import(import_node)
|
|
69
|
+
case _:
|
|
70
|
+
self.flag_may_appear = False
|
|
71
|
+
|
|
72
|
+
def _handle_import(self, node: Import) -> None:
|
|
73
|
+
for import_ in node.names:
|
|
74
|
+
name = import_.name
|
|
75
|
+
|
|
76
|
+
if name not in ("__static__", "__strict__", "__future__"):
|
|
77
|
+
self.flag_may_appear = False
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
if not self.flag_may_appear:
|
|
81
|
+
raise BadFlagException(
|
|
82
|
+
f"Cinder flag {name} must be at the top of a file"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if len(node.names) > 1:
|
|
86
|
+
raise BadFlagException(
|
|
87
|
+
f"{name} flag may not be combined with other imports",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if import_.asname is not None:
|
|
91
|
+
raise BadFlagException(f"{name} flag may not be aliased")
|
|
92
|
+
|
|
93
|
+
match name:
|
|
94
|
+
case "__static__":
|
|
95
|
+
self.is_static = True
|
|
96
|
+
case "__strict__":
|
|
97
|
+
self.is_strict = True
|