Skip to content

Commit 53497a9

Browse files
DinoVmeta-codesync[bot]
authored andcommitted
Add Pyrefly loader
Summary: Adds a loader for Pyrefly which uses the PyreflyCompiler Reviewed By: alexmalyshev Differential Revision: D96008532 fbshipit-source-id: eb6e079a8edf53c70ce27ce34b767d294cf1c570
1 parent c146b15 commit 53497a9

3 files changed

Lines changed: 181 additions & 13 deletions

File tree

cinderx/PythonLib/cinderx/compiler/static/pyrefly_compiler.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# pyre-strict
23

34
import ast
45
import sys
6+
from typing import Callable, Iterable
57

68
from cinderx.compiler.static.pyrefly_type_binder import (
79
PyreflyTypeBinder,
810
PyreflyTypeInfo,
911
)
1012
from cinderx.compiler.static.type_binder import TypeBinder
11-
from cinderx.compiler.strict.compiler import Compiler
13+
from cinderx.compiler.strict.compiler import Compiler, TIMING_LOGGER_TYPE
1214
from cinderx.compiler.strict.flag_extractor import Flags
1315
from cinderx.compiler.symbols import SymbolVisitor
1416

@@ -17,23 +19,43 @@ class PyreflyCompiler(Compiler):
1719
def __init__(
1820
self,
1921
type_info: PyreflyTypeInfo | None = None,
20-
non_static_modules: set[str] | None = None,
22+
static_opt_out: set[str] | None = None,
23+
static_opt_in: set[str] | None = None,
24+
path: Iterable[str] | None = None,
25+
stub_path: str | None = None,
26+
allow_list_prefix: Iterable[str] | None = None,
27+
allow_list_exact: Iterable[str] | None = None,
28+
log_time_func: Callable[[], TIMING_LOGGER_TYPE] | None = None,
29+
enable_patching: bool = False,
30+
use_py_compiler: bool = False,
31+
allow_list_regex: Iterable[str] | None = None,
2132
):
22-
super().__init__(sys.path, "", [], [])
33+
super().__init__(
34+
path or sys.path,
35+
stub_path or "",
36+
allow_list_prefix or [],
37+
allow_list_exact or [],
38+
log_time_func,
39+
enable_patching,
40+
use_py_compiler,
41+
allow_list_regex,
42+
)
2343
assert type_info is not None
2444
self.type_info = type_info
25-
self.non_static_modules = non_static_modules
45+
self.static_opt_in = static_opt_in
46+
self.static_opt_out = static_opt_out or set()
2647

2748
def get_flags(
2849
self, module_name: str, pyast: ast.Module, override_flags: Flags
2950
) -> Flags:
30-
if self.non_static_modules is not None:
31-
if module_name in self.non_static_modules:
32-
return Flags().merge(override_flags)
51+
if self.static_opt_in is not None:
52+
return Flags(is_static=module_name in self.static_opt_in).merge(
53+
override_flags
54+
)
3355

34-
return Flags(is_static=True).merge(override_flags)
35-
36-
return super().get_flags(module_name, pyast, override_flags)
56+
return Flags(is_static=module_name not in self.static_opt_out).merge(
57+
override_flags
58+
)
3759

3860
def make_type_binder(
3961
self,
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# pyre-strict
3+
4+
import sys
5+
from importlib.abc import Loader
6+
from importlib.machinery import (
7+
BYTECODE_SUFFIXES,
8+
EXTENSION_SUFFIXES,
9+
ExtensionFileLoader,
10+
FileFinder,
11+
SOURCE_SUFFIXES,
12+
SourcelessFileLoader,
13+
)
14+
from typing import Callable, Iterable, Mapping
15+
16+
from cinderx.compiler.static.pyrefly_compiler import PyreflyCompiler
17+
from cinderx.compiler.static.pyrefly_type_binder import PyreflyTypeInfo
18+
19+
from .compiler import Compiler, TIMING_LOGGER_TYPE
20+
from .loader import StrictSourceFileLoader
21+
22+
23+
EMPTY_TYPE_INFO = PyreflyTypeInfo(
24+
{
25+
"type_table": [],
26+
"locations": [],
27+
}
28+
)
29+
30+
31+
class PyreflyLoader(StrictSourceFileLoader):
32+
@classmethod
33+
def ensure_compiler(
34+
cls,
35+
path: Iterable[str],
36+
stub_path: str,
37+
allow_list_prefix: Iterable[str],
38+
allow_list_exact: Iterable[str],
39+
log_time_func: Callable[[], TIMING_LOGGER_TYPE] | None,
40+
enable_patching: bool = False,
41+
allow_list_regex: Iterable[str] | None = None,
42+
) -> Compiler:
43+
if (comp := cls.compiler) is None:
44+
comp = cls.compiler = PyreflyCompiler(
45+
type_info=EMPTY_TYPE_INFO,
46+
static_opt_out=None,
47+
static_opt_in=None,
48+
path=path,
49+
stub_path=stub_path,
50+
allow_list_prefix=allow_list_prefix,
51+
allow_list_exact=allow_list_exact,
52+
log_time_func=log_time_func,
53+
enable_patching=enable_patching,
54+
allow_list_regex=allow_list_regex or [],
55+
)
56+
return comp
57+
58+
def should_force_strict(self) -> bool:
59+
return True
60+
61+
62+
class PyreflyLoaderWithPatching(PyreflyLoader):
63+
def __init__(
64+
self,
65+
fullname: str,
66+
path: str,
67+
import_path: Iterable[str] | None = None,
68+
stub_path: str | None = None,
69+
allow_list_prefix: Iterable[str] | None = None,
70+
allow_list_exact: Iterable[str] | None = None,
71+
enable_patching: bool = True,
72+
log_source_load: Callable[[str, str | None, bool], None] | None = None,
73+
init_cached_properties: None
74+
| (
75+
Callable[
76+
[Mapping[str, str | tuple[str, bool]]],
77+
Callable[[type[object]], type[object]],
78+
]
79+
) = None,
80+
log_time_func: Callable[[], TIMING_LOGGER_TYPE] | None = None,
81+
use_py_compiler: bool = False,
82+
# The regexes are parsed on the C++ side, so re.Pattern is not accepted.
83+
allow_list_regex: Iterable[str] | None = None,
84+
) -> None:
85+
super().__init__(
86+
fullname,
87+
path,
88+
import_path,
89+
stub_path,
90+
allow_list_prefix,
91+
allow_list_exact,
92+
enable_patching,
93+
log_source_load,
94+
init_cached_properties,
95+
log_time_func,
96+
use_py_compiler,
97+
allow_list_regex,
98+
)
99+
100+
101+
def _get_supported_file_loaders(
102+
enable_patching: bool = False,
103+
) -> list[tuple[type[Loader], list[str]]]:
104+
"""Returns a list of file-based module loaders.
105+
106+
Each item is a tuple (loader, suffixes).
107+
"""
108+
extensions = ExtensionFileLoader, EXTENSION_SUFFIXES
109+
source = (
110+
(PyreflyLoaderWithPatching if enable_patching else PyreflyLoader),
111+
SOURCE_SUFFIXES,
112+
)
113+
bytecode = SourcelessFileLoader, BYTECODE_SUFFIXES
114+
return [extensions, source, bytecode]
115+
116+
117+
def install(enable_patching: bool = False) -> None:
118+
"""Installs a loader which is capable of loading and validating strict modules"""
119+
supported_loaders = _get_supported_file_loaders(enable_patching)
120+
121+
for index, hook in enumerate(sys.path_hooks):
122+
if not isinstance(hook, type):
123+
sys.path_hooks.insert(index, FileFinder.path_hook(*supported_loaders))
124+
break
125+
else:
126+
sys.path_hooks.insert(0, FileFinder.path_hook(*supported_loaders))
127+
128+
# We need to clear the path_importer_cache so that our new FileFinder will
129+
# start being used for existing directories we've loaded modules from.
130+
sys.path_importer_cache.clear()

cinderx/PythonLib/test_cinderx/test_compiler/test_static/pyrefly_binder.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,16 @@ def test_simple(self):
2626
)
2727

2828
def test_force_static(self):
29-
compiler = PyreflyCompiler(non_static_modules=set(), type_info=EMPTY_TYPE_INFO)
29+
compiler = PyreflyCompiler(static_opt_out=set(), type_info=EMPTY_TYPE_INFO)
30+
code, strict, static = compiler.load_compiled_module_from_source(
31+
"def f(x: int): return x.bit_length()", "foo.py", "foo", 0
32+
)
33+
self.assertTrue(static)
34+
self.assertFalse(strict)
35+
self.assertIn("<fixed-modules>", code.co_names)
36+
37+
def test_opt_in_static(self):
38+
compiler = PyreflyCompiler(static_opt_in={"foo"}, type_info=EMPTY_TYPE_INFO)
3039
code, strict, static = compiler.load_compiled_module_from_source(
3140
"def f(x: int): return x.bit_length()", "foo.py", "foo", 0
3241
)
@@ -35,9 +44,16 @@ def test_force_static(self):
3544
self.assertIn("<fixed-modules>", code.co_names)
3645

3746
def test_non_force_static(self):
38-
compiler = PyreflyCompiler(
39-
non_static_modules={"foo"}, type_info=EMPTY_TYPE_INFO
47+
compiler = PyreflyCompiler(static_opt_out={"foo"}, type_info=EMPTY_TYPE_INFO)
48+
code, strict, static = compiler.load_compiled_module_from_source(
49+
"def f(x: int): return x.bit_length()", "foo.py", "foo", 0
4050
)
51+
self.assertFalse(static)
52+
self.assertFalse(strict)
53+
self.assertNotIn("<fixed-modules>", code.co_names)
54+
55+
def test_non_opt_in(self):
56+
compiler = PyreflyCompiler(static_opt_in=set(), type_info=EMPTY_TYPE_INFO)
4157
code, strict, static = compiler.load_compiled_module_from_source(
4258
"def f(x: int): return x.bit_length()", "foo.py", "foo", 0
4359
)

0 commit comments

Comments
 (0)