Lib/compiler/static/compiler.py (373 lines of code) (raw):
# Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
from __future__ import annotations
import ast
import builtins
from ast import AST
from types import CodeType
from typing import Any, Optional, Dict, Tuple, Type, TYPE_CHECKING
from _static import (
posix_clock_gettime_ns,
RAND_MAX,
rand,
)
from .. import consts
from ..errors import ErrorSink
from ..optimizer import AstOptimizer
from ..readonly.type_binder import ReadonlyTypeBinder
from ..symbols import SymbolVisitor
from .declaration_visitor import DeclarationVisitor
from .module_table import ModuleTable
from .type_binder import TypeBinder
from .types import (
BoxFunction,
CastFunction,
Class,
ExtremumFunction,
IsInstanceFunction,
IsSubclassFunction,
LenFunction,
NumClass,
Object,
ReadonlyFunction,
IdentityDecorator,
ProdAssertFunction,
RevealTypeFunction,
SortedFunction,
TypeName,
TypeEnvironment,
UnboxFunction,
Value,
reflect_builtin_function,
)
if TYPE_CHECKING:
from . import Static38CodeGenerator
try:
import xxclassloader # pyre-ignore[21]: unknown module
except ImportError:
xxclassloader = None
class StrictBuiltins(Object[Class]):
def __init__(self, builtins: Dict[str, Value], type_env: TypeEnvironment) -> None:
super().__init__(type_env.dict)
self.builtins = builtins
def bind_subscr(
self,
node: ast.Subscript,
type: Value,
visitor: TypeBinder,
type_ctx: Optional[Class] = None,
) -> None:
slice = node.slice
type = visitor.type_env.DYNAMIC
if isinstance(slice, ast.Index):
val = slice.value
if isinstance(val, ast.Str):
builtin = self.builtins.get(val.s)
if builtin is not None:
type = builtin
elif isinstance(val, ast.Constant):
svalue = val.value
if isinstance(svalue, str):
builtin = self.builtins.get(svalue)
if builtin is not None:
type = builtin
visitor.set_type(node, type)
class Compiler:
def __init__(
self,
code_generator: Type[Static38CodeGenerator],
error_sink: Optional[ErrorSink] = None,
) -> None:
self.modules: Dict[str, ModuleTable] = {}
self.ast_cache: Dict[str, ast.Module] = {}
self.code_generator = code_generator
self.error_sink: ErrorSink = error_sink or ErrorSink()
self.type_env: TypeEnvironment = TypeEnvironment()
rand_max = NumClass(
TypeName("builtins", "int"),
self.type_env,
pytype=int,
literal_value=RAND_MAX,
is_final=True,
)
builtins_children = {
"object": self.type_env.object.exact_type(),
"type": self.type_env.type.exact_type(),
"None": self.type_env.none.instance,
"int": self.type_env.int.exact_type(),
"classmethod": self.type_env.class_method,
"complex": self.type_env.complex.exact_type(),
"str": self.type_env.str.exact_type(),
"bytes": self.type_env.bytes.exact_type(),
"bool": self.type_env.bool.exact_type(),
"float": self.type_env.float.exact_type(),
"len": LenFunction(self.type_env.function, boxed=True),
"min": ExtremumFunction(self.type_env.function, is_min=True),
"max": ExtremumFunction(self.type_env.function, is_min=False),
"list": self.type_env.list.exact_type(),
"tuple": self.type_env.tuple.exact_type(),
"set": self.type_env.set.exact_type(),
"sorted": SortedFunction(self.type_env.function),
"Exception": self.type_env.exception.exact_type(),
"BaseException": self.type_env.base_exception.exact_type(),
"isinstance": IsInstanceFunction(self.type_env),
"issubclass": IsSubclassFunction(self.type_env),
"staticmethod": self.type_env.static_method,
"reveal_type": RevealTypeFunction(self.type_env),
"property": self.type_env.property.exact_type(),
"<mutable>": IdentityDecorator(
TypeName("__strict__", "<mutable>"), self.type_env
),
"readonly_func": IdentityDecorator(
TypeName("builtins", "readonly_func"), self.type_env
),
"Readonly": self.type_env.readonly_type,
"readonly": ReadonlyFunction(self.type_env),
}
strict_builtins = StrictBuiltins(builtins_children, self.type_env)
typing_children = {
"Annotated": self.type_env.annotated,
"ClassVar": self.type_env.classvar,
# TODO: Need typed members for dict
"Dict": self.type_env.dict,
"List": self.type_env.list,
"Final": self.type_env.final,
"final": self.type_env.final_method,
"Literal": self.type_env.literal,
"NamedTuple": self.type_env.named_tuple,
"Protocol": self.type_env.protocol,
"Optional": self.type_env.optional,
"Union": self.type_env.union,
"Tuple": self.type_env.tuple,
"TYPE_CHECKING": self.type_env.bool.instance,
}
typing_extensions_children: Dict[str, Value] = {
"Annotated": self.type_env.annotated,
}
strict_modules_children: Dict[str, Value] = {
"mutable": IdentityDecorator(
TypeName("__strict__", "mutable"), self.type_env
),
"strict_slots": IdentityDecorator(
TypeName("__strict__", "strict_slots"), self.type_env
),
"loose_slots": IdentityDecorator(
TypeName("__strict__", "loose_slots"), self.type_env
),
"freeze_type": IdentityDecorator(
TypeName("__strict__", "freeze_type"), self.type_env
),
}
builtins_children["<builtins>"] = strict_builtins
self.modules["strict_modules"] = self.modules["__strict__"] = ModuleTable(
"strict_modules", "<strict-modules>", self, strict_modules_children
)
fixed_modules: Dict[str, Value] = {
"typing": StrictBuiltins(typing_children, self.type_env),
"typing_extensions": StrictBuiltins(
typing_extensions_children, self.type_env
),
"__strict__": StrictBuiltins(strict_modules_children, self.type_env),
"strict_modules": StrictBuiltins(
dict(strict_modules_children), self.type_env
),
}
builtins_children["<fixed-modules>"] = StrictBuiltins(
fixed_modules, self.type_env
)
self.builtins = self.modules["builtins"] = ModuleTable(
"builtins",
"<builtins>",
self,
builtins_children,
)
self.typing = self.modules["typing"] = ModuleTable(
"typing", "<typing>", self, typing_children
)
self.modules["typing_extensions"] = ModuleTable(
"typing_extensions", "<typing_extensions>", self, typing_extensions_children
)
self.statics = self.modules["__static__"] = ModuleTable(
"__static__",
"<__static__>",
self,
{
"Array": self.type_env.array.exact_type(),
"CheckedDict": self.type_env.checked_dict.exact_type(),
"CheckedList": self.type_env.checked_list.exact_type(),
"Enum": self.type_env.enum,
"Int64Enum": self.type_env.int64enum,
"IntEnum": self.type_env.int_enum,
"StringEnum": self.type_env.string_enum,
"allow_weakrefs": self.type_env.allow_weakrefs,
"box": BoxFunction(self.type_env.function),
"cast": CastFunction(self.type_env.function),
"clen": LenFunction(self.type_env.function, boxed=False),
"ExcContextDecorator": self.type_env.exc_context_decorator.exact_type(),
"ContextDecorator": self.type_env.context_decorator.exact_type(),
"dynamic_return": self.type_env.dynamic_return,
"size_t": self.type_env.uint64.exact_type(),
"ssize_t": self.type_env.int64.exact_type(),
"cbool": self.type_env.cbool.exact_type(),
"inline": self.type_env.inline,
# This is a way to disable the static compiler for
# individual functions/methods
"_donotcompile": self.type_env.donotcompile,
"int8": self.type_env.int8.exact_type(),
"int16": self.type_env.int16.exact_type(),
"int32": self.type_env.int32.exact_type(),
"int64": self.type_env.int64.exact_type(),
"uint8": self.type_env.uint8.exact_type(),
"uint16": self.type_env.uint16.exact_type(),
"uint32": self.type_env.uint32.exact_type(),
"uint64": self.type_env.uint64.exact_type(),
"char": self.type_env.char.exact_type(),
"double": self.type_env.double.exact_type(),
"unbox": UnboxFunction(self.type_env.function),
"checked_dicts": self.type_env.bool.instance,
"prod_assert": ProdAssertFunction(self.type_env),
"pydict": self.type_env.dict.exact_type(),
"PyDict": self.type_env.dict.exact_type(),
"Vector": self.type_env.vector.exact_type(),
"RAND_MAX": rand_max.instance,
"posix_clock_gettime_ns": reflect_builtin_function(
# pyre-ignore[6]: Pyre can't know this callable is a BuiltinFunctionType
posix_clock_gettime_ns,
None,
self.type_env,
),
"rand": reflect_builtin_function(
# pyre-ignore[6]: Pyre can't know this callable is a BuiltinFunctionType
rand,
None,
self.type_env,
),
"set_type_static": self.type_env.DYNAMIC,
},
)
self.modules["cinder"] = ModuleTable(
"cinder",
"<cinder>",
self,
{
"cached_property": self.type_env.cached_property,
"async_cached_property": self.type_env.async_cached_property,
},
)
if xxclassloader is not None:
spam_obj = self.type_env.spam_obj
assert spam_obj is not None
self.modules["xxclassloader"] = ModuleTable(
"xxclassloader",
"<xxclassloader>",
self,
{
"spamobj": spam_obj.exact_type(),
"XXGeneric": self.type_env.xx_generic.exact_type(),
"foo": reflect_builtin_function(
xxclassloader.foo,
None,
self.type_env,
),
"bar": reflect_builtin_function(
xxclassloader.bar,
None,
self.type_env,
),
"neg": reflect_builtin_function(
xxclassloader.neg,
None,
self.type_env,
),
},
)
def __getitem__(self, name: str) -> ModuleTable:
return self.modules[name]
def __setitem__(self, name: str, value: ModuleTable) -> None:
self.modules[name] = value
def add_module(
self, name: str, filename: str, tree: AST, optimize: int
) -> ast.Module:
tree = AstOptimizer(optimize=optimize > 0).visit(tree)
self.ast_cache[name] = tree
decl_visit = DeclarationVisitor(name, filename, self, optimize)
decl_visit.visit(tree)
decl_visit.finish_bind()
return tree
def bind(
self,
name: str,
filename: str,
tree: AST,
optimize: int,
enable_patching: bool = False,
) -> None:
self._bind(name, filename, tree, optimize, enable_patching)
def _bind(
self,
name: str,
filename: str,
tree: AST,
optimize: int,
enable_patching: bool = False,
) -> Tuple[AST, SymbolVisitor]:
cached_tree = self.ast_cache.get(name)
if cached_tree is None:
tree = self.add_module(name, filename, tree, optimize)
else:
tree = cached_tree
# Analyze variable scopes
s = self.code_generator._SymbolVisitor()
s.visit(tree)
# Analyze the types of objects within local scopes
type_binder = TypeBinder(
s,
filename,
self,
name,
optimize,
enable_patching=enable_patching,
)
type_binder.visit(tree)
return tree, s
def compile(
self,
name: str,
filename: str,
tree: AST,
optimize: int,
enable_patching: bool = False,
builtins: Dict[str, Any] = builtins.__dict__,
) -> CodeType:
code_gen = self.code_gen(
name, filename, tree, optimize, enable_patching, builtins
)
return code_gen.getCode()
def code_gen(
self,
name: str,
filename: str,
tree: AST,
optimize: int,
enable_patching: bool = False,
builtins: Dict[str, Any] = builtins.__dict__,
) -> Static38CodeGenerator:
tree, s = self._bind(name, filename, tree, optimize, enable_patching)
if self.error_sink.has_errors:
raise self.error_sink.errors[0]
# Analyze using readonly type binder
readonly_type_binder = ReadonlyTypeBinder(tree, filename, s)
if readonly_type_binder.error_sink.has_errors:
raise readonly_type_binder.error_sink.errors[0]
# Compile the code w/ the static compiler
graph = self.code_generator.flow_graph(
name, filename, s.scopes[tree], peephole_enabled=True
)
graph.setFlag(consts.CO_STATICALLY_COMPILED)
graph.extra_consts.append(tuple(self.modules[name].imported_from.items()))
code_gen = self.code_generator(
None,
tree,
s,
graph,
self,
name,
binder=readonly_type_binder,
flags=0,
optimization_lvl=optimize,
enable_patching=enable_patching,
builtins=builtins,
)
code_gen.visit(tree)
return code_gen
def import_module(self, name: str, optimize: int) -> Optional[ModuleTable]:
pass