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.
Files changed (68) hide show
  1. __static__/__init__.py +641 -0
  2. __static__/compiler_flags.py +8 -0
  3. __static__/enum.py +160 -0
  4. __static__/native_utils.py +77 -0
  5. __static__/type_code.py +48 -0
  6. __strict__/__init__.py +39 -0
  7. _cinderx.so +0 -0
  8. cinderx/__init__.py +577 -0
  9. cinderx/__pycache__/__init__.cpython-314.pyc +0 -0
  10. cinderx/_asyncio.py +156 -0
  11. cinderx/compileall.py +710 -0
  12. cinderx/compiler/__init__.py +40 -0
  13. cinderx/compiler/__main__.py +137 -0
  14. cinderx/compiler/config.py +7 -0
  15. cinderx/compiler/consts.py +72 -0
  16. cinderx/compiler/debug.py +70 -0
  17. cinderx/compiler/dis_stable.py +283 -0
  18. cinderx/compiler/errors.py +151 -0
  19. cinderx/compiler/flow_graph_optimizer.py +1287 -0
  20. cinderx/compiler/future.py +91 -0
  21. cinderx/compiler/misc.py +32 -0
  22. cinderx/compiler/opcode_cinder.py +18 -0
  23. cinderx/compiler/opcode_static.py +100 -0
  24. cinderx/compiler/opcodebase.py +158 -0
  25. cinderx/compiler/opcodes.py +991 -0
  26. cinderx/compiler/optimizer.py +547 -0
  27. cinderx/compiler/pyassem.py +3711 -0
  28. cinderx/compiler/pycodegen.py +7660 -0
  29. cinderx/compiler/pysourceloader.py +62 -0
  30. cinderx/compiler/static/__init__.py +1404 -0
  31. cinderx/compiler/static/compiler.py +629 -0
  32. cinderx/compiler/static/declaration_visitor.py +335 -0
  33. cinderx/compiler/static/definite_assignment_checker.py +280 -0
  34. cinderx/compiler/static/effects.py +160 -0
  35. cinderx/compiler/static/module_table.py +666 -0
  36. cinderx/compiler/static/type_binder.py +2176 -0
  37. cinderx/compiler/static/types.py +10580 -0
  38. cinderx/compiler/static/util.py +81 -0
  39. cinderx/compiler/static/visitor.py +91 -0
  40. cinderx/compiler/strict/__init__.py +69 -0
  41. cinderx/compiler/strict/class_conflict_checker.py +249 -0
  42. cinderx/compiler/strict/code_gen_base.py +409 -0
  43. cinderx/compiler/strict/common.py +507 -0
  44. cinderx/compiler/strict/compiler.py +352 -0
  45. cinderx/compiler/strict/feature_extractor.py +130 -0
  46. cinderx/compiler/strict/flag_extractor.py +97 -0
  47. cinderx/compiler/strict/loader.py +827 -0
  48. cinderx/compiler/strict/preprocessor.py +11 -0
  49. cinderx/compiler/strict/rewriter/__init__.py +5 -0
  50. cinderx/compiler/strict/rewriter/remove_annotations.py +84 -0
  51. cinderx/compiler/strict/rewriter/rewriter.py +975 -0
  52. cinderx/compiler/strict/runtime.py +77 -0
  53. cinderx/compiler/symbols.py +1754 -0
  54. cinderx/compiler/unparse.py +414 -0
  55. cinderx/compiler/visitor.py +194 -0
  56. cinderx/jit.py +230 -0
  57. cinderx/opcode.py +202 -0
  58. cinderx/static.py +113 -0
  59. cinderx/strictmodule.py +6 -0
  60. cinderx/test_support.py +341 -0
  61. cinderx-2026.1.16.2.dist-info/METADATA +15 -0
  62. cinderx-2026.1.16.2.dist-info/RECORD +68 -0
  63. cinderx-2026.1.16.2.dist-info/WHEEL +6 -0
  64. cinderx-2026.1.16.2.dist-info/licenses/LICENSE +21 -0
  65. cinderx-2026.1.16.2.dist-info/top_level.txt +5 -0
  66. opcodes/__init__.py +0 -0
  67. opcodes/assign_opcode_numbers.py +272 -0
  68. 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